apic.c (aarch64): fix GIC base address for real Pi 5 hardware
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run

FABRIC-3.md §IV.3 item 7's code audit found GICD_BASE_PA/GICC_BASE_PA
hardcoded to QEMU virt-machine constants (0x08000000/0x08010000),
self-documented as a deliberate exception because QEMU's aarch64 UEFI
firmware never forwards a DTB. That premise doesn't hold on the native
Pi 5 boot path (rpi5_native_boot.c), which does have a real DTB and calls
this same, unmodified apic_init() -- which ignored boot_info entirely and
always programmed the QEMU addresses. Real BCM2712 GIC-400 is at
0x10_7fff9000 (confirmed against bcm2712.dtsi's axi/gicv2 nodes), a
completely different region of the address space. Fixed per direct
instruction, naming this finding specifically.

apic_init() now calls gic_bases_from_dtb() first: fdt_valid(dtb) ->
fdt_find_node_by_compatible(dtb, "arm,gic-400") ->
fdt_find_prop_in_node(..., "reg", ...), reading the first two
2-address-cell/2-size-cell entries (GICD, then GICC -- the standard
arm,gic-400 binding order). Falls back to the QEMU constants on any
failure (no DTB, no matching node), so the existing QEMU/UEFI path is
unchanged. GICD_BASE_PA/GICC_BASE_PA became s_gicd_base/s_gicc_base
(module-static uintptr_t, no longer compile-time constants on this path)
-- every MMIO call site (apic_init, apic_spi_enable, apic_read_iar,
apic_eoi_intid) now reads through them.

Three-arch QEMU acceptance run exercised the guard itself, not just
compiled it: the aarch64 boot log shows "GICv2: no DTB GIC node -- using
QEMU virt-machine defaults" followed by the unchanged "distributor+CPU
interface enabled, PPI 30" line, then boots clean to zuse)ok>. The
success branch (a real DTB with a matching node) stays unverified until
real Pi 5 hardware -- this build never has a devicetree to exercise it
against.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
This commit is contained in:
Robert Allan James
2026-09-04 23:54:39 -04:00
co-authored by Claude Sonnet 5
parent 0f798256a0
commit dc9445abec
10 changed files with 27754 additions and 60 deletions
+39 -23
View File
@@ -540,30 +540,46 @@ blocker for free.
7. **Code audit pass — DONE, 2026-09-04.** Reviewed `arch/aarch64/apic.c`, `arch.c`,
`timer.c`, `interrupts.c` for the same class of QEMU-virt-vs-real-hardware assumption
§III item 6's amd64 audit looked for. Report only, per this project's own "identify,
don't fix unless asked" rule — nothing below was changed.
§III item 6's amd64 audit looked for. Report-only by default, per this project's own
"identify, don't fix unless asked" rule — three of the four findings below are report
only, nothing changed for them. **The GIC base address finding was fixed in code**, per
direct instruction naming it specifically after this audit landed — see that finding's
own text for what changed.
**Finding — severe, confirmed live, blocks reaching `ok>` on real hardware as the code
stands today**: `apic.c`'s own file header already self-documents `GICD_BASE_PA`/
`GICC_BASE_PA` (`0x08000000`/`0x08010000`) as "QEMU-virt-machine constants... a
deliberate, recorded exception," reasoned correctly at the time it was written: no DTB
was ever available to discover them from, because QEMU's own aarch64 UEFI firmware
doesn't forward one. That premise no longer holds — the native boot path (item 2, DONE)
receives a real DTB directly and calls the same, unmodified `kernel_main()` M4 sequence,
which calls `apic_init(boot_info)` unconditionally (confirmed: `kernel_main.c:413`, no
arch- or boot-path gating) — `apic_init()` itself still ignores `boot_info` entirely
(`(void)boot_info;`) and always programs the QEMU addresses. The real BCM2712 GIC-400 is
at `0x10_7fff9000` (distributor) / `0x10_7fffa000` (CPU interface 0) — confirmed directly
against `bcm2712.dtsi`'s own `gicv2` node (`compatible = "arm,gic-400"`), not recalled
a completely different region of the physical address space from QEMU's. With the MMU off
at this point in boot (per the standard aarch64 boot protocol and this kernel's own
`arch_mmu_init()` stub), a 32-bit MMIO write to an address BCM2712 doesn't decode as GIC
registers is a data abort, not a silent wrong-value write — this blocks item 10 below
(`ok>`) as the code stands, on the same critical path as item 1's still-missing `0x80000`
build target. Not fixed this pass — `fdt_find_node_by_compatible(dtb, "arm,gic-400")`
`fdt_find_prop_in_node(..., "reg", ...)` is the existing primitive that would supply the
real base once `apic_init()` is updated to use it when a DTB is present, falling back to
the QEMU constants when it (still correctly) isn't.
**Finding — severe, confirmed live, blocked reaching `ok>` on real hardware as the code
stood — FIXED 2026-09-04, per direct instruction (this item's own findings are normally
report-only; this one was explicitly asked for by name).** `apic.c`'s own file header
already self-documented `GICD_BASE_PA`/`GICC_BASE_PA` (`0x08000000`/`0x08010000`) as
"QEMU-virt-machine constants... a deliberate, recorded exception," reasoned correctly at
the time it was written: no DTB was ever available to discover them from, because QEMU's
own aarch64 UEFI firmware doesn't forward one. That premise no longer held — the native
boot path (item 2, DONE) receives a real DTB directly and calls the same, unmodified
`kernel_main()` M4 sequence, which calls `apic_init(boot_info)` unconditionally
(confirmed: `kernel_main.c:413`, no arch- or boot-path gating) — `apic_init()` itself
ignored `boot_info` entirely and always programmed the QEMU addresses. The real BCM2712
GIC-400 is at `0x10_7fff9000` (distributor) / `0x10_7fffa000` (CPU interface 0)
confirmed directly against `bcm2712.dtsi`'s own `axi`/`gicv2` nodes
(`compatible = "arm,gic-400"`, `#address-cells = <2>`/`#size-cells = <2>`, `axi`'s own
`ranges` identity-mapping this region — no offset needed, unlike `rpi5_dtb.c`'s
1-cell/offset `soc` peripherals), not recalled.
**Fix**: `apic.c` now tries `gic_bases_from_dtb()` first — `fdt_valid(dtb)`
`fdt_find_node_by_compatible(dtb, "arm,gic-400")` `fdt_find_prop_in_node(..., "reg",
...)`, reading the first two 2-address-cell/2-size-cell entries (GICD, then GICC — the
standard arm,gic-400 binding order) — and falls back to the QEMU constants only when no
DTB or no matching node is found, so the existing QEMU/UEFI path's behaviour (verified via
the mandatory 3-arch boot) is unchanged. `GICD_BASE_PA`/`GICC_BASE_PA` became
`s_gicd_base`/`s_gicc_base` (module-static `uintptr_t`, not `#define`s, since they're no
longer compile-time constants on this path) — every MMIO call site (`apic_init()`,
`apic_spi_enable()`, `apic_read_iar()`, `apic_eoi_intid()`) now reads through them.
The 3-arch acceptance run exercised the *guard*, not just compiled it: `apic_init()`'s
`gic_bases_from_dtb()` call runs unconditionally on every aarch64 boot, including this
one, and its `fdt_valid()` check correctly returned 0 on this system's QEMU/UEFI firmware
— confirmed via the boot log's own `GICv2: no DTB GIC node -- using QEMU virt-machine
defaults` line, followed by the unchanged `distributor+CPU interface enabled, PPI 30`.
What remains genuinely unexercised is the *success* branch (a real DTB with a matching
`"arm,gic-400"` node) — that needs a devicetree this build never has, so it stays
unverified until real Pi 5 hardware, same caveat as every other native-path item in this
list.
**Finding — doc-only, not a functional gap**: `arch_interrupts_init()`'s own doc comment
in `interrupts.c` claims "`VBAR_EL1` is written unconditionally regardless of the
detected level... if EL2, this is a known gap" — checked against `isr.S`