FABRIC-3.md: aarch64/riscv64 boot-path code audit pass (§IV.3/§V.3 item 7)
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run

Reviewed arch/aarch64/{apic,arch,timer,interrupts}.c and
arch/riscv64/{apic,plic,arch,interrupts,timer}.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 "identify, don't fix unless asked" rule --
no source files changed.

Findings, aarch64:
- Severe, confirmed live: apic.c's GICD/GICC base addresses are hardcoded
  QEMU-virt constants, self-documented as a deliberate exception because
  QEMU's aarch64 firmware never forwards a DTB. That premise no longer
  holds -- the native Pi 5 boot path (item 2) receives a real DTB and
  calls the same, unmodified kernel_main(), whose M4 sequence calls
  apic_init(boot_info) unconditionally (kernel_main.c:413); apic_init()
  still ignores boot_info entirely. Real BCM2712 GIC-400 is at
  0x10_7fff9000, confirmed against bcm2712.dtsi -- a different region of
  the address space entirely from the hardcoded 0x08000000. With the MMU
  off at this point in boot, this blocks reaching ok> on real hardware as
  the code stands.
- Doc-only correction: interrupts.c's own comment claims VBAR_EL2 is never
  installed ("a known gap"). Checked against isr.S and found stale -- the
  actual implementation already branches on aarch64_current_el() and
  installs vbar_el2/vbar_el1 correctly. No functional gap; the comment
  describes one the code already closed.
- arch_cold_reset() hardcodes the PSCI conduit to HVC, a QEMU-specific
  workaround (QEMU's AAVMF has no genuine EL3). The Pi 5's real ATF means
  EL3 firmware exists there, making SMC the conventional real-hardware
  conduit -- no runtime detection exists.

Findings, riscv64:
- plic.c's QEMU-virt-hardcoded PLIC_BASE is already tracked (§V.3 item 3);
  this pass confirms rather than rediscovers it.
- arch_early_init()'s satp clear is justified entirely by behavior
  observed under QEMU's EDK2 firmware; the native boot path (U-Boot+
  OpenSBI, no EDK2) doesn't share that observation, though the action is
  likely still safe since OpenSBI's handoff conventionally leaves satp=0
  already. Lowest-severity finding in the set.
- Clean: the SBI timer path and arch_cold_reset()'s SBI SRST call are both
  genuinely hardware-independent -- named as the portable pattern amd64's
  i8042-pulse reset and aarch64's hardcoded-HVC PSCI call both lack.

Doc-only change. Three-arch QEMU acceptance (amd64/aarch64/riscv64, in
order) run to confirm non-regression only.

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:31:38 -04:00
co-authored by Claude Sonnet 5
parent 6a6ff9353f
commit 0f798256a0
9 changed files with 27660 additions and 11 deletions
+92 -10
View File
@@ -538,13 +538,63 @@ blocker for free.
currently builds — item 1's separate-image build target (linking at `0x80000`) is still
outstanding future work, same gap that item's own text already flagged.
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.
**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 — 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`
(`aarch64_install_vectors`, lines 124146) and found **stale**: the actual implementation
already branches on `aarch64_current_el()` and writes `vbar_el2`/`vbar_el1` correctly,
with `el2_mode_flag` threading the same answer through the IRQ trampoline for
`ELR_EL1`/`SPSR_EL1` vs `ELR_EL2`/`SPSR_EL2` selection. The comment describes a gap the
code already closed; left as-is per report-only policy, but flagged so a future reader
doesn't take the stale comment at face value. Which EL the Pi 5's ATF hands the kernel
off at under the native path is itself unconfirmed (the `atf@0` reserved-memory region
found during item 2's work only establishes that EL3 firmware exists, not which EL the
kernel lands at) — moot for this specific gap now that both paths are actually wired, but
worth knowing for other EL-dependent code.
**Finding — same defect class as amd64's `arch_cold_reset()` finding, on the QEMU side of
this arch specifically**: `arch/aarch64/arch.c`'s `arch_cold_reset()` issues PSCI
`SYSTEM_RESET` via `HVC` — a fix already documented in-file as a QEMU-specific
workaround, because QEMU's AAVMF firmware has no genuine EL3/TrustZone secure monitor to
answer an `SMC`. The Pi 5's real ATF (confirmed present via the `atf@0` reservation) is
exactly the kind of genuine EL3 firmware PSCI's `SMC` conduit assumes — the conventional
real-hardware call, not `HVC`. No runtime conduit detection exists; this is hardcoded to
the QEMU-only fix with no fallback. Riscv64's equivalent (`arch/riscv64/arch.c`, SBI SRST)
is the portable pattern this and amd64's i8042-pulse version both lack — worth naming as
the standard to bring the other two toward, not fixed here.
**Hardware-dependent, after 2026-09-17 (not started until then):**
7. Build the boot media (SD card: `config.txt`, `bcm2712-rpi-5-b.dtb`, kernel image).
8. Connect HDMI + keyboard (observation decision above).
9. Boot; confirm `ok>`/`zuse)ok>` reached.
10. Mint a Zuse identity on real media, confirm re-attach — the `v2.4.0` gate's own
8. Build the boot media (SD card: `config.txt`, `bcm2712-rpi-5-b.dtb`, kernel image).
9. Connect HDMI + keyboard (observation decision above).
10. Boot; confirm `ok>`/`zuse)ok>` reached.
11. Mint a Zuse identity on real media, confirm re-attach — the `v2.4.0` gate's own
requirement, same shape as amd64's.
11. Update this section with results before moving to riscv64's own hardware-dependent steps.
12. Update this section with results before moving to riscv64's own hardware-dependent steps.
## V. riscv64 — Milk-V Mars
@@ -654,14 +704,46 @@ it doesn't. This is a real punch-list item, not a hypothetical.
tooling's actual invocation once building the first real image, not just cited from
VisionFive 2 research.
7. **Code audit pass — DONE, 2026-09-04.** Reviewed `arch/riscv64/apic.c`, `plic.c`,
`arch.c`, `interrupts.c`, `timer.c` for the same class of QEMU-virt-vs-real-hardware
assumption §III item 6's amd64 audit and item 7 above's aarch64 audit looked for. Report
only — nothing below was changed.
**Clean, and the most portable pattern of all three architectures**: the SBI timer path
(`apic.c`) is genuinely hardware-independent — it probes for the TIME extension at
runtime and reports loudly rather than assuming it's present, so it depends on nothing
but OpenSBI itself, present on both QEMU and (per §V.1's own research) the Mars's real
U-Boot+OpenSBI chain. `arch_cold_reset()` (`arch.c`) uses the SBI SRST extension — a real
standards-defined mechanism, not a board-specific hack — making it the one of the three
architectures' cold-reset implementations that does **not** need a finding here (contrast
amd64's i8042-pulse hack and aarch64's HVC-hardcoded PSCI call, both flagged above/in
§III). `timer.c` is confirmed DTB-first as this section's own preamble already stated:
`timebase-frequency` is read via `fdt_prop_u32()` with a *named* QEMU-only fallback
(`RISCV_TIMEBASE_HZ_FALLBACK`) used only when the DTB is absent or invalid.
**Finding — already tracked, this pass confirms rather than discovers it**: `plic.c`'s
`PLIC_BASE`/`PLIC_CONTEXT_S` hardcoded-to-QEMU-virt situation is exactly item 3 above,
already flagged as "a real punch-list item, not a hypothetical" before this audit ran.
Nothing new to add beyond confirming the file's own header comment is accurate and the
risk is real, not overstated.
**Finding — minor, reasoning doesn't transfer to the native path, action likely still
safe**: `arch_early_init()`'s explicit `satp` clear (Bare-mode switch) is justified in
its own comment entirely by behavior *observed under QEMU's EDK2 RISC-V firmware*
(confirmed `satp.MODE=10`/Sv57 live, kernel identity-mapped within it). Under the native
boot path (U-Boot+OpenSBI, no UEFI/EDK2 at all per §V.1) that specific observation cannot
apply — OpenSBI's S-mode handoff conventionally already leaves `satp=0` (Bare mode), most
likely making this a no-op there rather than a hazard, but the *stated justification* for
the switch (identity-mapped, safe to clear) was derived from a firmware stack the native
path doesn't use, and hasn't been re-confirmed for OpenSBI's actual handoff state. Lowest
severity of any finding across both audits — flagged for completeness, not urgency.
**Hardware-dependent, after 2026-09-17:**
7. Build and flash the boot image to QSPI flash (or attempt UART XMODEM recovery boot if QSPI
8. Build and flash the boot image to QSPI flash (or attempt UART XMODEM recovery boot if QSPI
flashing isn't set up yet — both are real supported paths per §V.1).
8. Connect HDMI + keyboard.
9. Boot; confirm `ok>`/`zuse)ok>` reached.
10. Mint a Zuse identity on real media, confirm re-attach — the `v2.5.0` gate's own
9. Connect HDMI + keyboard.
10. Boot; confirm `ok>`/`zuse)ok>` reached.
11. Mint a Zuse identity on real media, confirm re-attach — the `v2.5.0` gate's own
requirement.
11. Update this section with results.
12. Update this section with results.
---