Tüm eğitimler
TEKNİK REHBER GÖMÜLÜ LİNUX IRQ ALT SİSTEMİ 2026

Linux Interrupt Alt Sistemi
request_irq, GIC, Threaded IRQ

Kernel interrupt mimarisini GIC donanımından threaded handler'a kadar derinlemesine incele — sürücü yazan her geliştirici için kapsamlı başvuru kaynağı.

00 Interrupt Donanım Mimarisi

Bir çevre birimi CPU'ya "işim var" demek istediğinde donanım seviyesinde bir sinyal zinciri devreye girer. Bu zinciri anlamadan kernel tarafını doğru modellemek mümkün değildir.

Kesme Türleri

Hardware IRQFiziksel pin veya PCIe sinyal hattı üzerinden gelen, donanım tarafından tetiklenen kesme. CPU'yu mevcut bağlamdan çıkarır.
Software IRQ (SWI)Yazılım tarafından üretilen kesme — ARM'da svc komutu, x86'da int 0x80. Sistem çağrılarının temelini oluşturur.
NMINon-Maskable Interrupt — maskelenemez, genellikle donanım hataları (ECC, watchdog) için kullanılır. Kernel bu tür kesmeleri özel handler ile yönetir.
IPIInter-Processor Interrupt — çok çekirdekli sistemlerde çekirdekler arası sinyal. TLB flush, scheduler kick gibi amaçlarla kullanılır.

ARM Generic Interrupt Controller (GIC)

ARM SoC'lerde en yaygın interrupt controller ailesi GIC'tir. Günümüzde Cortex-A serisi sistemler GICv3 veya GICv4 kullanır; eski platformlar GICv2 ile devam eder.

Çevre birimi (GPIO, UART, ETH...)
        │ fiziksel kesme sinyali
        ▼
  GIC Distributor (GICD)
  ├─ IRQ enable/disable
  ├─ Priority ayarı
  ├─ CPU hedef seçimi (affinity)
  └─ Pending / Active takibi
        │
        ▼
  GIC CPU Interface (GICC)  — her CPU çekirdeği için ayrı
  ├─ IAR (Interrupt Acknowledge Register) — CPU hangi IRQ'yu işlediğini okur
  └─ EOIR (End of Interrupt Register)   — işlem bitince EOI yazılır
        │
        ▼
  CPU FIQ / IRQ vektörü
        │
        ▼
  Linux vectors_irq → handle_arch_irq() → irq_enter() → handler
    

GIC Interrupt Numaralandırması

AralıkTürKısaltmaKullanım
0–15Software GeneratedSGIIPI, çekirdekler arası mesajlaşma
16–31Private PeripheralPPIYerel timer (arch_timer), PMU
32–1019Shared PeripheralSPIUART, I2C, GPIO, ETH — tüm sistem IRQ'ları
1020–1023RezervÖzel durum kodları (spurious vb.)
8192+Locality-specific PPILPIGICv3/v4 MSI desteği (ITS)

Device Tree ile IRQ Tanımı

Bir SoC sürücüsünde kesme numarası genellikle Device Tree üzerinden gelir. GICv2 için standart interrupts özelliği şu biçimdedir:

/* arch/arm/boot/dts/example-soc.dts */
uart0: serial@12000000 {
    compatible = "example,uart-v1";
    reg = <0x12000000 0x1000>;
    /*
     * GICv2: <tip  SPI-numarası  tetikleme>
     *   tip: 0 = SPI, 1 = PPI
     *   tetikleme: 1 = kenar (rising), 4 = seviye (high)
     */
    interrupts = <0 42 4>;
    interrupt-parent = <&gic>;
};

x86 APIC Karşılaştırması

x86 mimarisinde yerel kesme kontrolörü LAPIC (Local APIC), sistem geneli yönlendirme ise I/O APIC tarafından yapılır. ARM GIC'e işlevsel olarak denktir; kernel her iki mimariyi de irq_chip soyutlama katmanı üzerinden yönetir.

01 request_irq ve free_irq

request_irq(), bir sürücünün Linux IRQ alt sistemine "bu IRQ numarasının handler'ını benim" demesidir. Tek satır gibi görünse de arka planda IRQ domain çözümlemesi, affinity ayarı ve /proc/interrupts kaydı gerçekleşir.

Temel API İmzaları

/* include/linux/interrupt.h */

/**
 * request_irq - kesme hattı talep et ve handler kaydet
 * @irq:     Linux sanal IRQ numarası (irq_of_parse_and_map ile elde edilir)
 * @handler: hardirq bağlamında çalışacak üst yarı handler
 * @flags:   IRQF_* bayrakları (aşağıda açıklanır)
 * @name:    /proc/interrupts satırında görünecek isim
 * @dev_id:  free_irq'da doğrulama ve handler'a aktarılan opak pointer
 *
 * Başarıda 0, hata durumunda negatif errno döner.
 */
int request_irq(unsigned int irq,
                irq_handler_t handler,
                unsigned long flags,
                const char *name,
                void *dev_id);

/* Yönetilen (devm) varyant — sürücü kaldırıldığında otomatik serbest bırakır */
int devm_request_irq(struct device *dev,
                     unsigned int irq,
                     irq_handler_t handler,
                     unsigned long flags,
                     const char *name,
                     void *dev_id);

/* Serbest bırakma */
void free_irq(unsigned int irq, void *dev_id);

/* Handler imzası */
typedef irqreturn_t (*irq_handler_t)(int irq, void *dev_id);
/* Dönüş değerleri: IRQ_HANDLED, IRQ_NONE, IRQ_WAKE_THREAD */

IRQF_* Bayrakları

IRQF_SHAREDAynı IRQ hattını birden fazla sürücü paylaşabilir. Her handler çağrıldığında kesmenin kendi donanımından gelip gelmediğini kontrol etmeli ve yabancıysa IRQ_NONE dönmelidir.
IRQF_TRIGGER_RISINGYükselen kenar tetiklemesi. Alternatifler: IRQF_TRIGGER_FALLING, IRQF_TRIGGER_HIGH, IRQF_TRIGGER_LOW. DT'deki değer bu bayrakla çakışmamalıdır.
IRQF_ONESHOTThreaded IRQ kullanımında zorunludur — thread tamamlanana kadar IRQ hattı yeniden etkinleştirilmez. Seviye tetiklemeli IRQ'larda deadlock'u önler.
IRQF_NO_THREADPREEMPT_RT çekirdeğinde bile bu IRQ'yu thread'e taşıma. Gerçekten kritik donanım kesmeleri için kullanılır.
IRQF_PERF_CRITICALIRQ latency kritik — bazı RT schedulera ipucu. Çok dikkatli kullanılmalı.

IRQ Numarası Alma — Device Tree Yolu

#include <linux/platform_device.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>

static int mydrv_probe(struct platform_device *pdev)
{
    struct mydrv_priv *priv;
    int irq, ret;

    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    /*
     * platform_get_irq() DT'deki "interrupts" özelliğini okur,
     * IRQ domain aracılığıyla Linux virtual IRQ numarasına çevirir.
     * İkinci parametre: DT'deki interrupts listesindeki indeks (0-tabanlı).
     */
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        dev_err(&pdev->dev, "IRQ alınamadı: %d\n", irq);
        return irq;
    }

    ret = devm_request_irq(&pdev->dev, irq,
                           mydrv_irq_handler,
                           IRQF_TRIGGER_HIGH,
                           dev_name(&pdev->dev),
                           priv);
    if (ret) {
        dev_err(&pdev->dev, "IRQ %d talep edilemedi: %d\n", irq, ret);
        return ret;
    }

    priv->irq = irq;
    platform_set_drvdata(pdev, priv);
    return 0;
}

IRQF_SHARED Kullanımı

/* Paylaşımlı IRQ — dev_id NULL olmamalı */
ret = request_irq(shared_irq,
                  mydrv_shared_handler,
                  IRQF_SHARED | IRQF_TRIGGER_RISING,
                  "mydrv-shared",
                  priv);   /* dev_id olarak priv kullan */

/* Handler: kendi donanımından mı geliyor? */
static irqreturn_t mydrv_shared_handler(int irq, void *dev_id)
{
    struct mydrv_priv *priv = dev_id;

    /* Donanım register'ı oku — bu kesme bize ait mi? */
    if (!(readl(priv->base + STATUS_REG) & IRQ_PENDING_BIT))
        return IRQ_NONE;   /* Başka sürücünün kesmesi, dokunma */

    /* Kendi işlemizi yap */
    writel(IRQ_CLEAR_BIT, priv->base + STATUS_REG);
    /* ... */
    return IRQ_HANDLED;
}

02 IRQ Handler Yazımı

Hardirq bağlamı, kernel'in en kısıtlı çalışma ortamıdır. Bu bağlamda yapılabilecekler ve yapılamayacaklar sürücünün doğruluğunu doğrudan belirler.

Hardirq Bağlamı Kısıtları

Uyku yasaksleep(), mutex_lock(), wait_event(), schedule() — tümü yasaktır. Bu fonksiyonlar çağrıldığında might_sleep() DEBUG modda uyarı verir.
GFP_KERNEL yasakBellek tahsisi yalnızca GFP_ATOMIC ile yapılabilir. GFP_ATOMIC hata olasılığı yüksek olduğundan tercih edilmemelidir — önceden tahsis edin.
Preemption kapalıHardirq handler süresince preemption ve yerel IRQ'lar devre dışıdır. Handler ne kadar uzun olursa sistem o kadar geç yanıt verir.
in_interrupt() == truein_interrupt() makrosu hardirq ve softirq bağlamlarında true döner. Bu makroyu kontrol eden API'lar (örn. bazı i2c fonksiyonları) bu bağlamda hata verebilir.

Doğru Handler Yapısı

static irqreturn_t mydrv_irq_handler(int irq, void *dev_id)
{
    struct mydrv_priv *priv = dev_id;
    u32 status;

    /*
     * 1. Status register'ı oku — hangi kesme kaynağı aktif?
     *    Okumak aynı zamanda "acknowledge" işlevi görür bazı IP'lerde.
     */
    status = readl(priv->base + IRQ_STATUS_REG);
    if (!status)
        return IRQ_NONE;   /* Spurious — bu donanım biz değil */

    /*
     * 2. Kesme kaynağını hemen kapat (level-triggered için kritik)
     *    Aksi hâlde handler dönünce IRQ tekrar tetiklenir.
     */
    writel(status, priv->base + IRQ_CLEAR_REG);

    /*
     * 3. Veriyi IRQ-safe buffer'a kopyala — softirq/thread erişecek
     *    spin_lock_irqsave GEREKSİZ: zaten IRQ kapalı bağlamdayız.
     *    Ama başka CPU'dan erişim olabilirse spin_lock() gerekir.
     */
    spin_lock(&priv->lock);
    priv->pending_status |= status;
    spin_unlock(&priv->lock);

    /*
     * 4. Alt yarıyı zamanla — asıl işlem orada yapılsın
     */
    tasklet_schedule(&priv->tasklet);
    /* ya da: queue_work(priv->wq, &priv->work); */

    return IRQ_HANDLED;
}

spin_lock_irqsave — Ne Zaman Gerekir

/*
 * SENARYO: Process context (örn. file_operations->write) ve IRQ handler
 * aynı spinlock'u paylaşıyor.
 *
 * Process context'te normal spin_lock kullansak:
 *   CPU A, lock'u tutarken IRQ gelir → IRQ handler lock almaya çalışır
 *   → Deadlock! (aynı CPU, IRQ kapalı değil)
 *
 * Çözüm: process context'te spin_lock_irqsave kullan.
 */

/* Process context (sysfs, write, ioctl...) */
static ssize_t mydrv_write(struct file *f, const char __user *buf,
                           size_t len, loff_t *off)
{
    struct mydrv_priv *priv = f->private_data;
    unsigned long flags;

    spin_lock_irqsave(&priv->lock, flags);   /* yerel IRQ'ları kapat + lock al */
    /* kritik bölge */
    spin_unlock_irqrestore(&priv->lock, flags); /* eski IRQ durumunu geri yükle */
    return len;
}

/* IRQ handler — zaten IRQ kapalı, sadece spin_lock yeterli */
static irqreturn_t mydrv_irq_handler(int irq, void *dev_id)
{
    struct mydrv_priv *priv = dev_id;

    spin_lock(&priv->lock);   /* başka CPU'nun aynı lock'u tutmasını engelle */
    /* kritik bölge */
    spin_unlock(&priv->lock);
    return IRQ_HANDLED;
}

İdeal Handler Süresi

Hardirq handler mümkün olduğunca kısa tutulmalıdır. Gerçek zamanlı sistemlerde hedef genellikle <10 µs'dir. Bunun üzerinde kalan işlemler mutlaka threaded IRQ veya workqueue'ya taşınmalıdır.

03 Threaded IRQ

Threaded IRQ, Linux 2.6.30 ile gelen ve PREEMPT_RT'nin temel taşlarından biri olan mekanizmadır. Kesme işleme mantığını bir kernel thread'ine taşıyarak latency ve öncelik yönetimini büyük ölçüde iyileştirir.

request_threaded_irq API

/* include/linux/interrupt.h */

/**
 * request_threaded_irq - üst ve alt yarı handler'ı ayrı kaydet
 *
 * @irq:         Linux virtual IRQ numarası
 * @handler:     Hardirq bağlamında çalışır (üst yarı / primary handler).
 *               NULL olabilir — kernel varsayılan "wake thread" handler'ı kullanır.
 * @thread_fn:   Kernel thread bağlamında çalışır (alt yarı).
 *               Bu fonksiyon uyuyabilir, mutex alabilir, kmalloc yapabilir.
 * @irqflags:    IRQF_ONESHOT dahil bayraklar
 * @devname:     /proc/interrupts'ta görünecek isim
 * @dev_id:      Her iki handler'a aktarılan opak pointer
 */
int request_threaded_irq(unsigned int irq,
                         irq_handler_t handler,
                         irq_handler_t thread_fn,
                         unsigned long irqflags,
                         const char *devname,
                         void *dev_id);

/* devm varyantı */
int devm_request_threaded_irq(struct device *dev,
                               unsigned int irq,
                               irq_handler_t handler,
                               irq_handler_t thread_fn,
                               unsigned long irqflags,
                               const char *devname,
                               void *dev_id);

Threaded IRQ Akışı

Donanım IRQ sinyali
        │
        ▼
  hardirq handler() — IRQ kapalı bağlam
  ├─ Donanımı acknowledge et
  ├─ Kritik verileri kopyala
  └─ IRQ_WAKE_THREAD döndür
        │
        ▼
  irq/N-devname (kernel thread) uyanır
        │  (schedule edilir, normal öncelik ile)
        ▼
  thread_fn() — process bağlamı
  ├─ mutex, semaphore kullanabilir
  ├─ kmalloc(GFP_KERNEL) yapabilir
  ├─ i2c/spi transfer başlatabilir
  └─ IRQ_HANDLED döndür
        │
        ▼
  IRQF_ONESHOT: IRQ hattı tekrar etkinleştirilir
    

Gerçek Dünya Örneği — I2C Dokunmatik Ekran

/* Dokunmatik ekran: her dokunuşta GPIO IRQ → I2C okuma gerekiyor
 * I2C okuma uyuyabilir → mutlaka thread'e taşınmalı */

static irqreturn_t ts_hardirq(int irq, void *dev_id)
{
    /* Sadece GPIO interrupt pending bitini sil — çok hızlı */
    /* Gerçek donanım okuma işlemi thread'de yapılacak */
    return IRQ_WAKE_THREAD;
}

static irqreturn_t ts_thread_fn(int irq, void *dev_id)
{
    struct ts_priv *ts = dev_id;
    u8 buf[6];
    int ret;

    /* I2C okuma — uyuyabilir, tamamen güvenli */
    ret = i2c_master_recv(ts->client, buf, sizeof(buf));
    if (ret != sizeof(buf)) {
        dev_err(&ts->client->dev, "I2C okuma hatası: %d\n", ret);
        return IRQ_HANDLED;
    }

    /* Input event gönder */
    input_report_abs(ts->input, ABS_X, (buf[1] << 8) | buf[2]);
    input_report_abs(ts->input, ABS_Y, (buf[3] << 8) | buf[4]);
    input_report_key(ts->input, BTN_TOUCH, buf[0] & 0x01);
    input_sync(ts->input);

    return IRQ_HANDLED;
}

static int ts_probe(struct i2c_client *client)
{
    struct ts_priv *ts;
    int ret;

    ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL);
    if (!ts)
        return -ENOMEM;

    ts->client = client;
    /* irq_to_gpio veya client->irq — I2C sürücüsünde IRQ DT'den gelir */

    ret = devm_request_threaded_irq(&client->dev,
                                    client->irq,
                                    ts_hardirq,    /* üst yarı */
                                    ts_thread_fn,  /* alt yarı */
                                    IRQF_ONESHOT | IRQF_TRIGGER_FALLING,
                                    "ts-irq",
                                    ts);
    return ret;
}

PREEMPT_RT ile İlişki

PREEMPT_RT yamalı çekirdekte, threaded olmayan tüm IRQ'lar otomatik olarak kernel thread'lerine alınır (forced-threaded IRQ). IRQF_NO_THREAD bayrağı bu dönüşümü engeller. Bu sayede IRQ handler'ların önceliği chrt ile ayarlanabilir:

# IRQ 45'e hizmet eden thread'i bul
grep "irq/45" /proc/*/status | grep -o '/proc/[0-9]*/status'

# SCHED_FIFO öncelik 90 ver
chrt -f -p 90 <PID>

# Alternatif: tüm IRQ thread önceliklerini ayarla
for pid in $(ps -eo pid,comm | grep "irq/" | awk '{print $1}'); do
    chrt -f -p 80 $pid 2>/dev/null
done

04 Softirq, Tasklet ve Workqueue

Linux, hardirq handler'dan ertelenen işleri üç farklı mekanizmayla yönetir. Her birinin farklı gecikme garantisi, eşzamanlılık modeli ve kısıtları vardır.

Ertelenmiş İş Hiyerarşisi

Hardirq Handler (en kısıtlı)
        │
        │ ertelenmiş iş
        ▼
  ┌─────────────┬─────────────┬──────────────────┐
  │   Softirq   │   Tasklet   │    Workqueue      │
  │ (static,    │ (softirq    │ (process context) │
  │  çekirdek   │  üstüne     │                   │
  │  tarafından │  inşa)      │                   │
  │  tanımlı)   │             │                   │
  ├─────────────┼─────────────┼──────────────────┤
  │ Uyuma: HAYIR│ Uyuma: HAYIR│ Uyuma: EVET       │
  │ Paralel:EVET│ Paralel:HAYIR│ Paralel: EVET    │
  │ (farklı CPU)│             │ (WQ_UNBOUND ile)  │
  └─────────────┴─────────────┴──────────────────┘
    

Softirq

Softirq'lar çekirdek kaynak kodunda statik olarak tanımlanmıştır; sürücü geliştiricisi yeni softirq ekleyemez. Var olan softirq türleri:

EnumÖncelikKullanım Yeri
HI_SOFTIRQ0 (en yüksek)Yüksek öncelikli tasklet'ler
TIMER_SOFTIRQ1Kernel timer wheel işleme
NET_TX_SOFTIRQ2Ağ paketi gönderimi
NET_RX_SOFTIRQ3Ağ paketi alımı (NAPI)
BLOCK_SOFTIRQ4Blok I/O tamamlama
SCHED_SOFTIRQ6CFS görev dağılımı (SMP)
TASKLET_SOFTIRQ7 (en düşük)Sürücü tasklet'leri

Tasklet API

#include <linux/interrupt.h>

/* Statik tanımlama */
DECLARE_TASKLET(name, func);            /* enabled */
DECLARE_TASKLET_DISABLED(name, func);   /* disabled */

/* Dinamik tanımlama (sürücü init'te) */
tasklet_init(&priv->tasklet, mydrv_tasklet_fn, (unsigned long)priv);

/* Handler */
static void mydrv_tasklet_fn(unsigned long data)
{
    struct mydrv_priv *priv = (struct mydrv_priv *)data;
    /* Softirq bağlamı: uyuma yok, GFP_ATOMIC gerekli */
    /* Aynı tasklet aynı anda yalnızca bir CPU'da çalışır */
    process_pending(priv);
}

/* IRQ handler'dan zamanla */
static irqreturn_t mydrv_irq(int irq, void *dev_id)
{
    struct mydrv_priv *priv = dev_id;
    /* ... hızlı ack ... */
    tasklet_schedule(&priv->tasklet);  /* zaten scheduled ise ikinci çağrı yok sayılır */
    return IRQ_HANDLED;
}

/* Temizlik */
tasklet_kill(&priv->tasklet);  /* bekler, sonra devre dışı bırakır */

Tasklet vs Workqueue — Karar Tablosu

KriterTaskletWorkqueue
Uyuma / blocking I/OYasakSerbest
GecikmeDüşük (~microsaniye)Daha yüksek (schedule gecikmesi)
Paralel çalışmaAynı anda tek CPUBirden fazla worker thread
Yeni kod önerisiKaçın — deprecated eğilimdeTercih edilmeli
Bellek ayak iziKüçükWorker thread stack'i

05 IRQ Affinity ve CPU Pin

IRQ affinity, bir kesmenin hangi CPU çekirdeği tarafından işleneceğini belirler. Yüksek hızlı NIC, gerçek zamanlı kontrol döngüsü veya NUMA topolojisi gerektiren sistemlerde kritik öneme sahiptir.

/proc/irq Arayüzü

# Sistemdeki tüm IRQ'ların listesi (kısaltılmış)
cat /proc/interrupts
#            CPU0       CPU1       CPU2       CPU3
#   0:    1234567          0          0          0  IR-IO-APIC    2-edge  timer
#  42:          0     987654          0          0  PCI-MSI 524288-edge  eth0-rx-0
#  43:          0          0    123456          0  PCI-MSI 524289-edge  eth0-rx-1

# IRQ 42'nin affinity maskesi (hangi CPU'lar işleyebilir)
cat /proc/irq/42/smp_affinity
# 2   ← bitmask: CPU1 (0b0010)

cat /proc/irq/42/smp_affinity_list
# 1   ← aynı şey, CPU numarası formatında

# IRQ 42'yi CPU2 ve CPU3'e ata (bitmask: 0b1100 = 0xc)
echo c > /proc/irq/42/smp_affinity

# Sadece CPU0'a zorla
echo 1 > /proc/irq/42/smp_affinity

irqbalance Servisi

Çoğu dağıtımda irqbalance daemon'u çalışır ve IRQ'ları CPU'lara otomatik dağıtır. Gerçek zamanlı sistemlerde bu daemon durdurulup affinity manuel ayarlanmalıdır:

# RT sistemde irqbalance'ı durdur
systemctl stop irqbalance
systemctl disable irqbalance

# Belirli IRQ'ları CPU'lara dağıt (başlangıç scripti)
#!/bin/bash
# Tüm ağ IRQ'larını CPU2-3'e taşı
for irq in $(grep eth0 /proc/interrupts | awk -F: '{print $1}'); do
    echo c > /proc/irq/${irq}/smp_affinity
done
# RT görev CPU0-1'de çalışıyor — ağ trafiğini uzak tut

Kernel API ile Affinity Ayarı

#include <linux/interrupt.h>
#include <linux/cpumask.h>

/* Belirli bir CPU'ya IRQ'yu sabitle */
static int mydrv_set_irq_affinity(struct mydrv_priv *priv, int cpu)
{
    struct cpumask mask;
    int ret;

    cpumask_clear(&mask);
    cpumask_set_cpu(cpu, &mask);

    ret = irq_set_affinity_hint(priv->irq, &mask);
    if (ret) {
        dev_warn(&priv->dev, "IRQ affinity ayarlanamadı: %d\n", ret);
        return ret;
    }

    ret = irq_set_affinity(priv->irq, &mask);
    return ret;
}

/* Sürücü kaldırıldığında affinity hint'i temizle */
static void mydrv_remove(struct platform_device *pdev)
{
    struct mydrv_priv *priv = platform_get_drvdata(pdev);
    irq_set_affinity_hint(priv->irq, NULL);
}

NUMA Topolojisinde Affinity

NUMA sistemlerde PCIe cihazının bağlı olduğu NUMA node'undaki CPU'lara IRQ atamak bellek erişim gecikmesini önemli ölçüde azaltır:

# PCIe cihazının NUMA node'unu öğren
cat /sys/bus/pci/devices/0000:01:00.0/numa_node
# 0

# O node'daki CPU'ları listele
cat /sys/devices/system/node/node0/cpulist
# 0-7,16-23

# IRQ'ları bu CPU'lara ata
echo ff > /proc/irq/42/smp_affinity   # CPU 0-7

isolcpus ile RT CPU Ayırma

# Kernel boot parametresi (grub/cmdline)
isolcpus=2,3 rcu_nocbs=2,3 nohz_full=2,3

# Sonuç: CPU 2-3'e IRQ ve kernel thread gönderilmez
# Sadece oraya taşınan RT görevler çalışır

06 MSI ve MSI-X

PCI/PCIe cihazları geleneksel pin tabanlı IRQ yerine Message Signaled Interrupts (MSI) kullanır. MSI, paylaşımlı IRQ sorununu ortadan kaldırır ve çok kuyruklu NIC/NVMe gibi yüksek bant genişlikli cihazlar için vazgeçilmezdir.

Pin IRQ vs MSI Farkı

ÖzellikPin-based IRQ (INTx)MSI / MSI-X
MekanizmaFiziksel sinyal hattıMemory write (mesaj)
PaylaşımIRQF_SHARED gerekebilirHer vektör benzersiz
Vektör sayısı1MSI: 32, MSI-X: 2048'e kadar
AffinityKısıtlıVektör başına bağımsız
NUMA uyumuZayıfGüçlü — vektör CPU'ya yakın
Hata tespitiZor (paylaşımlı)Kolay (cihaz başına)

MSI Etkinleştirme

#include <linux/pci.h>

static int mypcie_probe(struct pci_dev *pdev,
                        const struct pci_device_id *id)
{
    int ret, irq;

    ret = pci_enable_device(pdev);
    if (ret)
        return ret;

    pci_set_master(pdev);

    /*
     * MSI talep et — başarılı olursa pdev->irq güncellenir.
     * Başarısız olursa eski INTx kullanılabilir.
     */
    ret = pci_enable_msi(pdev);
    if (ret) {
        dev_warn(&pdev->dev, "MSI etkinleştirilemedi, INTx kullanılıyor\n");
        /* INTx yoluna devam et */
    }

    irq = pdev->irq;   /* MSI veya INTx, hangisi etkinse */

    ret = request_irq(irq, mypcie_irq_handler,
                      0,   /* MSI'da IRQF_SHARED gerekmez */
                      "mypcie",
                      pdev);
    return ret;
}

static void mypcie_remove(struct pci_dev *pdev)
{
    free_irq(pdev->irq, pdev);
    pci_disable_msi(pdev);
    pci_disable_device(pdev);
}

MSI-X — Çok Kuyruklu Cihazlar

#include <linux/pci.h>

#define MAX_QUEUES 8

struct mypcie_priv {
    struct pci_dev *pdev;
    int num_queues;
    struct msix_entry msix[MAX_QUEUES];
};

static int mypcie_setup_msix(struct mypcie_priv *priv)
{
    int i, ret;

    /* msix_entry dizisini hazırla */
    for (i = 0; i < MAX_QUEUES; i++)
        priv->msix[i].entry = i;  /* istenen vektör indeksi */

    /*
     * pci_enable_msix_range: min ve max vektör sayısı belirt.
     * Kernel en fazla alabileceği kadar verir.
     * Dönüş değeri: atanan vektör sayısı (pozitif) veya hata (negatif)
     */
    ret = pci_enable_msix_range(priv->pdev,
                                priv->msix,
                                1,           /* minimum */
                                MAX_QUEUES); /* maksimum */
    if (ret < 0)
        return ret;

    priv->num_queues = ret;
    dev_info(&priv->pdev->dev, "%d MSI-X vektörü atandı\n", ret);

    /* Her kuyruk için ayrı handler kaydet */
    for (i = 0; i < priv->num_queues; i++) {
        ret = request_irq(priv->msix[i].vector,
                          mypcie_queue_irq,
                          0,
                          "mypcie-queue",
                          &priv->queues[i]);
        if (ret)
            goto err_free;
    }

    return 0;

err_free:
    while (--i >= 0)
        free_irq(priv->msix[i].vector, &priv->queues[i]);
    pci_disable_msix(priv->pdev);
    return ret;
}

07 IRQ Domain — SoC Interrupt Controller Sürücüsü

IRQ domain, donanım IRQ numaralarını Linux sanal IRQ numaralarına (virq) eşleyen soyutlama katmanıdır. Her interrupt controller bir IRQ domain oluşturur; zincirleme controller'lar (cascade) iç içe domain hiyerarşisi kurar.

IRQ Domain Kavramı

Donanım HW IRQ 42
        │
        ▼
  irq_domain_add_linear()
  veya irq_domain_add_tree()
  ile oluşturulan domain
        │
        ▼  irq_create_mapping(domain, 42)
  Linux virq 120   ← /proc/interrupts'ta görünen numara
        │
        ▼
  request_irq(120, handler, ...)
    

Basit Interrupt Controller Sürücüsü

#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>

#define MY_INTC_NUM_IRQS  32
#define MY_INTC_STATUS    0x00
#define MY_INTC_MASK      0x04
#define MY_INTC_CLEAR     0x08

struct my_intc {
    void __iomem       *base;
    struct irq_domain  *domain;
    int                 parent_irq;
};

/* irq_chip: donanım üzerindeki mask/unmask/ack işlemleri */
static void my_intc_mask(struct irq_data *d)
{
    struct my_intc *intc = irq_data_get_irq_chip_data(d);
    u32 mask = readl(intc->base + MY_INTC_MASK);
    mask &= ~BIT(d->hwirq);
    writel(mask, intc->base + MY_INTC_MASK);
}

static void my_intc_unmask(struct irq_data *d)
{
    struct my_intc *intc = irq_data_get_irq_chip_data(d);
    u32 mask = readl(intc->base + MY_INTC_MASK);
    mask |= BIT(d->hwirq);
    writel(mask, intc->base + MY_INTC_MASK);
}

static void my_intc_ack(struct irq_data *d)
{
    struct my_intc *intc = irq_data_get_irq_chip_data(d);
    writel(BIT(d->hwirq), intc->base + MY_INTC_CLEAR);
}

static struct irq_chip my_irq_chip = {
    .name        = "my-intc",
    .irq_mask    = my_intc_mask,
    .irq_unmask  = my_intc_unmask,
    .irq_ack     = my_intc_ack,
};

/* irq_domain_ops: HW IRQ → virq eşlemesi */
static int my_intc_domain_map(struct irq_domain *d,
                               unsigned int virq,
                               irq_hw_number_t hwirq)
{
    struct my_intc *intc = d->host_data;

    irq_set_chip_data(virq, intc);
    irq_set_chip_and_handler(virq, &my_irq_chip, handle_level_irq);
    irq_set_noprobe(virq);
    return 0;
}

static const struct irq_domain_ops my_intc_domain_ops = {
    .map    = my_intc_domain_map,
    .xlate  = irq_domain_xlate_onecell,  /* DT'de tek hücreli IRQ tanımı */
};

/* Parent IRQ handler — chained interrupt controller */
static void my_intc_handler(struct irq_desc *desc)
{
    struct my_intc *intc = irq_desc_get_handler_data(desc);
    struct irq_chip *chip = irq_desc_get_chip(desc);
    u32 status;
    int hwirq;

    chained_irq_enter(chip, desc);

    status = readl(intc->base + MY_INTC_STATUS);
    while (status) {
        hwirq = __ffs(status);   /* en düşük set bit */
        generic_handle_irq(irq_find_mapping(intc->domain, hwirq));
        status &= ~BIT(hwirq);
    }

    chained_irq_exit(chip, desc);
}

static int my_intc_probe(struct platform_device *pdev)
{
    struct my_intc *intc;
    struct resource *res;
    int parent_irq;

    intc = devm_kzalloc(&pdev->dev, sizeof(*intc), GFP_KERNEL);
    if (!intc)
        return -ENOMEM;

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    intc->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(intc->base))
        return PTR_ERR(intc->base);

    /* Linear domain — sabit sayıda HW IRQ */
    intc->domain = irq_domain_add_linear(pdev->dev.of_node,
                                          MY_INTC_NUM_IRQS,
                                          &my_intc_domain_ops,
                                          intc);
    if (!intc->domain)
        return -ENOMEM;

    parent_irq = platform_get_irq(pdev, 0);
    irq_set_chained_handler_and_data(parent_irq,
                                     my_intc_handler,
                                     intc);

    platform_set_drvdata(pdev, intc);
    dev_info(&pdev->dev, "my-intc: %d IRQ, parent=%d\n",
             MY_INTC_NUM_IRQS, parent_irq);
    return 0;
}

08 Hata Ayıklama

IRQ sorunları — spurious kesmeler, handler çakışmaları, latency gerilemeleri — sistemin en zor hata ayıklama senaryolarından birini oluşturur. Linux bu amaçla zengin bir araç seti sunar.

/proc/interrupts Okuma

cat /proc/interrupts
#            CPU0       CPU1       CPU2       CPU3
#   0:   12873456          0          0          0  IR-IO-APIC    2-edge  timer
#   8:          0          1          0          0  IR-IO-APIC    8-edge  rtc0
#  42:          0    4567890          0          0  PCI-MSI 524288-edge  eth0-rx-0
#  43:          0          0    9876543          0  PCI-MSI 524289-edge  eth0-rx-1
# NMI:         12         11         10         11   Non-maskable interrupts
# ERR:          0
# MIS:          0   ← Missed/spurious IRQ sayısı — sıfır olmalı!

# Belirli bir IRQ'yu izle (1 saniye aralıkla)
watch -n1 'grep eth0 /proc/interrupts'

irqsoff Tracer — Maksimum IRQ Kapalı Süre

# ftrace irqsoff tracer'ı etkinleştir
echo 0 > /sys/kernel/debug/tracing/tracing_on
echo irqsoff > /sys/kernel/debug/tracing/current_tracer
echo 1 > /sys/kernel/debug/tracing/tracing_on

# Bir süre bekle, sonra durdur
sleep 5
echo 0 > /sys/kernel/debug/tracing/tracing_on

# En uzun IRQ kapalı kalma olayını görüntüle
cat /sys/kernel/debug/tracing/trace
# irqsoff latency trace v1.1.5 on 6.6.0-rt
# latency: 47 us, #4/4, CPU#1 | (M:preempt VP:0, KP:0, SP:0 HP:0)
# -----------------
# 1)  <idle>-0    |   0.000 us  | _raw_spin_lock_irqsave();
# 1)  <idle>-0    |  47.123 us  | _raw_spin_unlock_irqrestore();

lockdep ile IRQ Lock Doğrulama

/* Kernel CONFIG_LOCKDEP=y ile derlenmişse:
 *
 * Yanlış kullanım: process context'te spin_lock() — IRQ handler ile deadlock riski
 * lockdep bunu boot'ta veya ilk çağrıda tespit eder.
 *
 * Uyarı mesajı örneği:
 *
 * WARNING: possible irq lock inversion dependency detected
 * mydrv_write/1234 just changed the state of lock:
 * (mydrv_lock){....}, at: mydrv_write+0x40/0x80
 * but this lock took another, HARDIRQ-safe lock in the past:
 * (mydrv_irq_handler)
 *
 * Çözüm: process context'te spin_lock_irqsave kullan
 */

/* Doğrulama için bilinçli test */
#include <linux/lockdep.h>

/* Handler'ın gerçekten IRQ bağlamında çalıştığını assert et */
lockdep_assert_irqs_disabled();

/* Handler'ın process bağlamında çalıştığını assert et */
lockdep_assert_irqs_enabled();

spurious_irq Tespiti

/* Kernel spurious IRQ'yu otomatik raporlar:
 *
 * kernel: irq 42: nobody cared (try booting with the "irqpoll" option)
 * kernel: handlers:
 * kernel: [<ffffffff81234567>] mydrv_handler
 * kernel: Disabling IRQ #42
 *
 * Neden olur:
 * 1. IRQ handler IRQ_NONE döndürüyor ama IRQ devam ediyor
 * 2. Donanım status register okunmadan önce acknowledge edildi
 * 3. Edge-triggered IRQ'da clear işlemi yanlış sırada yapıldı
 *
 * Debug için: irqpoll boot parametresi ile tüm IRQ'lar poll moduna alınır
 */

/* Her handler'da bu kontrolü yap: */
static irqreturn_t safe_handler(int irq, void *dev_id)
{
    struct mydrv_priv *priv = dev_id;
    u32 status = readl(priv->base + STATUS_REG);

    if (!(status & OUR_IRQ_BIT)) {
        /* Bu satır çok sık görünüyorsa spurious IRQ var */
        dev_dbg(&priv->dev, "spurious IRQ (status=0x%08x)\n", status);
        return IRQ_NONE;
    }
    /* ... */
    return IRQ_HANDLED;
}

perf ile IRQ İstatistikleri

# IRQ event'lerini say (1 saniye boyunca)
perf stat -e irq:irq_handler_entry,irq:irq_handler_exit sleep 1

# Hangi handler en çok çağrılıyor?
perf record -e irq:irq_handler_entry -ag sleep 10
perf report

# ftrace ile handler giriş/çıkış zamanlaması
echo 'irq:irq_handler_entry irq:irq_handler_exit' > \
    /sys/kernel/debug/tracing/set_event
cat /sys/kernel/debug/tracing/trace_pipe

Yaygın Sorunlar ve Çözümler

IRQ stormSeviye tetiklemeli IRQ'da kaynak temizlenmeden handler dönüyor — IRQ tekrar tekrar tetikleniyor. Çözüm: status/clear sırasını kontrol et, IRQF_ONESHOT kullan.
Missed interruptHandler çalışmadan önce ikinci kesme geldi ve edge-triggered kontrolör bunu kaydetmedi. Çözüm: threaded IRQ + IRQF_ONESHOT kombinasyonu veya seviye tetiklemeye geç.
Handler hiç çağrılmıyorIRQ mask register'ı enable edilmemiş veya DT'de yanlış IRQ numarası. Çözüm: platform_get_irq() dönüş değerini logla, /proc/interrupts'ta sayacı izle.
Yüksek latencyHardirq handler çok uzun veya spin_lock tutma süresi fazla. Çözüm: irqsoff tracer ile ölç, işlemleri threaded IRQ'ya taşı.