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.

TargetReading Location
Numbering and DispatchSections 1–3
Execution Context and Deferred WorkSections 4–5
Exit and Effective AffinitySections 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

ConceptResponsibilityCommonly Confused With
hwirqHardware number within an interrupt domainNot a system-wide unique number
Linux IRQLogical identifier for IRQ management by the kernelNot equal to x86 CPU vectors
CPU vector / Architecture Interrupt IDArchitecture entry identification and routingSpecific meaning depends on APIC, GIC, etc.
irq_domainMaps and manages the relationship between hardware domains and Linux IRQsCan be hierarchical; not simply adding a fixed offset
irq_chipController operations, such as mask, ack, EOI, affinityNot equal to the handler that the driver uses to process device business
irq_descIRQ state, control flow, statistics, and actionsOne IRQ can be associated with multiple shared actions
irqactionHandler function, parameters, and thread information registered by the driverNot 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

Hardware notification to driver: distinct responsibilities Device event Level, edge, or MSI; not necessarily one notification per event 1 Interrupt controller and architecture entry Route to CPU and enter the interrupt/exception path 2 Linux IRQ descriptor and flow handler Coordinate controller mask, ack, and EOI 3 Driver primary handler Identify source, handle state, or return IRQ_WAKE_THREAD 4 Deferred work and resource reclamation Choose IRQ thread, NAPI, ordinary workqueue, etc. 5 EOI, clearing device state, masking IRQs, and stopping DMA are different operations.

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

OperationStatus Layer Handled
MaskWhether to continue dispatching notifications
AckController identification/acknowledgment
EOINotifies the controller that the relevant service phase has ended
Clear device status, consume completion queueDriver 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

Preparing the visual
EOI ends service, not the device’s interrupt cause

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 ValueMeaning
IRQ_NONEThis handler did not confirm processing its own event
IRQ_HANDLEDHandled its own interrupt event
IRQ_WAKE_THREADRequests 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

MechanismExecution CharacteristicsDesign Boundaries
softirqFixed categories, suspended and executed per CPU, possibly handled by ksoftirqdOrdinary softirq callbacks cannot sleep arbitrarily; can execute concurrently across CPUs
taskletOlder deferred mechanism based on softirqThe same tasklet instance will not execute simultaneously; not all tasklets of the same type are mutually exclusive
Regular workqueueWorker pool provides process contextCan sleep according to rules; a dedicated queue does not equal a fixed exclusive thread
WQ_BH workqueueUses BH/softirq context in kernels supporting this interfaceCannot blindly apply sleep assumptions from regular workqueues
IRQ threadKernel thread associated with IRQ actionHandles 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:

PhaseCondition to Establish
Stop new submissionsNo new business is generated
Control device source and DMAHardware no longer generates dangerous accesses
Unregister/sync IRQCorresponding handlers no longer use resources
Cancel or wait for background workPreviously queued callbacks no longer access context
Release resourcesPrevious 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.

from pathlib import Path

for irq in sorted(Path('/proc/irq').glob('[0-9]*'), key=lambda p: int(p.name))[:12]:
    values = {}
    for field in ('smp_affinity_list', 'effective_affinity_list'):
        try:
            values[field] = (irq / field).read_text().strip()
        except OSError:
            pass
    print(irq.name, values)

/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.