Understanding Linux Interrupt Number Mapping Mechanism
This article explores the Linux kernel's interrupt management architecture, detailing how hardware interrupt IDs are mapped to software IRQ numbers through the irq_domain structure, GIC controller design, and various mapping strategies such as linear, radix‑tree, and no‑map approaches.
1. Introduction
In the complex Linux operating system, interrupt management acts as a scheduler that ensures timely handling of hardware requests, maintaining efficient and stable operation. A crucial yet opaque part of this system is the mapping of hardware interrupt numbers to Linux IRQ numbers.
2. Interrupt Controllers
Different CPU architectures use different interrupt controllers: ARM employs the Generic Interrupt Controller (GIC) while x86 uses the Advanced Programmable Interrupt Controller (APIC). The GIC‑400 on an ARM Vexpress V2P‑CAIS‑CA7 platform supports multiple interrupt states (inactive, pending, active, active and pending) and categorises interrupts into SGI (0‑15), PPI (16‑31) and SPI (32‑1019).
The GIC consists of a distributor (arbitration unit) and a CPU interface. The distributor maintains a state machine for each interrupt source, while the CPU interface forwards selected interrupts to the processor.
3. Mapping Hardware Interrupt Numbers to Linux IRQ Numbers
Linux uses the request_irq() and request_threaded_irq() APIs, which work with software IRQ numbers rather than raw hardware IDs. For example, a QEMU virtual serial device has hardware ID 33, which must be mapped to a Linux IRQ.
On ARM64 platforms, devices are described using a Device Tree. A fragment of a Device Tree node for a PL011 UART looks like:
pl011@9000000 {
clock-names = "uartclk\Oapb_pclk";
clocks = < 0x8000 0x8000 >;
// interrupts: type (GIC_SPI=0), ID (0x01), trigger (level high)
interrupts = < 0x00 0x01 0x04 >;
reg = < 0x00 0x9000000 0x00 0x1000 >;
compatible = "arm,pl011\0arm,primecell";
};The kernel abstracts each interrupt controller with an irq_domain structure:
struct irq_domain {
struct list_head link;
const char *name;
const struct irq_domain_ops *ops;
...
irq_hw_number_t hwirq_max;
unsigned int revmap_direct_max_irq;
unsigned int revmap_size;
struct radix_tree_root revmap_tree;
struct mutex revmap_tree_mutex;
unsigned int linear_revmap[];
};During boot, the kernel registers an irq_domain for the GIC using __irq_domain_add() :
struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, int size,
irq_hw_number_t hwirq_max, int direct_max,
const struct irq_domain_ops *ops,
void *host_data)
{
...
domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
GFP_KERNEL, of_node_to_nid(of_node));
...
list_add(&domain->link, &irq_domain_list);
...
}The core mapping function is irq_of_parse_and_map() :
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
{
struct of_phandle_args oirq;
if (of_irq_parse_one(dev, index, &oirq))
return 0;
return irq_create_of_mapping(&oirq);
}This function parses the interrupts property, creates a irq_fwspec , and ultimately calls irq_create_fwspec_mapping() to establish the hardware‑to‑software mapping:
unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec)
{
...
// Find the matching irq_domain
if (fwspec->fwnode) {
domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_WIRED);
if (!domain)
domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_ANY);
} else {
domain = irq_default_domain;
}
...
// Translate hardware ID to Linux IRQ
if (irq_domain_translate(domain, fwspec, &hwirq, &type))
return 0;
...
virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, fwspec);
...
return virq;
}4. Mapping Strategies
The kernel supports three mapping methods:
Linear map : a fixed‑size table indexed by hardware IDs; simple and fast but memory‑inefficient for sparse ranges.
Tree map : a radix‑tree structure that scales well for large or sparse ID spaces, at the cost of slightly slower look‑ups.
No map : the hardware ID is used directly as the Linux IRQ when it is already unique and suitable.
5. Core Data Structures in Interrupt Management
irq_desc describes each Linux interrupt, holding the handler, status, and a pointer to irq_data .
irq_data links a software IRQ to its hardware counterpart ( irq and hwirq ) and references the associated irq_chip and irq_domain .
irq_chip provides operations for the underlying hardware controller (startup, shutdown, enable, disable, etc.).
irq_domain abstracts the interrupt controller, offering mapping operations via irq_domain_ops (match, map, unmap, xlate).
6. Conclusion
The Linux interrupt number mapping mechanism is essential for translating hardware interrupt IDs into kernel‑usable IRQ numbers. By understanding the GIC controller, the irq_domain abstraction, and the available mapping strategies, developers can better grasp how Linux manages and services hardware interrupts across diverse architectures.
Deepin Linux
Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.