Interrupt Concepts
Hardware interrupts are signals from devices that need CPU attention. Understanding interrupt concepts is fundamental to writing responsive device drivers.
What is an Interrupt?
An interrupt is an asynchronous signal indicating that a device needs attention:
sequenceDiagram
participant CPU as CPU (running process)
participant PIC as Interrupt Controller
participant DEV as Device
Note over CPU: Normal execution
DEV->>PIC: Assert IRQ line
PIC->>CPU: Interrupt signal
Note over CPU: Save current state
CPU->>CPU: Jump to handler
CPU->>DEV: Service interrupt
Note over CPU: Restore state
Note over CPU: Resume execution
Types of Interrupts
Hardware Interrupts (IRQs)
External signals from hardware devices:
flowchart LR
subgraph Devices
NIC[Network Card]
DISK[Disk Controller]
UART[Serial Port]
GPIO[GPIO Pin]
end
subgraph Controller["Interrupt Controller"]
GIC[GIC / APIC / PIC]
end
CPU[CPU]
NIC -->|IRQ| GIC
DISK -->|IRQ| GIC
UART -->|IRQ| GIC
GPIO -->|IRQ| GIC
GIC -->|"Prioritized interrupt"| CPU
Software Interrupts (Exceptions)
Generated by software or CPU:
- Exceptions: Division by zero, page faults
- System calls: INT 0x80, SYSCALL instruction
- Softirqs: Deferred interrupt processing
IRQ Numbers
Each interrupt source has a unique IRQ number:
/* Getting IRQ number from platform device */
int irq = platform_get_irq(pdev, 0); /* First IRQ */
int irq2 = platform_get_irq(pdev, 1); /* Second IRQ (if any) */
/* Named IRQ from Device Tree */
int irq = platform_get_irq_byname(pdev, "rx");
/* From Device Tree node directly */
int irq = of_irq_get(node, 0);
/* Legacy: From I/O resource */
struct resource *res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
int irq = res->start;
Virtual vs Hardware IRQ Numbers
Linux uses virtual IRQ numbers that map to hardware interrupt lines:
flowchart LR
subgraph Hardware
HWIRQ0["HW IRQ 32"]
HWIRQ1["HW IRQ 47"]
HWIRQ2["HW IRQ 128"]
end
subgraph Linux["Linux Virtual IRQs"]
VIRQ0["virq 45"]
VIRQ1["virq 46"]
VIRQ2["virq 47"]
end
subgraph Domain["IRQ Domain"]
MAP[Mapping]
end
HWIRQ0 --> MAP
HWIRQ1 --> MAP
HWIRQ2 --> MAP
MAP --> VIRQ0
MAP --> VIRQ1
MAP --> VIRQ2
Interrupt Context
Code runs in different contexts with different rules:
Process Context
Normal kernel code (system calls, kernel threads):
- Can sleep (wait_event, mutex_lock)
- Can allocate with GFP_KERNEL
- Has a valid
currenttask - Can access user space
Interrupt Context (hardirq)
Interrupt handler code:
- Cannot sleep
- Must use GFP_ATOMIC for allocation
currentis undefined/unreliable- Cannot access user space
- Should be fast
Softirq/Tasklet Context
Deferred interrupt work:
- Cannot sleep
- Can be preempted by hardirqs
- GFP_ATOMIC required
- Can run concurrently on different CPUs (softirqs)
Checking Context
#include <linux/preempt.h>
/* Check if in interrupt context */
if (in_interrupt()) {
/* hardirq, softirq, or NMI context */
/* Cannot sleep! */
}
/* Check specifically in hardirq */
if (in_irq()) {
/* In hardware interrupt handler */
}
/* Check if sleeping is allowed */
if (preemptible()) {
/* Safe to sleep */
}
/* Better: might_sleep() debug check */
might_sleep(); /* Warns if called from atomic context */
Interrupt Flow
flowchart TB
subgraph Hardware
IRQ[IRQ Signal]
end
subgraph Entry["Kernel Entry"]
SAVE[Save CPU State]
DISABLE[Disable Preemption]
ACK[Acknowledge Controller]
end
subgraph Dispatch["IRQ Dispatch"]
LOOKUP[Find Handler]
CALL[Call Handler Chain]
end
subgraph Handler["Your Handler"]
CHECK[Check if yours]
PROCESS[Handle interrupt]
SCHED[Schedule bottom half]
RET[Return IRQ_HANDLED]
end
subgraph Exit["Kernel Exit"]
SOFTIRQ[Run pending softirqs]
ENABLE[Enable Preemption]
RESTORE[Restore CPU State]
end
IRQ --> SAVE
SAVE --> DISABLE
DISABLE --> ACK
ACK --> LOOKUP
LOOKUP --> CALL
CALL --> CHECK
CHECK --> PROCESS
PROCESS --> SCHED
SCHED --> RET
RET --> SOFTIRQ
SOFTIRQ --> ENABLE
ENABLE --> RESTORE
Interrupt Latency
Time from interrupt assertion to handler execution:
flowchart TB
A["IRQ Asserted"]
B["Controller Routes"]
C["CPU Acknowledges"]
D["Context Switch"]
E["Handler Runs"]
A -->|"~ns"| B
B -->|"~ns"| C
C -->|"~us"| D
D -->|"~us"| E
Factors affecting latency:
- Interrupt controller configuration
- CPU frequency and state
- Other IRQs being processed
- Preemption/interrupt disable periods
Interrupt Affinity
IRQs can be pinned to specific CPUs:
# View current affinity
cat /proc/irq/45/smp_affinity
# Set affinity (hexadecimal CPU mask)
echo 2 > /proc/irq/45/smp_affinity # CPU 1 only
echo f > /proc/irq/45/smp_affinity # CPUs 0-3
# View effective affinity
cat /proc/irq/45/effective_affinity
Programmatic control:
#include <linux/interrupt.h>
/* Set IRQ affinity */
cpumask_t mask;
cpumask_clear(&mask);
cpumask_set_cpu(2, &mask); /* CPU 2 */
irq_set_affinity(irq, &mask);
Edge vs Level Triggered
flowchart LR
subgraph Edge["Edge Triggered"]
direction LR
E1["Signal: LOW → HIGH"]
E2["IRQ fires once on transition"]
end
subgraph Level["Level Triggered"]
direction LR
L1["Signal: HIGH"]
L2["IRQ fires while signal active"]
end
Edge --> EN["One IRQ per edge"]
Level --> LN["Continuous until cleared"]
style E1 fill:#738f99,stroke:#0277bd
style L1 fill:#8f8a73,stroke:#f9a825
Edge Triggered
- Interrupt fires on signal transition (rising/falling edge)
- Miss interrupt if signal changes while processing
- Common for external events (button press)
Level Triggered
- Interrupt fires while signal is active
- Must clear interrupt source or will re-trigger
- Common for device status (data available)
/* Edge triggered */
ret = request_irq(irq, handler, IRQF_TRIGGER_RISING, "mydev", data);
/* Level triggered */
ret = request_irq(irq, handler, IRQF_TRIGGER_HIGH, "mydev", data);
/* Both edges */
ret = request_irq(irq, handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"mydev", data);
Viewing System Interrupts
# View all interrupts and counts
cat /proc/interrupts
# Example output:
# CPU0 CPU1 CPU2 CPU3
# 0: 23 0 0 0 IO-APIC 2-edge timer
# 8: 0 0 0 1 IO-APIC 8-edge rtc0
# 45: 124567 3241 12034 8234 PCI-MSI 524288-edge eth0
Columns show:
- IRQ number
- Per-CPU interrupt counts
- Interrupt controller type
- Trigger type
- Device name(s)
Summary
- Interrupts are asynchronous hardware signals
- IRQ numbers uniquely identify interrupt sources
- Interrupt context has strict rules (no sleeping)
- Edge vs level triggering affects handler design
- Interrupt affinity controls CPU routing
- Check context with
in_interrupt(),in_irq()
Next
Learn how to request and free IRQs in your driver.