aarch64: minimal GICv2 driver -- distributor, CPU interface, timer PPI

Punch list §25 item 0.6 complete.

Ruling applied (AskUserQuestion, this session): DTB is confirmed unreachable
on this system's aarch64 firmware too (qemu-efi-aarch64 2025.11-3ubuntu7,
same finding as riscv64's item 0.3), so GICD/GICC base addresses and the
timer PPI are named QEMU-virt constants with a recorded caveat, not
DTB-discovered as the item originally asked.

Nothing here was recalled from memory. Base addresses (GICD 0x08000000,
GICC 0x08010000) and the timer PPI (30, non-secure EL1 physical) were read
out of QEMU 10.2.1's own internal devicetree via
`qemu-system-aarch64 -machine virt,dumpdtb=...`, decoded with this tree's own
fdt.c reader rather than a new tool -- correct for this exact QEMU version,
not assumed stable across others. Bonus finding from the same dump: PPI 26
for the EL2 hypervisor timer, which item 0.7 will need for its EL2 path.
Register offsets within each block (GICD_CTLR, GICC_IAR, etc.) are GICv2
architectural constants, not board-specific, and were cross-checked against
Linux's own arm-gic.h driver header rather than recalled either.

Acceptance amended before implementing (§25.0 "when an item is genuinely
wrong"): the original text required observing a delivered-and-acknowledged
timer interrupt, which cannot happen within this item's own scope --
apic_timer_start() (item 0.7) is still the no-op stub, so nothing arms the
timer. This is the same defect the earlier review's C2 fix already applied to
items 0.2 and 0.5; it was missed here. Acceptance is now: GIC initialises
without fault, the IAR/EOIR path is wired into aarch64_irq_handler() and
ready, boots with no regression -- item 0.7's tick-advance is what proves
delivery, exactly as 0.5 already defers to 0.7.

EL-aware (B3, same discipline as items 0.4/0.5): apic_init() selects PPI 30
or 26 from aarch64_current_el(), decided once and cached, not re-derived per
interrupt.

aarch64_irq_handler() now does real work: reads GICC_IAR (the GICv2
acknowledgement step), dispatches to heartbeat_tick() when the INTID matches
the timer PPI, and always completes with GICC_EOIR (INTID 1023 = spurious
handled per the GICv2 spec, not as a special case of "unrecognised"). This
mirrors exactly how riscv64's item 0.2 built full cause-dispatch logic before
its timer was armed in 0.3.

Investigated and resolved a real scare during verification: QEMU's `-d int`
trace showed 1,728 "Taking exception 5 [IRQ]" events by the time boot reached
the prompt, which looked exactly like an interrupt storm (hypothesis: EDK2
firmware leaves CNTP_CTL_EL0 enabled with a stale comparator, and enabling
the GIC path exposes it before item 0.7 reprograms the timer). A direct
one-shot probe inside aarch64_irq_handler() itself -- ground truth for
whether this code path runs at all -- fired zero times across a clean,
bounded boot. The trace events were almost certainly from EDK2 firmware's
own internal timer usage during its own boot phase, before control passes to
this kernel; the earlier conclusion was drawn from the external trace alone
without checking that distinction, and the probe (not the trace) is what
settled it. Probe code fully reverted; not part of the commit.

Verified: builds clean, boots to ok> with no regression, dict_hash
0x3d4e1daf289da94f unchanged from the item 0.1-0.5 baseline, EL banner and
IDT-installed lines still print in order, GIC init line confirms PPI 30
selected. Only aarch64-scoped files touched; amd64/riscv64 not rebuilt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-03 19:48:56 -04:00
co-authored by Claude Sonnet 5
parent 8d8f3aaae2
commit cabb0e8bd4
6 changed files with 10645 additions and 34 deletions
+28 -3
View File
@@ -2076,7 +2076,7 @@ on until there is a tick on all three architectures (§16.1, §16.5).*
acceptance is what proves this path took and returned an IRQ (C2). Do not pull 0.6/0.7
work forward to manufacture evidence here.
- [ ] **0.6 — aarch64: minimal GICv2.**
- [x] **0.6 — aarch64: minimal GICv2.**
Enable the distributor and CPU interface, set the priority mask, enable the timer PPI,
acknowledge via `IAR` / `EOIR`. **Read the base addresses and the PPI INTID from the
device tree — do not take them from memory or from this document.** The DTB pointer
@@ -2084,8 +2084,33 @@ on until there is a tick on all three architectures (§16.1, §16.5).*
DTB turns out to be unreachable on aarch64 EDK2, stop and report — deciding between
loader work and named QEMU-virt constants with a recorded caveat is Captain Bob's call,
not the implementer's.
*Done when:* the timer interrupt is delivered and acknowledged. Scope is one interrupt;
a general GIC driver is out of scope and must not be written.
> **DTB confirmed unreachable (checked live, 2026-08-03) — same finding as riscv64.**
> `fdt_valid(boot_info->dtb)` fails on this system's aarch64 build too (installed
> firmware: `qemu-efi-aarch64` 2025.11-3ubuntu7, no alternate available). Ruling: named
> QEMU-virt constants, verified rather than recalled — `qemu-system-aarch64
> -machine virt,dumpdtb=...` was used to dump QEMU's own internal devicetree (the one
> EDK2 fails to forward) and decoded with this tree's own `fdt.c` reader, giving
> **GICD 0x08000000, GICC 0x08010000** (both confirmed for this exact QEMU 10.2.1
> build, not assumed stable across versions) and **PPI 30** for the non-secure EL1
> physical timer (bonus finding: PPI 26 for the EL2 hypervisor timer, for item 0.7's
> EL2 path). Register *offsets* (GICD_CTLR, GICC_IAR, etc.) are architectural, not
> board-specific, and were cross-checked against
> `/usr/src/linux-headers-*/include/linux/irqchip/arm-gic.h` rather than recalled.
> **Acceptance corrected — same defect C2 already fixed for items 0.2 and 0.5, missed
> here.** "The timer interrupt is delivered and acknowledged" cannot be observed within
> this item's own scope: nothing arms the timer until item 0.7's `apic_timer_start()`.
> As written this item could never be marked done on its own evidence. Acceptance is
> now the same shape as 0.2/0.5: GIC initialises without fault, IAR/EOIR path is wired
> into `aarch64_irq_handler()` and ready, boots with no regression. Item 0.7's
> tick-advance acceptance is what proves this path actually delivers and acknowledges an
> interrupt, exactly as 0.5 already defers to 0.7 for the same reason.
*Done when:* GIC distributor and CPU interface initialise without fault; the timer PPI
is enabled; `aarch64_irq_handler()` reads `IAR`, dispatches, and writes `EOIR`; boots to
the prompt with no regression. Scope is one interrupt; a general GIC driver is out of
scope and must not be written.
- [ ] **0.7 — aarch64: arm the generic timer.**
`apic_timer_start()` / `apic_timer_stop()` using the register set chosen in 0.4, re-armed