rpi5_native_boot.c: carve /reserved-memory out of the Pi 5 memory map
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s

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:
Robert Allan James
2026-09-04 22:24:57 -04:00
co-authored by Claude Sonnet 5
parent 67e3fb7459
commit 7187d68082
12 changed files with 28050 additions and 50 deletions
+22 -6
View File
@@ -341,12 +341,28 @@ blocker for free.
§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
**`/reserved-memory` carving — DONE 2026-09-04.** Originally deferred here as needing
interval-splitting logic written blind against hardware not yet in hand — revisited once an
actual reservation was confirmed to exist rather than assumed either way: fetched
`bcm2712-ds.dtsi` directly and found a real `reserved-memory` node with one static child
(`atf@0`, `reg`-addressed, ARM Trusted Firmware's own region) and one dynamic child
(`linux,cma`, `size`/`alloc-ranges` only, no fixed address — skipped, nothing fixed to carve
and no allocator this early to service it against anyway). `collect_reserved_ranges()`
walks `reserved-memory`'s children via two new `fdt.c` primitives —
`fdt_find_node_by_name()` (needed since `/reserved-memory` has neither `compatible` nor
`device_type` per DT spec §3.5.4) and `fdt_next_child_node()` (one exported symbol, not the
two-primitive general sibling-walker originally sketched — collapsed after review, since
this codebase's only real use is "iterate one node's direct children," not general tree
navigation) — reading 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, not `qsort` — freestanding, no libc). `no-map`/`reusable` flags are not
distinguished; every static reservation is excluded from `EfiConventionalMemory` regardless.
`RPI5_MAX_MEMMAP_ENTRIES` is the exact worst-case count
(`RAM_REGIONS * (2*RESERVED_RANGES + 1)`), recomputed rather than estimated — the
`rpi5_mailbox.c` buffer-size bug is the standing lesson for this pattern. 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.
+1 -1
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-09-04T18:40:17Z -->
<!-- Generated by mkcapsule --manifest 2026-09-05T02:22:29Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. -->
BIN
View File
Binary file not shown.
+41
View File
@@ -102,6 +102,47 @@ const void* fdt_find_node_by_compatible(const void* fdt, const char* compatible)
*/
const void* fdt_find_node_by_device_type(const void* fdt, const char* type);
/**
* @brief Find the first node whose own name matches @p name.
*
* Node names follow the DT spec §2.2.1 `name[@unit-address]` convention —
* matches if @p name equals the node's name up to (not including) an `@`
* suffix, or the whole name if there is none. For a singleton node with
* no unit address (`/reserved-memory` being the concrete case this was
* added for), this is an exact match. 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 name Node name to match, NUL-terminated, no `@` suffix.
* @return An opaque handle to the matched node, for use with
* @c fdt_find_prop_in_node() / @c fdt_next_child_node() only —
* or NULL if no node matches.
*/
const void* fdt_find_node_by_name(const void* fdt, const char* name);
/**
* @brief Iterate the direct children of one node.
*
* Pass @p prev_child as NULL to get the first child; pass a previous
* result back in to get the next one. Stops (returns NULL) once there are
* no more children. Skips over each child's own descendants correctly
* (so a child with grandchildren doesn't confuse the scan), but does not
* itself descend into them — only direct children of @p parent are ever
* returned.
*
* @param fdt Blob, already checked with @c fdt_valid().
* @param parent Handle from one of the `fdt_find_node_by_*()`
* functions.
* @param prev_child NULL for the first child, or a handle previously
* returned by this function for @p parent to
* continue from.
* @return Handle to the next direct child, for use with
* @c fdt_find_prop_in_node() / @c fdt_next_child_node() only —
* or NULL once @p parent's children are exhausted.
*/
const void* fdt_next_child_node(const void* fdt, const void* parent,
const void* prev_child);
/**
* @brief Find a property by name, scoped to one node.
*
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
+181
View File
@@ -325,6 +325,187 @@ const void* fdt_find_node_by_device_type(const void* fdt, const char* type)
return (void*)0;
}
/**
* @brief Test whether a node-name string (blob text, possibly followed by
* an `@unit-address`) matches @p want up to that boundary.
*/
static int node_name_eq(const char* node_name, const char* want)
{
while (*want && *node_name && *node_name == *want)
{
node_name++;
want++;
}
if (*want) return 0;
return *node_name == '\0' || *node_name == '@';
}
const void* fdt_find_node_by_name(const void* fdt, const char* name)
{
const fdt_header_t* h = (const fdt_header_t*)fdt;
const unsigned char *base, *p, *end;
uint32_t size_struct;
if (!fdt_valid(fdt) || !name) 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;
/* Matching by the node's own inline name needs no strings-block
* lookup, unlike the compatible/device_type finders above. */
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;
if (node_name_eq((const char*)p, name))
{
return (const void*)(((uintptr_t)(q + 1) + 3u) & ~(uintptr_t)3u);
}
p = (const unsigned char*)(((uintptr_t)(q + 1) + 3u) & ~(uintptr_t)3u);
}
else if (token == FDT_PROP)
{
uint32_t len;
if (p + 8 > end) break;
len = be32(p);
p += 8;
if (len > (uint32_t)(end - p)) break;
p = (const unsigned char*)(((uintptr_t)(p + len) + 3u) & ~(uintptr_t)3u);
}
else if (token == FDT_END_NODE || token == FDT_NOP)
{
/* no payload */
}
else
{
break;
}
}
return (void*)0;
}
/**
* @brief Advance @p p past one complete node body (properties, then every
* descendant, recursively via a depth counter) starting just inside it
* (i.e. @p p is a node handle, right after its own FDT_BEGIN_NODE name),
* stopping just after the matching FDT_END_NODE.
*
* Internal to @c fdt_next_child_node() — a node handle's own children may
* have further descendants even though this reader's public node-lookup
* functions never return anything but direct-child handles, so finding
* the *next sibling* after a child still requires skipping past whatever
* that child itself contains.
*/
static const unsigned char* skip_node_body(const unsigned char* p, const unsigned char* end)
{
int depth = 1;
while (p + 4 <= end && depth > 0)
{
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) return end;
p = (const unsigned char*)(((uintptr_t)(q + 1) + 3u) & ~(uintptr_t)3u);
depth++;
}
else if (token == FDT_PROP)
{
uint32_t len;
if (p + 8 > end) return end;
len = be32(p);
p += 8;
if (len > (uint32_t)(end - p)) return end;
p = (const unsigned char*)(((uintptr_t)(p + len) + 3u) & ~(uintptr_t)3u);
}
else if (token == FDT_END_NODE)
{
depth--;
}
else if (token == FDT_NOP)
{
/* no payload */
}
else
{
return end;
}
}
return p;
}
const void* fdt_next_child_node(const void* fdt, const void* parent,
const void* prev_child)
{
const fdt_header_t* h = (const fdt_header_t*)fdt;
const unsigned char *end, *p;
if (!fdt_valid(fdt) || !parent) return (void*)0;
end = (const unsigned char*)fdt + be32(&h->off_dt_struct) + be32(&h->size_dt_struct);
p = prev_child ? skip_node_body((const unsigned char*)prev_child, end)
: (const unsigned char*)parent;
/* If prev_child is NULL, p starts at parent's own leading property
* list -- skip past those the same way fdt_find_prop_in_node() does.
* If prev_child is non-NULL, skip_node_body() already consumed that
* child's own properties and descendants, so this loop sees only
* FDT_BEGIN_NODE (the next sibling) or FDT_END_NODE (parent closing)
* immediately. */
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) return (void*)0;
return (const void*)(((uintptr_t)(q + 1) + 3u) & ~(uintptr_t)3u);
}
else if (token == FDT_PROP)
{
uint32_t len;
if (p + 8 > end) break;
len = be32(p);
p += 8;
if (len > (uint32_t)(end - p)) break;
p = (const unsigned char*)(((uintptr_t)(p + len) + 3u) & ~(uintptr_t)3u);
}
else if (token == FDT_NOP)
{
/* no payload */
}
else
{
/* FDT_END_NODE (parent closing), FDT_END, or unknown */
return (void*)0;
}
}
return (void*)0;
}
const void* fdt_find_prop_in_node(const void* fdt, const void* node,
const char* name, uint32_t* len_out)
{