# 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). **Decided in conversation, 2026-09-04:** - **Observation: HDMI-only for this board's own bring-up.** No second Pi, no dedicated USB-serial adapter available. The Milk-V Mars could in principle serve as a GPIO-UART bridge once it arrives (same 40-pin-header shape as the SER5 plan), but using it to observe the Pi 5 before the Mars has been independently validated itself would be a chicken-and-egg dependency, not a real plan. Revisit serial capture later if genuinely needed, once at least one board is proven working — not a blocker for this pass. - **Both boards (Pi 5, Milk-V Mars) arrive 2026-09-17.** Real runway exists to finish the design/code work below *before* any hardware is in hand — "plan well before doing," per direct instruction. **Still genuinely open, not yet decided:** - **A pinned GPIO VM, theory-stage** — see `FABRIC-4.md` §2. Raised in conversation, not yet scoped; downstream of §IV.1's own native-boot-flow work (a GPIO VM needs GPIO addresses from the DTB the same way the rest of this bring-up does). ### 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`](https://github.com/worproject/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), `x1`–`x3` 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). Deliberately **not a blocker for the first native boot** — reaching `ok>` doesn't require a live entropy backend; `rng_get_bytes()` already has a "no entropy backend available" WARNING path (`rng.c`) rather than a hard failure, so this can land after boot succeeds. ### IV.3 — Punch list: design/code work, no hardware needed (before 2026-09-17) Traced against real code before writing this, not estimated: `boot_info->acpi_table`'s only aarch64-relevant consumers today are `pci_init()` (`kernel_main.c:589`, unconditional, not amd64-gated — relevant because RP1 is PCIe-attached on real Pi 5 hardware) and this session's own `running_under_hypervisor()` (`arch/aarch64/timer.c`, already degrades safely to "not a hypervisor" when `acpi_table` is `NULL` — no fix needed there). `ioapic_init()`/`i8042_init()` are `#ifdef ARCH_AMD64`-gated, irrelevant here. `arch/aarch64/apic.c` (GIC init) already only ever tries `boot_info->dtb`, never `acpi_table` — its own doc comment already anticipated DTB-based discovery, just blocked until now because QEMU's own UEFI firmware never publishes one; Pi 5 native boot removes that blocker for free. 1. **Entry stub**: new `boot/native_rpi5_entry.S` (or similar) — linked at `0x80000`, receives `x0` = DTB pointer per the researched protocol (§IV.1), minimal early setup (stack — either a small fixed BSS region, matching `BootInfo.kernel_stack_base`'s existing "zero = fall back to 2 MiB BSS stack" convention, or something new if that's insufficient this early). 2. **DTB → `BootInfo` constructor**: new C function populating the *existing* `BootInfo` struct (`include/starkernel/uefi.h`) from the DTB instead of UEFI protocols — `dtb` = the real pointer, `acpi_table` = `NULL` (already the correct value for "no ACPI," per IV's own research above), `runtime_services` = `NULL`, `memory_map`/`framebuffer`/`args` populated from DTB `/memory`+`/reserved-memory`, the mailbox interface (next item), and DTB `/chosen` `bootargs` respectively. Then calls the **existing, unmodified** `kernel_main()` — this is the crux of why most of M1–M9 stays shared. 3. **Mailbox-property-interface framebuffer driver** — genuinely new code (§IV.1's own assessment), populating `BootInfo.framebuffer` the same shape UEFI GOP currently does, so `console.c`/`vt100.c`/`framebuffer.c` need no changes at all downstream. 4. **`fdt.c`/`fdt.h` extension — DONE 2026-09-04.** Added `fdt_find_node_by_compatible()` (matches any entry in a node's NUL-separated `compatible` list, first match in document order) and `fdt_find_prop_in_node()` (scoped to that one node's own direct properties only — stops at the first child node or the node's own end, never descends or continues into a sibling). Same minimal, non-tree-building style as the existing reader — no new state, no allocation, one linear scan per call. Verified 3-arch boot to `ok>` (compile-only — no caller wired in yet; this is the shared primitive items 1/3 above and §V.3 item 3 below will each call once built). 5. **`pci_init()` DTB path**: a devicetree-based alternative for RP1 discovery, since `boot_info->acpi_table` will be `NULL` on this path and RP1 is PCIe-attached, not directly memory-mapped. 6. **`config.txt` contents, decided from research**: `kernel=kernel_2712.img` (or `kernel8.img` with `os_check=0` if the Pi-5-specific name isn't used), `arm_64bit=1`, pointing at `bcm2712-rpi-5-b.dtb`. **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 requirement, same shape as amd64's. 11. Update this section with results before moving to riscv64's own hardware-dependent steps. ## 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`. ### V.1 — Boot chain: resolved, researched 2026-09-04 **Resolved, not left open.** The Mars is a documented mainline U-Boot board target in its own right ([U-Boot docs — Milk-V Mars](https://docs.u-boot.org/en/latest/board/starfive/milk-v_mars.html)), and it uses **the exact same U-Boot binaries as the StarFive VisionFive 2** — same SoC (StarFive JH7110), board identity detected at SPL time, devicetree patched accordingly, no separate Mars-specific firmware. This directly answers §V's own previously-open question: **U-Boot + OpenSBI + devicetree, not UEFI** — same fork this kernel already decided for aarch64 (§IV.1), now confirmed for riscv64 too. **Boot chain, concretely:** 1. BootROM (ZSBL), StarFive's on-chip loader at `0x2A000000`, selects boot media by GPIO pins. 2. U-Boot SPL (FSBL) — initializes DRAM, configures PLLs. 3. OpenSBI (`fw_dynamic.bin`) — M-mode runtime services. 4. U-Boot main, S-mode, depends on OpenSBI. 5. Boot media: QSPI flash (recommended) or UART XMODEM (recovery). SD/eMMC boot modes are deprecated in current U-Boot. **Entry protocol, from real VisionFive 2 bare-metal work (same SoC, directly applicable per §VI's own cross-reference):** - Entry point `0x40000000`. - Core identification via the `mhartid` CSR — the SiFive S7 monitor core is hart 0, the four U74 application cores are harts 1–4 (matches the QEMU riscv64 target's own hart numbering convention already assumed elsewhere in this codebase — worth double-checking, not assuming, once real hardware is in hand). - UART at `0x10000000`, 115200 baud, already initialized by firmware before handoff. - Custom bare-metal images package via `vf2-imager` (invokes U-Boot's `mkimage`) into a FIT image — same tooling should apply to the Mars, unconfirmed until tried. - **Not yet found**: what registers carry the DTB pointer/hart ID at the actual kernel entry point under this specific chain (the source consulted covered the image-packaging tooling, not the OpenSBI→kernel handoff register convention). **Resolved 2026-09-04**: standard RISC-V SBI boot protocol, confirmed via OpenSBI's own docs — `a0`=hart ID, `a1`=DTB pointer, S-mode entry. Not chain-specific guesswork; this is the universal convention OpenSBI's `FW_DYNAMIC` firmware type uses regardless of vendor, so it applies to this chain directly. **What this means for the codebase** — same shape of fork as aarch64 (§IV.1): a non-UEFI entry path, a DTB-driven `BootInfo` equivalent (the existing `starkernel/hal/fdt.c` reader extends here too, same as for the Pi 5), no ACPI. **Decided in conversation, 2026-09-04: observation is HDMI-only**, same reasoning and same constraint as the Pi 5 (§IV) — no bridge hardware available for this board's own first bring-up either; the Mars has its own HDMI 2.0 output (§VI). ### V.2 — Peripheral RNG and Zkr: still genuinely open - Zkr/RNDR instruction availability on the Mars's actual CPU (riscv64 Scalar Crypto extension support varies by implementation) — not yet confirmed; the VisionFive 2 bare-metal research above didn't surface this either, would need its own targeted look (or a real-hardware probe of `misa`/the Zkr extension discovery mechanism). Deliberately **not a blocker for first boot**, same reasoning as §IV.2's aarch64 RNG gap — `rng_get_bytes()` already WARNs rather than hard-fails with no backend. - **Whether the Mars needs the same pinned-GPIO-VM treatment as the Pi 5** — explicitly **not decided either way**, per direct instruction ("same for Milk-V (? not sure here)"). See `FABRIC-4.md` §2. The Mars does have its own 40-pin GPIO header (§VI), so the open question is the VM architecture around it, not whether the hardware exists. ### V.3 — Punch list: design/code work, no hardware needed (before 2026-09-17) Traced against real code before writing this, same discipline as §IV.3: `pci_init()` (`kernel_main.c:589`, unconditional) is the one real `acpi_table` consumer relevant here too — the Mars's M.2 E-Key slot (§VI) is PCIe-attached, same shape of gap as the Pi 5's RP1. `riscv64/timer.c` is **already** fully DTB-driven (both `timebase-frequency` and this session's own hypervisor-detection check) — no further work needed there; it was built DTB- first from the start, unlike aarch64's timer which needed a new ACPI-based check today. **One real, already-flagged risk found while tracing this**: `arch/riscv64/apic.c`'s own doc comment says the PLIC base address is "a constant, not discovered from `boot_info->dtb`" — and `arch/riscv64/plic.c`'s own doc comment (predating this document) already warned `PLIC_BASE`/`PLIC_CONTEXT_S` are "QEMU-virt-specific... not assumed stable across" other configurations. That warning becomes concrete now: the JH7110's real PLIC address on the Mars is not confirmed to match QEMU-virt's, and the interrupt controller will not work correctly if it doesn't. This is a real punch-list item, not a hypothetical. 1. **Entry stub**: new native riscv64 entry point at `0x40000000` (§V.1), receiving `a0`=hart ID, `a1`=DTB pointer directly (now-confirmed SBI convention) — no UEFI, no PE loader. 2. **DTB → `BootInfo` constructor**: same shape as aarch64's (§IV.3 item 2) — `dtb`=real pointer, `acpi_table`=`NULL`, memory map from DTB `/memory`+`/reserved-memory`, `args` from `/chosen`/`bootargs`. 3. **PLIC base address: make it DTB-discovered**, not the current QEMU-virt-specific constant — the one concrete, already-flagged risk above. `fdt.c`'s node-scoped lookup extension (§IV.3 item 4, **DONE 2026-09-04**, shared with the Pi 5's UART/mailbox addresses) is the primitive this calls (`fdt_find_node_by_compatible(fdt, "sifive,plic-1.0.0")` → `fdt_find_prop_in_node(..., "reg", ...)`, plausible compatible string, not yet confirmed against the Mars's real DTB) — the actual PLIC-init call site update is still open, only the primitive it needs now exists. 4. **Framebuffer for HDMI output**: JH7110's display path is genuinely unresearched this pass — unlike the Pi 5's mailbox interface (well-documented, reused across many Pi bare- metal projects), no equivalent research done yet for JH7110's own display controller. Flagged here rather than assumed simple. 5. **`pci_init()` DTB path**: shares the same new code §IV.3 item 5 scopes for the Pi 5's RP1 — one implementation, two consumers (M.2 here, RP1 there), assuming the underlying DTB PCI binding shape is similar enough (ECAM-based, most likely, but not yet confirmed for JH7110 specifically). 6. **Boot image packaging**: `vf2-imager`/`mkimage`-based FIT image (§V.1) — confirm this tooling's actual invocation once building the first real image, not just cited from VisionFive 2 research. **Hardware-dependent, after 2026-09-17:** 7. 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 requirement. 11. Update this section with results. --- ## VI. Hardware identification reference Per-board SoC/CPU facts, consolidated here so later sections don't have to re-derive them. Researched 2026-09-04 (web search, sources cited); anything not directly confirmed against the actual unit in hand is flagged as such rather than assumed. ### amd64 — Beelink SER5 (reference/development machine) - **CPU: AMD Ryzen 7 family.** Beelink has shipped the "SER5" name with several different Ryzen 7 SKUs over its product life (5700U, 5800H, 7735HS all confirmed to exist under this branding) — **exact SKU on this unit not yet confirmed**; check `dmesg`/BIOS/the physical unit when convenient (`cat /proc/cpuinfo` or the BIOS splash screen under Linux/before LithosAnanke boots, since LithosAnanke itself has no CPU-identification word yet). Not load-bearing for this document's own genericity requirement (§III) — the boot path must not depend on which SKU this is, by design — but worth pinning down for this reference's own accuracy. - **Architecture generation**: Zen2 (5700U/5800H) or Zen3 (7735HS) depending on the SKU above — matters for any future CPU-feature-detection work (e.g. RDRAND is present on all of these; that part's already confirmed live via `rng: backend = rdrand`, §III). - Sources: [Gentoo wiki — SER5 5560U](https://wiki.gentoo.org/wiki/Beelink_SER5_AMD_Ryzen_5_5560U_Mini_PC), [Starry Hope — SER5](https://www.starryhope.com/minipcs/models/beelink-ser5-mini-pc/), [Starry Hope — SER5 Pro](https://www.starryhope.com/minipcs/models/beelink-ser5-pro-mini-pc/), [Minixpc — SER5 Max](https://minixpc.com/blogs/news/beelink-ser5-max-review-powered-by-amd-ryzen7-5800h-processor). ### aarch64 — Raspberry Pi 5 (sole target) - **SoC: Broadcom BCM2712.** - **CPU**: quad-core 64-bit Arm Cortex-A76, 2.4 GHz, 512 KB per-core L2 cache, 2 MB shared L3. - **GPU**: VideoCore VII, 12-core, 800 MHz, OpenGL ES 3.1 + Vulkan 1.2 (not relevant to LithosAnanke's own framebuffer work — that goes through the mailbox property interface, §IV.1 — but recorded here for completeness). - **RAM**: LPDDR4X-4267, board variants at 1/2/4/8/16 GB, 32-bit memory interface, ~17 GB/s bandwidth. - **I/O**: RP1 companion chip (PCIe 2.0 x4-attached) handles GPIO, USB 2.0/3.0, Gigabit Ethernet, CSI/DSI, analog video — confirmed separately (§IV.2) to have no RNG peripheral in its own published peripheral list. - **Cortex-A76 and `FEAT_RNG` (ARMv8.5 `RNDR`/`RNDRRS`)**: not confirmed present — A76 is not among the cores that typically implement this feature (more common on newer cores like Cortex-X2/A710); if this matters for any future entropy-source decision, verify via `ID_AA64ISAR0_EL1` directly on the real board rather than assuming either way. - Sources: [CNX Software — Pi 5 launch](https://www.cnx-software.com/2023/09/28/raspberry-pi-5-sbc-broadcom-bcm2712-quad-core-cortex-a76-soc/), [Raspberry Pi — Processors doc](https://www.raspberrypi.com/documentation/computers/processors.html), [sbcwiki — BCM2712](https://sbcwiki.com/docs/soc-manufacturers/broadcom/bcm2712/boards/rasperrypi-5/). ### riscv64 — Milk-V Mars (sole target) - **SoC: StarFive JH7110**, 28 nm. - **CPU**: 4× SiFive U74-MC application cores (RV64GC) + 1× SiFive S7 monitor core, up to 1.5 GHz. - **RAM**: up to 8 GB LPDDR4; storage via eMMC slot + microSD slot. - **I/O**: 3× USB 3.0, 1× USB 2.0, HDMI 2.0 (4K), Gigabit Ethernet with PoE support, M.2 E-Key (WiFi/BT), 4-lane + 2-lane MIPI CSI, 40-pin GPIO header. - **Physical**: designed to Raspberry Pi 3B dimensions — cases/heatsinks/fans for that form factor are compatible. - **Multimedia**: H.264/H.265 4K@60fps decode, H.265 1080p@30fps encode (not relevant to LithosAnanke's own bring-up, recorded for completeness). - Same JH7110 SoC as the StarFive VisionFive 2 — any VisionFive 2 bring-up material found while researching §V's own boot-chain question is likely directly applicable here too, worth checking first before assuming Mars-specific research is needed from scratch. - Sources: [milkv.io — Mars overview](https://milkv.io/docs/mars/overview), [milkv.io — Mars product page](https://milkv.io/mars), [TinyComputers.io — Mars review](https://tinycomputers.io/posts/milk-v-mars-review.html). ### Noted for later, not yet in scope — BeagleBone Black Added to this reference per direct instruction 2026-09-04, **recorded only — no work scoped around it yet.** Genuinely different from the three targets above: the BeagleBone Black's SoC is a **32-bit ARM** part, not aarch64 — a fourth architecture this kernel has no support for at all today (amd64/aarch64/riscv64 only), not another board under an existing one. - **SoC: TI Sitara AM335x.** - **CPU**: single-core ARM Cortex-A8, 1 GHz, armv7-a (32-bit) — up to ~2000 MIPS. - **RAM**: 512 MB DDR3L. Storage: 4 GB eMMC (default boot source) + microSD (secondary/ overridable to primary). - **Other on-die units**: PowerVR SGX530 3D GPU; 2× PRU (Programmable Realtime Unit) 32-bit 200 MHz microcontrollers — real-time I/O coprocessors, no equivalent on any of the three boards above; crypto accelerators. - **Boot modes**: eMMC, microSD, serial, USB. - Sources: [element14 — BBB product page](https://www.element14.com/community/docs/DOC-84108/l/beaglebone-black-development-board-with-1ghz-am335x-arm%C3%A3%C3%A2-cortex-a8-processor), [TI.com — BEAGL-BONE-BLACK](https://www.ti.com/tool/BEAGL-BONE-BLACK). ### Noted for later, not yet in scope — Zynq-7000 (Puzhi PZ7010/PZ7020 "StarLite") Added per direct instruction 2026-09-04, **recorded only — no work scoped around it yet.** Unlike BeagleBone Black above, this one isn't a random addition: `ROADMAP.md` already names **Zynq FPGA as the next big milestone beyond v2.5.0** — "the step where the battle-tested amd64/aarch64/riscv64 story rides on configurable silicon," and the three-product split decided alongside it names "hardware steady-state machinery with sealed executions, HOL-proven" as the FPGA-native product this board would ultimately serve. This entry just puts a concrete, purchasable board under that already-named milestone. - **Board**: Puzhi PZ7010-StarLite (XC7Z010) or PZ7020-StarLite (XC7Z020) — same board design, two SoC variants. 90×60mm, black PCB, immersion gold finish. - **SoC: Xilinx/AMD Zynq-7000**, combining a **Processing System (PS)** — dual-core ARM Cortex-A9, up to 667 MHz (`-1` speed grade) or 800 MHz (`-2`, XC7Z020 only) — with **Programmable Logic (PL)**, 28 nm Artix-7/Kintex-7-based FPGA fabric. Genuinely a fifth architecture class in this reference: ARMv7-A again (like BeagleBone Black), but a different core (Cortex-A9 vs. A8) *and* an FPGA fabric with no equivalent on any board above — this is the "configurable silicon" milestone `ROADMAP.md` already flagged as reshaping the hardware story (soft/hard CPU cores, PL fabric, non-standard memory map, custom peripherals), not a small per-board addition even in concept. - **PS details** (identical between both variants): 256 KB on-chip memory, DDR3 controller, 32 KB I-cache + 32 KB D-cache per core, 512 KB shared L2. - **PL resources (the actual XC7Z010 vs. XC7Z020 difference)**: XC7Z010 — 4,400 logic slices, 17,600 6-input LUTs, 35,200 flip-flops, 270 KB block RAM, 80 DSP slices. XC7Z020 — 13,300 logic slices, 53,200 LUTs, 106,400 flip-flops, 630 KB block RAM, 220 DSP slices. - **RAM/storage**: 512 MB/1 GB DDR3, QSPI flash, EEPROM, SD boot. - **I/O**: JTAG, UART, HDMI out, Gigabit Ethernet, USB 2.0 host, 40-pin expansion; MIPI CSI on the 7020 variant only. - Sources: [Puzhi — PZ7010-StarLite](https://www.en.puzhi.com/Product/AMD-FPGA-Development-Board/Zynq-7000-SoC/PZ7010-StarLite), [Puzhi — PZ7020-StarLite](https://www.en.puzhi.com/Product/AMD-FPGA-Development-Board/Zynq-7000-SoC/PZ7020-StarLite), [Xilinx/AMD — Zynq-7000 SoC Data Sheet (DS190)](https://www.mouser.com/datasheet/2/903/ds190-Zynq-7000-Overview-1595492.pdf), [PCBSync — XC7Z010 vs XC7Z020 comparison](https://pcbsync.com/xilinx-xc7z010/).