Files
LithosAnanke/kernel/src/hal/fdt.c
T
rajamesandClaude Opus 5.5 0e2c60dac3 fix(raspi): the Pi 5's native path turns the MMU and caches on before any C; bounded mailbox; framebuffer address from dma-ranges
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>
2026-10-14 12:58:35 -04:00

751 lines
22 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
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;
}