Windows Kernel – APIC

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:

APIC block diagram

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));
RegisterOffsetDescription
APIC ID0x20Processor identifier.
APIC Version0x30APIC version and features.
Task Priority Register (TPR)0x80Controls the priority threshold for interrupt delivery.
EOI Register0x0B0Signals end‑of‑interrupt.
Spurious Interrupt Vector Register (SVR)0xF0Enables the LAPIC and sets the spurious vector.

Initialization Sequence

  1. Parse the ACPI MADT to locate LAPIC and I/O APIC base addresses.
  2. Map the APIC registers into kernel virtual address space.
  3. 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.
  4. Program the I/O APIC redirection entries based on the system's IRQ routing.
  5. 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.