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
237 lines
16 KiB
Markdown
237 lines
16 KiB
Markdown
# 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 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.1` — **Fast-forward**, `d2a0305..72c14cb`,
|
||
confirming the investigated ancestor relationship held exactly as expected; no conflict, no
|
||
merge commit.
|
||
3. `git push origin master` — `origin/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.1` → `2.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 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 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 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.
|
||
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:**
|
||
- 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_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.
|