On this page
Introduction
The Advanced Programmable Interrupt Controller (APIC) is the core component for handling interrupts in modern x86‑64 systems. Windows kernel uses both the Local APIC (LAPIC) and the I/O APIC to route hardware interrupts to the appropriate processor.
APIC Architecture
Windows leverages a hierarchical interrupt model:
- Local APIC (LAPIC) – per‑CPU interrupt controller that manages timer, inter‑processor interrupts (IPIs), and local vector table (LVT) entries.
- I/O APIC – global controller that maps external IRQ lines to LAPIC destinations via redirection entries.
Key Registers
The following registers are accessed via the memory‑mapped region defined in the ACPI MADT table.
// Example of reading the LAPIC ID register
UINT32 lapic_id = READ_REGISTER_ULONG((volatile ULONG *) (LAPIC_BASE + 0x20));
Register | Offset | Description |
---|---|---|
APIC ID | 0x20 | Processor identifier. |
APIC Version | 0x30 | APIC version and features. |
Task Priority Register (TPR) | 0x80 | Controls the priority threshold for interrupt delivery. |
EOI Register | 0x0B0 | Signals end‑of‑interrupt. |
Spurious Interrupt Vector Register (SVR) | 0xF0 | Enables the LAPIC and sets the spurious vector. |
Initialization Sequence
- Parse the ACPI MADT to locate LAPIC and I/O APIC base addresses.
- Map the APIC registers into kernel virtual address space.
- Initialize each LAPIC:
- Enable the LAPIC by setting the SVR.1
- Set the logical destination mode.
- Configure LVT entries for timer, error, and thermal sensor.
- Program the I/O APIC redirection entries based on the system's IRQ routing.
- Enable the CPU’s interrupt flag (IF) after the APIC is ready.
Interrupt Handling Model
When an external interrupt arrives, the I/O APIC forwards it to the target LAPIC, which then raises the appropriate vector. The Windows kernel’s interrupt dispatcher selects the ISR routine based on the vector, masks lower‑priority interrupts, and processes the ISR on the current CPU.
Sample Code
Below is a minimal example that configures a timer interrupt using the LAPIC.
#include <ntddk.h>
#define LAPIC_BASE 0xFEE00000
#define LAPIC_TIMER 0x320
#define LAPIC_TICR 0x380
#define LAPIC_TDCR 0x3E0
VOID SetLapiTimer(UINT32 interval)
{
// Set divide configuration (divide by 16)
WRITE_REGISTER_ULONG((volatile ULONG *) (LAPIC_BASE + LAPIC_TDCR), 0x3);
// Set initial count
WRITE_REGISTER_ULONG((volatile ULONG *) (LAPIC_BASE + LAPIC_TICR), interval);
// Program LVT entry (vector 0x40, periodic mode)
WRITE_REGISTER_ULONG((volatile ULONG *) (LAPIC_BASE + LAPIC_TIMER), 0x20040);
}
For complete driver implementation, see the Examples section.