rpi5_native_boot.c: carve /reserved-memory out of the Pi 5 memory map
Previously deferred (rpi5_native_boot.c's own header comment flagged this as needing interval-splitting logic written blind against hardware not yet in hand) -- revisited by fetching bcm2712-ds.dtsi directly rather than assuming reserved-memory was empty or absent. It has one static child (atf@0, ARM Trusted Firmware's own region) and one dynamic child (linux,cma, size/alloc-ranges only, no fixed reg) -- the dynamic one is skipped, nothing fixed to carve and no allocator this early to service it against anyway. Adds two fdt.c primitives: fdt_find_node_by_name() (reserved-memory has neither compatible nor device_type per DT spec) and fdt_next_child_node() -- one exported symbol, not the two-primitive general sibling-walker originally sketched, collapsed after review since the only real use here is "iterate one node's direct children." collect_reserved_ranges() reads each child's own #address-cells/ #size-cells with a fallback to root's only if absent -- confirmed necessary, not just defensive: reserved-memory's own declared <2>/<1> genuinely differs from root's <2>/<2>. emit_region_with_carveouts() clips a sorted reserved-range list against each RAM region, emitting alternating EfiConventionalMemory gaps and EfiReservedMemoryType carve-outs (insertion sort, no libc qsort in freestanding). RPI5_MAX_MEMMAP_ENTRIES is the exact worst-case count, recomputed rather than estimated -- the rpi5_mailbox.c buffer-size bug is the standing lesson for this pattern. 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
67e3fb7459
commit
7187d68082
@@ -22,27 +22,35 @@
|
||||
* this is the crux of why most of M1-M9 stays shared between the UEFI
|
||||
* and native boot paths.
|
||||
*
|
||||
* `/memory`'s own `reg` (root `#address-cells`/`#size-cells`, confirmed
|
||||
* against `bcm2712.dtsi`'s actual root node -- `<2>`/`<2>` -- not assumed)
|
||||
* is turned into `EfiConventionalMemory` descriptors, with `/reserved-memory`
|
||||
* children that carry a static `reg` (`brcm,bcm2712-ds.dtsi`'s real
|
||||
* `reserved-memory` node has one, `atf@0`, confirmed by fetching that file
|
||||
* rather than assumed) carved out as `EfiReservedMemoryType` descriptors
|
||||
* instead of being silently left inside a conventional range. Children
|
||||
* with no `reg` (`linux,cma`, which uses `size`/`alloc-ranges` -- a
|
||||
* dynamic reservation with no fixed address, confirmed against the same
|
||||
* file) have nothing fixed to carve and are skipped -- there is no
|
||||
* allocator this early to service a dynamic reservation against anyway.
|
||||
* `no-map`/`reusable` flags are not distinguished: every static
|
||||
* reservation is excluded from `EfiConventionalMemory` regardless of
|
||||
* which flag it carries, since this pass has no different treatment for
|
||||
* either. `/reserved-memory` itself may declare its own
|
||||
* `#address-cells`/`#size-cells` different from the root's (confirmed:
|
||||
* `bcm2712-ds.dtsi`'s does -- `<2>`/`<1>`, not `<2>`/`<2>`) -- read from
|
||||
* the node itself, falling back to root's only if absent, per DT spec
|
||||
* §3.5.4's own inheritance rule.
|
||||
*
|
||||
* 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.
|
||||
* - **Root and `/reserved-memory` `#address-cells`/`#size-cells` must
|
||||
* each be exactly 1 or 2** -- other widths halt rather than guess.
|
||||
*/
|
||||
|
||||
#include "starkernel/rpi5_native_boot.h"
|
||||
@@ -51,16 +59,33 @@
|
||||
#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
|
||||
|
||||
#define RPI5_MAX_RAM_REGIONS 4
|
||||
#define RPI5_MAX_RESERVED_RANGES 16
|
||||
/* Worst case per RAM region: one EfiConventionalMemory gap before, plus
|
||||
* one EfiReservedMemoryType segment for, each reserved range that
|
||||
* intersects it, plus one trailing conventional segment after the last
|
||||
* one = 2*RESERVED_RANGES + 1. Total worst case across all regions:
|
||||
* RAM_REGIONS * (2*RESERVED_RANGES + 1). Recomputed exactly, not
|
||||
* estimated -- rpi5_mailbox.c's own buffer-size bug is the lesson here.
|
||||
* Real DTBs need far fewer: bcm2712-ds.dtsi's own reserved-memory node
|
||||
* has exactly one static reservation (atf@0) against one RAM region. */
|
||||
#define RPI5_MAX_MEMMAP_ENTRIES (RPI5_MAX_RAM_REGIONS * (2 * RPI5_MAX_RESERVED_RANGES + 1))
|
||||
|
||||
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];
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint64_t addr;
|
||||
uint64_t size;
|
||||
} rpi5_range_t;
|
||||
|
||||
static uint32_t be32_at(const unsigned char *p)
|
||||
{
|
||||
return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) |
|
||||
@@ -92,31 +117,20 @@ static void rpi5_native_halt(void)
|
||||
}
|
||||
|
||||
/**
|
||||
* @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).
|
||||
* @brief Parse `/memory`'s `reg` into raw (addr, size) ranges, 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).
|
||||
* @return Number of ranges written (0 on any failure).
|
||||
*/
|
||||
static uint32_t build_memory_map(const void *dtb)
|
||||
static uint32_t collect_ram_regions(const void *dtb, uint32_t acells, uint32_t scells,
|
||||
rpi5_range_t *out, uint32_t max)
|
||||
{
|
||||
uint32_t acells, scells;
|
||||
const void *mem_node;
|
||||
const void *reg;
|
||||
uint32_t reg_len;
|
||||
uint32_t entry_bytes, count, i;
|
||||
uint32_t reg_len, 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;
|
||||
|
||||
@@ -127,26 +141,207 @@ static uint32_t build_memory_map(const void *dtb)
|
||||
if (entry_bytes == 0) return 0;
|
||||
|
||||
count = reg_len / entry_bytes;
|
||||
if (count > RPI5_MAX_MEMMAP_ENTRIES) count = RPI5_MAX_MEMMAP_ENTRIES;
|
||||
if (count > max) count = max;
|
||||
|
||||
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;
|
||||
|
||||
out[i].addr = read_cells(p, acells);
|
||||
out[i].size = read_cells(p + acells * 4u, scells);
|
||||
p += entry_bytes;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read one node-scoped `#address-cells`/`#size-cells`-style u32
|
||||
* property, falling back to @p fallback if the node doesn't declare its
|
||||
* own (DT spec §3.5.4's own inheritance rule for `/reserved-memory`).
|
||||
*/
|
||||
static uint32_t node_cells_or_fallback(const void *dtb, const void *node,
|
||||
const char *prop_name, uint32_t fallback)
|
||||
{
|
||||
uint32_t len;
|
||||
const void *v = fdt_find_prop_in_node(dtb, node, prop_name, &len);
|
||||
if (!v || len != 4) return fallback;
|
||||
return be32_at((const unsigned char *)v);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Collect every `/reserved-memory` child's static `reg` range(s).
|
||||
* Children with no `reg` (dynamic reservations -- `size`/`alloc-ranges`
|
||||
* only) are skipped: there is no allocator this early to service a
|
||||
* dynamic reservation against, and nothing fixed to carve either way.
|
||||
*
|
||||
* @return Number of ranges written (0 if `/reserved-memory` is absent or
|
||||
* empty of static reservations -- not an error, just nothing to carve).
|
||||
*/
|
||||
static uint32_t collect_reserved_ranges(const void *dtb, uint32_t root_acells,
|
||||
uint32_t root_scells,
|
||||
rpi5_range_t *out, uint32_t max)
|
||||
{
|
||||
const void *rsvmem;
|
||||
const void *child;
|
||||
uint32_t acells, scells, entry_bytes, count;
|
||||
|
||||
rsvmem = fdt_find_node_by_name(dtb, "reserved-memory");
|
||||
if (!rsvmem) return 0;
|
||||
|
||||
acells = node_cells_or_fallback(dtb, rsvmem, "#address-cells", root_acells);
|
||||
scells = node_cells_or_fallback(dtb, rsvmem, "#size-cells", root_scells);
|
||||
if ((acells != 1 && acells != 2) || (scells != 1 && scells != 2)) return 0;
|
||||
|
||||
entry_bytes = (acells + scells) * 4u;
|
||||
count = 0;
|
||||
child = (const void *)0;
|
||||
|
||||
while (count < max &&
|
||||
(child = fdt_next_child_node(dtb, rsvmem, child)) != (const void *)0)
|
||||
{
|
||||
uint32_t reg_len, n, i;
|
||||
const unsigned char *p;
|
||||
const void *reg = fdt_find_prop_in_node(dtb, child, "reg", ®_len);
|
||||
|
||||
if (!reg) continue;
|
||||
|
||||
n = reg_len / entry_bytes;
|
||||
p = (const unsigned char *)reg;
|
||||
for (i = 0; i < n && count < max; i++)
|
||||
{
|
||||
out[count].addr = read_cells(p, acells);
|
||||
out[count].size = read_cells(p + acells * 4u, scells);
|
||||
count++;
|
||||
p += entry_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/** @brief Insertion sort -- small, fixed N; no libc qsort in freestanding. */
|
||||
static void sort_ranges_by_addr(rpi5_range_t *arr, uint32_t n)
|
||||
{
|
||||
uint32_t i, j;
|
||||
for (i = 1; i < n; i++)
|
||||
{
|
||||
rpi5_range_t key = arr[i];
|
||||
j = i;
|
||||
while (j > 0 && arr[j - 1].addr > key.addr)
|
||||
{
|
||||
arr[j] = arr[j - 1];
|
||||
j--;
|
||||
}
|
||||
arr[j] = key;
|
||||
}
|
||||
}
|
||||
|
||||
static void add_memmap_entry(uint32_t *idx, uint32_t max_entries, uint32_t type,
|
||||
uint64_t addr, uint64_t size)
|
||||
{
|
||||
if (*idx >= max_entries) return;
|
||||
|
||||
g_rpi5_memmap[*idx].Type = type;
|
||||
g_rpi5_memmap[*idx].PhysicalStart = addr;
|
||||
g_rpi5_memmap[*idx].VirtualStart = addr;
|
||||
/* Floor to whole pages either way: any leftover fractional page is
|
||||
* simply covered by no descriptor at all, which is safe in both
|
||||
* directions -- PMM's bitmap starts fully reserved and only clears
|
||||
* pages an EfiConventionalMemory descriptor explicitly lists
|
||||
* (memory/pmm.c's Pass 3), so an uncovered fractional page just
|
||||
* stays reserved by default, same as if it were never mentioned. */
|
||||
g_rpi5_memmap[*idx].NumberOfPages = size / 4096u;
|
||||
g_rpi5_memmap[*idx].Attribute = 0;
|
||||
(*idx)++;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Emit one RAM region as alternating conventional/reserved
|
||||
* descriptors, clipping @p reserved (already sorted by address) to the
|
||||
* region's own bounds.
|
||||
*/
|
||||
static void emit_region_with_carveouts(uint64_t ram_addr, uint64_t ram_size,
|
||||
const rpi5_range_t *reserved, uint32_t reserved_count,
|
||||
uint32_t *idx, uint32_t max_entries)
|
||||
{
|
||||
uint64_t cursor = ram_addr;
|
||||
uint64_t ram_end = ram_addr + ram_size;
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0; i < reserved_count && *idx < max_entries; i++)
|
||||
{
|
||||
uint64_t r_start = reserved[i].addr;
|
||||
uint64_t r_end = r_start + reserved[i].size;
|
||||
uint64_t clip_start, clip_end;
|
||||
|
||||
if (r_end <= cursor) continue; /* fully before the cursor already */
|
||||
if (r_start >= ram_end) break; /* sorted -- no more overlaps possible */
|
||||
|
||||
if (r_start > cursor)
|
||||
{
|
||||
add_memmap_entry(idx, max_entries, EfiConventionalMemory,
|
||||
cursor, r_start - cursor);
|
||||
}
|
||||
|
||||
clip_start = r_start > cursor ? r_start : cursor;
|
||||
clip_end = r_end < ram_end ? r_end : ram_end;
|
||||
if (clip_end > clip_start)
|
||||
{
|
||||
add_memmap_entry(idx, max_entries, EfiReservedMemoryType,
|
||||
clip_start, clip_end - clip_start);
|
||||
cursor = clip_end;
|
||||
}
|
||||
}
|
||||
|
||||
if (cursor < ram_end)
|
||||
{
|
||||
add_memmap_entry(idx, max_entries, EfiConventionalMemory, cursor, ram_end - cursor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build `g_rpi5_memmap` from `/memory` with `/reserved-memory`'s
|
||||
* static children carved out. See this file's own top doc comment for
|
||||
* exactly what is and is not handled.
|
||||
*
|
||||
* @return Number of entries written (0 on any failure -- absence of
|
||||
* `/reserved-memory` itself is not a failure, just zero carve-outs).
|
||||
*/
|
||||
static uint32_t build_memory_map(const void *dtb)
|
||||
{
|
||||
uint32_t acells, scells;
|
||||
rpi5_range_t ram_regions[RPI5_MAX_RAM_REGIONS];
|
||||
rpi5_range_t reserved[RPI5_MAX_RESERVED_RANGES];
|
||||
uint32_t ram_count, reserved_count, idx, i;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ram_count = collect_ram_regions(dtb, acells, scells, ram_regions, RPI5_MAX_RAM_REGIONS);
|
||||
if (ram_count == 0) return 0;
|
||||
|
||||
reserved_count = collect_reserved_ranges(dtb, acells, scells,
|
||||
reserved, RPI5_MAX_RESERVED_RANGES);
|
||||
sort_ranges_by_addr(reserved, reserved_count);
|
||||
|
||||
idx = 0;
|
||||
for (i = 0; i < ram_count; i++)
|
||||
{
|
||||
emit_region_with_carveouts(ram_regions[i].addr, ram_regions[i].size,
|
||||
reserved, reserved_count,
|
||||
&idx, RPI5_MAX_MEMMAP_ENTRIES);
|
||||
}
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
void rpi5_native_boot(uint64_t dtb)
|
||||
{
|
||||
const void *dtb_ptr = (const void *)(uintptr_t)dtb;
|
||||
|
||||
Reference in New Issue
Block a user