Cortex-M has NVIC integrated in the core — simple, deterministic, phone/MCU-sized.
Cortex-A needs to route interrupts across:
Many cores (1-128+)
Multiple Exception Levels (EL0-EL3)
Two security states (Secure / Non-secure)
Virtualized guests (vGIC)
Scale + complexity = a dedicated architectural interrupt controller IP, the GIC.
Every A-profile OS (Linux, XNU, Windows) drives the GIC via a standard memory-mapped + system-register interface. Kernel has one GIC driver, not one per silicon vendor.
An architectural vs IP distinction
"GIC" can mean two things: GIC Architecture (spec, e.g. GICv3) or GIC IP block (Arm-sold IP, e.g. GIC-600). An Arm A-profile SoC must implement the architecture; most use the Arm IP, but some implement their own (AWS Graviton 1 did).
SBSA requires GICv3+
From SBSA Level 3 (2016), Arm server platforms must have GICv3 or later. GICv2 is extinct on server silicon.
03
GIC Generations
Version
Year
Max INTIDs
Max cores
Key features
GICv1
2004
1020
8
Original; Cortex-A9/A15 classic
GICv2
2011
1020
8
Virtualization extensions (optional); MMIO-only
GICv3
2014
> 1 M (LPIs)
Unbounded
Affinity routing, MSIs (ITS), LPIs, system registers (ICC_*_EL1)
GICv4
2016
> 1 M
Unbounded
Direct injection of vLPIs to guest vCPUs — massive KVM speedup
GICv4.1
2019
> 1 M
Unbounded
Direct injection of vSGIs (inter-processor) for guest-to-guest fast path
GICv3 was the biggest shift: the distributor + per-CPU redistributor + system-register interface model. GICv4.x adds direct hardware paths for virt IRQs without hypervisor round-trips.
Global device interrupt — UART, Ethernet, SATA, fabric-level
LPI
Locality-specific Peripheral Interrupt
8192+
GICv3+ only; used for MSI/MSI-X (1 M+ supported)
Extended SPI
Extended SPI (GICv3.1)
4096-5119
Additional 1024 SPIs for big systems
Extended PPI
Extended PPI (GICv3.1)
1056-1119
Additional 64 PPIs per core
SGI + PPI + SPI come from the Distributor (and its redistributor partners). LPIs come from the ITS (Interrupt Translation Service), which translates an EventID + DeviceID from a PCIe MSI write into an LPI number + target redistributor.
05
GICv3 Architecture
Distributor — global state for SPIs; enable/priority/target routing tables.
Redistributors — one per CPU; hold PPI + SGI + LPI state for that CPU.
CPU interface — in GICv3, accessed via system registers (ICC_IAR1_EL1, ICC_EOIR1_EL1, ICC_PMR_EL1), not MMIO. Much faster.
Affinity routing — each core has a 32-bit Aff3.Aff2.Aff1.Aff0; interrupts are routed to targets by affinity tuple.
06
An Interrupt's Journey (SPI from a UART)
UART raises its level interrupt line → Distributor.
Distributor marks the SPI Pending; consults target routing (GICD_IROUTER<n>) to pick a CPU affinity.
Distributor forwards to target CPU's redistributor.
Redistributor raises CPU's IRQ (or FIQ) line.
CPU enters exception at lower-EL synchronous IRQ vector of EL1 (or EL2 / EL3 depending on routing).
Handler reads ICC_IAR1_EL1 → gets INTID and acknowledges.
Handler services device, clears device IRQ.
Handler writes ICC_EOIR1_EL1 → GIC moves INT from Active to Inactive.
GIC supports up to 256 priority values (8 bits); many implementations expose 5-8 bits.
Each INTID has a priority register (GICD_IPRIORITYR<n> for SPI, GICR_IPRIORITYR<n> for PPI/SGI).
The Priority Mask Register (ICC_PMR_EL1) blocks interrupts below a given priority at the CPU interface.
The Binary Point Register (ICC_BPR1_EL1) splits the priority field into group vs sub-priority — only group differences preempt.
Interrupts with higher priority (lower numeric value) preempt running lower-priority handlers — similar to Cortex-M NVIC but at much larger scale.
Two groups
Group 0 = Secure IRQ → routed as FIQ to the Secure world (EL3). Group 1 = Non-secure IRQ → routed as IRQ to EL1/EL2. Separate group-specific ICC_IAR0/ICC_IAR1/EOIR registers.
Compare to Cortex-M NVIC
NVIC: fixed 8-bit priority field, up to 256 exceptions, fully automatic tail-chaining, lazy FP stacking. GIC is larger (SPIs/LPIs) but lacks tail-chaining — the software model is different.
08
ITS — Interrupt Translation Service
Problem: PCIe MSI/MSI-X devices write to a memory address to signal an interrupt. The CPU needs to turn that write into an LPI pointed at a specific core.
GICv3 introduced the ITS to do exactly this, in hardware.
PCIe device writes to ITS's GITS_TRANSLATER register, supplying an EventID.
ITS looks up DeviceID (from AXI Requester ID) + EventID in its tables → produces (LPI ID, target CPU).
ITS injects LPI into the target redistributor → CPU IRQ.
Tables are in DRAM, programmed by firmware/OS: Device Table, Collection Table, Interrupt Translation Table.
// ITS lookup (simplified)
PCIe MSI write: DeviceID + EventID
|
v
ITS Device Table[DeviceID]
-> pointer to this device's ITT
|
v
ITS Interrupt Translation Table[EventID]
-> { LPI ID, Collection ID }
|
v
ITS Collection Table[Collection ID]
-> target Redistributor (CPU)
|
v
Redistributor raises LPI->CPU
ITS commands (MAPD, MAPI, MAPC, SYNC, INV) are issued via a DRAM-resident command queue. Linux GIC-ITS driver sends these to set up a device's MSI routing.
09
Virtualization — vGIC
A guest at EL1 believes it has its own GIC. The hypervisor at EL2 arranges this via the vGIC:
Guest accesses to ICC_*_EL1 registers are trapped (HCR_EL2 settings) and remapped to virtual state
Guest IRQ delivery uses a virtual CPU interface (ICH_*_EL2 registers)
Up to 16 list registers (ICH_LR<n>_EL2) hold guest-pending interrupts
GICv2 had rudimentary vGIC (optional); GICv3 made it mandatory.
GICv4 adds direct injection of vLPIs — the ITS can target a guest's redistributor directly, no hypervisor trap. Huge for I/O-heavy VMs.
GICv4.1 adds direct injection of vSGIs — guest-to-guest IPIs also skip the hypervisor.
Why vGIC matters
Without direct injection, every interrupt delivered to a guest costs ~2× EL1↔EL2 switches (~2000 cycles). GICv4 vLPI direct injection takes this to zero — huge for DPU / SR-IOV NICs with 100k+ IRQs/sec.
Nested virt note
Under nested virtualization (guest hypervisor inside a host hypervisor), vGIC gets an extra level. GICv3 handles this; GICv4 direct injection does not yet apply to nested guests.
10
GIC IP Products
IP
Arch
Year
Max cores
Max SPIs
ITS
Target
GIC-400
GICv2
2012
8
480
No
Phone SoC (A53/A57-era)
GIC-500
GICv3
2014
128
960
Yes
First GICv3 IP, servers + high-end phones
GIC-600
GICv3.1
2016
Unbounded
> 1000 + ESPI
Yes, multiple
Server-class; multi-chip support
GIC-600AE
GICv3
2019
-
-
Yes
Automotive ASIL-D
GIC-700
GICv4.1
2022
Unbounded
> 4000
Yes
Modern server + Neoverse Gen 2/3
Modern Cortex-A phones typically use GIC-600 or GIC-700. Every Neoverse-based server uses GIC-600 or GIC-700. GIC-400 is legacy (A72-era and older mobile).
11
Power Management — PPI Timers & Wake-up
Every CPU has a PPI-timer interrupt (PPI 30 — CNTP physical EL1 timer; PPI 27 for EL1 virtual; PPI 26 for EL2 timer).
These are delivered per core via the redistributor — no cross-CPU traffic.
Timers + CPU idle states coordinate via PSCI: OS tells TF-A/EL3 to put a core to sleep; GIC retains interrupt pending state so the core wakes on IRQ.
The GIC wake-up interface — redistributor can be quiesced to save power but must remain responsive to incoming interrupts that will wake the core.
GIC itself has multiple power domains — on GIC-600/700 the distributor and redistributors can be power-managed independently.
Idle state flow
(1) Linux picks an idle state. (2) Kernel calls cpu_do_idle() → PSCI CPU_SUSPEND. (3) TF-A saves GIC state (via GICR_WAKER) and parks the core. (4) Any pending interrupt at the redistributor un-parks the core. (5) On return, GIC state is restored.
WFI + GIC
When a core runs WFI, the redistributor retains state; the CPU clock gates. Interrupt arrival wakes the core without any software intervention.
12
Secure vs Non-secure Interrupts
Each INTID has a Group (G0/G1) and a Non-secure bit.
Routing per Group:
Group 0 (Secure, unbanked) → FIQ to Secure EL1/EL3
Group 1 Non-secure → IRQ to Non-secure EL1/EL2
Group 1 Secure → FIQ to Secure EL1 (via SCR_EL3 routing)
Banked registers: secure and non-secure worlds each see their own view of most GIC registers. Secure state can see all; non-secure is limited.
TF-A / secure monitor at EL3 configures GIC security before handing off to Non-secure.
Why it matters
Secure interrupts (e.g., TrustZone secure timer) must not be visible or serviceable by the Non-secure kernel. The GIC enforces this at the redistributor by Group + Non-secure attribution, not by trusting software.
RME / CCA
With Armv9 RME, GIC-700 is aware of the four worlds. Realm interrupts get their own routing, with enforcement at the redistributor + ITS.
13
GIC and Linux
ACPI path: UEFI-booted servers describe GIC topology in the MADT table (distributor base, redistributor bases, ITS bases).
Device-tree path: phones + embedded use DT nodes like:
compatible = "arm,gic-v3"
#interrupt-cells = <3> (type + ID + flags)
Linux driver: drivers/irqchip/irq-gic-v3.c — ~3000 LoC, implements everything from IRQ allocation to GICv4 direct injection.
IRQ domain abstraction isolates the driver from per-SoC board code.
Every interrupt in /proc/interrupts on a Cortex-A Linux box comes from this driver (SPI/PPI/SGI/LPI hierarchical).
perf counters for irq_handler_entry + irq_handler_exit — latency histograms.
Common failure modes:
"Interrupt stuck" — usually EOI not issued
"Lost wakeup" — priority mask wrong in idle
"CPU never takes IRQ" — affinity masks wrong
"Guest hangs" — vGIC list-register exhaustion
The "spurious" INTID
INTID 1023 is reserved for "spurious". Returned from ICC_IAR1_EL1 if no interrupt is active. Common cause: interrupt was asserted then de-asserted before the CPU could ack. Handler should just EOI and return.
GIC PMU events
GIC-600 / GIC-700 expose their own PMU — count LPI injections, ITS translations, TLB-like cache hits in the redistributor. Invaluable for DPU / high-IRQ-rate debugging.
15
GIC vs x86 APIC/IOAPIC
Concept
Arm GIC
x86 APIC family
Per-CPU component
Redistributor
Local APIC (LAPIC / x2APIC)
Global component
Distributor
IOAPIC (one per host bridge)
MSI routing
ITS
IOAPIC with MSI redirection
IPIs
SGI (0-15)
ICR IPI messages
Address model
Fixed registers + LPI IDs
Memory-mapped APIC page
Interrupt #s
24-bit INTID (up to 1 M)
8-bit vector (256)
Virtualization
Architectural vGIC; GICv4 direct inject
APICv (post-2010 Intel)
Same conceptual split (per-CPU + global) but architectural specs diverged heavily. GIC's 24-bit INTID space is far larger than x86's 8-bit — necessary for 1000+ PCIe functions with MSI-X vectors.
16
Lessons
"Why two halves — Distributor + Redistributors?" → Distributor holds SPI state (global); redistributors hold per-core PPI/SGI/LPI state (private). Scales to thousands of cores because per-core state is distributed, not central.
"What is an LPI and why do we need ITS?" → LPI = Locality-specific Peripheral Interrupt, used for PCIe MSI/MSI-X. ITS translates (DeviceID, EventID) → LPI + target CPU, avoiding the SPI namespace (which is too small for 1000s of PCIe functions).
"What does GICv4 add?" → direct injection of vLPIs — ITS delivers guest interrupts to guest redistributors without hypervisor trap. GICv4.1 adds vSGI direct injection.
"Difference between ICC_IAR1_EL1 and ICC_EOIR1_EL1?" → IAR = Interrupt Acknowledge Register, reads the next pending INTID (moves it Pending → Active). EOIR = End-of-Interrupt, writes back to mark Active → Inactive.
"Why Group 0 vs Group 1?" → Group 0 is Secure-side (FIQ to EL3), Group 1 is Non-secure + Secure-EL1 variants. Separates TrustZone OS interrupts from Linux.
"Why GICv3 for servers but GIC-400 for old phones?" → GIC-400 (GICv2) tops out at 8 cores and no LPIs — insufficient for server MSI loads. Every modern A-profile system uses GICv3+.
17
References
Arm Ltd. — Arm Generic Interrupt Controller Architecture Specification (IHI 0069 / IHI 0048) — covers GICv2 and GICv3/v4 Arm Ltd. — GIC-400 / 500 / 600 / 700 Technical Reference Manuals Arm Ltd. — Learn the architecture: GICv3 and GICv4 overview — excellent free intro Arm DEN 0029 — Server Base System Architecture — what a server must implement Linux kernel — drivers/irqchip/irq-gic-v3.c, irq-gic-v3-its.c, Documentation/arch/arm64/ Marc Zyngier — KVM/Arm vGIC blog posts + upstream patches (2013-2022) Arm Community Blog — GICv4.1 direct-inject explanations (2020) Hotchips — 2019 "Interrupts in Arm SoCs" talk
Presentation built with Reveal.js 4.6 · Saira Semi Condensed + Archivo + Share Tech Mono
Educational use.