ARM64 OS Handbook
🔍

Chapter 12: Interrupts

What You Will Learn in This Chapter
  • The difference between exceptions and interrupts
  • How the GIC (Generic Interrupt Controller) works
  • Interrupt types: SPI, PPI, SGI
  • How to configure and enable interrupts for a device
  • How to write an interrupt handler
  • How to acknowledge and end interrupts in the GIC

12.1 Exceptions vs Interrupts

Interrupts are a type of exception, but they differ from synchronous exceptions in an important way: interrupts are asynchronous. They happen at any time, unrelated to the currently executing instruction. A timer fires, a network packet arrives, a key is pressed -- these events trigger interrupts.

ARM64 provides two interrupt lines: IRQ (normal interrupt) and FIQ (fast interrupt). FIQ has higher priority and a separate set of banked registers, allowing faster handling. Most systems use IRQ for all devices and reserve FIQ for special purposes.

12.2 The Generic Interrupt Controller (GIC)

The GIC is a hardware component that manages interrupt delivery. It receives interrupt signals from devices, prioritizes them, and sends them to the appropriate CPU core. On QEMU virt, the GICv3 is at physical address 0x08000000.

The GIC has three main components:

  • Distributor (GICD): Global configuration, enables/disables interrupts, sets priority
  • Redistributor (GICR): Per-CPU configuration, pending management, wake-up
  • CPU Interface (ICC): Per-CPU interrupt handling, acknowledge, end-of-interrupt

12.3 Interrupt Types

TypeID RangeDescription
SGI (Software Generated)0-15Inter-processor interrupts (IPI). One CPU sends to another.
PPI (Private Peripheral)16-31Per-CPU interrupts (timer, PMU). Each CPU has its own.
SPI (Shared Peripheral)32-1019Device interrupts shared across CPUs (UART, network, disk).

Key interrupt IDs on QEMU virt:

IDTypeDevice
30PPISystem timer (CNTP)
33SPIUART (PL011)
34SPIRTC
64+SPIVirtIO devices

12.4 GIC Register Access

/* Distributor registers (offsets from 0x08000000) */
#define GICD_CTLR     0x0000   /* Control register */
#define GICD_TYPER    0x0004   /* Type register (number of interrupts) */
#define GICD_ISENABLER 0x0100  /* Interrupt set-enable (one bit per ID) */
#define GICD_ICENABLER 0x0180  /* Interrupt clear-enable */
#define GICD_ISPEND   0x0200   /* Interrupt set-pending */
#define GICD_ICPEND   0x0280   /* Interrupt clear-pending */
#define GICD_ICFGR    0x0C00   /* Interrupt config (level/edge) */

/* CPU interface registers (offsets from 0x08000000 + 0x40000) */
#define ICC_PMR       0x0004   /* Priority mask register */
#define ICC_IAR0      0x000C   /* Interrupt acknowledge register (current EL) */
#define ICC_EOIR0     0x0010   /* End of interrupt register */
#define ICC_HPPIR0    0x0018   /* Highest pending interrupt */

/* Enable a specific interrupt */
void gic_enable_interrupt(uint32_t irq_id) {
    volatile uint32_t *isenabler = (uint32_t *)(GICD_BASE + GICD_ISENABLER);
    isenabler[irq_id / 32] = (1 << (irq_id % 32));
}

/* Acknowledge an interrupt (read the interrupt ID) */
uint32_t gic_acknowledge(void) {
    volatile uint32_t *icc_iar = (uint32_t *)(GICD_BASE + 0x40000 + ICC_IAR0);
    return *icc_iar & 0x3FF;
}

/* Signal end of interrupt */
void gic_end_of_interrupt(uint32_t irq_id) {
    volatile uint32_t *icc_eoir = (uint32_t *)(GICD_BASE + 0x40000 + ICC_EOIR0);
    *icc_eoir = irq_id;
}

12.5 Writing an Interrupt Handler

The interrupt handler follows a fixed sequence:

  1. Save context (registers we will modify)
  2. Acknowledge the interrupt (read ICC_IAR0 to get the interrupt ID)
  3. Handle the interrupt (call the device-specific handler)
  4. Signal end-of-interrupt (write to ICC_EOIR0)
  5. Restore context and return via ERET
void irq_handler(void) {
    uint32_t irq_id = gic_acknowledge();
    switch (irq_id) {
        case 30:  timer_handler();  break;
        case 33:  uart_handler();   break;
        default:
            uart_puts("Spurious IRQ: ");
            uart_puthex(irq_id);
            uart_puts("\r\n");
    }
    gic_end_of_interrupt(irq_id);
}

12.6 Interrupt Priority and Masking

The GIC supports 16 priority levels (0-255, with 0 being highest). The Priority Mask Register (ICC_PMR) sets the minimum priority that will be delivered to the CPU. Interrupts with priority higher (lower numeric value) than PMR are delivered; those with lower priority are held pending.

/* Set priority mask: only interrupts with priority < 0x80 will fire */
volatile uint32_t *icc_pmr = (uint32_t *)(GICD_BASE + 0x40000 + ICC_PMR);
*icc_pmr = 0x80;

At the CPU level, the DAIF bits in PSTATE also control interrupt delivery:

  • msr DAIFSet, #1: mask FIQ
  • msr DAIFSet, #2: mask IRQ
  • msr DAIFSet, #4: mask SError
  • msr DAIFClr, #2: unmask IRQ

12.7 Our Implementation

Our kernel's interrupt handling is initialized in this order:

  1. gic_init(): configure distributor, set all interrupts to non-secure group 1
  2. gic_enable_interrupt(id): enable specific interrupts for UART, timer, etc.
  3. msr DAIFClr, #2: unmask IRQs at the CPU level
  4. The IRQ vector in the exception table catches all interrupts
  5. irq_handler() in C dispatches to device-specific handlers

For now, we have two interrupt sources: the system timer (for the scheduler tick) and the UART (for serial input). As we add more device drivers, we will add more interrupt IDs to the dispatch table.

12.8 Exercises

Exercise 1: Interrupt Count

Add a counter to irq_handler that increments on every IRQ. Print the count every time the timer fires.

Exercise 2: Spurious Interrupt Detection

Read ICC_HPPIR0 before acknowledging. If it reads 1023 (spurious), skip the handler. Print a message for spurious interrupts.

12.9 Summary

Interrupts are asynchronous exceptions that allow the CPU to respond to hardware events. The GIC receives interrupts from devices, prioritizes them, and delivers them to the CPU. Our kernel initializes the GIC, installs an IRQ handler that acknowledges the interrupt, dispatches to the appropriate device handler, and signals end-of-interrupt. This mechanism is the foundation for all device driver interaction.