Files
LithosAnanake/FABRIC-3.md
T
Robert Allan JamesandClaude Sonnet 5 a66af477ac
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: aarch64 boot-chain decision -- native boot flow, not UEFI
Researched both options before deciding. The UEFI path (rpi5-uefi,
TF-A+EDK2, SBBR-compliant) is real but archived since 2025-02-04 --
support ended when newer Pi EEPROM firmware broke compatibility, and
its own README says ACPI support is limited/incomplete. Decided:
native boot flow instead (config.txt/kernel_2712.img/DTB, x0=DTB
pointer at entry, no ACPI at all).

Named the real scope rather than estimating it small: a new,
non-UEFI aarch64 entry path, a DTB-driven BootInfo equivalent, and a
new mailbox-property-interface framebuffer driver (no precedent in
this codebase). The one genuine piece of reusable groundwork:
starkernel/hal/fdt.c's minimal FDT reader, already built for
riscv64's timebase-frequency lookup, extends directly to Pi 5
peripheral discovery.

Also documents the peripheral-RNG research: BCM2712 has no
brcm,bcm2712-rng200 (or equivalent) entry anywhere in current
mainline Linux, and RP1's own published peripheral list doesn't
mention an RNG -- genuinely unresolved, not just under-researched.

Not yet turned into a punch list -- the boot-chain fork's own shape
needs thinking through first.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 12:34:30 -04:00

20 KiB
Raw Blame History

FABRIC-3.md — bare metal boot

Status: Living working document, opened 2026-09-04 as the successor to FABRIC-2.md (now closed/archival — see its own header). Topic for this document, per direct instruction: bare metal boot — getting LithosAnanke to actually boot on real hardware, not just QEMU. FABRIC-2.md §I.6 (Milestone 8) already named this as the one item that pass couldn't close from a coding session at all, for exactly this reason — it needs a real machine and a human physically present. This document is where that work, and everything downstream of it, gets tracked.

How to use this document going forward. New findings, new punch-list items, and new decisions for bare-metal-boot work get added here, not to FABRIC-2.md. Same discipline every prior document in this series used: write the decision and its reasoning down before building, close items with a dated note citing real evidence, never silently drop a stale claim.


I.1 — Task 1: merge v2.0.1 into master, verify build/function equivalence

Written up before executing, per direct instruction and this series' own standing discipline.

Why this is task 1. FABRIC-2.md's entire 7-step closure pass (§I.1–§I.5, §I.7, plus today's FABRIC-series rename) happened on the v2.0.1 branch, not master. Before any real bare-metal-boot work starts, that work needs to land where .claude/CLAUDE.md says the project's sole production line actually lives: master. Doing this first, cleanly, before starting new work avoids ever having two divergent lines to reconcile later.

Investigated before writing this up, not assumed:

  • git merge-base --is-ancestor master v2.0.1true. master (local HEAD d2a0305) is a strict ancestor of v2.0.1 (HEAD b031b80) — v2.0.1 is exactly master plus 47 commits forward, no divergent history on either side. This means the "merge" is a pure fast-forward, not a real three-way merge — nothing to resolve, no conflict possible.
  • origin/master carries exactly one commit beyond local master (58c59e8, "Initial commit") that local master hadn't fetched yet — confirmed already contained in v2.0.1's own history (git merge-base --is-ancestor 58c59e8 v2.0.1 — true), so it introduces no discrepancy either.
  • master's own tree still has the old FABRIC.md/FABRIC-2.md/FABRIC-3.md naming (unrenamed) — expected, since today's rename commit (b031b80) only exists on v2.0.1 so far. The fast-forward brings the rename to master along with everything else; nothing separate needs doing for it.

Plan:

  1. Fast-forward master to v2.0.1's tip (git checkout master && git merge --ff-only v2.0.1) — refuses loudly instead of silently doing a real merge if the ancestor relationship somehow isn't what the investigation above found, so this step re-verifies its own precondition.
  2. Push master to origin.
  3. Verify build/function equivalence on a genuinely clean tree, not by inference: git clean (after confirming nothing untracked-but-wanted is present), then the full acceptance sequence .claude/CLAUDE.md already mandates for any kernel change — clean qemu on all three architectures, in the foreground, one at a time, each reaching ok> and shutting down cleanly. Since the tree is byte-identical to v2.0.1's post-fast-forward, this is expected to reproduce exactly what v2.0.1's own last acceptance pass already showed — the point of re-running it here is to confirm that expectation holds on master itself, not to assume it from the fast-forward alone.
  4. Return to v2.0.1 as the working branch afterward (.claude/CLAUDE.md's own rule: always return to the correct working branch after any out-of-branch work), unless told otherwise.

DONE 2026-09-04, exactly as planned:

  1. Committed the write-up above on v2.0.1 first (72c14cb), pushed. This became v2.0.1's new tip.
  2. git checkout master && git merge --ff-only v2.0.1Fast-forward, d2a0305..72c14cb, confirming the investigated ancestor relationship held exactly as expected; no conflict, no merge commit.
  3. git push origin masterorigin/master moved 58c59e8..72c14cb.
  4. Verified on a genuinely clean master tree, not inferred from the fast-forward:
    • Hosted build (make clean && make): clean compile, zero warnings, same as v2.0.1.
    • Full 3-arch kernel acceptance (clean qemu, amd64/aarch64/riscv64, each in the foreground): all three reached (zuse) ok>/ok> and shut down cleanly, zero build errors, zero unexpected warnings — identical outcome to v2.0.1's own last acceptance pass, confirmed directly rather than assumed. Logs: logs/20260904-113208/amd64/, logs/20260904-113320/aarch64/, logs/20260904-113552/riscv64/.
  5. master and v2.0.1 are now identical (72c14cb on both, origin and local). Returned to v2.0.1 as the working branch per plan step 4.

Task 1 closed. master genuinely is the production line again, current through today's FABRIC-series rename and the full FABRIC-2.md §I closure. Bare-metal-boot work (this document's actual topic) starts from here.

I.2 — Task 2: version correction — the v2.0.1 bump and v2.0.0 tag were premature

Direct instruction, 2026-09-04: the LITHOS_VERSION bump to 2.0.1 (and the branch name that followed it) got ahead of the real state — per Makefile.starkernel's own versioning policy (v2.0.0 = QEMU release, even major/LTS; v2.0.1 = the SER5 hardware-track line, RDRAND backend + thumbdrive image goal), claiming 2.0.1 implies hardware-track progress that was never actually verified on real hardware — that verification is precisely FABRIC-3.md's whole open topic (§I.6 in the closed FABRIC-2.md). The current master HEAD is, correctly, still a v2.0.0-class QEMU-only release. "Nothing harmful" — a version-label correction, not a functional rollback.

Found and fixed while correcting this, not left half-done:

  • A real gap in the FABRIC-series rename from earlier today: Makefile.starkernel, Kconfig.kernel, scripts/bleach_zuse_img.sh, four proof/*.thy files, and src/starkernel/arch/amd64/isr.S all still had stale FABRIC.md/FABRIC-2.md/FABRIC-3.md citations — the original sweep's file-list only matched --include=*.md/*.c/*.h/*.4th, which silently skipped every file without one of those four extensions. Found by re-grepping with the extensions excluded instead of included. Fixed with the same safe placeholder-substitution technique the original rename used (each file, one pass, ordered FABRIC-3→2→1→0 placeholders then resolved) — verified no double-shifted or broken references remained afterward. .claude/settings.local.json's own historical Bash-permission-grant log (literal past command strings naming the file as it was called at the time) was deliberately left alone — rewriting it would falsify an audit trail, not fix a stale citation.
  • ClaudeEXPORT/memories.json/conversations.json also still reference the old names — left untouched on purpose, same reasoning as the memory note on that archive: it's a frozen export, mining material, not live documentation to keep in sync.

Changes:

  1. Makefile.starkernel: LITHOS_VERSION ?= 2.0.12.0.0.
  2. The rename-gap fix above (7 files).
  3. Verified 3-arch boot (clean qemu, amd64/aarch64/riscv64, each in the foreground): all three show LithosAnanke v2.0.0 in the boot banner (confirmed directly in each serial log, not assumed from the Makefile edit alone), zero build errors, zero unexpected warnings, clean shutdown.
  4. Moved the existing v2.0.0 git tag (previously at 2efd7fe, the original QEMU-release milestone commit — that commit and its own message stay fully intact in history, only the tag pointer moves) to the current master/v2.0.1-branch HEAD, per explicit instruction — the prior tag placement was itself part of the same "got ahead of myself" correction, not a separate decision. No remote tag existed yet (git ls-remote --tags origin was empty for v2.0.0), so no destructive remote operation was needed, only a local move-and-push.
  5. Follow-up, same day: v2.0.1 (the working branch this and Task 1 happened on) deleted, local and origin — confirmed a strict ancestor of master's new HEAD first, so nothing was lost. master is the repo's only branch from here on.

II. Three architectures, three different hardware scopes

Per direct instruction, 2026-09-04. The real-hardware targets are not symmetric across architectures — each gets its own section below because the actual scope of "done" is different for each:

  • amd64 — genericity is the goal, not just the SER5. The Beelink SER5 is the machine in hand and the development/reference target, but the real requirement is broader: this needs to boot on any x86_64 machine — laptop, desktop, tower, or mini PC — not just one vendor's quirks. SER5-only success is necessary but not sufficient; anything that works only because of an SER5-specific assumption (a particular ACPI table shape, a specific UEFI implementation's quirks) is a bug against this goal, not a deferred nice-to-have.
  • aarch64 — Raspberry Pi 5, and only the Raspberry Pi 5. No genericity requirement across aarch64 boards — this is the one and only target for this architecture.
  • riscv64 — Milk-V Mars, and only the Milk-V Mars. Same as aarch64: one specific board, not a generic riscv64-SBC goal.

How to use sections IIIV below. Same discipline as everything else in this series: plan before building, one section at a time, iterating — not all three architectures in parallel, and not front-loading a complete plan before any real hardware is in front of us. Each section starts with what's already true (existing repo infrastructure, already-decided policy) and what's still genuinely unknown, not assumed.

Already true, not to be re-derived:

  • ROADMAP.md's "Board-by-board hardware rollout" already names this v2.2.0's gate: the generic GPT/FAT32 thumbdrive image (make -f Makefile.starkernel ARCH=amd64 thumbdrive, already built — Makefile.starkernel:1018) flashes to and boots on the real SER5 via its real UEFI, reaching POST + ok>, with the amd64 RDRAND entropy backend (src/starkernel/rng/rng.c, already built and part of master) serving live entropy.
  • iso-usb (Makefile.starkernel:1060) is the alternate, novice-friendly path (UEFI isohybrid ISO for tools like GNOME Disks "Restore Disk Image...") — same underlying image, different flashing UX.
  • FABRIC-2.md §I.6's own 8-step physical-boot sequence (build ISO, identify the target device, flash it, physically boot, decide an observation method, confirm POST, confirm ok>, document) is the closest thing to an existing plan — but it predates the genericity requirement and was written with no hardware in hand yet.

Decided in conversation, 2026-09-04:

  • Observation: HDMI (interactive) + serial (logged transcript), both. The kernel's own VT100 framebuffer console (console.c/vt100.c/framebuffer.c) already gives a real interactive display over HDMI — no new code needed there. Serial capture, if the SER5 exposes a UART header, uses the Raspberry Pi's own GPIO UART as the USB-serial bridge (already available hardware, not a purchase blocker) — this needs the SER5's own UART pins physically identified first (not yet confirmed it has an accessible header at all).
  • Genericity is verified by standards-compliance, not a second machine — no second x86_64 box is available right now. The bar is: nothing in the boot path may depend on an SER5-specific assumption (a particular ACPI table shape, a specific UEFI implementation's quirk) — argued by code audit against real UEFI/ACPI standards, not by testing on a second board, until one becomes available. This is a real constraint on the punch list below (item 6), not a deferred nice-to-have.
  • Secure Boot: already disabled on this SER5. No signed-loader work needed for this pass — "Secure Boot disabled in firmware setup" is the supported path, documented as such rather than built around.

Punch list, this cadence's actual next steps:

  1. Build the generic thumbdrive image: make -f Makefile.starkernel ARCH=amd64 thumbdrive.
  2. Flash it to a USB stick (dd, per the target's own existing usage message).
  3. Physically inspect the SER5 for an exposed UART header/pins; if present, wire the Raspberry Pi's GPIO UART to it as the serial bridge. If absent, HDMI-only for this pass — not a blocker, just a scope note for step 7's log.
  4. Connect HDMI + keyboard to the SER5.
  5. Boot the SER5 from the flashed stick (firmware boot-order menu as needed — Secure Boot already disabled, confirmed above, so no signing prompt expected).
  6. Code audit pass (can happen before or in parallel with 15, doesn't need the hardware in hand): review the amd64 boot path (src/starkernel/boot/uefi_loader.c, arch/amd64/*.c) for anything that assumes SER5-specific hardware rather than standard UEFI/ACPI — this is what "genericity" actually rests on per the decision above, not the SER5 boot succeeding alone.
  7. Capture the boot: confirm POST reaches the same 1012/0/0 result QEMU shows, confirm ok>/zuse)ok>, confirm rng: backend = rdrand (live entropy, not the QEMU-only virtio-rng path), save the serial transcript (if wired) the same way logs/ already keeps QEMU's.
  8. Mint a Zuse identity on a second thumbdrive on the real SER5, confirm it re-attaches — the same real-hardware round-trip ROADMAP.md's v2.2.0 gate already names.
  9. Update this section with results — pass/fail per step, any SER5-specific or genuinely generic-UEFI finding either way, before moving to aarch64.

IV. aarch64 — Raspberry Pi 5

Already true: ROADMAP.md names this v2.4.0's gate: boots on the real board, aarch64 peripheral-RNG backend live, Zuse mint/attach on real media. The peripheral-RNG backend itself is not yet built — today's rng_get_bytes() (src/starkernel/rng/rng.c) only has a virtio-rng path, real on QEMU, meaningless on real Pi 5 hardware (no virtio device there).

Genuinely open, not yet decided:

  • Same observation-method question as amd64 (no QEMU serial socket on real hardware) — possibly shared tooling/approach across both boards once decided once.

IV.1 — Boot-chain decision: UEFI vs. native, researched 2026-09-04

Researched, not assumed (web search, current as of this session):

UEFI option investigated and found weak. A real UEFI+ACPI firmware for Pi 5 exists — rpi5-uefi (TF-A + EDK2, SBBR-compliant). But: it's archived as of 2025-02-04, support ended because newer Pi EEPROM firmware broke compatibility with it; its own README says ACPI support is "under development and limited to a few devices"; RP1 Ethernet/GPIO/PWM/EEPROM don't work under it. This kernel's whole aarch64 boot path (boot/uefi_loader.c, BootInfo->acpi_table) assumes UEFI+ACPI the same way amd64 and the QEMU aarch64 target do — but that assumption may not hold on a real, current-firmware Pi 5 at all.

Native boot flow — the real alternative, researched concretely:

  • Boot partition needs bcm2712-rpi-5-b.dtb, config.txt, and the kernel image itself — Pi 5 firmware defaults to loading kernel_2712.img, falling back to kernel8.img if that's absent.
  • config.txt needs os_check=0 for a non-Linux image, or the firmware assumes Linux and loads from 0x200000 instead of the classic Pi bare-metal load address 0x80000.
  • Entry protocol: x0 = 32-bit DTB pointer (upper 32 bits of the 64-bit register unspecified — must mask before use), x1x3 reserved/zero. No UEFI PE loader, no ACPI at all — a completely different entry shape from boot/uefi_loader.c.
  • Framebuffer: the VideoCore mailbox property interface (channel 8) — a real, different mechanism from UEFI GOP, no precedent anywhere in this codebase today.

Decision, per direct instruction 2026-09-04: native boot flow. Not UEFI. The archived, partially-working UEFI project is too fragile a foundation to build a real-hardware release on top of.

What this actually means for the codebase, named honestly rather than estimated small:

  • A new, non-UEFI entry path for aarch64 real hardware — this kernel's boot sequence currently assumes uefi_loader.c's PE-loader shape unconditionally on aarch64; a Pi 5 native boot needs its own entry point (linked at 0x80000, receiving x0 = DTB pointer directly, no BootInfo from UEFI at all).
  • A DTB-driven BootInfo equivalent replacing ACPI-sourced data for this path — memory map, peripheral addresses (UART, etc.) all come from the devicetree instead.
  • One real, genuine piece of reusable groundwork: starkernel/hal/fdt.c/fdt.h, the minimal FDT reader already built for riscv64's timebase-frequency lookup (arch/riscv64/timer.c), is directly extensible for this — parsing bcm2712-rpi-5-b.dtb for peripheral addresses is the same kind of lookup, not a new mechanism.
  • A new mailbox-property-interface framebuffer driver — genuinely new code, no existing precedent in this codebase, needed before the VT100 console framework (console.c/vt100.c/framebuffer.c) has anything to draw onto for this board.
  • This is a real architectural fork for aarch64, not a small per-board addition — QEMU aarch64 keeps its existing UEFI+ACPI path unchanged; Pi 5 real hardware gets a second, parallel entry path. Not yet scoped into a punch list — that's the next step, once this fork's own shape (how much of kernel_main.c's post-entry sequence can stay shared between the two paths vs. needs its own branch) is thought through.

IV.2 — Peripheral RNG: unresolved, not just under-researched

ROADMAP.md names an "aarch64 peripheral-RNG backend" as part of v2.4.0's gate. Researched directly rather than assumed still-TODO: Broadcom's iproc-rng200 block (real, on Pi 4/BCM2711 as brcm,bcm2711-rng200) has no bcm2712 compatible-string entry anywhere in current mainline Linux (checked the actual driver's of_device_id table directly). The RP1 companion chip's own published peripheral list (GPIO/USB/Ethernet/DMA/ADC/PLLs/SRAM/ UARTs/SPIs) doesn't mention an RNG either. Two real possibilities, not yet distinguished: BCM2712 still has the RNG200 block but Linux hasn't wired it into a devicetree binding yet, or it genuinely isn't exposed to the ARM cores this generation. No public register address exists to target right now — this needs either a Broadcom datasheet (if one becomes available) or direct hardware probing once the board is in hand (scan the known BCM2711 RNG200 offset region on the BCM2712 memory map and see if anything responds — risky without a datasheet confirming it's safe to touch, so likely a "board in hand, careful probe" task, not a today task).

Already true: ROADMAP.md names this (generically, "Milk-V") as part of v2.5.0's gate: boots on the real board, the Zkr (RNDR) entropy backend live. Same gap as aarch64: rng_get_bytes() has no riscv64 hardware-RNG path today, only virtio-rng.

Genuinely open, not yet decided:

  • Milk-V Mars's actual boot chain — this project's QEMU riscv64 target boots via UEFI (EDK2 RISCV_VIRT firmware, confirmed in Makefile.starkernel's own qemu recipe), but real riscv64 SBCs commonly boot via U-Boot + OpenSBI + a devicetree instead of UEFI. Which one the Mars actually uses is not yet confirmed — this is the single most consequential unknown across all three sections, since it could mean this board needs a genuinely different boot entry path, not just different peripheral addresses.
  • Zkr/RNDR instruction availability on the Mars's actual CPU (riscv64 Scalar Crypto extension support varies by implementation) — not yet confirmed.
  • Same observation-method question as the other two boards.