boards/BRINGUP.md R2/R3, FABRIC-3 SXXXIX. The entry stub cleans the cache over the image, builds an identity map (the image's GiB Normal, the rest Device) and enables MMU and caches at EL2 (or EL1); rpi5_mmu.c makes the devicetree's RAM Normal and the framebuffer uncached. Mailbox waits give up after one second; the buffer is cleaned around the exchange; the framebuffer address goes through dma-ranges, not a Pi 4 mask. New rpi5-rehearsal target runs the native image on QEMU virt at EL2. It found three more that would each have stopped the board: the PL011 shift (mine, c9519173; the host test asserted it), the GIC address not carried through /soc's ranges (new fdt_reg_address(), checked against the real Pi 5 DTB), and no timer IRQ at EL2 (HCR_EL2.IMO/FMO/AMO). Rehearsal: POST 1052 passed, same parity hash as UEFI. Standard acceptance on three ISAs: POST 1052, hash 0x82f4ae7cb2e2d424. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
751 lines
22 KiB
C
751 lines
22 KiB
C
/*
|
||
StarForth — Steady-State Virtual Machine Runtime
|
||
Copyright (c) 2023–2025 Robert A. James. All rights reserved.
|
||
Licensed under the StarForth License, Version 1.0.
|
||
*/
|
||
|
||
/**
|
||
* fdt.c - Minimal flattened-devicetree reader
|
||
*
|
||
* Devicetree Specification v0.4 §5. Everything in the blob is big-endian; the
|
||
* kernel targets are little-endian, so every field goes through be32().
|
||
*/
|
||
|
||
#include "starkernel/fdt.h"
|
||
|
||
/* Structure block tokens (DT spec §5.4.1) */
|
||
#define FDT_BEGIN_NODE 0x00000001u
|
||
#define FDT_END_NODE 0x00000002u
|
||
#define FDT_PROP 0x00000003u
|
||
#define FDT_NOP 0x00000004u
|
||
#define FDT_END 0x00000009u
|
||
|
||
#define FDT_MAGIC 0xd00dfeedu
|
||
|
||
/* Header layout (DT spec §5.2), all fields big-endian u32 */
|
||
typedef struct
|
||
{
|
||
uint32_t magic;
|
||
uint32_t totalsize;
|
||
uint32_t off_dt_struct;
|
||
uint32_t off_dt_strings;
|
||
uint32_t off_mem_rsvmap;
|
||
uint32_t version;
|
||
uint32_t last_comp_version;
|
||
uint32_t boot_cpuid_phys;
|
||
uint32_t size_dt_strings;
|
||
uint32_t size_dt_struct;
|
||
} fdt_header_t;
|
||
|
||
/**
|
||
* @brief Byte-swap a big-endian 32-bit field from the blob.
|
||
*
|
||
* Read bytewise rather than as a @c uint32_t load: structure-block tokens are
|
||
* only guaranteed 4-byte aligned relative to the block start, and property
|
||
* values carry no alignment guarantee at all.
|
||
*/
|
||
static uint32_t be32(const void* p)
|
||
{
|
||
const unsigned char* b = (const unsigned char*)p;
|
||
return ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) |
|
||
((uint32_t)b[2] << 8) | (uint32_t)b[3];
|
||
}
|
||
|
||
/** @brief Freestanding string compare; returns 1 when equal. */
|
||
static int str_eq(const char* a, const char* b)
|
||
{
|
||
while (*a && (*a == *b))
|
||
{
|
||
a++;
|
||
b++;
|
||
}
|
||
return *a == *b;
|
||
}
|
||
|
||
int fdt_valid(const void* fdt)
|
||
{
|
||
const fdt_header_t* h = (const fdt_header_t*)fdt;
|
||
uint32_t total, off_struct, size_struct, off_strings, size_strings;
|
||
|
||
if (!fdt) return 0;
|
||
if (be32(&h->magic) != FDT_MAGIC) return 0;
|
||
|
||
total = be32(&h->totalsize);
|
||
off_struct = be32(&h->off_dt_struct);
|
||
size_struct = be32(&h->size_dt_struct);
|
||
off_strings = be32(&h->off_dt_strings);
|
||
size_strings = be32(&h->size_dt_strings);
|
||
|
||
/* Both blocks must lie inside the blob. Written as subtraction against
|
||
* total so a wrapped sum cannot smuggle an out-of-range block past the
|
||
* check. */
|
||
if (total < sizeof(fdt_header_t)) return 0;
|
||
if (off_struct > total || size_struct > total - off_struct) return 0;
|
||
if (off_strings > total || size_strings > total - off_strings) return 0;
|
||
|
||
return 1;
|
||
}
|
||
|
||
const void* fdt_find_prop(const void* fdt, const char* name, uint32_t* len_out)
|
||
{
|
||
const fdt_header_t* h = (const fdt_header_t*)fdt;
|
||
const unsigned char *base, *p, *end, *strings;
|
||
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;
|
||
strings = base + be32(&h->off_dt_strings);
|
||
|
||
while (p + 4 <= end)
|
||
{
|
||
uint32_t token = be32(p);
|
||
p += 4;
|
||
|
||
if (token == FDT_BEGIN_NODE)
|
||
{
|
||
/* NUL-terminated node name, padded to a 4-byte boundary */
|
||
const unsigned char* q = p;
|
||
while (q < end && *q) q++;
|
||
if (q >= end) break;
|
||
p = (const unsigned char*)(((uintptr_t)(q + 1) + 3u) & ~(uintptr_t)3u);
|
||
}
|
||
else if (token == FDT_PROP)
|
||
{
|
||
uint32_t len, nameoff;
|
||
const unsigned char* val;
|
||
|
||
if (p + 8 > end) break;
|
||
len = be32(p);
|
||
nameoff = be32(p + 4);
|
||
p += 8;
|
||
val = p;
|
||
if (len > (uint32_t)(end - p)) break;
|
||
|
||
if (str_eq((const char*)(strings + nameoff), name))
|
||
{
|
||
if (len_out) *len_out = len;
|
||
return (const void*)val;
|
||
}
|
||
|
||
p = (const unsigned char*)(((uintptr_t)(p + len) + 3u) & ~(uintptr_t)3u);
|
||
}
|
||
else if (token == FDT_END_NODE || token == FDT_NOP)
|
||
{
|
||
/* no payload */
|
||
}
|
||
else
|
||
{
|
||
/* FDT_END, or a token this reader does not know: stop. */
|
||
break;
|
||
}
|
||
}
|
||
|
||
return (void*)0;
|
||
}
|
||
|
||
int fdt_prop_u32(const void* fdt, const char* name, uint32_t* out)
|
||
{
|
||
uint32_t len = 0;
|
||
const void* val = fdt_find_prop(fdt, name, &len);
|
||
|
||
if (!val || len != 4 || !out) return 0;
|
||
*out = be32(val);
|
||
return 1;
|
||
}
|
||
|
||
/**
|
||
* @brief Test whether NUL-separated string list @p list (length @p len)
|
||
* contains @p want as one of its entries.
|
||
*
|
||
* "compatible" properties are formatted as one or more NUL-terminated
|
||
* strings concatenated (DT spec §2.3.1) -- each entry's own embedded NUL
|
||
* is real property data, not synthesized, so str_eq() (which walks until
|
||
* either side's NUL) terminates correctly at each entry's real boundary.
|
||
*/
|
||
static int compat_list_contains(const char* list, uint32_t len, const char* want)
|
||
{
|
||
uint32_t i = 0;
|
||
|
||
while (i < len)
|
||
{
|
||
const char* s = list + i;
|
||
uint32_t slen = 0;
|
||
while (i + slen < len && s[slen]) slen++;
|
||
if (str_eq(s, want)) return 1;
|
||
i += slen + 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
const void* fdt_find_node_by_compatible(const void* fdt, const char* compatible)
|
||
{
|
||
const fdt_header_t* h = (const fdt_header_t*)fdt;
|
||
const unsigned char *base, *p, *end, *strings;
|
||
uint32_t size_struct;
|
||
int fresh_node = 0; /* 1 = haven't yet seen a non-PROP token since the
|
||
* last FDT_BEGIN_NODE -- i.e. still inside that
|
||
* node's own leading property list, per the DT
|
||
* spec's ordering guarantee (all of a node's
|
||
* direct properties precede its children). */
|
||
const void* node_body_start = (void*)0;
|
||
|
||
if (!fdt_valid(fdt) || !compatible) 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;
|
||
strings = base + be32(&h->off_dt_strings);
|
||
|
||
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;
|
||
p = (const unsigned char*)(((uintptr_t)(q + 1) + 3u) & ~(uintptr_t)3u);
|
||
fresh_node = 1;
|
||
node_body_start = (const void*)p;
|
||
}
|
||
else if (token == FDT_PROP)
|
||
{
|
||
uint32_t len, nameoff;
|
||
const unsigned char* val;
|
||
|
||
if (p + 8 > end) break;
|
||
len = be32(p);
|
||
nameoff = be32(p + 4);
|
||
p += 8;
|
||
val = p;
|
||
if (len > (uint32_t)(end - p)) break;
|
||
|
||
if (fresh_node &&
|
||
str_eq((const char*)(strings + nameoff), "compatible") &&
|
||
compat_list_contains((const char*)val, len, compatible))
|
||
{
|
||
return node_body_start;
|
||
}
|
||
|
||
p = (const unsigned char*)(((uintptr_t)(p + len) + 3u) & ~(uintptr_t)3u);
|
||
/* fresh_node stays 1 -- still within this same node's own
|
||
* leading property list. */
|
||
}
|
||
else if (token == FDT_END_NODE)
|
||
{
|
||
fresh_node = 0;
|
||
}
|
||
else if (token == FDT_NOP)
|
||
{
|
||
/* no payload; does not end the leading property list */
|
||
}
|
||
else
|
||
{
|
||
/* FDT_END, or a token this reader does not know: stop. */
|
||
break;
|
||
}
|
||
}
|
||
|
||
return (void*)0;
|
||
}
|
||
|
||
const void* fdt_find_node_by_device_type(const void* fdt, const char* type)
|
||
{
|
||
const fdt_header_t* h = (const fdt_header_t*)fdt;
|
||
const unsigned char *base, *p, *end, *strings;
|
||
uint32_t size_struct;
|
||
int fresh_node = 0;
|
||
const void* node_body_start = (void*)0;
|
||
|
||
if (!fdt_valid(fdt) || !type) 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;
|
||
strings = base + be32(&h->off_dt_strings);
|
||
|
||
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;
|
||
p = (const unsigned char*)(((uintptr_t)(q + 1) + 3u) & ~(uintptr_t)3u);
|
||
fresh_node = 1;
|
||
node_body_start = (const void*)p;
|
||
}
|
||
else if (token == FDT_PROP)
|
||
{
|
||
uint32_t len, nameoff;
|
||
const unsigned char* val;
|
||
|
||
if (p + 8 > end) break;
|
||
len = be32(p);
|
||
nameoff = be32(p + 4);
|
||
p += 8;
|
||
val = p;
|
||
if (len > (uint32_t)(end - p)) break;
|
||
|
||
if (fresh_node &&
|
||
str_eq((const char*)(strings + nameoff), "device_type") &&
|
||
str_eq((const char*)val, type))
|
||
{
|
||
return node_body_start;
|
||
}
|
||
|
||
p = (const unsigned char*)(((uintptr_t)(p + len) + 3u) & ~(uintptr_t)3u);
|
||
}
|
||
else if (token == FDT_END_NODE)
|
||
{
|
||
fresh_node = 0;
|
||
}
|
||
else if (token == FDT_NOP)
|
||
{
|
||
/* no payload; does not end the leading property list */
|
||
}
|
||
else
|
||
{
|
||
/* FDT_END, or a token this reader does not know: stop. */
|
||
break;
|
||
}
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
int fdt_complete(const void* fdt)
|
||
{
|
||
const fdt_header_t* h = (const fdt_header_t*)fdt;
|
||
uint32_t size_struct;
|
||
|
||
if (!fdt_valid(fdt)) return 0;
|
||
size_struct = be32(&h->size_dt_struct);
|
||
if (size_struct < 4 || (size_struct & 3u)) return 0;
|
||
return be32((const unsigned char*)fdt + be32(&h->off_dt_struct) + size_struct - 4) == FDT_END;
|
||
}
|
||
|
||
const void* fdt_root_node(const void* fdt)
|
||
{
|
||
const fdt_header_t* h = (const fdt_header_t*)fdt;
|
||
const unsigned char *p, *end;
|
||
|
||
if (!fdt_valid(fdt)) return (void*)0;
|
||
|
||
p = (const unsigned char*)fdt + be32(&h->off_dt_struct);
|
||
end = p + be32(&h->size_dt_struct);
|
||
|
||
while (p + 4 <= end)
|
||
{
|
||
uint32_t token = be32(p);
|
||
p += 4;
|
||
|
||
if (token == FDT_NOP) continue;
|
||
if (token != FDT_BEGIN_NODE) break;
|
||
|
||
{
|
||
const unsigned char* q = p;
|
||
while (q < end && *q) q++;
|
||
if (q >= end) break;
|
||
return (const void*)(((uintptr_t)(q + 1) + 3u) & ~(uintptr_t)3u);
|
||
}
|
||
}
|
||
|
||
return (void*)0;
|
||
}
|
||
|
||
const void* fdt_find_child(const void* fdt, const void* parent,
|
||
const char* name, uint32_t name_len)
|
||
{
|
||
const fdt_header_t* h = (const fdt_header_t*)fdt;
|
||
const unsigned char *end, *p;
|
||
|
||
if (!fdt_valid(fdt) || !parent || !name) return (void*)0;
|
||
|
||
end = (const unsigned char*)fdt + be32(&h->off_dt_struct) + be32(&h->size_dt_struct);
|
||
p = (const unsigned char*)parent;
|
||
|
||
/* parent's own properties first, then its children one after another:
|
||
* each child that is not the one wanted is skipped whole. */
|
||
while (p + 4 <= end)
|
||
{
|
||
uint32_t token = be32(p);
|
||
p += 4;
|
||
|
||
if (token == FDT_BEGIN_NODE)
|
||
{
|
||
const unsigned char* q = p;
|
||
const unsigned char* body;
|
||
uint32_t i;
|
||
|
||
while (q < end && *q) q++;
|
||
if (q >= end) return (void*)0;
|
||
body = (const unsigned char*)(((uintptr_t)(q + 1) + 3u) & ~(uintptr_t)3u);
|
||
|
||
/* the whole name, unit address and all, and nothing more */
|
||
if ((uint32_t)(q - p) == name_len)
|
||
{
|
||
for (i = 0; i < name_len && p[i] == (unsigned char)name[i]; i++) { }
|
||
if (i == name_len) return (const void*)body;
|
||
}
|
||
p = skip_node_body(body, end);
|
||
}
|
||
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)
|
||
{
|
||
const fdt_header_t* h = (const fdt_header_t*)fdt;
|
||
const unsigned char *p, *end, *strings;
|
||
|
||
if (!fdt_valid(fdt) || !node || !name) return (void*)0;
|
||
|
||
p = (const unsigned char*)node;
|
||
end = (const unsigned char*)fdt + be32(&h->off_dt_struct) + be32(&h->size_dt_struct);
|
||
strings = (const unsigned char*)fdt + be32(&h->off_dt_strings);
|
||
|
||
/* node points just past this node's own FDT_BEGIN_NODE name -- scan
|
||
* only its leading property list; stop at the first non-PROP/NOP
|
||
* token (a child's FDT_BEGIN_NODE, or this node's own FDT_END_NODE),
|
||
* matching fdt_find_node_by_compatible()'s own scoping rule. Never
|
||
* descends into children, never continues into a sibling. */
|
||
while (p + 4 <= end)
|
||
{
|
||
uint32_t token = be32(p);
|
||
p += 4;
|
||
|
||
if (token == FDT_NOP) continue;
|
||
if (token != FDT_PROP) break;
|
||
|
||
{
|
||
uint32_t len, nameoff;
|
||
const unsigned char* val;
|
||
|
||
if (p + 8 > end) break;
|
||
len = be32(p);
|
||
nameoff = be32(p + 4);
|
||
p += 8;
|
||
val = p;
|
||
if (len > (uint32_t)(end - p)) break;
|
||
|
||
if (str_eq((const char*)(strings + nameoff), name))
|
||
{
|
||
if (len_out) *len_out = len;
|
||
return (const void*)val;
|
||
}
|
||
|
||
p = (const unsigned char*)(((uintptr_t)(p + len) + 3u) & ~(uintptr_t)3u);
|
||
}
|
||
}
|
||
|
||
return (void*)0;
|
||
}
|
||
|
||
/* ---- a node's address, carried through its buses (2026-10-14) ---- */
|
||
|
||
#define FDT_PATH_MAX 12u
|
||
|
||
static uint32_t node_cells(const void* fdt, const void* node, const char* name, uint32_t otherwise)
|
||
{
|
||
uint32_t len;
|
||
const void* v = fdt_find_prop_in_node(fdt, node, name, &len);
|
||
return (v && len == 4) ? be32(v) : otherwise;
|
||
}
|
||
|
||
static uint64_t read_cells_at(const unsigned char* p, uint32_t n)
|
||
{
|
||
return n == 2 ? ((uint64_t)be32(p) << 32) | be32(p + 4) : (uint64_t)be32(p);
|
||
}
|
||
|
||
static int node_enabled(const void* fdt, const void* node)
|
||
{
|
||
uint32_t len;
|
||
const char* v = (const char*)fdt_find_prop_in_node(fdt, node, "status", &len);
|
||
return !v || compat_list_contains(v, len, "okay") || compat_list_contains(v, len, "ok");
|
||
}
|
||
|
||
/* Depth first: the path from the root to the first enabled node
|
||
* compatible with `want` -- path[0] the root, path[depth-1] the node. */
|
||
static uint32_t path_to(const void* fdt, const void* node, const char* want,
|
||
const void** path, uint32_t depth)
|
||
{
|
||
const void* child = (const void*)0;
|
||
uint32_t len;
|
||
const char* c;
|
||
|
||
if (depth >= FDT_PATH_MAX) return 0;
|
||
path[depth] = node;
|
||
c = (const char*)fdt_find_prop_in_node(fdt, node, "compatible", &len);
|
||
if (c && compat_list_contains(c, len, want) && node_enabled(fdt, node)) return depth + 1;
|
||
while ((child = fdt_next_child_node(fdt, node, child)) != (const void*)0)
|
||
{
|
||
uint32_t d = path_to(fdt, child, want, path, depth + 1);
|
||
if (d) return d;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
int fdt_reg_address(const void* fdt, const char* compatible, uint32_t index, uint64_t* out)
|
||
{
|
||
const void* path[FDT_PATH_MAX];
|
||
const unsigned char* reg;
|
||
const void* root;
|
||
uint32_t depth, len, ac, sc, i;
|
||
uint64_t addr;
|
||
|
||
if (!out || !compatible || !fdt_complete(fdt)) return 0;
|
||
root = fdt_root_node(fdt);
|
||
if (!root) return 0;
|
||
depth = path_to(fdt, root, compatible, path, 0);
|
||
if (depth < 2) return 0;
|
||
|
||
/* entry `index` of its reg, in its parent's cells */
|
||
ac = node_cells(fdt, path[depth - 2], "#address-cells", 2);
|
||
sc = node_cells(fdt, path[depth - 2], "#size-cells", 1);
|
||
if (ac < 1 || ac > 2 || sc > 2) return 0;
|
||
reg = (const unsigned char*)fdt_find_prop_in_node(fdt, path[depth - 1], "reg", &len);
|
||
if (!reg || len < 4u * (ac + sc) * (index + 1u)) return 0;
|
||
addr = read_cells_at(reg + 4u * (ac + sc) * index, ac);
|
||
|
||
/* up through each bus to the CPU's addresses: an empty ranges is the
|
||
* same addresses above, no ranges at all is no way up */
|
||
for (i = depth - 2; i >= 1; i--)
|
||
{
|
||
uint32_t cac = node_cells(fdt, path[i], "#address-cells", 2);
|
||
uint32_t csc = node_cells(fdt, path[i], "#size-cells", 1);
|
||
uint32_t pac = node_cells(fdt, path[i - 1], "#address-cells", 2);
|
||
const unsigned char* r = (const unsigned char*)fdt_find_prop_in_node(fdt, path[i], "ranges", &len);
|
||
uint32_t entry, at;
|
||
int carried = 0;
|
||
|
||
if (!r) return 0;
|
||
if (len == 0) continue;
|
||
if (cac < 1 || cac > 2 || pac < 1 || pac > 2 || csc < 1 || csc > 2) return 0;
|
||
entry = 4u * (cac + pac + csc);
|
||
for (at = 0; at + entry <= len; at += entry)
|
||
{
|
||
uint64_t from = read_cells_at(r + at, cac);
|
||
uint64_t to = read_cells_at(r + at + 4u * cac, pac);
|
||
uint64_t size = read_cells_at(r + at + 4u * (cac + pac), csc);
|
||
if (addr >= from && addr - from < size) { addr = to + (addr - from); carried = 1; break; }
|
||
}
|
||
if (!carried) return 0;
|
||
}
|
||
*out = addr;
|
||
return 1;
|
||
}
|