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`
|
§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`
|
(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.
|
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 —
|
**`/reserved-memory` carving — DONE 2026-09-04.** Originally deferred here as needing
|
||||||
carving reserved sub-ranges out of `/memory`'s span needs interval-splitting logic that
|
interval-splitting logic written blind against hardware not yet in hand — revisited once an
|
||||||
would be written blind against hardware not yet in hand, exactly the kind of code that
|
actual reservation was confirmed to exist rather than assumed either way: fetched
|
||||||
hides a bug until real silicon; left as its own future item. `memory/pmm.c`'s Pass 3 only
|
`bcm2712-ds.dtsi` directly and found a real `reserved-memory` node with one static child
|
||||||
ever clears pages this file explicitly lists as `EfiConventionalMemory`, so the gap is
|
(`atf@0`, `reg`-addressed, ARM Trusted Firmware's own region) and one dynamic child
|
||||||
"less usable RAM than optimal," never "reserved RAM wrongly marked free." Verified 3-arch
|
(`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
|
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
|
the existing UEFI/QEMU path calls `rpi5_native_boot()`, so this cannot be exercised until
|
||||||
real hardware.
|
real hardware.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Capsule Block Manifest — Auto-generated
|
# 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. -->
|
<!-- 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.
@@ -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);
|
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.
|
* @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
@@ -22,27 +22,35 @@
|
|||||||
* this is the crux of why most of M1-M9 stays shared between the UEFI
|
* this is the crux of why most of M1-M9 stays shared between the UEFI
|
||||||
* and native boot paths.
|
* 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
|
* What this deliberately does NOT do yet, named honestly rather than
|
||||||
* silently skipped:
|
* 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
|
* - **The mailbox framebuffer request uses a fixed 1920x1080x32
|
||||||
* default**, not real display negotiation (no EDID query exists in
|
* default**, not real display negotiation (no EDID query exists in
|
||||||
* this codebase). Flagged, not guessed past this comment -- revisit
|
* this codebase). Flagged, not guessed past this comment -- revisit
|
||||||
* once real hardware and a real attached display are in hand.
|
* once real hardware and a real attached display are in hand.
|
||||||
* - **Root `#address-cells`/`#size-cells` must be exactly 1 or 2** --
|
* - **Root and `/reserved-memory` `#address-cells`/`#size-cells` must
|
||||||
* confirmed against `bcm2712.dtsi`'s actual root node (2/2) before
|
* each be exactly 1 or 2** -- other widths halt rather than guess.
|
||||||
* writing this, not assumed; other widths halt rather than guess.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "starkernel/rpi5_native_boot.h"
|
#include "starkernel/rpi5_native_boot.h"
|
||||||
@@ -51,16 +59,33 @@
|
|||||||
#include "starkernel/cmdline.h"
|
#include "starkernel/cmdline.h"
|
||||||
#include "starkernel/uefi.h"
|
#include "starkernel/uefi.h"
|
||||||
|
|
||||||
#define RPI5_MAX_MEMMAP_ENTRIES 8
|
|
||||||
#define RPI5_FB_DEFAULT_WIDTH 1920u
|
#define RPI5_FB_DEFAULT_WIDTH 1920u
|
||||||
#define RPI5_FB_DEFAULT_HEIGHT 1080u
|
#define RPI5_FB_DEFAULT_HEIGHT 1080u
|
||||||
#define RPI5_FB_DEFAULT_BPP 32u
|
#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);
|
extern void kernel_main(BootInfo *boot_info);
|
||||||
|
|
||||||
static BootInfo g_rpi5_boot_info = {0};
|
static BootInfo g_rpi5_boot_info = {0};
|
||||||
static EFI_MEMORY_DESCRIPTOR g_rpi5_memmap[RPI5_MAX_MEMMAP_ENTRIES];
|
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)
|
static uint32_t be32_at(const unsigned char *p)
|
||||||
{
|
{
|
||||||
return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) |
|
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
|
* @brief Parse `/memory`'s `reg` into raw (addr, size) ranges, honoring
|
||||||
* root's own `#address-cells`/`#size-cells` (NOT the `soc` node's -- see
|
* the root's own `#address-cells`/`#size-cells` (NOT the `soc` node's --
|
||||||
* this file's own doc comment; `/memory` is a direct child of root).
|
* 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 *mem_node;
|
||||||
const void *reg;
|
const void *reg;
|
||||||
uint32_t reg_len;
|
uint32_t reg_len, entry_bytes, count, i;
|
||||||
uint32_t entry_bytes, count, i;
|
|
||||||
const unsigned char *p;
|
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");
|
mem_node = fdt_find_node_by_device_type(dtb, "memory");
|
||||||
if (!mem_node) return 0;
|
if (!mem_node) return 0;
|
||||||
|
|
||||||
@@ -127,26 +141,207 @@ static uint32_t build_memory_map(const void *dtb)
|
|||||||
if (entry_bytes == 0) return 0;
|
if (entry_bytes == 0) return 0;
|
||||||
|
|
||||||
count = reg_len / entry_bytes;
|
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;
|
p = (const unsigned char *)reg;
|
||||||
for (i = 0; i < count; i++)
|
for (i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
uint64_t addr = read_cells(p, acells);
|
out[i].addr = read_cells(p, acells);
|
||||||
uint64_t size = read_cells(p + acells * 4u, scells);
|
out[i].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;
|
p += entry_bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
return count;
|
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)
|
void rpi5_native_boot(uint64_t dtb)
|
||||||
{
|
{
|
||||||
const void *dtb_ptr = (const void *)(uintptr_t)dtb;
|
const void *dtb_ptr = (const void *)(uintptr_t)dtb;
|
||||||
|
|||||||
@@ -325,6 +325,187 @@ const void* fdt_find_node_by_device_type(const void* fdt, const char* type)
|
|||||||
return (void*)0;
|
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 void* fdt_find_prop_in_node(const void* fdt, const void* node,
|
||||||
const char* name, uint32_t* len_out)
|
const char* name, uint32_t* len_out)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user