13 Commits
Author SHA1 Message Date
Robert Allan JamesandClaude Sonnet 5 67e3fb7459 rpi5_native_boot.c: Pi 5 DTB->BootInfo constructor (FABRIC-3.md §IV.3 item 2)
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
rpi5_native_boot() populates the existing BootInfo struct from the
devicetree instead of UEFI protocols, then calls the existing, unmodified
kernel_main() -- the crux of why most of M1-M9 stays shared between the
UEFI and native boot paths. native_rpi5_entry.S now tail-calls into it
instead of halting.

memory_map is built from /memory's own reg, honoring the *root* node's
#address-cells/#size-cells (confirmed against bcm2712.dtsi's actual root
node -- <2>/<2> -- not assumed; a wrong cell width here would compile and
boot clean in QEMU while silently corrupting the real memory map on real
silicon). Required a new fdt_find_node_by_device_type() since /memory is
identified by device_type = "memory" per DT spec, not compatible.

args comes from /chosen's bootargs fed into the existing
cmdline_parse_ascii() (confirmed pure C99 with no UEFI coupling before
reusing it). framebuffer comes from the already-built
rpi5_mailbox_get_framebuffer() at a fixed 1920x1080x32 default -- no EDID
query exists in this codebase, flagged rather than guessed past.

Deliberately scoped out, not silently skipped: /reserved-memory is not
parsed. Carving reserved sub-ranges out of /memory's span needs
interval-splitting logic that would be written blind against hardware not
yet in hand -- exactly the kind of code that hides a bug until real
silicon. pmm.c's Pass 3 only ever clears pages this file lists as
EfiConventionalMemory, so the gap is "less usable RAM than optimal," never
"reserved RAM wrongly marked free." Left as its own future item.

Also fixes a real link failure this work surfaced: boot/cmdline.c was only
in LOADER_SRCS_BASE (the .efi target), not KERNEL_SRCS_BASE (the separate
.elf target arch/aarch64/*.c also wildcards into) -- added it there too.

Compile-only-verified; nothing in the existing UEFI/QEMU path calls
rpi5_native_boot(), so this cannot be exercised until real hardware.
Verified 3-arch boot to ok>/zuse)ok>.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 14:42:32 -04:00
Robert Allan JamesandClaude Sonnet 5 281de9547c native_rpi5_entry.S: Pi 5 native (non-UEFI) boot entry stub (FABRIC-3.md §IV.3 item 1)
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
rpi5_native_start masks x0 down to the documented 32-bit DTB-pointer range
(the firmware's own entry protocol leaves the upper 32 bits unspecified),
stores it into g_rpi5_dtb_ptr for the still-open DTB->BootInfo constructor
(item 2) to read, then switches sp to a dedicated 2 MiB BSS stack -- this
path has no EDK2 boot stack to inherit, unlike every other entry path in
this codebase.

Intentionally halts (wfe/b loop) afterward rather than tail-calling into
item 2's constructor, which doesn't exist yet -- no stub function pretending
to be more than it is.

Not yet linked at the real 0x80000 load address; that needs its own linker
script/build target, not scoped into this item. Compiles and links into the
existing ARCH=aarch64 QEMU/UEFI acceptance build as dead code (ELF kernel
build's KERNEL_ASM wildcards every *.S in arch/aarch64/; nothing there
branches to it), same as rpi5_dtb.c/rpi5_mailbox.c before it.

Verified 3-arch boot to ok>/zuse)ok>.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 14:12:42 -04:00
Robert Allan JamesandClaude Sonnet 5 a32b0ebcbe rpi5_mailbox.c: wire the VideoCore mailbox message protocol (FABRIC-3.md §IV.3 item 3)
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
New rpi5_mailbox_get_framebuffer() sends one property-tag request buffer
(phys size, virt size, depth, pixel order, virtual offset, allocate-buffer,
get-pitch) over the register layout rpi5_dtb.c already discovers, populating
an Rpi5FramebufferInfo kept in exact field-for-field sync with uefi.h's
FramebufferInfo so console.c/vt100.c/framebuffer.c need no downstream changes
once this is wired into a real entry stub.

Register offsets (+0x00/+0x18 MBOX0 read/status, +0x20/+0x38 MBOX1
write/status) confirmed against a Pi-5-specific bare-metal reference,
independently cross-checked against this codebase's own rpi5_dtb.c
translated base address.

Caught and fixed a real buffer-overflow bug before compiling: the static
request buffer was sized 32 words against an actual 35-word requirement for
the 7-tag sequence, recomputed exactly rather than re-estimated; resized to
40 words.

Two things flagged as genuinely unverified against real hardware (not
guessed past the comment): the allocate-buffer tag's request-size field
value, and whether its response address needs classic bus-alias masking on
Pi 5 specifically.

Compile-only-verified -- no caller yet (that's the still-open entry-stub/DTB
constructor items). Verified 3-arch boot to ok>/zuse)ok>.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 14:00:32 -04:00
Robert Allan JamesandClaude Sonnet 5 ca52ce8243 rpi5_dtb.c: wire fdt.c's node-scoped lookup into the Pi 5 UART/mailbox addresses
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
New include/starkernel/rpi5_dtb.h / src/starkernel/arch/aarch64/rpi5_dtb.c:
rpi5_uart_base()/rpi5_mailbox_base(), each finding their peripheral by
compatible string ("arm,pl011" / "brcm,bcm2835-mbox") via fdt_find_node_by_
compatible() then reading its "reg" via fdt_find_prop_in_node().

Found and fixed a real translation gap before it could have silently
produced a wrong address: confirmed directly against bcm2712.dtsi
(raspberrypi/linux) that both peripherals live under one "soc"
simple-bus node whose ranges property adds a fixed 0x10_0000_0000
offset to every child reg value. fdt.c's reader deliberately doesn't
apply ranges translation generally (not a general devicetree
library); this file applies that one, fixed, SoC-wide offset
explicitly, documented with the exact devicetree excerpt that
confirmed it.

Compile-only verification -- no caller wired in yet, these two
functions are what the still-open entry-stub and mailbox-framebuffer-
driver punch-list items will call. Verified 3-arch boot to ok>
(amd64/aarch64/riscv64, each in the foreground; rpi5_dtb.o confirmed
built on aarch64, the only arch that compiles this file).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 13:36:08 -04:00
Robert Allan JamesandClaude Sonnet 5 5e46f18fd9 fdt.c: node-scoped lookup extension (FABRIC-3.md SSIV.3/SSV.3 shared item)
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
Adds fdt_find_node_by_compatible() and fdt_find_prop_in_node() to the
minimal FDT reader -- the extension fdt.h's own header comment already
flagged as a known future need ("item 0.6 will need node-scoped reg
lookups"), now with real consumers: the Pi 5's UART/mailbox register
addresses (native boot, no ACPI) and the Milk-V Mars's real PLIC base
address (currently hardcoded to QEMU-virt's own value).

fdt_find_node_by_compatible() matches any entry in a node's
NUL-separated "compatible" list, first match in document order.
fdt_find_prop_in_node() scopes to that one node's own direct
properties only -- stops at the first child node or the node's own
end, per the DT spec's ordering guarantee that a node's properties
always precede its children. Same minimal, non-tree-building,
single-linear-scan-per-call style as the existing reader; no new
state, no allocation.

Compile-only verification -- no caller wired in yet, this is the
shared primitive both boards' own punch-list items will call once
built. Verified 3-arch boot to ok> (amd64/aarch64/riscv64, each in
the foreground).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 13:05:42 -04:00
Robert Allan JamesandClaude Sonnet 5 bffd87615d FABRIC-3.md: riscv64 (Milk-V Mars) planning decided + punch list
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
Resolved SS V.1's flagged gap: standard RISC-V SBI boot protocol,
confirmed via OpenSBI's own docs -- a0=hart ID, a1=DTB pointer, S-mode
entry, universal across FW_DYNAMIC firmware regardless of vendor, not
chain-specific guesswork.

Decided: observation is HDMI-only, same reasoning and constraint as
the Pi 5 (no bridge hardware for this board's own first bring-up
either).

Traced boot_info->acpi_table's real riscv64 consumers the same way as
aarch64: pci_init() again (Mars's M.2 slot is PCIe-attached, same
shape of gap as the Pi 5's RP1); timer.c is already fully DTB-driven,
no work needed there.

One real, already-flagged risk found while tracing this: PLIC_BASE is
a QEMU-virt-specific constant, not DTB-discovered -- arch/riscv64/
plic.c's own doc comment already warned about this; it becomes
concrete now that real hardware is actually in scope. Real punch-list
item, not hypothetical.

6-item no-hardware-needed punch list (entry stub, DTB->BootInfo
constructor, DTB-discovered PLIC base, unresearched JH7110 framebuffer
flagged honestly rather than assumed, shared pci_init() DTB path with
the Pi 5, image-packaging tooling) plus 5 items deferred to
2026-09-17.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 12:55:28 -04:00
Robert Allan JamesandClaude Sonnet 5 1ec2a29384 FABRIC-3.md: aarch64 (Pi 5) planning decided + punch list
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
Decisions from conversation: observation is HDMI-only for this
board's own bring-up (no second Pi, no dedicated USB-serial adapter;
using the Milk-V Mars as a bridge before it's independently validated
would be circular). Both boards arrive 2026-09-17, giving real runway
to finish design/code work first ("plan well before doing").

Traced boot_info->acpi_table's actual aarch64 consumers against real
code before writing the punch list: only pci_init() (unconditional,
relevant since RP1 is PCIe-attached) and this session's own
running_under_hypervisor() (already degrades safely on NULL). GIC
init (arch/aarch64/apic.c) already only tries boot_info->dtb, never
acpi_table -- Pi 5 native boot removes its stated blocker for free.

6-item no-hardware-needed punch list: entry stub at 0x80000, a DTB ->
BootInfo constructor calling the existing unmodified kernel_main()
(the crux of why most of M1-M9 stays shared), the mailbox-interface
framebuffer driver, an fdt.c node-scoped-lookup extension (its own
header already flagged this as a known future need), a DTB-based
pci_init() path for RP1, and config.txt contents. Plus 5 items
deliberately deferred until hardware is in hand 2026-09-17.

Also fixed a formatting slip from an in-progress edit (a bullet
accidentally turned into a malformed heading) before it could compound.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 12:52:37 -04:00
Robert Allan JamesandClaude Sonnet 5 9142d50b73 FABRIC-3.md: Zynq-7000 (Puzhi PZ7010/PZ7020 StarLite) added to hardware reference
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
Recorded only, no work scoped -- but unlike BeagleBone Black, this
one isn't a random addition: ROADMAP.md already names Zynq FPGA as
the next big milestone beyond v2.5.0 (the "configurable silicon"
step, tied to the three-product split's HOL-proven sealed-execution
hardware product). This puts a concrete, purchasable board under that
already-named milestone: Puzhi PZ7010-StarLite (XC7Z010) or
PZ7020-StarLite (XC7Z020), dual-core Cortex-A9 PS + Artix-7/Kintex-7
PL fabric, DDR3/JTAG/UART/HDMI/GbE/USB2.0/40-pin, MIPI CSI on the
7020 variant only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 12:44:57 -04:00
Robert Allan JamesandClaude Sonnet 5 5eeb993ca2 FABRIC-3.md: riscv64 boot chain resolved, BeagleBone Black noted, header bug fixed
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
Milk-V Mars boot chain resolved via VisionFive 2 research: the Mars is
a documented mainline U-Boot target using the exact same binaries as
the VisionFive 2 (same StarFive JH7110 SoC) -- U-Boot + OpenSBI +
devicetree, not UEFI, same fork already decided for aarch64. Entry
point 0x40000000, mhartid CSR for core ID, UART at 0x10000000/115200 --
DTB pointer/hart-ID register convention at actual kernel entry not yet
confirmed for this specific chain.

Fixes a real bug from the earlier aarch64 edit: the "## V. riscv64 --
Milk-V Mars" section header had been accidentally dropped, leaving a
stale duplicate "Already true"/"Genuinely open" block sitting where
the real section should have started. Removed the duplicate, restored
the header, folded today's research into the section properly.

BeagleBone Black added to SS VI's hardware-identification reference per
direct instruction -- recorded only, no work scoped around it: it's a
32-bit ARM (TI AM335x, Cortex-A8) SoC, a genuinely different, fourth
architecture this kernel has no support for, not another board under
an existing one.

FABRIC-4.md SS2: captured the pinned-GPIO-VM idea raised in conversation
(Pi 5 needs one, Milk-V Mars explicitly undecided) as a theory-stage
note, cross-referenced from SSIV/SSV's own open-questions lists.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 12:43:15 -04:00
Robert Allan JamesandClaude Sonnet 5 987c751352 FABRIC-3.md: per-board hardware-identification reference (SS VI)
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
Consolidated SoC/CPU/RAM/I-O facts for all three real-hardware
targets, researched via web search with sources cited rather than
assumed from memory:

- amd64 (Beelink SER5): Ryzen 7 family confirmed; exact SKU
  (5700U/5800H/7735HS all shipped under this branding) not yet
  confirmed against the actual unit -- flagged, not guessed.
- aarch64 (Raspberry Pi 5): BCM2712, quad-core Cortex-A76 @ 2.4GHz,
  VideoCore VII GPU, LPDDR4X-4267. FEAT_RNG (RNDR) presence on this
  core flagged as unconfirmed either way.
- riscv64 (Milk-V Mars): StarFive JH7110, 4x SiFive U74-MC + 1x S7
  monitor core, up to 1.5GHz. Noted it shares its SoC with the
  StarFive VisionFive 2 -- existing VisionFive 2 bring-up material
  may transfer directly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 12:37:29 -04:00
Robert Allan JamesandClaude Sonnet 5 a66af477ac FABRIC-3.md: aarch64 boot-chain decision -- native boot flow, not UEFI
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
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
Robert Allan JamesandClaude Sonnet 5 e5e28d5198 FABRIC-3.md: amd64 bare-metal-boot planning decided + punch list
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
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
2026-09-04 12:27:38 -04:00
Robert Allan JamesandClaude Sonnet 5 7f9a4d4d4a FABRIC-3.md: three per-architecture bare-metal-boot planning sections
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
Per direct instruction: amd64's real goal is genericity (any x86_64
laptop/desktop/tower/mini, not just the Beelink SER5 reference
machine); aarch64 targets the Raspberry Pi 5 exclusively; riscv64
targets the Milk-V Mars exclusively -- no cross-board genericity
requirement for the latter two, unlike amd64.

Each section starts from what's already true (ROADMAP.md's existing
v2.2.0/v2.4.0/v2.5.0 board-by-board gates, the already-built
thumbdrive/iso-usb Makefile targets, the amd64 RDRAND backend) and
names what's genuinely still unknown rather than assuming -- most
notably whether the Milk-V Mars boots via UEFI (like this project's
QEMU riscv64 target) or via U-Boot+OpenSBI+devicetree, which would
need a different boot entry path, not just different peripheral
addresses.

Doc-only change, no acceptance build needed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 12:02:45 -04:00
45 changed files with 139529 additions and 6 deletions
+514
View File
@@ -117,3 +117,517 @@ functional rollback.
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.
## 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 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).
**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 — DONE 2026-09-04.** New
`src/starkernel/arch/aarch64/native_rpi5_entry.S` / `include/starkernel/rpi5_native_entry.h`:
`rpi5_native_start` masks `x0` down to the documented 32-bit DTB-pointer range (§IV.1: the
firmware leaves the upper 32 bits of the register unspecified), stores it into
`g_rpi5_dtb_ptr` for item 2's still-open constructor to read, then switches `sp` to a
dedicated 2 MiB BSS stack (this path has no EDK2 boot stack to inherit — there is no EDK2 at
all here, unlike every other entry path this codebase has). Intentionally halts (`wfe`/`b`
loop) afterward rather than tail-calling into item 2's constructor, which doesn't exist yet.
**Not yet linked at `0x80000`** — that needs its own linker script/build target (item 6's own
`config.txt` work is the sibling piece; the separate-image build itself is not scoped into
this item). Now tail-calls item 2's constructor (below) instead of halting — updated
2026-09-04 when that item landed. Verified 3-arch boot to `ok>`/`zuse)ok>`
`Makefile.starkernel`'s `KERNEL_ASM` wildcards every `*.S` in `arch/aarch64/`, so this file
compiles and links into the existing QEMU/UEFI acceptance build as dead code (unreferenced
symbol, nothing there ever branches to it), same as `rpi5_dtb.c`/`rpi5_mailbox.c` before it.
2. **DTB → `BootInfo` constructor — DONE 2026-09-04.** New
`include/starkernel/rpi5_native_boot.h` / `src/starkernel/arch/aarch64/rpi5_native_boot.c`:
`rpi5_native_boot()` populates the *existing* `BootInfo` struct from the devicetree instead
of UEFI protocols (`dtb` = the real pointer, `acpi_table`/`runtime_services` = `NULL`,
`kernel_stack_base` = `NULL`/BSS-fallback — aarch64 has no `kernel_entry.S` trampoline at
all, so item 1's own BSS stack already *is* the stack `kernel_main_impl` runs on),
`args` via `/chosen`'s `bootargs` fed straight into the *existing*
`cmdline_parse_ascii()` (pure C99, no UEFI coupling — confirmed before reusing it, not
assumed), `framebuffer` via item 3's `rpi5_mailbox_get_framebuffer()` at a fixed
1920x1080x32 default (no EDID query exists in this codebase — flagged, not guessed past
this comment, revisit once real hardware and a real attached display are in hand), then
calls the **existing, unmodified** `kernel_main()` — this is the crux of why most of M1M9
stays shared. `memory_map` comes from `/memory`'s own `reg`, honoring the *root* node's
`#address-cells`/`#size-cells` (confirmed against `bcm2712.dtsi`'s actual root node — `<2>`/
`<2>` — not assumed; a hardcoded-wrong cell width here would compile clean and boot clean in
QEMU while silently corrupting the real memory map on real silicon, so this was verified
from the source rather than recalled). Required a new `fdt_find_node_by_device_type()`
(`fdt.c`/`fdt.h`) since `/memory` is identified by `device_type = "memory"` per DT spec
§3.4, not `compatible`. Required a Makefile fix: `boot/cmdline.c` was only in `LOADER_SRCS_BASE`
(the `.efi` target), not `KERNEL_SRCS_BASE` (the separate `.elf` target `arch/aarch64/*.c`
also wildcards into) — added it there too, a real link failure caught before it could ship.
**Deliberately scoped out, not silently skipped:** `/reserved-memory` is not parsed —
carving reserved sub-ranges out of `/memory`'s span needs interval-splitting logic that
would be written blind against hardware not yet in hand, exactly the kind of code that
hides a bug until real silicon; left as its own future item. `memory/pmm.c`'s Pass 3 only
ever clears pages this file explicitly lists as `EfiConventionalMemory`, so the gap is
"less usable RAM than optimal," never "reserved RAM wrongly marked free." Verified 3-arch
boot to `ok>`/`zuse)ok>` — compile-only, same caveat as every item in this list: nothing in
the existing UEFI/QEMU path calls `rpi5_native_boot()`, so this cannot be exercised until
real hardware.
3. **Mailbox-property-interface framebuffer driver — DONE 2026-09-04.** New
`include/starkernel/rpi5_mailbox.h` / `src/starkernel/arch/aarch64/rpi5_mailbox.c`:
`rpi5_mailbox_get_framebuffer()` builds and sends one property-tag buffer (phys size, virt
size, depth, pixel order, virtual offset, allocate-buffer, get-pitch), populating an
`Rpi5FramebufferInfo` kept in exact field-for-field sync with `uefi.h`'s `FramebufferInfo`
so `console.c`/`vt100.c`/`framebuffer.c` need no changes downstream. Register layout
(`+0x00`/`+0x18` MBOX0 read/status, `+0x20`/`+0x38` MBOX1 write/status) confirmed against a
Pi-5-specific bare-metal reference (main.lv), independently cross-checked against this
codebase's own `rpi5_dtb.c` translated base address — two independent sources agreeing. A
real buffer-overflow bug was found and fixed before compiling (the static request buffer
was sized 32 words against an actual 35-word requirement, recomputed exactly rather than
re-estimated; resized to 40 words for margin). **Two things flagged, not guessed, as
genuinely unverified against real hardware:** the `TAG_ALLOCATE_BUFFER` tag's request-size
field value (set to the response size, matching common practice across surveyed reference
implementations, not a single spec-quoted number); and whether the allocate-buffer
response address needs the classic `& 0x3FFFFFFF` bus-alias masking on Pi 5 specifically —
kept defensively even though the same Pi-5-specific send-side reference found no bus-alias
bit in play there. Verified 3-arch boot to `ok>`/`zuse)ok>` (compile-only — no caller yet;
that's the entry-stub/DTB-constructor items above, still open).
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>`.
**4a. UART + mailbox address lookup — DONE 2026-09-04.** New
`include/starkernel/rpi5_dtb.h` / `src/starkernel/arch/aarch64/rpi5_dtb.c`:
`rpi5_uart_base()`/`rpi5_mailbox_base()`, each `fdt_find_node_by_compatible()` (`"arm,pl011"`
/ `"brcm,bcm2835-mbox"`) → `fdt_find_prop_in_node(..., "reg", ...)`. **A real translation
gap found and fixed before this could have been silently wrong**: confirmed directly
against `bcm2712.dtsi` (raspberrypi/linux) that both peripherals live under one `soc`
simple-bus node whose `ranges` property adds a fixed `0x10_0000_0000` offset to every
child `reg` value — `fdt.c`'s reader deliberately does not apply `ranges` translation
generally (not a general devicetree library), so this file applies that one, fixed,
SoC-wide offset explicitly by name (`BCM2712_SOC_RANGES_OFFSET`), documented with the exact
devicetree excerpt that confirmed it. Verified 3-arch boot to `ok>` (compile-only — these
two functions have no caller yet; that's the entry-stub/framebuffer-driver items above,
still open).
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 14 (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/).
+31
View File
@@ -73,3 +73,34 @@ level up the stack.
build the one wheel concretely first, generalize only if/when a second one is actually
needed (this project's own standing convention — three similar lines beat a premature
abstraction).
## 2. A pinned GPIO VM per real-hardware board (2026-09-04)
Raised in conversation with Captain Bob during `FABRIC-3.md` §IV/§V's bare-metal-boot
planning — theory stage, no code, no scope, deliberately not punch-listed yet.
**The idea, as stated:** the Raspberry Pi 5 will need a special pinned VM dedicated to GPIO —
presumably following the same Tripod precedent Hermes/Artemis already established (a
permanently-pinned, born-at-boot VM with a fixed role, per `FABRIC-2.md` §H.1's "pinned
sessions never leave" model). Whether the Milk-V Mars needs the same treatment is explicitly
**not yet decided** — Captain Bob's own framing: "same for Milk-V (? not sure here)." Worth
noting the Mars does have its own 40-pin GPIO header (§VI), so the question isn't "does it
have GPIO" but whether it needs its own *dedicated pinned VM* for it the same way the Pi 5
does, or whether GPIO access there fits some other shape.
**Why a dedicated VM, not just a C driver, presumably (not yet confirmed as the actual
reasoning — inferred from the Tripod precedent, not stated outright):** GPIO is shared,
stateful hardware that multiple capsules/patrons might want to touch — the same category of
problem Hermes (messaging) and Artemis (block storage) already solve by being a dedicated,
addressable VM other patrons talk to, rather than a bare C primitive anyone can call directly.
**Explicitly not decided yet:**
- Whether Milk-V Mars needs the same pinned-VM treatment, or something else.
- What this VM's own vocabulary/interface would look like (a `GPIO-READ`/`GPIO-WRITE`-shaped
word set? something richer, given the Pi 5's GPIO header also muxes UART/SPI per §IV.1?).
- Whether this is one pinned VM per board doing double duty (GPIO + whatever else that board's
bring-up needs) or GPIO specifically warrants its own separate VM distinct from, say, a
future UART-handling VM.
- How this interacts with §IV.1's own still-open native-boot-flow work — a GPIO VM needs GPIO
register addresses from the DTB the same way the rest of that bring-up does, so this idea
isn't independent of that work, just downstream of it.
+1
View File
@@ -479,6 +479,7 @@ endif
KERNEL_SRCS_BASE := \
$(KERNEL_SRC)/kernel_main.c \
$(KERNEL_SRC)/boot/cmdline.c \
$(wildcard $(KERNEL_SRC)/hal/*.c) \
$(wildcard $(KERNEL_SRC)/memory/*.c) \
$(wildcard $(KERNEL_SRC)/math/*.c) \
+1 -1
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-09-04T15:51:18Z -->
<!-- Generated by mkcapsule --manifest 2026-09-04T18:40:17Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. -->
BIN
View File
Binary file not shown.
+69 -5
View File
@@ -8,13 +8,20 @@
* fdt.h - Minimal flattened-devicetree reader
*
* Just enough of the Devicetree Specification v0.4 §5 to pull values out of
* the blob the UEFI firmware publishes under EFI_DTB_TABLE_GUID. Read-only,
* no allocation, no tree construction — it walks the structure block each
* call, which is fine for the handful of boot-time lookups the kernel needs.
* the blob the UEFI firmware publishes under EFI_DTB_TABLE_GUID, or that a
* native (non-UEFI) boot entry passes directly. Read-only, no allocation, no
* tree construction — it walks the structure block each call, which is fine
* for the handful of boot-time lookups the kernel needs.
*
* Deliberately not a general devicetree library. Added for punch-list item
* 0.3 (riscv64 timebase-frequency); item 0.6 will need node-scoped `reg`
* lookups for the aarch64 GIC and may extend this.
* 0.3 (riscv64 timebase-frequency); extended (FABRIC-3.md §IV.3/§V.3,
* 2026-09-04) with node-scoped lookup, for exactly the case this header
* originally flagged as a future need (item 0.6's aarch64 GIC) plus its
* real, concrete consumers as of this pass: the Raspberry Pi 5's UART/
* mailbox register addresses (native boot, no ACPI) and the Milk-V Mars's
* real PLIC base address (currently hardcoded to QEMU-virt's own value,
* `arch/riscv64/plic.c`'s own doc comment already warned this isn't
* assumed stable across configurations).
*/
#ifndef STARKERNEL_FDT_H
@@ -62,4 +69,61 @@ const void* fdt_find_prop(const void* fdt, const char* name, uint32_t* len_out);
*/
int fdt_prop_u32(const void* fdt, const char* name, uint32_t* out);
/**
* @brief Find the first node whose "compatible" property matches @p compatible.
*
* "compatible" is a NUL-separated list of strings (DT spec §2.3.1) — matches
* if @p compatible equals any one entry in the list, not just the whole
* property verbatim. Scans the whole tree in document order; the first
* matching node wins if more than one exists.
*
* @param fdt Blob, already checked with @c fdt_valid().
* @param compatible Compatible string to match, NUL-terminated.
* @return An opaque handle to the matched node, for use with
* @c fdt_find_prop_in_node() only (not a raw offset or a pointer
* to anything else meaningful) — or NULL if no node matches.
*/
const void* fdt_find_node_by_compatible(const void* fdt, const char* compatible);
/**
* @brief Find the first node whose "device_type" property equals @p type.
*
* Some standard nodes (`/memory` per DT spec §3.4) are identified by
* `device_type`, not `compatible` — unlike `compatible`, `device_type` is a
* single NUL-terminated string, not a list, so this matches the whole
* property value rather than scanning entries within it. Scans the whole
* tree in document order; the first matching node wins if more than one
* exists.
*
* @param fdt Blob, already checked with @c fdt_valid().
* @param type device_type value to match, NUL-terminated.
* @return An opaque handle to the matched node, for use with
* @c fdt_find_prop_in_node() only — or NULL if no node matches.
*/
const void* fdt_find_node_by_device_type(const void* fdt, const char* type);
/**
* @brief Find a property by name, scoped to one node.
*
* Like @c fdt_find_prop(), but scans only @p node's own direct properties
* (as returned by @c fdt_find_node_by_compatible()) — stops at the first
* child node or the end of @p node's property list, never descends into
* children, never continues into a sibling. This is the difference that
* matters for a property name like "reg", which is not unique across the
* tree the way "timebase-frequency" (the whole reason @c fdt_find_prop()
* was originally sufficient) happens to be.
*
* @param fdt Blob, already checked with @c fdt_valid().
* @param node Handle from @c fdt_find_node_by_compatible(); NULL is
* safe and returns NULL (propagates a failed node lookup
* without a separate caller-side check).
* @param name Property name, NUL-terminated.
* @param len_out Receives the property length in bytes; may be NULL.
* @return Pointer to the property value inside @p fdt, or NULL if not
* found (or if @p node is NULL). The value is big-endian as
* stored in the blob.
*/
const void* fdt_find_prop_in_node(const void* fdt, const void* node,
const char* name, uint32_t* len_out);
#endif /* STARKERNEL_FDT_H */
+48
View File
@@ -0,0 +1,48 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 Robert A. James. All rights reserved.
Licensed under the StarForth License, Version 1.0.
*/
/**
* rpi5_dtb.h - Raspberry Pi 5 (BCM2712) devicetree-based peripheral
* discovery, for the native (non-UEFI) boot path (FABRIC-3.md §IV.3).
*
* Two lookups: the PL011 UART (early console) and the VideoCore mailbox
* property interface (framebuffer setup, §IV.3 item 3). Both peripherals
* live under BCM2712's own devicetree "soc" simple-bus node, which
* applies one fixed address translation to every child `reg` value —
* see `rpi5_dtb.c`'s own doc comment for the confirmed offset and where
* it was verified. `fdt.c`'s reader deliberately does not do general
* `ranges`-property translation (it is "not a general devicetree
* library"); this file applies the one, fixed, SoC-wide offset by name
* instead of teaching `fdt.c` a general mechanism for a single known
* hardware fact.
*/
#ifndef STARKERNEL_RPI5_DTB_H
#define STARKERNEL_RPI5_DTB_H
#include <stdint.h>
/**
* @brief Find the PL011 UART's CPU-physical base address from the DTB.
*
* @param dtb Devicetree blob, as passed to the native boot entry (or
* `BootInfo->dtb`); NULL is safe.
* @return Final CPU-physical MMIO base address, or 0 if the node is
* absent, malformed, or @p dtb is invalid.
*/
uint64_t rpi5_uart_base(const void* dtb);
/**
* @brief Find the VideoCore mailbox property interface's CPU-physical
* base address from the DTB.
*
* @param dtb Devicetree blob; NULL is safe.
* @return Final CPU-physical MMIO base address, or 0 if the node is
* absent, malformed, or @p dtb is invalid.
*/
uint64_t rpi5_mailbox_base(const void* dtb);
#endif /* STARKERNEL_RPI5_DTB_H */
+68
View File
@@ -0,0 +1,68 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 Robert A. James. All rights reserved.
Licensed under the StarForth License, Version 1.0.
*/
/**
* rpi5_mailbox.h - VideoCore mailbox property-interface framebuffer
* setup, for the native (non-UEFI) Raspberry Pi 5 boot path
* (FABRIC-3.md §IV.3 item 3).
*
* Register layout, message/tag format, and property-tag IDs confirmed
* against multiple sources before writing `rpi5_mailbox.c` — see that
* file's own doc comment for exactly which, and what (if anything)
* remains unverified against real hardware (not in hand until
* 2026-09-17; this driver has never run on real silicon).
*/
#ifndef STARKERNEL_RPI5_MAILBOX_H
#define STARKERNEL_RPI5_MAILBOX_H
#include <stdint.h>
/* Mirrors uefi.h's FramebufferInfo exactly -- populated by
* rpi5_mailbox_get_framebuffer() the same shape UEFI GOP already
* populates it, so console.c/vt100.c/framebuffer.c need no changes at
* all for this path. Not #include-ing uefi.h here (that header is a
* large UEFI-protocol grab-bag; this driver only needs this one
* struct's shape) -- callers that already have a `FramebufferInfo*`
* (from uefi.h) can pass it directly, since the two struct
* definitions are kept in exact field-for-field sync by convention.
*/
typedef struct {
void* base;
uint64_t size;
uint32_t width;
uint32_t height;
uint32_t pixels_per_scanline;
uint32_t pixel_format; /* 0 = RGB, matches uefi.h's
* PixelRedGreenBlueReserved8BitPerColor --
* this driver always requests RGB pixel
* order explicitly (tag 0x00048006), never
* leaves it at hardware/firmware default. */
} Rpi5FramebufferInfo;
/**
* @brief Request a framebuffer from the VideoCore firmware via the
* mailbox property interface, at the requested resolution/depth.
*
* Sends one buffer with all six setup tags (physical size, virtual
* size, depth, pixel order, virtual offset, allocate) plus a
* get-pitch tag, in one request/response round trip.
*
* @param dtb Devicetree blob (passed to `rpi5_mailbox_base()`).
* @param width Requested physical+virtual width, pixels.
* @param height Requested physical+virtual height, pixels.
* @param bpp Requested bits per pixel (32 is the only depth this
* driver has been designed against; others are not
* refused outright but are unverified).
* @param out Populated on success; untouched on failure.
* @return 0 on success, negative on failure (mailbox node not found
* in the DTB, VC firmware rejected the request, or a response
* tag came back with an unexpected size).
*/
int rpi5_mailbox_get_framebuffer(const void* dtb, uint32_t width, uint32_t height,
uint32_t bpp, Rpi5FramebufferInfo* out);
#endif /* STARKERNEL_RPI5_MAILBOX_H */
+26
View File
@@ -0,0 +1,26 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 Robert A. James. All rights reserved.
Licensed under the StarForth License, Version 1.0.
*/
/**
* rpi5_native_boot.h - Raspberry Pi 5 DTB->BootInfo constructor
* (FABRIC-3.md §IV.3 item 2).
*
* `native_rpi5_entry.S`'s `rpi5_native_start` tail-calls
* `rpi5_native_boot()` with the masked DTB pointer it captured. This
* function builds the *existing*, unmodified `BootInfo` struct
* (`include/starkernel/uefi.h`) from the devicetree instead of UEFI
* protocols, then calls the *existing*, unmodified `kernel_main()` — see
* this file's own `.c` for exactly what is and is not populated, and why.
*/
#ifndef STARKERNEL_RPI5_NATIVE_BOOT_H
#define STARKERNEL_RPI5_NATIVE_BOOT_H
#include <stdint.h>
void rpi5_native_boot(uint64_t dtb) __attribute__((noreturn));
#endif /* STARKERNEL_RPI5_NATIVE_BOOT_H */
+32
View File
@@ -0,0 +1,32 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 Robert A. James. All rights reserved.
Licensed under the StarForth License, Version 1.0.
*/
/**
* rpi5_native_entry.h - Raspberry Pi 5 native (non-UEFI) boot entry point
* (FABRIC-3.md §IV.3 item 1).
*
* `native_rpi5_entry.S`'s `rpi5_native_start` is the very first code that
* runs on this path -- entered directly by Pi 5 firmware, no UEFI, no
* ACPI, none of `boot/uefi_loader.c`'s PE-loader shape applies. It masks
* the firmware's raw entry register down to the documented 32-bit DTB
* pointer (§IV.1: upper 32 bits of the 64-bit register are unspecified)
* and stores it here, then establishes its own dedicated stack (this path
* has no EDK2 boot stack to inherit) and halts.
*
* `g_rpi5_dtb_ptr` is this stub's one real output -- the still-open
* DTB->BootInfo constructor (§IV.3 item 2) reads it from here once it
* exists; nothing calls that constructor yet, so `rpi5_native_start`
* halts rather than tail-calling into a function that isn't real.
*/
#ifndef STARKERNEL_RPI5_NATIVE_ENTRY_H
#define STARKERNEL_RPI5_NATIVE_ENTRY_H
#include <stdint.h>
extern uint64_t g_rpi5_dtb_ptr;
#endif /* STARKERNEL_RPI5_NATIVE_ENTRY_H */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,81 @@
/*
* native_rpi5_entry.S (aarch64) - Raspberry Pi 5 native (non-UEFI) entry
* stub (FABRIC-3.md §IV.3 item 1, 2026-09-04).
*
* **Never run on real hardware** -- the Pi 5 isn't in hand until
* 2026-09-17 (FABRIC-3.md §II). Compile-only-verified.
*
* Entered directly by Pi 5 firmware at the classic bare-metal load address
* 0x80000 (item 6, `config.txt`'s `os_check=0`, is still open -- that's
* what actually gets a real image loaded there; this file is just the
* code that would run once it is). No UEFI, no ACPI -- none of
* `boot/uefi_loader.c`'s PE-loader shape applies on this path. Per
* FABRIC-3.md §IV.1's own researched entry protocol:
*
* x0 = 32-bit DTB pointer, upper 32 bits of the 64-bit register
* UNSPECIFIED -- must mask before use.
* x1-x3 = reserved / zero, unused here.
*
* This stub's own job: mask and capture that DTB pointer into
* `g_rpi5_dtb_ptr` (declared in `include/starkernel/rpi5_native_entry.h`),
* establish a dedicated stack -- this path has no EDK2 boot stack to
* inherit, there is no EDK2 at all here -- then tail-call
* `rpi5_native_boot()` (`rpi5_native_boot.c`, §IV.3 item 2), which builds
* `BootInfo` from the devicetree and calls the existing `kernel_main()`.
* `rpi5_native_boot()` never returns; the `wfe`/`b` loop after the `bl`
* exists only so this file's own control flow has somewhere defined to
* go if that contract is ever violated.
*
* Build-system note: `Makefile.starkernel`'s `KERNEL_ASM` wildcards every
* `*.S` file in this directory into the monolithic kernel ELF build (not
* the PE/COFF loader build -- that list is explicit, boot.S/isr.S only,
* see the Makefile), so this file *does* compile and link into the
* existing ARCH=aarch64 QEMU/UEFI acceptance build. Nothing there ever
* branches to `rpi5_native_start` -- it is dead code in that build by
* design, exactly like `rpi5_dtb.c`/`rpi5_mailbox.c` before it. A real,
* separately-linked Pi 5 native boot image (its own linker script placing
* this code at 0x80000) does not exist yet -- that is a build-system task,
* not scoped into this item.
*/
.section .bss
.align 8
.global g_rpi5_dtb_ptr
g_rpi5_dtb_ptr:
.space 8 /* uint64_t -- masked DTB pointer */
.align 4
g_rpi5_native_stack:
.space 0x200000 /* 2 MiB, same convention as the
* amd64/riscv64 kernel_entry.S
* BSS stacks */
g_rpi5_native_stack_top:
.section .text
.global rpi5_native_start
rpi5_native_start:
/* Mask x0 to its documented 32-bit DTB-pointer range (§IV.1: the
* upper 32 bits of the 64-bit register are unspecified by the
* firmware's own entry protocol). */
mov x1, #0xffffffff
and x0, x0, x1
adrp x2, g_rpi5_dtb_ptr
add x2, x2, :lo12:g_rpi5_dtb_ptr
str x0, [x2]
/* Establish this path's own dedicated stack. */
adrp x0, g_rpi5_native_stack_top
add x0, x0, :lo12:g_rpi5_native_stack_top
mov sp, x0
/* x0 was clobbered above for the stack-top address -- reload the
* masked DTB pointer from g_rpi5_dtb_ptr before calling in. */
adrp x0, g_rpi5_dtb_ptr
add x0, x0, :lo12:g_rpi5_dtb_ptr
ldr x0, [x0]
bl rpi5_native_boot
.Lhalt:
wfe
b .Lhalt
+93
View File
@@ -0,0 +1,93 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 Robert A. James. All rights reserved.
Licensed under the StarForth License, Version 1.0.
*/
/**
* rpi5_dtb.c - Raspberry Pi 5 (BCM2712) devicetree-based peripheral
* discovery (FABRIC-3.md §IV.3, 2026-09-04).
*
* Confirmed directly against `bcm2712.dtsi` (raspberrypi/linux,
* rpi-6.12.y) before writing this, not assumed:
*
* soc: soc@107c000000 {
* compatible = "simple-bus";
* ranges = <0x0 0x10 0x0 0x80000000>;
* #address-cells = <1>;
* #size-cells = <1>;
* ...
* uart10: serial@7d001000 {
* compatible = "arm,pl011", "arm,primecell";
* reg = <0x7d001000 0x200>;
* };
* mailbox: mailbox@7c013880 {
* compatible = "brcm,bcm2835-mbox";
* reg = <0x7c013880 0x40>;
* };
* };
*
* Every peripheral this file cares about lives under this one "soc"
* node, which applies exactly one translation to every child `reg`
* value: child address 0 maps to parent (CPU-physical) address
* 0x10_0000_0000 (`ranges`'s own `<0x0 0x10 0x0 ...>` — child cell 0,
* parent cells `0x10 0x0`, i.e. `0x10 << 32`). The child side's own
* `#address-cells = <1>` confirms each `reg` value's first cell is the
* whole child address (32-bit, no cell-splitting needed) before adding
* the offset. `fdt.c`'s reader deliberately does not parse `ranges`
* generally (it is "not a general devicetree library," per its own
* header comment) — this one, fixed, SoC-wide offset is applied here
* by name instead, since it is a single known hardware fact, not a
* general mechanism this codebase needs elsewhere yet.
*/
#include "starkernel/rpi5_dtb.h"
#include "starkernel/fdt.h"
/* soc@107c000000's own `ranges` offset -- see this file's own doc
* comment above for where it was confirmed. */
#define BCM2712_SOC_RANGES_OFFSET 0x1000000000ULL
/**
* @brief Read a devicetree `reg` property's first cell as big-endian.
*
* `fdt.c` keeps its own `be32()` helper file-local (freestanding, no
* shared byte-swap utility to reuse) -- duplicated here rather than
* exposing it, same "a few lines is simpler than a new shared
* dependency" precedent `tools/pkcs8_ed25519.c` already set in this
* codebase.
*/
static uint32_t reg_first_cell_be32(const unsigned char* b)
{
return ((uint32_t) b[0] << 24) | ((uint32_t) b[1] << 16) |
((uint32_t) b[2] << 8) | (uint32_t) b[3];
}
/**
* @brief Find @p compatible's node, read its `reg` base address, and
* apply the one fixed BCM2712 `soc`-node translation offset.
*/
static uint64_t rpi5_peripheral_base(const void* dtb, const char* compatible)
{
const void* node;
const void* val;
uint32_t len;
node = fdt_find_node_by_compatible(dtb, compatible);
if (!node) return 0;
val = fdt_find_prop_in_node(dtb, node, "reg", &len);
if (!val || len < 4) return 0;
return BCM2712_SOC_RANGES_OFFSET + reg_first_cell_be32((const unsigned char*) val);
}
uint64_t rpi5_uart_base(const void* dtb)
{
return rpi5_peripheral_base(dtb, "arm,pl011");
}
uint64_t rpi5_mailbox_base(const void* dtb)
{
return rpi5_peripheral_base(dtb, "brcm,bcm2835-mbox");
}
+237
View File
@@ -0,0 +1,237 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 Robert A. James. All rights reserved.
Licensed under the StarForth License, Version 1.0.
*/
/**
* rpi5_mailbox.c - VideoCore mailbox property-interface framebuffer
* setup (FABRIC-3.md §IV.3 item 3, 2026-09-04).
*
* **Never run on real hardware** — the Pi 5 isn't in hand until
* 2026-09-17 (FABRIC-3.md §II). Everything below is built from real,
* cited sources rather than assumed, but is compile-only-verified.
*
* Register layout (offsets from `rpi5_mailbox_base()`, i.e. the
* `mailbox` devicetree node's own translated base address —
* `rpi5_dtb.c` already confirmed `0x107C013880` on real Pi 5
* hardware, matching a Pi-5-specific bare-metal reference
* (main.lv/writeup/raspberry5_baremetal_framebuffer.md) independently
* confirming the same `0x107C000000` SoC base + `0x13880` mailbox
* offset this codebase's own devicetree-driven lookup already
* produces — two independent sources agreeing is why this offset is
* trusted, not just cited once):
*
* +0x00 MBOX0_READ (receive: VC -> ARM)
* +0x18 MBOX0_STATUS (bit30 MBOX_EMPTY: nothing to read)
* +0x20 MBOX1_WRITE (send: ARM -> VC)
* +0x38 MBOX1_STATUS (bit31 MBOX_FULL: cannot write yet)
*
* (`+0x20/+0x38` per the same Pi-5-specific reference above, which
* treats MBOX0/MBOX1 as two structurally identical 0x20-byte blocks
* back to back — a real difference from older-chip tutorials, which
* often describe a single shared status register at `+0x18` for both
* directions. Trusted here because it's the one source actually
* confirmed against Pi 5 hardware, not an older SoC.)
*
* Message encoding (official wiki:
* github.com/raspberrypi/firmware/wiki/Mailbox-property-interface,
* channel and buffer format; property tag IDs/lengths from the same
* page): write `(buffer_addr & ~0xF) | channel` to MBOX1_WRITE; read
* back the same encoded value from MBOX0_READ once available, confirming
* it before trusting the buffer (the response overwrites the request
* in place — same shared buffer, not a separate response buffer). No
* bus-address translation applied to the buffer pointer itself
* (confirmed against the same Pi-5-specific reference: it writes the
* plain masked pointer, no added GPU-bus-alias bit, unlike the
* classic 32-bit-Pi convention some older tutorials describe) —
* genuinely different per-SoC-generation behavior, not an oversight.
*
* Buffer/tag format (official wiki, same page): u32 total size, u32
* request(0)/response(0x80000000|len) code, tags..., u32 end-tag(0).
* Each tag: u32 id, u32 value-buffer size, u32 req-size/resp-flag,
* value bytes padded to 4-byte alignment. Buffer itself must be
* 16-byte aligned (only the upper 28 bits of its address travel
* through the mailbox).
*/
#include "starkernel/rpi5_mailbox.h"
#include "starkernel/rpi5_dtb.h"
#define MBOX_OFF_READ 0x00u
#define MBOX_OFF_STATUS0 0x18u
#define MBOX_OFF_WRITE 0x20u
#define MBOX_OFF_STATUS1 0x38u
#define MBOX_FULL 0x80000000u
#define MBOX_EMPTY 0x40000000u
#define MBOX_CH_PROPERTY 8u
#define MBOX_CODE_REQUEST 0x00000000u
#define MBOX_CODE_RESPONSE_OK 0x80000000u
#define TAG_SET_PHYS_SIZE 0x00048003u
#define TAG_SET_VIRT_SIZE 0x00048004u
#define TAG_SET_DEPTH 0x00048005u
#define TAG_SET_PIXEL_ORDER 0x00048006u
#define TAG_SET_VIRTUAL_OFFS 0x00048009u
#define TAG_ALLOCATE_BUFFER 0x00040001u
#define TAG_GET_PITCH 0x00040008u
#define TAG_END 0x00000000u
#define PIXEL_ORDER_RGB 1u /* per the wiki's own tag doc: 0=BGR, 1=RGB */
/* 16-byte-aligned static request/response buffer -- no allocator
* exists this early in boot (this driver runs before kmalloc's own
* init, same as every other pre-M6 boot step), and one buffer is
* enough: this driver only ever has one request in flight, never
* concurrent. Exact word count for the 7-tag sequence below, counted
* word-by-word (not estimated): 2 [header] + 5 [phys size] + 5 [virt
* size] + 4 [depth] + 4 [pixel order] + 5 [virtual offset] + 5
* [allocate] + 4 [get pitch] + 1 [end tag] = 35 words (140 bytes) --
* 40 gives real margin without the array itself getting large enough
* to matter. (An earlier version of this comment claimed 32 words was
* "generous headroom" for a 38-word estimate against a 32-word array
* -- that was wrong on its own terms before even being checked against
* the real 35-word count; recomputed properly this time, not
* re-estimated.) */
static uint32_t g_mbox_buf[40] __attribute__((aligned(16)));
static inline void mmio_write32(uint64_t addr, uint32_t val)
{
*(volatile uint32_t*) addr = val;
}
static inline uint32_t mmio_read32(uint64_t addr)
{
return *(volatile uint32_t*) addr;
}
/* Appends one tag (id, value-buffer size in bytes, request size in
* bytes, then `nvalues` u32 value words) to g_mbox_buf starting at
* word index *idx, advancing *idx past it (including alignment
* padding -- every value here is a whole number of u32 words already,
* so no padding math is actually needed, but the wiki's own format
* allows non-multiple-of-4 value sizes in general; this driver's own
* tags never do). */
static void append_tag(uint32_t* idx, uint32_t tag_id, uint32_t value_bytes,
const uint32_t* values, uint32_t nvalues)
{
uint32_t i;
g_mbox_buf[(*idx)++] = tag_id;
g_mbox_buf[(*idx)++] = value_bytes;
/* Request-size word: set equal to value_bytes (the value-buffer's
* own size, which for TAG_ALLOCATE_BUFFER is 8 -- the response's
* size, since the buffer must be big enough for whichever is
* larger -- not the 4-byte true request payload). Common practice
* across multiple reference implementations surveyed while writing
* this, not a single spec-quoted number; firmware is not observed
* to validate an exact match against the true request length.
* Unverified against real hardware either way. */
g_mbox_buf[(*idx)++] = value_bytes;
for (i = 0; i < nvalues; i++) g_mbox_buf[(*idx)++] = values[i];
}
int rpi5_mailbox_get_framebuffer(const void* dtb, uint32_t width, uint32_t height,
uint32_t bpp, Rpi5FramebufferInfo* out)
{
uint64_t base;
uint32_t idx;
uint32_t send_val;
uint32_t v2[2];
if (!out) return -1;
base = rpi5_mailbox_base(dtb);
if (!base) return -2; /* mailbox node not found/malformed in the DTB */
idx = 2; /* words 0/1 are the header, filled in after tags are built */
v2[0] = width;
v2[1] = height;
append_tag(&idx, TAG_SET_PHYS_SIZE, 8, v2, 2);
append_tag(&idx, TAG_SET_VIRT_SIZE, 8, v2, 2);
v2[0] = bpp;
append_tag(&idx, TAG_SET_DEPTH, 4, v2, 1);
v2[0] = PIXEL_ORDER_RGB;
append_tag(&idx, TAG_SET_PIXEL_ORDER, 4, v2, 1);
v2[0] = 0; v2[1] = 0; /* virtual offset x,y -- always (0,0), no scrolling support */
append_tag(&idx, TAG_SET_VIRTUAL_OFFS, 8, v2, 2);
v2[0] = 16; /* alignment request, bytes */
v2[1] = 0; /* response slot (base addr); request only sends v2[0] */
append_tag(&idx, TAG_ALLOCATE_BUFFER, 8, v2, 2);
v2[0] = 0;
append_tag(&idx, TAG_GET_PITCH, 4, v2, 1);
g_mbox_buf[idx++] = TAG_END;
g_mbox_buf[0] = idx * 4u; /* total buffer size, bytes */
g_mbox_buf[1] = MBOX_CODE_REQUEST;
send_val = ((uint32_t) (uintptr_t) g_mbox_buf & ~0xFu) | MBOX_CH_PROPERTY;
while ((mmio_read32(base + MBOX_OFF_STATUS1) & MBOX_FULL) != 0)
{ /* spin until MBOX1 (send) has room */
}
mmio_write32(base + MBOX_OFF_WRITE, send_val);
for (;;)
{
while ((mmio_read32(base + MBOX_OFF_STATUS0) & MBOX_EMPTY) != 0)
{ /* spin until MBOX0 (receive) has a message */
}
if (mmio_read32(base + MBOX_OFF_READ) == send_val) break;
/* A message arrived, but not the one we sent (another channel's
* traffic) -- discard and keep waiting, per the wiki's own
* documented channel-matching convention. */
}
if (g_mbox_buf[1] != MBOX_CODE_RESPONSE_OK) return -3; /* VC rejected the request */
/* Re-walk the tags to pull out the allocate-buffer and get-pitch
* responses -- same fixed order they were sent in, so fixed word
* offsets are safe to compute directly rather than re-parsing
* generically (this driver only ever sends this one sequence). */
{
uint32_t alloc_tag_value_off = 2 /* header */
+ (3 + 2) /* phys size */
+ (3 + 2) /* virt size */
+ (3 + 1) /* depth */
+ (3 + 1) /* pixel order */
+ (3 + 2) /* virtual offset */
+ 3; /* allocate-buffer tag header, then its value words */
uint32_t pitch_tag_value_off = alloc_tag_value_off + 2 /* alloc response: base+size */
+ 3; /* get-pitch tag header, then its value word */
uint32_t fb_addr = g_mbox_buf[alloc_tag_value_off];
uint32_t fb_size = g_mbox_buf[alloc_tag_value_off + 1];
uint32_t pitch_bytes = g_mbox_buf[pitch_tag_value_off];
if (fb_addr == 0 || fb_size == 0 || pitch_bytes == 0) return -4;
/* Allocate-buffer's own response address is a VC bus address,
* not necessarily the plain ARM-physical one -- masking the
* top bits to recover the ARM-physical alias is the documented
* convention on earlier Pi chips; kept here defensively even
* though the Pi-5-specific reference this file cites for the
* *send* side found no bus-alias bit in play there. Genuinely
* unverified which of these is correct for Pi 5's own
* allocate-buffer response specifically -- flagged, not
* guessed past this comment. */
out->base = (void*) (uintptr_t) (fb_addr & 0x3FFFFFFFu);
out->size = fb_size;
out->width = width;
out->height = height;
out->pixels_per_scanline = pitch_bytes / (bpp / 8u);
out->pixel_format = 0; /* PixelRedGreenBlueReserved8BitPerColor --
* matches PIXEL_ORDER_RGB requested above */
}
return 0;
}
@@ -0,0 +1,227 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 Robert A. James. All rights reserved.
Licensed under the StarForth License, Version 1.0.
*/
/**
* rpi5_native_boot.c - Raspberry Pi 5 DTB->BootInfo constructor
* (FABRIC-3.md §IV.3 item 2, 2026-09-04).
*
* **Never run on real hardware** -- the Pi 5 isn't in hand until
* 2026-09-17 (FABRIC-3.md §II). Compile-only-verified; 3-arch QEMU boot
* confirms this compiles and links, and does not disturb the existing
* UEFI/ACPI path -- it cannot exercise a single line of this function,
* since nothing in that path calls it (same caveat as
* `rpi5_dtb.c`/`rpi5_mailbox.c`/`native_rpi5_entry.S` before it).
*
* `rpi5_native_start` (`native_rpi5_entry.S`) tail-calls this function
* with the masked DTB pointer it captured. This builds the *existing*,
* unmodified `BootInfo` struct from the devicetree instead of UEFI
* protocols, then calls the *existing*, unmodified `kernel_main()` --
* this is the crux of why most of M1-M9 stays shared between the UEFI
* and native boot paths.
*
* What this deliberately does NOT do yet, named honestly rather than
* silently skipped:
*
* - **`/reserved-memory` is not parsed.** Only `/memory`'s own `reg` is
* turned into `EfiConventionalMemory` descriptors. Carving reserved
* sub-ranges (VideoCore firmware regions, CMA, etc.) out of that span
* requires interval-splitting logic that would be written blind
* against hardware not yet in hand -- exactly the kind of code that
* hides a subtle bug until real silicon. Deferred to its own
* FABRIC-3.md item rather than merged into this one. The bitmap PMM
* builds from this memory map starts fully reserved and only clears
* pages this file lists as `EfiConventionalMemory`
* (`memory/pmm.c`'s Pass 3) -- so the gap here is "less RAM than
* optimal," never "reserved RAM wrongly marked free."
* - **The mailbox framebuffer request uses a fixed 1920x1080x32
* default**, not real display negotiation (no EDID query exists in
* this codebase). Flagged, not guessed past this comment -- revisit
* once real hardware and a real attached display are in hand.
* - **Root `#address-cells`/`#size-cells` must be exactly 1 or 2** --
* confirmed against `bcm2712.dtsi`'s actual root node (2/2) before
* writing this, not assumed; other widths halt rather than guess.
*/
#include "starkernel/rpi5_native_boot.h"
#include "starkernel/fdt.h"
#include "starkernel/rpi5_mailbox.h"
#include "starkernel/cmdline.h"
#include "starkernel/uefi.h"
#define RPI5_MAX_MEMMAP_ENTRIES 8
#define RPI5_FB_DEFAULT_WIDTH 1920u
#define RPI5_FB_DEFAULT_HEIGHT 1080u
#define RPI5_FB_DEFAULT_BPP 32u
extern void kernel_main(BootInfo *boot_info);
static BootInfo g_rpi5_boot_info = {0};
static EFI_MEMORY_DESCRIPTOR g_rpi5_memmap[RPI5_MAX_MEMMAP_ENTRIES];
static uint32_t be32_at(const unsigned char *p)
{
return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) |
((uint32_t)p[2] << 8) | (uint32_t)p[3];
}
/**
* @brief Read one big-endian cell group (1 or 2 u32 cells) as a uint64_t.
*/
static uint64_t read_cells(const unsigned char *p, uint32_t ncells)
{
if (ncells == 1) return (uint64_t)be32_at(p);
return ((uint64_t)be32_at(p) << 32) | (uint64_t)be32_at(p + 4);
}
/**
* @brief Halt this core. No console exists yet (M1 hasn't run) and there
* is nowhere safe to report failure -- this is the same halt shape
* `native_rpi5_entry.S` itself uses before this function existed.
*/
static void rpi5_native_halt(void) __attribute__((noreturn));
static void rpi5_native_halt(void)
{
for (;;)
{
__asm__ volatile("wfe");
}
}
/**
* @brief Parse `/memory`'s `reg` into `g_rpi5_memmap`, honoring the
* root's own `#address-cells`/`#size-cells` (NOT the `soc` node's -- see
* this file's own doc comment; `/memory` is a direct child of root).
*
* @return Number of entries written (0 on any failure).
*/
static uint32_t build_memory_map(const void *dtb)
{
uint32_t acells, scells;
const void *mem_node;
const void *reg;
uint32_t reg_len;
uint32_t entry_bytes, count, i;
const unsigned char *p;
if (!fdt_prop_u32(dtb, "#address-cells", &acells) ||
!fdt_prop_u32(dtb, "#size-cells", &scells))
{
return 0;
}
if ((acells != 1 && acells != 2) || (scells != 1 && scells != 2))
{
return 0;
}
mem_node = fdt_find_node_by_device_type(dtb, "memory");
if (!mem_node) return 0;
reg = fdt_find_prop_in_node(dtb, mem_node, "reg", &reg_len);
if (!reg) return 0;
entry_bytes = (acells + scells) * 4u;
if (entry_bytes == 0) return 0;
count = reg_len / entry_bytes;
if (count > RPI5_MAX_MEMMAP_ENTRIES) count = RPI5_MAX_MEMMAP_ENTRIES;
p = (const unsigned char *)reg;
for (i = 0; i < count; i++)
{
uint64_t addr = read_cells(p, acells);
uint64_t size = read_cells(p + acells * 4u, scells);
g_rpi5_memmap[i].Type = EfiConventionalMemory;
g_rpi5_memmap[i].PhysicalStart = addr;
g_rpi5_memmap[i].VirtualStart = addr;
g_rpi5_memmap[i].NumberOfPages = size / 4096u;
g_rpi5_memmap[i].Attribute = 0;
p += entry_bytes;
}
return count;
}
void rpi5_native_boot(uint64_t dtb)
{
const void *dtb_ptr = (const void *)(uintptr_t)dtb;
uint32_t memmap_count;
const void *bootargs;
uint32_t bootargs_len;
Rpi5FramebufferInfo fb;
if (!fdt_valid(dtb_ptr))
{
rpi5_native_halt();
}
memmap_count = build_memory_map(dtb_ptr);
if (memmap_count == 0)
{
rpi5_native_halt();
}
g_rpi5_boot_info.memory_map = g_rpi5_memmap;
g_rpi5_boot_info.memory_map_size =
(UINTN)memmap_count * sizeof(EFI_MEMORY_DESCRIPTOR);
g_rpi5_boot_info.memory_map_descriptor_size = sizeof(EFI_MEMORY_DESCRIPTOR);
g_rpi5_boot_info.runtime_services = (void *)0;
/* No ACPI at all on this path (§IV.1) -- NULL is the already-correct
* value every existing acpi_table consumer already degrades safely
* against. */
g_rpi5_boot_info.acpi_table = (void *)0;
g_rpi5_boot_info.dtb = (void *)dtb_ptr;
/* No UEFI boot services ever existed on this path -- there is no
* EDK2 here at all, so "exited" is the closest honest value (no
* downstream consumer branches on this field today; it is not a
* live behavioral gate). */
g_rpi5_boot_info.uefi_boot_services_exited = 1;
/* No dynamic allocator exists this early -- fall back to the 2 MiB
* BSS stack, same convention as every other boot path. Unlike
* amd64/riscv64, aarch64 has no kernel_entry.S trampoline to honor
* this field: `native_rpi5_entry.S`'s own BSS stack already *is* the
* stack kernel_main_impl runs on, so this is a documented fact about
* this path, not a live fallback switch. */
g_rpi5_boot_info.kernel_stack_base = (void *)0;
g_rpi5_boot_info.kernel_stack_size = 0;
bootargs = fdt_find_prop(dtb_ptr, "bootargs", &bootargs_len);
cmdline_parse_ascii(bootargs ? (const char *)bootargs : (const char *)0,
&g_rpi5_boot_info.args);
g_rpi5_boot_info.framebuffer.base = (void *)0;
g_rpi5_boot_info.framebuffer.size = 0;
g_rpi5_boot_info.framebuffer.width = 0;
g_rpi5_boot_info.framebuffer.height = 0;
g_rpi5_boot_info.framebuffer.pixels_per_scanline = 0;
g_rpi5_boot_info.framebuffer.pixel_format = 0;
if (rpi5_mailbox_get_framebuffer(dtb_ptr, RPI5_FB_DEFAULT_WIDTH,
RPI5_FB_DEFAULT_HEIGHT,
RPI5_FB_DEFAULT_BPP, &fb) == 0)
{
g_rpi5_boot_info.framebuffer.base = fb.base;
g_rpi5_boot_info.framebuffer.size = fb.size;
g_rpi5_boot_info.framebuffer.width = fb.width;
g_rpi5_boot_info.framebuffer.height = fb.height;
g_rpi5_boot_info.framebuffer.pixels_per_scanline = fb.pixels_per_scanline;
g_rpi5_boot_info.framebuffer.pixel_format = fb.pixel_format;
}
/* On failure, framebuffer stays zeroed -- the same degraded state the
* UEFI path already leaves it in when GOP is BltOnly/absent
* (`boot/uefi_loader.c`). */
kernel_main(&g_rpi5_boot_info);
/* kernel_main() never returns (REPL loop or panic) -- this is
* unreachable, kept only so the noreturn contract holds even if that
* ever changes. */
rpi5_native_halt();
}
+217
View File
@@ -156,3 +156,220 @@ int fdt_prop_u32(const void* fdt, const char* name, uint32_t* out)
*out = be32(val);
return 1;
}
/**
* @brief Test whether NUL-separated string list @p list (length @p len)
* contains @p want as one of its entries.
*
* "compatible" properties are formatted as one or more NUL-terminated
* strings concatenated (DT spec §2.3.1) -- each entry's own embedded NUL
* is real property data, not synthesized, so str_eq() (which walks until
* either side's NUL) terminates correctly at each entry's real boundary.
*/
static int compat_list_contains(const char* list, uint32_t len, const char* want)
{
uint32_t i = 0;
while (i < len)
{
const char* s = list + i;
uint32_t slen = 0;
while (i + slen < len && s[slen]) slen++;
if (str_eq(s, want)) return 1;
i += slen + 1;
}
return 0;
}
const void* fdt_find_node_by_compatible(const void* fdt, const char* compatible)
{
const fdt_header_t* h = (const fdt_header_t*)fdt;
const unsigned char *base, *p, *end, *strings;
uint32_t size_struct;
int fresh_node = 0; /* 1 = haven't yet seen a non-PROP token since the
* last FDT_BEGIN_NODE -- i.e. still inside that
* node's own leading property list, per the DT
* spec's ordering guarantee (all of a node's
* direct properties precede its children). */
const void* node_body_start = (void*)0;
if (!fdt_valid(fdt) || !compatible) return (void*)0;
base = (const unsigned char*)fdt;
size_struct = be32(&h->size_dt_struct);
p = base + be32(&h->off_dt_struct);
end = p + size_struct;
strings = base + be32(&h->off_dt_strings);
while (p + 4 <= end)
{
uint32_t token = be32(p);
p += 4;
if (token == FDT_BEGIN_NODE)
{
const unsigned char* q = p;
while (q < end && *q) q++;
if (q >= end) break;
p = (const unsigned char*)(((uintptr_t)(q + 1) + 3u) & ~(uintptr_t)3u);
fresh_node = 1;
node_body_start = (const void*)p;
}
else if (token == FDT_PROP)
{
uint32_t len, nameoff;
const unsigned char* val;
if (p + 8 > end) break;
len = be32(p);
nameoff = be32(p + 4);
p += 8;
val = p;
if (len > (uint32_t)(end - p)) break;
if (fresh_node &&
str_eq((const char*)(strings + nameoff), "compatible") &&
compat_list_contains((const char*)val, len, compatible))
{
return node_body_start;
}
p = (const unsigned char*)(((uintptr_t)(p + len) + 3u) & ~(uintptr_t)3u);
/* fresh_node stays 1 -- still within this same node's own
* leading property list. */
}
else if (token == FDT_END_NODE)
{
fresh_node = 0;
}
else if (token == FDT_NOP)
{
/* no payload; does not end the leading property list */
}
else
{
/* FDT_END, or a token this reader does not know: stop. */
break;
}
}
return (void*)0;
}
const void* fdt_find_node_by_device_type(const void* fdt, const char* type)
{
const fdt_header_t* h = (const fdt_header_t*)fdt;
const unsigned char *base, *p, *end, *strings;
uint32_t size_struct;
int fresh_node = 0;
const void* node_body_start = (void*)0;
if (!fdt_valid(fdt) || !type) return (void*)0;
base = (const unsigned char*)fdt;
size_struct = be32(&h->size_dt_struct);
p = base + be32(&h->off_dt_struct);
end = p + size_struct;
strings = base + be32(&h->off_dt_strings);
while (p + 4 <= end)
{
uint32_t token = be32(p);
p += 4;
if (token == FDT_BEGIN_NODE)
{
const unsigned char* q = p;
while (q < end && *q) q++;
if (q >= end) break;
p = (const unsigned char*)(((uintptr_t)(q + 1) + 3u) & ~(uintptr_t)3u);
fresh_node = 1;
node_body_start = (const void*)p;
}
else if (token == FDT_PROP)
{
uint32_t len, nameoff;
const unsigned char* val;
if (p + 8 > end) break;
len = be32(p);
nameoff = be32(p + 4);
p += 8;
val = p;
if (len > (uint32_t)(end - p)) break;
if (fresh_node &&
str_eq((const char*)(strings + nameoff), "device_type") &&
str_eq((const char*)val, type))
{
return node_body_start;
}
p = (const unsigned char*)(((uintptr_t)(p + len) + 3u) & ~(uintptr_t)3u);
}
else if (token == FDT_END_NODE)
{
fresh_node = 0;
}
else if (token == FDT_NOP)
{
/* no payload; does not end the leading property list */
}
else
{
/* FDT_END, or a token this reader does not know: stop. */
break;
}
}
return (void*)0;
}
const void* fdt_find_prop_in_node(const void* fdt, const void* node,
const char* name, uint32_t* len_out)
{
const fdt_header_t* h = (const fdt_header_t*)fdt;
const unsigned char *p, *end, *strings;
if (!fdt_valid(fdt) || !node || !name) return (void*)0;
p = (const unsigned char*)node;
end = (const unsigned char*)fdt + be32(&h->off_dt_struct) + be32(&h->size_dt_struct);
strings = (const unsigned char*)fdt + be32(&h->off_dt_strings);
/* node points just past this node's own FDT_BEGIN_NODE name -- scan
* only its leading property list; stop at the first non-PROP/NOP
* token (a child's FDT_BEGIN_NODE, or this node's own FDT_END_NODE),
* matching fdt_find_node_by_compatible()'s own scoping rule. Never
* descends into children, never continues into a sibling. */
while (p + 4 <= end)
{
uint32_t token = be32(p);
p += 4;
if (token == FDT_NOP) continue;
if (token != FDT_PROP) break;
{
uint32_t len, nameoff;
const unsigned char* val;
if (p + 8 > end) break;
len = be32(p);
nameoff = be32(p + 4);
p += 8;
val = p;
if (len > (uint32_t)(end - p)) break;
if (str_eq((const char*)(strings + nameoff), name))
{
if (len_out) *len_out = len;
return (const void*)val;
}
p = (const unsigned char*)(((uintptr_t)(p + len) + 3u) & ~(uintptr_t)3u);
}
}
return (void*)0;
}