Decisions from conversation: observation is HDMI (interactive) + serial via the Raspberry Pi's own GPIO UART as the bridge, if the SER5 exposes a UART header (not yet confirmed); genericity is verified by a code audit against real UEFI/ACPI standards rather than a second physical machine (none available); Secure Boot is already disabled on this SER5, so no signed-loader work is needed for this pass. 9-item punch list follows: build+flash the thumbdrive image, physically inspect for a UART header, connect HDMI, boot, a code-audit pass for SER5-specific assumptions (the actual genericity bar), capture the boot (POST/ok>/rdrand backend/serial transcript), mint+re-attach Zuse on real hardware, then update this section with results before moving to aarch64. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
16 KiB
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.1— true.master(local HEADd2a0305) is a strict ancestor ofv2.0.1(HEADb031b80) —v2.0.1is exactlymasterplus 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/mastercarries exactly one commit beyond localmaster(58c59e8, "Initial commit") that localmasterhadn't fetched yet — confirmed already contained inv2.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 oldFABRIC.md/FABRIC-2.md/FABRIC-3.mdnaming (unrenamed) — expected, since today's rename commit (b031b80) only exists onv2.0.1so far. The fast-forward brings the rename tomasteralong with everything else; nothing separate needs doing for it.
Plan:
- Fast-forward
mastertov2.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. - Push
mastertoorigin. - 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.mdalready mandates for any kernel change —clean qemuon all three architectures, in the foreground, one at a time, each reachingok>and shutting down cleanly. Since the tree is byte-identical tov2.0.1's post-fast-forward, this is expected to reproduce exactly whatv2.0.1's own last acceptance pass already showed — the point of re-running it here is to confirm that expectation holds onmasteritself, not to assume it from the fast-forward alone. - Return to
v2.0.1as 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:
- Committed the write-up above on
v2.0.1first (72c14cb), pushed. This becamev2.0.1's new tip. git checkout master && git merge --ff-only v2.0.1— Fast-forward,d2a0305..72c14cb, confirming the investigated ancestor relationship held exactly as expected; no conflict, no merge commit.git push origin master—origin/mastermoved58c59e8..72c14cb.- Verified on a genuinely clean
mastertree, not inferred from the fast-forward:- Hosted build (
make clean && make): clean compile, zero warnings, same asv2.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 tov2.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/.
- Hosted build (
masterandv2.0.1are now identical (72c14cbon both,originand local). Returned tov2.0.1as 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, fourproof/*.thyfiles, andsrc/starkernel/arch/amd64/isr.Sall still had staleFABRIC.md/FABRIC-2.md/FABRIC-3.mdcitations — 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, orderedFABRIC-3→2→1→0placeholders 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.jsonalso 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:
Makefile.starkernel:LITHOS_VERSION ?= 2.0.1→2.0.0.- The rename-gap fix above (7 files).
- Verified 3-arch boot (
clean qemu, amd64/aarch64/riscv64, each in the foreground): all three showLithosAnanke v2.0.0in the boot banner (confirmed directly in each serial log, not assumed from the Makefile edit alone), zero build errors, zero unexpected warnings, clean shutdown. - Moved the existing
v2.0.0git tag (previously at2efd7fe, the original QEMU-release milestone commit — that commit and its own message stay fully intact in history, only the tag pointer moves) to the currentmaster/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 originwas empty forv2.0.0), so no destructive remote operation was needed, only a local move-and-push. - Follow-up, same day:
v2.0.1(the working branch this and Task 1 happened on) deleted, local andorigin— confirmed a strict ancestor ofmaster's new HEAD first, so nothing was lost.masteris 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 III–V 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.
III. amd64 — generic x86_64 bare metal (reference hardware: Beelink SER5)
Already true, not to be re-derived:
ROADMAP.md's "Board-by-board hardware rollout" already names thisv2.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 ofmaster) 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, confirmok>, 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:
- Build the generic thumbdrive image:
make -f Makefile.starkernel ARCH=amd64 thumbdrive. - Flash it to a USB stick (
dd, per the target's own existing usage message). - 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.
- Connect HDMI + keyboard to the SER5.
- Boot the SER5 from the flashed stick (firmware boot-order menu as needed — Secure Boot already disabled, confirmed above, so no signing prompt expected).
- Code audit pass (can happen before or in parallel with 1–5, 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. - Capture the boot: confirm POST reaches the same
1012/0/0result QEMU shows, confirmok>/zuse)ok>, confirmrng: backend = rdrand(live entropy, not the QEMU-onlyvirtio-rngpath), save the serial transcript (if wired) the same waylogs/already keeps QEMU's. - Mint a Zuse identity on a second thumbdrive on the real SER5, confirm it re-attaches —
the same real-hardware round-trip
ROADMAP.md'sv2.2.0gate already names. - 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:
- Raspberry Pi 5's own boot chain — UEFI (e.g. via the community EDK2 port) or the Pi's native boot flow? This determines whether the existing UEFI-targeted boot code needs a different entry path for this board at all, or just different ACPI/DTB-provided data.
- Which peripheral RNG the Pi 5 actually exposes, and how to read it (memory-mapped peripheral vs. a firmware call) — not yet researched.
- Same observation-method question as amd64 (no QEMU serial socket on real hardware) — possibly shared tooling/approach across both boards once decided once.
V. riscv64 — Milk-V Mars
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_VIRTfirmware, confirmed inMakefile.starkernel's ownqemurecipe), 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.