Interrupt Subsystem
On this page
Devices continuously report interrupts, yet DMA may still write to memory after masking. Here, we need to trace notifications, device status, and background work separately.
| Target | Reading Location |
|---|---|
| Numbering and Dispatch | Sections 1–3 |
| Execution Context and Deferred Work | Sections 4–5 |
| Exit and Effective Affinity | Sections 6–7 |
Interrupts are a mechanism for devices to notify the CPU of events. A notification may correspond to multiple completed requests, and a single request may involve multiple notifications. Therefore, drivers should ultimately check device status, queues, or completion records, rather than treating interrupt counts directly as business completion counts.
1. Three Numbering Systems and Four Objects
| Concept | Responsibility | Commonly Confused With |
|---|---|---|
| hwirq | Hardware number within an interrupt domain | Not a system-wide unique number |
| Linux IRQ | Logical identifier for IRQ management by the kernel | Not equal to x86 CPU vectors |
| CPU vector / Architecture Interrupt ID | Architecture entry identification and routing | Specific meaning depends on APIC, GIC, etc. |
| irq_domain | Maps and manages the relationship between hardware domains and Linux IRQs | Can be hierarchical; not simply adding a fixed offset |
| irq_chip | Controller operations, such as mask, ack, EOI, affinity | Not equal to the handler that the driver uses to process device business |
| irq_desc | IRQ state, control flow, statistics, and actions | One IRQ can be associated with multiple shared actions |
| irqaction | Handler function, parameters, and thread information registered by the driver | Not necessarily one-to-one with a dedicated physical line |
A GPIO controller can act as a subordinate to another interrupt controller, meaning interrupt domains can form a hierarchy. PCI MSI can also go through multiple layers of management, including the MSI domain, remapping, and CPU routing. Drivers usually obtain the Linux IRQ provided by the subsystem and register handlers; they should not guess hardware numbers. See the irq_domain documentation.
2. Event Dispatch and Acknowledgment
The architecture entry saves the necessary context and establishes the kernel processing environment. Specific stack switching and register saving rules vary by architecture and entry type. You cannot write the same fixed function chain for syscall entries, x86 external interrupts, and ARM exception handling.
The generic IRQ layer calls the flow handler installed for that IRQ, then calls the irq_chip and driver action. generic_handle_irq_desc() is a general dispatch-related interface and should not be drawn side-by-side with handle_edge_irq() and handle_fasteoi_irq() as three trigger types.
Mask, Ack, EOI, and Device Status
| Operation | Status Layer Handled |
|---|---|
| Mask | Whether to continue dispatching notifications |
| Ack | Controller identification/acknowledgment |
| EOI | Notifies the controller that the relevant service phase has ended |
| Clear device status, consume completion queue | Driver processes the actual event source according to the device protocol |
Controller acknowledgment and device business completion occur separately; one cannot replace the other.
If a level-triggered source remains active, it can notify again after unmasking; for edge-triggered sources, you must consider whether the notification is latched and handle new edges during processing. Different flow handlers organize operations for these constraints. fasteoi cannot simply be written as "mask first, process, then unmask at the end"—its core is the corresponding EOI protocol, handling masking based on status, oneshot, and other conditions.
Sending EOI to the controller does not automatically clear the device's interrupt reason, and masking the IRQ does not automatically stop DMA. Reversing these two layers can lead to interrupt storms or DMA writes after release. See generic IRQ.
A still-asserted level can notify again after unmasking. Edge sources have different latching/new-edge conditions. Fasteoi is organized around its EOI contract; masking also depends on state and oneshot, not one mandatory mask-handler-unmask sequence. EOI does not clear the device cause; IRQ masking does not stop DMA. Generic IRQ
The device cause, controller in-service state, and mask are distinct. EOI does not clear the device cause; masking does not stop DMA or clear work queues.
3. Shared IRQs and Return Values
Handlers on shared lines must confirm whether the event belongs to them. dev_id identifies the corresponding registration item and private context, and must also be matched during unregistration; you cannot unconditionally claim successful interrupt processing if you cannot identify the source.
| Return Value | Meaning |
|---|---|
| IRQ_NONE | This handler did not confirm processing its own event |
| IRQ_HANDLED | Handled its own interrupt event |
| IRQ_WAKE_THREAD | Requests execution of the registered thread_fn |
IRQ_WAKE_THREAD is a return value, not a registration flag named IRQF_WAKE_THREAD. Continuously unclaimed IRQs may trigger kernel exception statistics and handling, but thresholds and paths depend on the implementation; you cannot hardcode "exactly one hundred thousand times will definitely shut it down."
MSI usually avoids the source identification issues of traditional shared lines, but queue merging, interrupt aggregation, and completion processing still exist. pci_alloc_irq_vectors() returns the actual number allocated, and then pci_irq_vector() is used to obtain each IRQ; it does not fixedly return the application limit, nor does it directly return the first IRQ. See PCIe and USB Driver Model.
4. Primary Handler and Threaded IRQ
request_threaded_irq() can register a primary handler and a thread_fn. The primary handler should remain short and non-blocking in the actual hard interrupt path; return IRQ_WAKE_THREAD when subsequent thread processing is needed.
IRQF_ONESHOT causes the core to keep the IRQ masked during thread processing as agreed, preventing re-dispatch before related processing is completed. It does not guarantee that one hardware event corresponds to exactly one thread wake-up; the thread usually still needs to drain device status or queues.
When constraints are met, you can use the default primary handler with a thread_fn; shared lines especially require correct source identification and coordination. PREEMPT_RT's mandatory threading and exceptions change the actual context; you cannot infer that all handlers can sleep based on process name or API name alone.
IRQ threads can perform appropriate sleep operations, but cannot wait while holding a raw spinlock or depending on a path blocked by themselves. Threading reduces the length of hard interrupt segments but does not automatically fix lock ordering and resource lifetime.
5. softirq, tasklet, and workqueue
| Mechanism | Execution Characteristics | Design Boundaries |
|---|---|---|
| softirq | Fixed categories, suspended and executed per CPU, possibly handled by ksoftirqd | Ordinary softirq callbacks cannot sleep arbitrarily; can execute concurrently across CPUs |
| tasklet | Older deferred mechanism based on softirq | The same tasklet instance will not execute simultaneously; not all tasklets of the same type are mutually exclusive |
| Regular workqueue | Worker pool provides process context | Can sleep according to rules; a dedicated queue does not equal a fixed exclusive thread |
| WQ_BH workqueue | Uses BH/softirq context in kernels supporting this interface | Cannot blindly apply sleep assumptions from regular workqueues |
| IRQ thread | Kernel thread associated with IRQ action | Handles device protocol and oneshot/shared constraints |
The existence of ksoftirqd does not turn softirq callbacks into ordinary work functions that can arbitrarily take mutexes. Tasklets still exist in some kernel paths; new implementations should usually choose more appropriate threading, NAPI, or workqueue mechanisms; you cannot simply claim it has been deleted from the kernel.
Ten notifications, why work doesn't necessarily run ten times
When the same work is already pending, queue_work() may return false. It saves the scheduling state of "this work needs to be executed," not ten independent business events.
If ten IRQs all schedule the same work, the event count must be saved by other queues/counters, or the work must drain the device's queryable completion status. This way, even if scheduling notifications are merged, the business facts will not be reduced to just one.
The parallelism, ordering, and memory reclaim context of multiple works are determined by queue flags, pools, and configuration; use the corresponding ordered interfaces when strict ordering is required. See the workqueue documentation.
6. Exit Path: Stop New Activity First, Then Wait for Old Activity
Shut down activity sources step-by-step according to dependencies during exit:
| Phase | Condition to Establish |
|---|---|
| Stop new submissions | No new business is generated |
| Control device source and DMA | Hardware no longer generates dangerous accesses |
| Unregister/sync IRQ | Corresponding handlers no longer use resources |
| Cancel or wait for background work | Previously queued callbacks no longer access context |
| Release resources | Previous users have exited |
The actual order is affected by driver dependencies; especially prevent any callback from restarting already stopped resources.
disable_irq_nosync() does not wait for handlers currently executing; disable_irq() and synchronize_irq() and other waiting interfaces cannot be called while holding locks required by the handler. synchronize_irq() itself is not a measure to permanently prevent subsequent new IRQs.
free_irq() waits for and revokes the corresponding handler registration, but does not automatically stop device hardware, nor does it automatically cancel previously queued work. Shared IRQs cannot arbitrarily affect other devices by disabling the entire IRQ line; you should prioritize correctly controlling your own device source.
7. Affinity and Read-Only Observation
/proc/irq/N/smp_affinity_list expresses the requested CPU set, and effective_affinity_list shows the effective routing when supported. Managed IRQs, controller capabilities, CPU hotplug, irqbalance, and driver policies can all affect the actual result.
=
=
pass
/proc/interrupts also contains lines for certain architecture-specific events; not every line is a regular device IRQ with reassignable affinity. Concentrated count growth on a single CPU is not necessarily an error: you need to judge based on queue mapping, NUMA, NAPI, and business load.