This post contains some notes relating to the code execution flow in the Linux kernel when an interrupt request (IRQ) is being addressed by the system. According to the documentation, the IRQ subsystem provides three main layers of abstraction:

  1. High-level driver API
  2. High-level IRQ flow handlers
  3. Chip-level hardware encapsulation

The IRQ handling process deals mostly with flow handlers and chip-level abstraction, so the IRQ driver API will not be explored here.

When some significant hardware change occurs, low-level architecture code either calls the handle_irq function stored in the IRQ descriptor (irq_desc) directly or call a more generic generic_handle_irq_desc function. Because hardware can provide different types of interrupt sources (the rising/falling edge of an electric signal, the voltage level (low/high) of a circuit line) different actions may need to be taken in each case. The high-level IRQ flow handlers provide pre-defined approaches to deal with hardware interrupts. These flow handlers are assigned to the interrupt descriptors at boot time or during device initialization. It is also possible for architecture code to implement specific flow handlers. Whatever function best suits the interrupt flow handling, irq_desc holds a pointer to it which is then used to give appropriate continuation to the IRQ handling.

Assuming the flow handler is one of the high-level IRQ flow handlers, it may do a few things before proceeding with the IRQ handling. For instance, the hardware interrupt controller may somehow need an ack from the CPU, signaling that the interrupt was properly received. It may also be needed to mask (disable) and/or unmask (enable) interrupts for some chips. The beauty in it is that the flow handlers don’t need to know architecture-specific details to accomplish these actions, all of it can be done relying on the irq_chip abstraction which encapsulates the hardware relevant functions. Afterward, handle_irq_event is called to set the IRQ state as “in progress”, acquire the IRQ description lock, and then call handle_irq_event_percpu. If handle_percpu_irq is the flow handler being used, the number of IRQs handled by the CPU is increment and handle_irq_event_percpu is called directly (per CPU IRQs are not serialized).

handle_irq_event_percpu does nothing but calling __handle_irq_event_percpu and waiting for its return to add some randomness to the pool of interrupts handled by the CPU. __handle_irq_event_percpu is where the IRQ subsystem finally let other stakeholders of the interrupt event take actions. The IRQ descriptor has a list of actions, each one having a handler. The handle irq event function calls the handler function for each action as can be seen in both the diagram and code below. This handler is known as the “top half” of IRQ handling. The action may have an additional handler function (thread_fn) which, if not NULL, is executed in a threaded interrupt context (this is the bottom half of IRQ handling). Both the handler and the threaded handler functions are assigned to the irq_desc struct by calling request_threaded_irq, which is often done, direct or indirectly, by device drivers during device initialization. Therefore, the for_each_action_of_desc loop in __handle_irq_event_percpu is very important because it is where drivers get notified of hardware events and can work over them, for instance, fetching data from a device.

Function call diagram for the IRQ handling process.
The __handle_irq_event_percpu function. The place where IRQ actions are invoked.

After all the actions have been executed or any of them have returned IRQ_HANDLED, __handle_irq_event_percpu returns an oring with all the flags returned by each executed handler. These flags are used by handle_irq_event_percpu to add randomness to IRQ handling as mentioned above. After that, the flags are returned back to handle_irq_event (or to handle_percpu_irq) which in turn returns the flags back to the flow handlers. Though none of the generic flow handlers use the returned flags, the will be available in case architecture-specific flow handlers want to use them.

While seeking to understand the IRQ handling process in the Linux kernel, I consulted some related works that, despite not referenced above, helped me to put all the pieces together to the point of writing these notes.

The Linux Device Drivers book has an entire chapter about interrupts.

Alison and Peloton’s talk about IRQs in the Embeded Linux Conference.

In tree files:

  • Documentation/core-api/genericirq.rst
  • include/linux/interrupt.h
  • include/linux/irqdesc.h
  • kernel/irq/chip.c
  • kernel/irq/handle.c
  • kernel/irq/manage.c

Revision History:

  • Rev1 (2020-05-07): Release