rpi5_native_boot.c: Pi 5 DTB->BootInfo constructor (FABRIC-3.md §IV.3 item 2)
rpi5_native_boot() populates the existing BootInfo struct from the devicetree instead of UEFI protocols, then calls the existing, unmodified kernel_main() -- the crux of why most of M1-M9 stays shared between the UEFI and native boot paths. native_rpi5_entry.S now tail-calls into it instead of halting. memory_map is built from /memory's own reg, honoring the *root* node's #address-cells/#size-cells (confirmed against bcm2712.dtsi's actual root node -- <2>/<2> -- not assumed; a wrong cell width here would compile and boot clean in QEMU while silently corrupting the real memory map on real silicon). Required a new fdt_find_node_by_device_type() since /memory is identified by device_type = "memory" per DT spec, not compatible. args comes from /chosen's bootargs fed into the existing cmdline_parse_ascii() (confirmed pure C99 with no UEFI coupling before reusing it). framebuffer comes from the already-built rpi5_mailbox_get_framebuffer() at a fixed 1920x1080x32 default -- no EDID query exists in this codebase, flagged rather than guessed past. Deliberately scoped out, not silently skipped: /reserved-memory is not parsed. Carving reserved sub-ranges out of /memory's span needs interval-splitting logic that would be written blind against hardware not yet in hand -- exactly the kind of code that hides a bug until real silicon. pmm.c's Pass 3 only ever clears pages this file lists as EfiConventionalMemory, so the gap is "less usable RAM than optimal," never "reserved RAM wrongly marked free." Left as its own future item. Also fixes a real link failure this work surfaced: boot/cmdline.c was only in LOADER_SRCS_BASE (the .efi target), not KERNEL_SRCS_BASE (the separate .elf target arch/aarch64/*.c also wildcards into) -- added it there too. Compile-only-verified; nothing in the existing UEFI/QEMU path calls rpi5_native_boot(), so this cannot be exercised until real hardware. Verified 3-arch boot to ok>/zuse)ok>. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
281de9547c
commit
67e3fb7459
@@ -16,14 +16,15 @@
|
||||
* UNSPECIFIED -- must mask before use.
|
||||
* x1-x3 = reserved / zero, unused here.
|
||||
*
|
||||
* This stub's own job, and nothing more yet: mask and capture that DTB
|
||||
* pointer into `g_rpi5_dtb_ptr` (declared in
|
||||
* `include/starkernel/rpi5_native_entry.h`) where the still-open
|
||||
* DTB->BootInfo constructor (§IV.3 item 2) can find it, then establish a
|
||||
* dedicated stack -- this path has no EDK2 boot stack to inherit, there is
|
||||
* no EDK2 at all here. It intentionally halts afterward rather than
|
||||
* tail-calling into a function that does not exist yet; wiring that call
|
||||
* is item 2's own job, not this one's.
|
||||
* 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
|
||||
@@ -68,9 +69,13 @@ rpi5_native_start:
|
||||
add x0, x0, :lo12:g_rpi5_native_stack_top
|
||||
mov sp, x0
|
||||
|
||||
/* §IV.3 item 2 (DTB->BootInfo constructor) does not exist yet --
|
||||
* halt rather than branch to a function that isn't real. Replace
|
||||
* this loop with a tail call once that item lands. */
|
||||
/* 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,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();
|
||||
}
|
||||
Reference in New Issue
Block a user