Fundamentals 18 min read

Understanding Linux ARM Interrupt Mechanism and Kernel Implementation

This article explains the ARM hardware interrupt flow, the Linux kernel's handling of IRQ and FIQ, the construction of exception vector tables, the role of asmlinkage, interrupt registration, shared interrupt models, and the specific implementation for the S3C2410 platform.

Deepin Linux
Deepin Linux
Deepin Linux
Understanding Linux ARM Interrupt Mechanism and Kernel Implementation

Linux implements an interrupt model that appears to lack explicit priority levels and supports nested exceptions, which contradicts some traditional understandings; this article investigates the actual behavior by starting from the original hardware specifications.

ARM processors define seven operating modes (USR, SYS, SVC, IRQ, FIQ, UND, ABT) and five exception modes (SVC, IRQ, FIQ, UND, ABT). IRQ and FIQ are the asynchronous interrupt modes used for external events such as button presses or timer expirations.

Exception entry (FIQ example):

Save the next instruction address (LR) to R14_fiq.

Copy CPSR to SPSR_fiq.

Change CPSR mode bits to FIQ mode.

Set PC to the appropriate exception vector address.

Exception exit:

Restore PC from LR (R14_fiq).

Copy SPSR_fiq back to CPSR.

Clear the interrupt disable flag if it was set.

When an external IRQ occurs, the CPU switches to IRQ mode, jumps to address 0x18 (the IRQ entry), loads the actual service routine address from the vector table, determines the interrupt source, executes the corresponding handler, and finally clears the interrupt flag before returning.

Key conclusions from the ARM side are:

IRQ mode can be pre‑empted only by FIQ; FIQ cannot be pre‑empted.

ARM provides programmable interrupt priority.

Linux adapts to ARM hardware by constructing an exception vector table during early boot. The relevant code in arch/arm/kernel/head.S calls trap_init() :

void __init trap_init(void)
{
    unsigned long vectors = CONFIG_VECTORS_BASE;
    …
    memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
    memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
    memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);
    …
}
#define CONFIG_VECTORS_BASE 0xffff0000

On most ARMv4 and later CPUs the vector table can reside at address 0x0 or 0xffff0000, selectable via the CP15 V bit. The vector table symbols __vectors_start and __vectors_end are defined in arch/arm/kernel/entry-armv.S :

.globl __vectors_start
__vectors_start:
    swi SYS_ERROR0
    b vector_und + stubs_offset
    ldr pc, .LCvswi + stubs_offset
    b vector_pabt + stubs_offset
    b vector_dabt + stubs_offset
    b vector_addrexcptn + stubs_offset
    b vector_irq + stubs_offset
    b vector_fiq + stubs_offset
.globl __vectors_end
__vectors_end:

The stubs_offset macro aligns the stub code with the relocated vector table:

.equ stubs_offset, __vectors_start + 0x200 - __stubs_start

When an IRQ arrives, execution reaches the vector_irq entry in entry-armv.S . The vector_stub macro dispatches to the appropriate handler based on the current mode, ultimately invoking irq_handler :

.macro irq_handler
get_irqnr_preamble r5, lr
1: get_irqnr_and_base r0, r6, r5, lr @ determine IRQ number
movne r1, sp
adrne lr, 1b
bne asm_do_IRQ @ call the C handler
…
.endm

The macro get_irqnr_and_base reads the interrupt controller registers (e.g., INTPND and INTOFFSET ) to compute the Linux IRQ number, handling both single and multiple pending bits.

.macro get_irqnr_and_base, irqnr, irqstat, base, tmp
    mov \base, #S3C24XX_VA_IRQ
    ldr \irqstat, [ \base, #INTPND ]
    teq \irqstat, #0
    beq 1002f
    ldr \irqnr, [ \base, #INTOFFSET ]
    …
    1001:
    adds \irqnr, \irqnr, #IRQ_EINT0
    1002:
    .endm

The C side handler asm_do_IRQ (marked with asmlinkage ) saves the register context, looks up the irq_desc array, and calls desc_handle_irq which finally invokes the driver’s registered irqaction :

asmlinkage void asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
    struct pt_regs *old_regs = set_irq_regs(regs);
    struct irq_desc *desc = irq_desc + irq;
    if (irq >= NR_IRQS)
        desc = &bad_irq_desc;
    irq_enter();
    desc_handle_irq(irq, desc);
    irq_finish(irq);
    irq_exit();
    set_irq_regs(old_regs);
}
static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
{
    desc->handle_irq(irq, desc);
}

Interrupt descriptors are defined in include/asm/arch/irq.h . Each irq_desc contains a pointer to an irqaction chain, where drivers register their handler via request_irq() :

int request_irq(unsigned int irq, irq_handler_t handler, IRQF_SHARED, const char *devname, void *dev_id)

The dev_id argument must be unique; it allows free_irq() to identify and remove a specific handler from a shared line:

void free_irq(unsigned int irq, void *dev_id)

For the S3C2410 platform, sub‑interrupts (EINT4‑7, EINT8‑23, etc.) are handled by demultiplexing functions registered in arch/arm/plat-s3c24xx/irq.c :

void __init s3c24xx_init_irq(void)
{
    set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);
    set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8);
    …
}

static void s3c_irq_demux_extint4t7(unsigned int irq, struct irq_desc *desc)
{
    unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND);
    unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);
    eintpnd &= ~eintmsk;
    eintpnd &= 0xff;
    while (eintpnd) {
        irq = __ffs(eintpnd);
        eintpnd &= ~(1 << irq);
        irq += (IRQ_EINT4 - 4);
        desc_handle_irq(irq, irq_desc + irq);
    }
}

In summary, the ARM/Linux interrupt subsystem for the S3C2410/S3C2440 platform demonstrates that:

Nested interrupts are limited (FIQ cannot be pre‑empted, IRQ can be pre‑empted only by FIQ).

Interrupt priority is programmable.

IRQ numbers are derived from hardware‑specific definitions in header files and may require translation via macros.

kernelLinuxARMembeddedDevice Driversinterrupts
Deepin Linux
Written by

Deepin Linux

Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.