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
svc komutu, x86'da int 0x80. Sistem çağrılarının temelini oluşturur.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ık | Tür | Kısaltma | Kullanım |
|---|---|---|---|
| 0–15 | Software Generated | SGI | IPI, çekirdekler arası mesajlaşma |
| 16–31 | Private Peripheral | PPI | Yerel timer (arch_timer), PMU |
| 32–1019 | Shared Peripheral | SPI | UART, I2C, GPIO, ETH — tüm sistem IRQ'ları |
| 1020–1023 | Rezerv | — | Özel durum kodları (spurious vb.) |
| 8192+ | Locality-specific PPI | LPI | GICv3/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ı
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ı
sleep(), mutex_lock(), wait_event(), schedule() — tümü yasaktır. Bu fonksiyonlar çağrıldığında might_sleep() DEBUG modda uyarı verir.GFP_ATOMIC ile yapılabilir. GFP_ATOMIC hata olasılığı yüksek olduğundan tercih edilmemelidir — önceden tahsis edin.in_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 | Öncelik | Kullanım Yeri |
|---|---|---|
| HI_SOFTIRQ | 0 (en yüksek) | Yüksek öncelikli tasklet'ler |
| TIMER_SOFTIRQ | 1 | Kernel timer wheel işleme |
| NET_TX_SOFTIRQ | 2 | Ağ paketi gönderimi |
| NET_RX_SOFTIRQ | 3 | Ağ paketi alımı (NAPI) |
| BLOCK_SOFTIRQ | 4 | Blok I/O tamamlama |
| SCHED_SOFTIRQ | 6 | CFS görev dağılımı (SMP) |
| TASKLET_SOFTIRQ | 7 (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
| Kriter | Tasklet | Workqueue |
|---|---|---|
| Uyuma / blocking I/O | Yasak | Serbest |
| Gecikme | Düşük (~microsaniye) | Daha yüksek (schedule gecikmesi) |
| Paralel çalışma | Aynı anda tek CPU | Birden fazla worker thread |
| Yeni kod önerisi | Kaçın — deprecated eğilimde | Tercih edilmeli |
| Bellek ayak izi | Küçük | Worker 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ı
| Özellik | Pin-based IRQ (INTx) | MSI / MSI-X |
|---|---|---|
| Mekanizma | Fiziksel sinyal hattı | Memory write (mesaj) |
| Paylaşım | IRQF_SHARED gerekebilir | Her vektör benzersiz |
| Vektör sayısı | 1 | MSI: 32, MSI-X: 2048'e kadar |
| Affinity | Kısıtlı | Vektör başına bağımsız |
| NUMA uyumu | Zayıf | Güçlü — vektör CPU'ya yakın |
| Hata tespiti | Zor (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