[Linux] IRQ management
Introduction
This is an explanation of how interrupt management works in the Linux kernel (ver = 5.10).
,--> hwirq
| |
`--------> `<irq_desc>
` | |
`----------------> `<irq_data>
` | | |
`------------------------> `<irq_chip>
| | .irq_startup
.handler | .irq_shutdown
.hwirq <--> irq .irq_enable
.domain .irq_disable
.parent_data .irq_ack
.irq_mask
.irq_unmask
.irq_eoi
.irq_set_affinity
...
.parent_device
hwirq
hwirq is the actual hardware-specific interrupt number.
The Linux kernel manages this by mapping it to a virtual interrupt number.
Developers define hardware interrupt numbers in the device-tree.
label: device@10000000 {
status = "okay";
compatible = "samsung,device";
interrupts = <GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH>;
...
}
Number 38 is the hardware interrupt number, and this information can be found in the SoC or device datasheet.
In the Linux kernel, this is managed as hwirq.
IRQ Information Management
The Linux kernel manages IRQ information through various objects.
Structures containing interrupt information include irq_chip, irq_desc, and irq_data.
irq_desc
irq_desc is the top-level descriptor for an IRQ line.
It stores the overall state of the corresponding IRQ.
It also maps the handler information connected to that IRQ.
The basic structure looks like this (include/linux/irqdesc.h):
struct irq_desc {
struct irq_common_data irq_common_data;
struct irq_data irq_data;
unsigned int __percpu *kstat_irqs;
irq_flow_handler_t handle_irq;
struct irqaction *action; /* IRQ action list */
...
struct mutex request_mutex;
int parent_irq;
struct module *owner;
const char *name;
} ____cacheline_internodealigned_in_smp;
Because it contains irq_data, if you know the irq_desc, you can retrieve the irq_data.
Conversely, you can use the container_of macro to get it.
This is implemented in the kernel.
irq_data
Inside irq_desc (or within its hierarchy), this structure holds the actual hardware IRQ number and information needed to control the IRQ (like irq_chip and hardware-specific flags).
It stores hardware-level IRQ numbers (hwirq), tree structure data (parent/child irq_data), and IRQ line options (masking, trigger types, etc.).
It can also store SoC or architecture-dependent data required when invoking methods provided by the irq_chip.
The basic structure looks like this (include/linux/irq.h):
struct irq_data {
u32 mask;
unsigned int irq;
unsigned long hwirq;
struct irq_common_data *common;
struct irq_chip *chip;
struct irq_domain *domain;
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
struct irq_data *parent_data;
#endif
void *chip_data;
};
Because it contains irq_chip, if you know the irq_data, you can retrieve the irq_chip.
Conversely, you can use the container_of macro to get it.
This is implemented in the kernel.
irq_chip
This is a table of function pointers that implements operations such as activating/deactivating hardware interrupts (mask/unmask), ack, and eoi (End Of Interrupt).
It provides control functions for the corresponding IRQ line (or group).
Examples include irq_chip->irq_ack, irq_chip->irq_mask, irq_chip->irq_unmask, and irq_chip->irq_eoi.
Typically, different interrupt control logic is implemented for each hardware type, such as ARM GIC (Generic Interrupt Controller), GPIO-based interrupt controllers, or PCI interrupts.
The basic structure looks like this (include/linux/irq.h):
struct irq_chip {
struct device *parent_device;
const char *name;
unsigned int (*irq_startup)(struct irq_data *data);
...
void (*irq_ack)(struct irq_data *data);
void (*irq_mask)(struct irq_data *data);
void (*irq_mask_ack)(struct irq_data *data);
void (*irq_unmask)(struct irq_data *data);
void (*irq_eoi)(struct irq_data *data);
int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
...
unsigned long flags;
};
IRQ handler
In the Linux kernel, interrupt handler functions have the irqreturn_t type.
The handler for each IRQ is managed within the irqaction of the irq_desc.
(include/linux/interrupt.h)
typedef irqreturn_t (*irq_handler_t)(int, void *);
...
struct irqaction {
irq_handler_t handler;
void *dev_id;
void __percpu *percpu_dev_id;
struct irqaction *next;
irq_handler_t thread_fn;
...
unsigned int irq;
unsigned int flags;
unsigned long thread_flags;
unsigned long thread_mask;
const char *name;
struct proc_dir_entry *dir;
} ____cacheline_internodealigned_in_smp;
irqreturn
Interrupts interrupt the CPU’s normal scheduling and consume resources.
Therefore, interrupts must be processed very quickly and concisely.
Sometimes, there may be a need to perform many operations inside an interrupt handler.
The Linux kernel offers interrupt deferred processing mechanisms (such as threaded IRQs and worker threads) for this, but those will be omitted here.
Interrupt handlers must not perform complex calculations or call scheduling functions.
When the handler completes all its tasks, it returns IRQ_HANDLED.
(include/linux/irqreturn.h)
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
Every time a handler returns IRQ_HANDLED, the kernel increments the corresponding interrupt count by 1 in /proc/interrupts.
callstack
In the ARM-A architecture, most interrupts are managed by the GIC (Generic Interrupt Controller).
Therefore, a typical callstack looks like this:
=> ...
=> __handle_irq_event_percpu
=> handle_irq_event_percpu
=> handle_irq_event
=>handle_fasteoi_irq
=>generic_handle_irq
=>__handle_domain_irq
=>gic_handle_irq
=>el1_irq
Here, each driver’s interrupt handler is invoked within handle_fasteoi_irq.
By tracing the driver code, you can see that the interrupt handler is called in code similar to the following:
irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
{
irqreturn_t retval = IRQ_NONE;
unsigned int irq = desc->irq_data.irq;
struct irqaction *action;
record_irq_time(desc);
for_each_action_of_desc(desc, action) {
irqreturn_t res;
...
trace_irq_handler_entry(irq, action);
res = action->handler(irq, action->dev_id);
trace_irq_handler_exit(irq, action, res);
...
switch (res) {
case IRQ_WAKE_THREAD:
...
__irq_wake_thread(desc, action);
fallthrough; /* to add to randomness */
case IRQ_HANDLED:
*flags |= action->flags;
break;
default:
break;
}
retval |= res;
}
return retval;
}
The part that actually calls the interrupt handler is res = action->handler(irq, action->dev_id);.
You can see that interrupt handlers are managed via the action structure, and it invokes the handler member of the action structure.
It also shows how the execution proceeds based on the handler’s return value.
It handles deferred interrupt processing if IRQ_WAKE_THREAD is returned, and then falls through to check for IRQ_HANDLED when interrupt processing is complete.
Leave a comment