Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67e3fb7459 | ||
|
|
281de9547c | ||
|
|
a32b0ebcbe | ||
|
|
ca52ce8243 | ||
|
|
5e46f18fd9 | ||
|
|
bffd87615d | ||
|
|
1ec2a29384 | ||
|
|
9142d50b73 | ||
|
|
5eeb993ca2 | ||
|
|
987c751352 | ||
|
|
a66af477ac | ||
|
|
e5e28d5198 | ||
|
|
7f9a4d4d4a |
+514
@@ -117,3 +117,517 @@ functional rollback.
|
|||||||
the prior tag placement was itself part of the same "got ahead of myself" correction, not a
|
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
|
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.
|
`v2.0.0`), so no destructive remote operation was needed, only a local move-and-push.
|
||||||
|
5. **Follow-up, same day**: `v2.0.1` (the working branch this and Task 1 happened on) deleted,
|
||||||
|
local and `origin` — confirmed a strict ancestor of `master`'s new HEAD first, so nothing
|
||||||
|
was lost. `master` is the repo's only branch from here on.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## II. Three architectures, three different hardware scopes
|
||||||
|
|
||||||
|
Per direct instruction, 2026-09-04. The real-hardware targets are **not** symmetric across
|
||||||
|
architectures — each gets its own section below because the actual scope of "done" is
|
||||||
|
different for each:
|
||||||
|
|
||||||
|
- **amd64 — genericity is the goal, not just the SER5.** The Beelink SER5 is the machine in
|
||||||
|
hand and the development/reference target, but the real requirement is broader: this needs
|
||||||
|
to boot on *any* x86_64 machine — laptop, desktop, tower, or mini PC — not just one vendor's
|
||||||
|
quirks. SER5-only success is necessary but not sufficient; anything that works only because
|
||||||
|
of an SER5-specific assumption (a particular ACPI table shape, a specific UEFI
|
||||||
|
implementation's quirks) is a bug against this goal, not a deferred nice-to-have.
|
||||||
|
- **aarch64 — Raspberry Pi 5, and only the Raspberry Pi 5.** No genericity requirement across
|
||||||
|
aarch64 boards — this is the one and only target for this architecture.
|
||||||
|
- **riscv64 — Milk-V Mars, and only the Milk-V Mars.** Same as aarch64: one specific board,
|
||||||
|
not a generic riscv64-SBC goal.
|
||||||
|
|
||||||
|
**How to use sections III–V below.** Same discipline as everything else in this series: plan
|
||||||
|
before building, one section at a time, iterating — not all three architectures in parallel,
|
||||||
|
and not front-loading a complete plan before any real hardware is in front of us. Each section
|
||||||
|
starts with what's already true (existing repo infrastructure, already-decided policy) and
|
||||||
|
what's still genuinely unknown, not assumed.
|
||||||
|
|
||||||
|
## III. amd64 — generic x86_64 bare metal (reference hardware: Beelink SER5)
|
||||||
|
|
||||||
|
**Already true, not to be re-derived:**
|
||||||
|
- `ROADMAP.md`'s "Board-by-board hardware rollout" already names this `v2.2.0`'s gate: the
|
||||||
|
generic GPT/FAT32 thumbdrive image (`make -f Makefile.starkernel ARCH=amd64 thumbdrive`,
|
||||||
|
already built — `Makefile.starkernel:1018`) flashes to and boots on the real SER5 via its
|
||||||
|
real UEFI, reaching POST + `ok>`, with the amd64 RDRAND entropy backend
|
||||||
|
(`src/starkernel/rng/rng.c`, already built and part of `master`) serving live entropy.
|
||||||
|
- `iso-usb` (`Makefile.starkernel:1060`) is the alternate, novice-friendly path (UEFI
|
||||||
|
isohybrid ISO for tools like GNOME Disks "Restore Disk Image...") — same underlying image,
|
||||||
|
different flashing UX.
|
||||||
|
- `FABRIC-2.md` §I.6's own 8-step physical-boot sequence (build ISO, identify the target
|
||||||
|
device, flash it, physically boot, decide an observation method, confirm POST, confirm
|
||||||
|
`ok>`, document) is the closest thing to an existing plan — but it predates the genericity
|
||||||
|
requirement and was written with no hardware in hand yet.
|
||||||
|
|
||||||
|
**Decided in conversation, 2026-09-04:**
|
||||||
|
- **Observation: HDMI (interactive) + serial (logged transcript), both.** The kernel's own
|
||||||
|
VT100 framebuffer console (`console.c`/`vt100.c`/`framebuffer.c`) already gives a real
|
||||||
|
interactive display over HDMI — no new code needed there. Serial capture, if the SER5
|
||||||
|
exposes a UART header, uses the Raspberry Pi's own GPIO UART as the USB-serial bridge
|
||||||
|
(already available hardware, not a purchase blocker) — this needs the SER5's own UART pins
|
||||||
|
physically identified first (not yet confirmed it has an accessible header at all).
|
||||||
|
- **Genericity is verified by standards-compliance, not a second machine** — no second x86_64
|
||||||
|
box is available right now. The bar is: nothing in the boot path may depend on an
|
||||||
|
SER5-specific assumption (a particular ACPI table shape, a specific UEFI implementation's
|
||||||
|
quirk) — argued by code audit against real UEFI/ACPI standards, not by testing on a second
|
||||||
|
board, until one becomes available. This is a real constraint on the punch list below (item
|
||||||
|
6), not a deferred nice-to-have.
|
||||||
|
- **Secure Boot: already disabled on this SER5.** No signed-loader work needed for this pass —
|
||||||
|
"Secure Boot disabled in firmware setup" is the supported path, documented as such rather
|
||||||
|
than built around.
|
||||||
|
|
||||||
|
**Punch list, this cadence's actual next steps:**
|
||||||
|
1. Build the generic thumbdrive image: `make -f Makefile.starkernel ARCH=amd64 thumbdrive`.
|
||||||
|
2. Flash it to a USB stick (`dd`, per the target's own existing usage message).
|
||||||
|
3. Physically inspect the SER5 for an exposed UART header/pins; if present, wire the
|
||||||
|
Raspberry Pi's GPIO UART to it as the serial bridge. If absent, HDMI-only for this pass —
|
||||||
|
not a blocker, just a scope note for step 7's log.
|
||||||
|
4. Connect HDMI + keyboard to the SER5.
|
||||||
|
5. Boot the SER5 from the flashed stick (firmware boot-order menu as needed — Secure Boot
|
||||||
|
already disabled, confirmed above, so no signing prompt expected).
|
||||||
|
6. **Code audit pass** (can happen before or in parallel with 1–5, doesn't need the hardware
|
||||||
|
in hand): review the amd64 boot path (`src/starkernel/boot/uefi_loader.c`,
|
||||||
|
`arch/amd64/*.c`) for anything that assumes SER5-specific hardware rather than standard
|
||||||
|
UEFI/ACPI — this is what "genericity" actually rests on per the decision above, not the
|
||||||
|
SER5 boot succeeding alone.
|
||||||
|
7. Capture the boot: confirm POST reaches the same `1012/0/0` result QEMU shows, confirm
|
||||||
|
`ok>`/`zuse)ok>`, confirm `rng: backend = rdrand` (live entropy, not the QEMU-only
|
||||||
|
`virtio-rng` path), save the serial transcript (if wired) the same way `logs/` already
|
||||||
|
keeps QEMU's.
|
||||||
|
8. Mint a Zuse identity on a second thumbdrive on the real SER5, confirm it re-attaches —
|
||||||
|
the same real-hardware round-trip `ROADMAP.md`'s `v2.2.0` gate already names.
|
||||||
|
9. Update this section with results — pass/fail per step, any SER5-specific or genuinely
|
||||||
|
generic-UEFI finding either way, before moving to aarch64.
|
||||||
|
|
||||||
|
## IV. aarch64 — Raspberry Pi 5
|
||||||
|
|
||||||
|
**Already true:** `ROADMAP.md` names this `v2.4.0`'s gate: boots on the real board, aarch64
|
||||||
|
peripheral-RNG backend live, Zuse mint/attach on real media. The peripheral-RNG backend itself
|
||||||
|
is **not yet built** — today's `rng_get_bytes()` (`src/starkernel/rng/rng.c`) only has a
|
||||||
|
`virtio-rng` path, real on QEMU, meaningless on real Pi 5 hardware (no virtio device there).
|
||||||
|
|
||||||
|
**Decided in conversation, 2026-09-04:**
|
||||||
|
- **Observation: HDMI-only for this board's own bring-up.** No second Pi, no dedicated
|
||||||
|
USB-serial adapter available. The Milk-V Mars could in principle serve as a GPIO-UART
|
||||||
|
bridge once it arrives (same 40-pin-header shape as the SER5 plan), but using it to observe
|
||||||
|
the Pi 5 before the Mars has been independently validated itself would be a chicken-and-egg
|
||||||
|
dependency, not a real plan. Revisit serial capture later if genuinely needed, once at least
|
||||||
|
one board is proven working — not a blocker for this pass.
|
||||||
|
- **Both boards (Pi 5, Milk-V Mars) arrive 2026-09-17.** Real runway exists to finish the
|
||||||
|
design/code work below *before* any hardware is in hand — "plan well before doing," per
|
||||||
|
direct instruction.
|
||||||
|
|
||||||
|
**Still genuinely open, not yet decided:**
|
||||||
|
- **A pinned GPIO VM, theory-stage** — see `FABRIC-4.md` §2. Raised in conversation, not yet
|
||||||
|
scoped; downstream of §IV.1's own native-boot-flow work (a GPIO VM needs GPIO addresses
|
||||||
|
from the DTB the same way the rest of this bring-up does).
|
||||||
|
|
||||||
|
### IV.1 — Boot-chain decision: UEFI vs. native, researched 2026-09-04
|
||||||
|
|
||||||
|
**Researched, not assumed** (web search, current as of this session):
|
||||||
|
|
||||||
|
**UEFI option investigated and found weak.** A real UEFI+ACPI firmware for Pi 5 exists —
|
||||||
|
[`rpi5-uefi`](https://github.com/worproject/rpi5-uefi) (TF-A + EDK2, SBBR-compliant). But:
|
||||||
|
it's **archived as of 2025-02-04**, support ended because newer Pi EEPROM firmware broke
|
||||||
|
compatibility with it; its own README says ACPI support is "under development and limited to
|
||||||
|
a few devices"; RP1 Ethernet/GPIO/PWM/EEPROM don't work under it. This kernel's whole
|
||||||
|
aarch64 boot path (`boot/uefi_loader.c`, `BootInfo->acpi_table`) assumes UEFI+ACPI the same
|
||||||
|
way amd64 and the QEMU aarch64 target do — but that assumption may not hold on a real,
|
||||||
|
current-firmware Pi 5 at all.
|
||||||
|
|
||||||
|
**Native boot flow — the real alternative, researched concretely:**
|
||||||
|
- Boot partition needs `bcm2712-rpi-5-b.dtb`, `config.txt`, and the kernel image itself —
|
||||||
|
Pi 5 firmware defaults to loading `kernel_2712.img`, falling back to `kernel8.img` if that's
|
||||||
|
absent.
|
||||||
|
- `config.txt` needs `os_check=0` for a non-Linux image, or the firmware assumes Linux and
|
||||||
|
loads from `0x200000` instead of the classic Pi bare-metal load address `0x80000`.
|
||||||
|
- Entry protocol: `x0` = 32-bit DTB pointer (upper 32 bits of the 64-bit register
|
||||||
|
unspecified — must mask before use), `x1`–`x3` reserved/zero. **No UEFI PE loader, no ACPI
|
||||||
|
at all** — a completely different entry shape from `boot/uefi_loader.c`.
|
||||||
|
- Framebuffer: the VideoCore **mailbox property interface** (channel 8) — a real, different
|
||||||
|
mechanism from UEFI GOP, no precedent anywhere in this codebase today.
|
||||||
|
|
||||||
|
**Decision, per direct instruction 2026-09-04: native boot flow.** Not UEFI. The archived,
|
||||||
|
partially-working UEFI project is too fragile a foundation to build a real-hardware release
|
||||||
|
on top of.
|
||||||
|
|
||||||
|
**What this actually means for the codebase, named honestly rather than estimated small:**
|
||||||
|
- A **new, non-UEFI entry path** for aarch64 real hardware — this kernel's boot sequence
|
||||||
|
currently assumes `uefi_loader.c`'s PE-loader shape unconditionally on aarch64; a Pi 5
|
||||||
|
native boot needs its own entry point (linked at `0x80000`, receiving `x0` = DTB pointer
|
||||||
|
directly, no `BootInfo` from UEFI at all).
|
||||||
|
- A **DTB-driven `BootInfo` equivalent** replacing ACPI-sourced data for this path — memory
|
||||||
|
map, peripheral addresses (UART, etc.) all come from the devicetree instead.
|
||||||
|
- **One real, genuine piece of reusable groundwork**: `starkernel/hal/fdt.c`/`fdt.h`, the
|
||||||
|
minimal FDT reader already built for riscv64's `timebase-frequency` lookup
|
||||||
|
(`arch/riscv64/timer.c`), is directly extensible for this — parsing `bcm2712-rpi-5-b.dtb`
|
||||||
|
for peripheral addresses is the same kind of lookup, not a new mechanism.
|
||||||
|
- A **new mailbox-property-interface framebuffer driver** — genuinely new code, no existing
|
||||||
|
precedent in this codebase, needed before the VT100 console framework
|
||||||
|
(`console.c`/`vt100.c`/`framebuffer.c`) has anything to draw onto for this board.
|
||||||
|
- This is a real architectural fork for aarch64, not a small per-board addition — QEMU
|
||||||
|
aarch64 keeps its existing UEFI+ACPI path unchanged; Pi 5 real hardware gets a second,
|
||||||
|
parallel entry path. **Not yet scoped into a punch list** — that's the next step, once this
|
||||||
|
fork's own shape (how much of `kernel_main.c`'s post-entry sequence can stay shared between
|
||||||
|
the two paths vs. needs its own branch) is thought through.
|
||||||
|
|
||||||
|
### IV.2 — Peripheral RNG: unresolved, not just under-researched
|
||||||
|
|
||||||
|
`ROADMAP.md` names an "aarch64 peripheral-RNG backend" as part of `v2.4.0`'s gate. Researched
|
||||||
|
directly rather than assumed still-TODO: Broadcom's `iproc-rng200` block (real, on Pi 4/BCM2711
|
||||||
|
as `brcm,bcm2711-rng200`) has **no `bcm2712` compatible-string entry anywhere in current
|
||||||
|
mainline Linux** (checked the actual driver's `of_device_id` table directly). The RP1
|
||||||
|
companion chip's own published peripheral list (GPIO/USB/Ethernet/DMA/ADC/PLLs/SRAM/
|
||||||
|
UARTs/SPIs) doesn't mention an RNG either. Two real possibilities, not yet distinguished:
|
||||||
|
BCM2712 still has the RNG200 block but Linux hasn't wired it into a devicetree binding yet, or
|
||||||
|
it genuinely isn't exposed to the ARM cores this generation. No public register address exists
|
||||||
|
to target right now — this needs either a Broadcom datasheet (if one becomes available) or
|
||||||
|
direct hardware probing once the board is in hand (scan the known BCM2711 RNG200 offset region
|
||||||
|
on the BCM2712 memory map and see if anything responds — risky without a datasheet confirming
|
||||||
|
it's safe to touch, so likely a "board in hand, careful probe" task, not a today task).
|
||||||
|
Deliberately **not a blocker for the first native boot** — reaching `ok>` doesn't require a
|
||||||
|
live entropy backend; `rng_get_bytes()` already has a "no entropy backend available" WARNING
|
||||||
|
path (`rng.c`) rather than a hard failure, so this can land after boot succeeds.
|
||||||
|
|
||||||
|
### IV.3 — Punch list: design/code work, no hardware needed (before 2026-09-17)
|
||||||
|
|
||||||
|
Traced against real code before writing this, not estimated: `boot_info->acpi_table`'s only
|
||||||
|
aarch64-relevant consumers today are `pci_init()` (`kernel_main.c:589`, unconditional, not
|
||||||
|
amd64-gated — relevant because RP1 is PCIe-attached on real Pi 5 hardware) and this session's
|
||||||
|
own `running_under_hypervisor()` (`arch/aarch64/timer.c`, already degrades safely to "not a
|
||||||
|
hypervisor" when `acpi_table` is `NULL` — no fix needed there).
|
||||||
|
`ioapic_init()`/`i8042_init()` are `#ifdef ARCH_AMD64`-gated, irrelevant here.
|
||||||
|
`arch/aarch64/apic.c` (GIC init) already only ever tries `boot_info->dtb`, never
|
||||||
|
`acpi_table` — its own doc comment already anticipated DTB-based discovery, just blocked
|
||||||
|
until now because QEMU's own UEFI firmware never publishes one; Pi 5 native boot removes that
|
||||||
|
blocker for free.
|
||||||
|
|
||||||
|
1. **Entry stub — 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 M1–M9
|
||||||
|
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 1–4 (matches the QEMU riscv64 target's own hart numbering
|
||||||
|
convention already assumed elsewhere in this codebase — worth double-checking, not
|
||||||
|
assuming, once real hardware is in hand).
|
||||||
|
- UART at `0x10000000`, 115200 baud, already initialized by firmware before handoff.
|
||||||
|
- Custom bare-metal images package via `vf2-imager` (invokes U-Boot's `mkimage`) into a FIT
|
||||||
|
image — same tooling should apply to the Mars, unconfirmed until tried.
|
||||||
|
- **Not yet found**: what registers carry the DTB pointer/hart ID at the actual kernel entry
|
||||||
|
point under this specific chain (the source consulted covered the image-packaging tooling,
|
||||||
|
not the OpenSBI→kernel handoff register convention). **Resolved 2026-09-04**: standard
|
||||||
|
RISC-V SBI boot protocol, confirmed via OpenSBI's own docs — `a0`=hart ID, `a1`=DTB pointer,
|
||||||
|
S-mode entry. Not chain-specific guesswork; this is the universal convention OpenSBI's
|
||||||
|
`FW_DYNAMIC` firmware type uses regardless of vendor, so it applies to this chain directly.
|
||||||
|
|
||||||
|
**What this means for the codebase** — same shape of fork as aarch64 (§IV.1): a non-UEFI
|
||||||
|
entry path, a DTB-driven `BootInfo` equivalent (the existing `starkernel/hal/fdt.c` reader
|
||||||
|
extends here too, same as for the Pi 5), no ACPI.
|
||||||
|
|
||||||
|
**Decided in conversation, 2026-09-04: observation is HDMI-only**, same reasoning and same
|
||||||
|
constraint as the Pi 5 (§IV) — no bridge hardware available for this board's own first
|
||||||
|
bring-up either; the Mars has its own HDMI 2.0 output (§VI).
|
||||||
|
|
||||||
|
### V.2 — Peripheral RNG and Zkr: still genuinely open
|
||||||
|
|
||||||
|
- Zkr/RNDR instruction availability on the Mars's actual CPU (riscv64 Scalar Crypto extension
|
||||||
|
support varies by implementation) — not yet confirmed; the VisionFive 2 bare-metal research
|
||||||
|
above didn't surface this either, would need its own targeted look (or a real-hardware
|
||||||
|
probe of `misa`/the Zkr extension discovery mechanism). Deliberately **not a blocker for
|
||||||
|
first boot**, same reasoning as §IV.2's aarch64 RNG gap — `rng_get_bytes()` already
|
||||||
|
WARNs rather than hard-fails with no backend.
|
||||||
|
- **Whether the Mars needs the same pinned-GPIO-VM treatment as the Pi 5** — explicitly
|
||||||
|
**not decided either way**, per direct instruction ("same for Milk-V (? not sure here)").
|
||||||
|
See `FABRIC-4.md` §2. The Mars does have its own 40-pin GPIO header (§VI), so the open
|
||||||
|
question is the VM architecture around it, not whether the hardware exists.
|
||||||
|
|
||||||
|
### V.3 — Punch list: design/code work, no hardware needed (before 2026-09-17)
|
||||||
|
|
||||||
|
Traced against real code before writing this, same discipline as §IV.3: `pci_init()`
|
||||||
|
(`kernel_main.c:589`, unconditional) is the one real `acpi_table` consumer relevant here too
|
||||||
|
— the Mars's M.2 E-Key slot (§VI) is PCIe-attached, same shape of gap as the Pi 5's RP1.
|
||||||
|
`riscv64/timer.c` is **already** fully DTB-driven (both `timebase-frequency` and this
|
||||||
|
session's own hypervisor-detection check) — no further work needed there; it was built DTB-
|
||||||
|
first from the start, unlike aarch64's timer which needed a new ACPI-based check today.
|
||||||
|
|
||||||
|
**One real, already-flagged risk found while tracing this**: `arch/riscv64/apic.c`'s own doc
|
||||||
|
comment says the PLIC base address is "a constant, not discovered from `boot_info->dtb`" —
|
||||||
|
and `arch/riscv64/plic.c`'s own doc comment (predating this document) already warned
|
||||||
|
`PLIC_BASE`/`PLIC_CONTEXT_S` are "QEMU-virt-specific... not assumed stable across" other
|
||||||
|
configurations. That warning becomes concrete now: the JH7110's real PLIC address on the Mars
|
||||||
|
is not confirmed to match QEMU-virt's, and the interrupt controller will not work correctly if
|
||||||
|
it doesn't. This is a real punch-list item, not a hypothetical.
|
||||||
|
|
||||||
|
1. **Entry stub**: new native riscv64 entry point at `0x40000000` (§V.1), receiving `a0`=hart
|
||||||
|
ID, `a1`=DTB pointer directly (now-confirmed SBI convention) — no UEFI, no PE loader.
|
||||||
|
2. **DTB → `BootInfo` constructor**: same shape as aarch64's (§IV.3 item 2) — `dtb`=real
|
||||||
|
pointer, `acpi_table`=`NULL`, memory map from DTB `/memory`+`/reserved-memory`, `args` from
|
||||||
|
`/chosen`/`bootargs`.
|
||||||
|
3. **PLIC base address: make it DTB-discovered**, not the current QEMU-virt-specific
|
||||||
|
constant — the one concrete, already-flagged risk above. `fdt.c`'s node-scoped lookup
|
||||||
|
extension (§IV.3 item 4, **DONE 2026-09-04**, shared with the Pi 5's UART/mailbox
|
||||||
|
addresses) is the primitive this calls
|
||||||
|
(`fdt_find_node_by_compatible(fdt, "sifive,plic-1.0.0")` → `fdt_find_prop_in_node(..., "reg", ...)`,
|
||||||
|
plausible compatible string, not yet confirmed against the Mars's real DTB) — the actual
|
||||||
|
PLIC-init call site update is still open, only the primitive it needs now exists.
|
||||||
|
4. **Framebuffer for HDMI output**: JH7110's display path is genuinely unresearched this
|
||||||
|
pass — unlike the Pi 5's mailbox interface (well-documented, reused across many Pi bare-
|
||||||
|
metal projects), no equivalent research done yet for JH7110's own display controller.
|
||||||
|
Flagged here rather than assumed simple.
|
||||||
|
5. **`pci_init()` DTB path**: shares the same new code §IV.3 item 5 scopes for the Pi 5's RP1
|
||||||
|
— one implementation, two consumers (M.2 here, RP1 there), assuming the underlying DTB PCI
|
||||||
|
binding shape is similar enough (ECAM-based, most likely, but not yet confirmed for JH7110
|
||||||
|
specifically).
|
||||||
|
6. **Boot image packaging**: `vf2-imager`/`mkimage`-based FIT image (§V.1) — confirm this
|
||||||
|
tooling's actual invocation once building the first real image, not just cited from
|
||||||
|
VisionFive 2 research.
|
||||||
|
|
||||||
|
**Hardware-dependent, after 2026-09-17:**
|
||||||
|
7. Build and flash the boot image to QSPI flash (or attempt UART XMODEM recovery boot if QSPI
|
||||||
|
flashing isn't set up yet — both are real supported paths per §V.1).
|
||||||
|
8. Connect HDMI + keyboard.
|
||||||
|
9. Boot; confirm `ok>`/`zuse)ok>` reached.
|
||||||
|
10. Mint a Zuse identity on real media, confirm re-attach — the `v2.5.0` gate's own
|
||||||
|
requirement.
|
||||||
|
11. Update this section with results.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## VI. Hardware identification reference
|
||||||
|
|
||||||
|
Per-board SoC/CPU facts, consolidated here so later sections don't have to re-derive them.
|
||||||
|
Researched 2026-09-04 (web search, sources cited); anything not directly confirmed against
|
||||||
|
the actual unit in hand is flagged as such rather than assumed.
|
||||||
|
|
||||||
|
### amd64 — Beelink SER5 (reference/development machine)
|
||||||
|
|
||||||
|
- **CPU: AMD Ryzen 7 family.** Beelink has shipped the "SER5" name with several different
|
||||||
|
Ryzen 7 SKUs over its product life (5700U, 5800H, 7735HS all confirmed to exist under this
|
||||||
|
branding) — **exact SKU on this unit not yet confirmed**; check `dmesg`/BIOS/the physical
|
||||||
|
unit when convenient (`cat /proc/cpuinfo` or the BIOS splash screen under Linux/before
|
||||||
|
LithosAnanke boots, since LithosAnanke itself has no CPU-identification word yet). Not
|
||||||
|
load-bearing for this document's own genericity requirement (§III) — the boot path must not
|
||||||
|
depend on which SKU this is, by design — but worth pinning down for this reference's own
|
||||||
|
accuracy.
|
||||||
|
- **Architecture generation**: Zen2 (5700U/5800H) or Zen3 (7735HS) depending on the SKU above
|
||||||
|
— matters for any future CPU-feature-detection work (e.g. RDRAND is present on all of
|
||||||
|
these; that part's already confirmed live via `rng: backend = rdrand`, §III).
|
||||||
|
- Sources: [Gentoo wiki — SER5 5560U](https://wiki.gentoo.org/wiki/Beelink_SER5_AMD_Ryzen_5_5560U_Mini_PC),
|
||||||
|
[Starry Hope — SER5](https://www.starryhope.com/minipcs/models/beelink-ser5-mini-pc/),
|
||||||
|
[Starry Hope — SER5 Pro](https://www.starryhope.com/minipcs/models/beelink-ser5-pro-mini-pc/),
|
||||||
|
[Minixpc — SER5 Max](https://minixpc.com/blogs/news/beelink-ser5-max-review-powered-by-amd-ryzen7-5800h-processor).
|
||||||
|
|
||||||
|
### aarch64 — Raspberry Pi 5 (sole target)
|
||||||
|
|
||||||
|
- **SoC: Broadcom BCM2712.**
|
||||||
|
- **CPU**: quad-core 64-bit Arm Cortex-A76, 2.4 GHz, 512 KB per-core L2 cache, 2 MB shared L3.
|
||||||
|
- **GPU**: VideoCore VII, 12-core, 800 MHz, OpenGL ES 3.1 + Vulkan 1.2 (not relevant to
|
||||||
|
LithosAnanke's own framebuffer work — that goes through the mailbox property interface,
|
||||||
|
§IV.1 — but recorded here for completeness).
|
||||||
|
- **RAM**: LPDDR4X-4267, board variants at 1/2/4/8/16 GB, 32-bit memory interface, ~17 GB/s
|
||||||
|
bandwidth.
|
||||||
|
- **I/O**: RP1 companion chip (PCIe 2.0 x4-attached) handles GPIO, USB 2.0/3.0, Gigabit
|
||||||
|
Ethernet, CSI/DSI, analog video — confirmed separately (§IV.2) to have no RNG peripheral in
|
||||||
|
its own published peripheral list.
|
||||||
|
- **Cortex-A76 and `FEAT_RNG` (ARMv8.5 `RNDR`/`RNDRRS`)**: not confirmed present — A76 is not
|
||||||
|
among the cores that typically implement this feature (more common on newer cores like
|
||||||
|
Cortex-X2/A710); if this matters for any future entropy-source decision, verify via `ID_AA64ISAR0_EL1`
|
||||||
|
directly on the real board rather than assuming either way.
|
||||||
|
- Sources: [CNX Software — Pi 5 launch](https://www.cnx-software.com/2023/09/28/raspberry-pi-5-sbc-broadcom-bcm2712-quad-core-cortex-a76-soc/),
|
||||||
|
[Raspberry Pi — Processors doc](https://www.raspberrypi.com/documentation/computers/processors.html),
|
||||||
|
[sbcwiki — BCM2712](https://sbcwiki.com/docs/soc-manufacturers/broadcom/bcm2712/boards/rasperrypi-5/).
|
||||||
|
|
||||||
|
### riscv64 — Milk-V Mars (sole target)
|
||||||
|
|
||||||
|
- **SoC: StarFive JH7110**, 28 nm.
|
||||||
|
- **CPU**: 4× SiFive U74-MC application cores (RV64GC) + 1× SiFive S7 monitor core, up to
|
||||||
|
1.5 GHz.
|
||||||
|
- **RAM**: up to 8 GB LPDDR4; storage via eMMC slot + microSD slot.
|
||||||
|
- **I/O**: 3× USB 3.0, 1× USB 2.0, HDMI 2.0 (4K), Gigabit Ethernet with PoE support, M.2 E-Key
|
||||||
|
(WiFi/BT), 4-lane + 2-lane MIPI CSI, 40-pin GPIO header.
|
||||||
|
- **Physical**: designed to Raspberry Pi 3B dimensions — cases/heatsinks/fans for that form
|
||||||
|
factor are compatible.
|
||||||
|
- **Multimedia**: H.264/H.265 4K@60fps decode, H.265 1080p@30fps encode (not relevant to
|
||||||
|
LithosAnanke's own bring-up, recorded for completeness).
|
||||||
|
- Same JH7110 SoC as the StarFive VisionFive 2 — any VisionFive 2 bring-up material found
|
||||||
|
while researching §V's own boot-chain question is likely directly applicable here too, worth
|
||||||
|
checking first before assuming Mars-specific research is needed from scratch.
|
||||||
|
- Sources: [milkv.io — Mars overview](https://milkv.io/docs/mars/overview),
|
||||||
|
[milkv.io — Mars product page](https://milkv.io/mars),
|
||||||
|
[TinyComputers.io — Mars review](https://tinycomputers.io/posts/milk-v-mars-review.html).
|
||||||
|
|
||||||
|
### Noted for later, not yet in scope — BeagleBone Black
|
||||||
|
|
||||||
|
Added to this reference per direct instruction 2026-09-04, **recorded only — no work scoped
|
||||||
|
around it yet.** Genuinely different from the three targets above: the BeagleBone Black's
|
||||||
|
SoC is a **32-bit ARM** part, not aarch64 — a fourth architecture this kernel has no support
|
||||||
|
for at all today (amd64/aarch64/riscv64 only), not another board under an existing one.
|
||||||
|
|
||||||
|
- **SoC: TI Sitara AM335x.**
|
||||||
|
- **CPU**: single-core ARM Cortex-A8, 1 GHz, armv7-a (32-bit) — up to ~2000 MIPS.
|
||||||
|
- **RAM**: 512 MB DDR3L. Storage: 4 GB eMMC (default boot source) + microSD (secondary/
|
||||||
|
overridable to primary).
|
||||||
|
- **Other on-die units**: PowerVR SGX530 3D GPU; 2× PRU (Programmable Realtime Unit) 32-bit
|
||||||
|
200 MHz microcontrollers — real-time I/O coprocessors, no equivalent on any of the three
|
||||||
|
boards above; crypto accelerators.
|
||||||
|
- **Boot modes**: eMMC, microSD, serial, USB.
|
||||||
|
- Sources: [element14 — BBB product page](https://www.element14.com/community/docs/DOC-84108/l/beaglebone-black-development-board-with-1ghz-am335x-arm%C3%A3%C3%A2-cortex-a8-processor),
|
||||||
|
[TI.com — BEAGL-BONE-BLACK](https://www.ti.com/tool/BEAGL-BONE-BLACK).
|
||||||
|
|
||||||
|
### Noted for later, not yet in scope — Zynq-7000 (Puzhi PZ7010/PZ7020 "StarLite")
|
||||||
|
|
||||||
|
Added per direct instruction 2026-09-04, **recorded only — no work scoped around it yet.**
|
||||||
|
Unlike BeagleBone Black above, this one isn't a random addition: `ROADMAP.md` already names
|
||||||
|
**Zynq FPGA as the next big milestone beyond v2.5.0** — "the step where the battle-tested
|
||||||
|
amd64/aarch64/riscv64 story rides on configurable silicon," and the three-product split
|
||||||
|
decided alongside it names "hardware steady-state machinery with sealed executions,
|
||||||
|
HOL-proven" as the FPGA-native product this board would ultimately serve. This entry just
|
||||||
|
puts a concrete, purchasable board under that already-named milestone.
|
||||||
|
|
||||||
|
- **Board**: Puzhi PZ7010-StarLite (XC7Z010) or PZ7020-StarLite (XC7Z020) — same board design,
|
||||||
|
two SoC variants. 90×60mm, black PCB, immersion gold finish.
|
||||||
|
- **SoC: Xilinx/AMD Zynq-7000**, combining a **Processing System (PS)** — dual-core ARM
|
||||||
|
Cortex-A9, up to 667 MHz (`-1` speed grade) or 800 MHz (`-2`, XC7Z020 only) — with
|
||||||
|
**Programmable Logic (PL)**, 28 nm Artix-7/Kintex-7-based FPGA fabric. Genuinely a fifth
|
||||||
|
architecture class in this reference: ARMv7-A again (like BeagleBone Black), but a
|
||||||
|
different core (Cortex-A9 vs. A8) *and* an FPGA fabric with no equivalent on any board
|
||||||
|
above — this is the "configurable silicon" milestone `ROADMAP.md` already flagged as
|
||||||
|
reshaping the hardware story (soft/hard CPU cores, PL fabric, non-standard memory map,
|
||||||
|
custom peripherals), not a small per-board addition even in concept.
|
||||||
|
- **PS details** (identical between both variants): 256 KB on-chip memory, DDR3 controller,
|
||||||
|
32 KB I-cache + 32 KB D-cache per core, 512 KB shared L2.
|
||||||
|
- **PL resources (the actual XC7Z010 vs. XC7Z020 difference)**: XC7Z010 — 4,400 logic slices,
|
||||||
|
17,600 6-input LUTs, 35,200 flip-flops, 270 KB block RAM, 80 DSP slices. XC7Z020 — 13,300
|
||||||
|
logic slices, 53,200 LUTs, 106,400 flip-flops, 630 KB block RAM, 220 DSP slices.
|
||||||
|
- **RAM/storage**: 512 MB/1 GB DDR3, QSPI flash, EEPROM, SD boot.
|
||||||
|
- **I/O**: JTAG, UART, HDMI out, Gigabit Ethernet, USB 2.0 host, 40-pin expansion; MIPI CSI on
|
||||||
|
the 7020 variant only.
|
||||||
|
- Sources: [Puzhi — PZ7010-StarLite](https://www.en.puzhi.com/Product/AMD-FPGA-Development-Board/Zynq-7000-SoC/PZ7010-StarLite),
|
||||||
|
[Puzhi — PZ7020-StarLite](https://www.en.puzhi.com/Product/AMD-FPGA-Development-Board/Zynq-7000-SoC/PZ7020-StarLite),
|
||||||
|
[Xilinx/AMD — Zynq-7000 SoC Data Sheet (DS190)](https://www.mouser.com/datasheet/2/903/ds190-Zynq-7000-Overview-1595492.pdf),
|
||||||
|
[PCBSync — XC7Z010 vs XC7Z020 comparison](https://pcbsync.com/xilinx-xc7z010/).
|
||||||
|
|||||||
+31
@@ -73,3 +73,34 @@ level up the stack.
|
|||||||
build the one wheel concretely first, generalize only if/when a second one is actually
|
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
|
needed (this project's own standing convention — three similar lines beat a premature
|
||||||
abstraction).
|
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.
|
||||||
|
|||||||
@@ -479,6 +479,7 @@ endif
|
|||||||
|
|
||||||
KERNEL_SRCS_BASE := \
|
KERNEL_SRCS_BASE := \
|
||||||
$(KERNEL_SRC)/kernel_main.c \
|
$(KERNEL_SRC)/kernel_main.c \
|
||||||
|
$(KERNEL_SRC)/boot/cmdline.c \
|
||||||
$(wildcard $(KERNEL_SRC)/hal/*.c) \
|
$(wildcard $(KERNEL_SRC)/hal/*.c) \
|
||||||
$(wildcard $(KERNEL_SRC)/memory/*.c) \
|
$(wildcard $(KERNEL_SRC)/memory/*.c) \
|
||||||
$(wildcard $(KERNEL_SRC)/math/*.c) \
|
$(wildcard $(KERNEL_SRC)/math/*.c) \
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Capsule Block Manifest — Auto-generated
|
# 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. -->
|
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
||||||
<!-- Hand-written justifications and immutability notes live -->
|
<!-- Hand-written justifications and immutability notes live -->
|
||||||
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
||||||
|
|||||||
Binary file not shown.
@@ -8,13 +8,20 @@
|
|||||||
* fdt.h - Minimal flattened-devicetree reader
|
* fdt.h - Minimal flattened-devicetree reader
|
||||||
*
|
*
|
||||||
* Just enough of the Devicetree Specification v0.4 §5 to pull values out of
|
* 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,
|
* the blob the UEFI firmware publishes under EFI_DTB_TABLE_GUID, or that a
|
||||||
* no allocation, no tree construction — it walks the structure block each
|
* native (non-UEFI) boot entry passes directly. Read-only, no allocation, no
|
||||||
* call, which is fine for the handful of boot-time lookups the kernel needs.
|
* 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
|
* Deliberately not a general devicetree library. Added for punch-list item
|
||||||
* 0.3 (riscv64 timebase-frequency); item 0.6 will need node-scoped `reg`
|
* 0.3 (riscv64 timebase-frequency); extended (FABRIC-3.md §IV.3/§V.3,
|
||||||
* lookups for the aarch64 GIC and may extend this.
|
* 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
|
#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);
|
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 */
|
#endif /* STARKERNEL_FDT_H */
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
StarForth — Steady-State Virtual Machine Runtime
|
||||||
|
Copyright (c) 2023–2025 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 */
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
StarForth — Steady-State Virtual Machine Runtime
|
||||||
|
Copyright (c) 2023–2025 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 */
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
StarForth — Steady-State Virtual Machine Runtime
|
||||||
|
Copyright (c) 2023–2025 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 */
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
StarForth — Steady-State Virtual Machine Runtime
|
||||||
|
Copyright (c) 2023–2025 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
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
StarForth — Steady-State Virtual Machine Runtime
|
||||||
|
Copyright (c) 2023–2025 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");
|
||||||
|
}
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
/*
|
||||||
|
StarForth — Steady-State Virtual Machine Runtime
|
||||||
|
Copyright (c) 2023–2025 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) 2023–2025 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", ®_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();
|
||||||
|
}
|
||||||
@@ -156,3 +156,220 @@ int fdt_prop_u32(const void* fdt, const char* name, uint32_t* out)
|
|||||||
*out = be32(val);
|
*out = be32(val);
|
||||||
return 1;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user