Table of Contents

Interrupt delivery

A device asks for the CPU's attention by raising an interrupt request (IRQ), which travels through an interrupt controller: the I/O APIC and local APIC on x64, the GIC on ARM64. The controller numbers the sources, can mask each one, and turns a request into the number the kernel dispatches on. After the handler, the kernel signals end of interrupt so the controller can deliver the next one.

End of interrupt informs the controller, not the device. The device keeps its cause pending in a status register, which only its driver knows how to clear. A level-triggered line stays asserted while a cause is pending, so a handler that leaves it set runs again at once, forever. On an edge-triggered line the controller sees only the transition, so a cause left pending may never produce another edge, and the device falls silent. Either way, the handler acknowledges the device first. Masking the source is the other tool, for a cause only a thread can clear.

A classic PCI function signals on a legacy pin (INTx), a level-triggered wire with board-specific routing that several devices may share, so each handler on it checks that its own device raised it. MSI and MSI-X replace the wire with a memory write: the device writes a value to an address, both chosen by the kernel, and the controller turns the write into an interrupt. Messages are never shared, need no board routing, and a device can have one per queue. MSI-X keeps its message table, with a mask bit per entry, in one of the device's BARs.

In the driver kit, a PCI node's legacy line is routed on x64 only, for lines 3 to 15, never shared, and programmed edge-triggered, which QEMU delivers but a real chipset may not; its messages are MSI-X only, up to 32, need a GICv3 Interrupt Translation Service (ITS) on ARM64, and connecting one turns the line off. InterruptHandle.Mask() and Unmask() stop and resume one source, at the controller for a line and in the table for a message. See Interrupts, The legacy line and Message interrupts.