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
-6
@@ -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,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. -->
|
||||
|
||||
Binary file not shown.
@@ -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
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user