Files
StarForth/src/starkernel/hal/fdt.c
T
Robert Allan JamesandClaude Sonnet 5 5e46f18fd9 fdt.c: node-scoped lookup extension (FABRIC-3.md SSIV.3/SSV.3 shared item)
Adds fdt_find_node_by_compatible() and fdt_find_prop_in_node() to the
minimal FDT reader -- the extension fdt.h's own header comment already
flagged as a known future need ("item 0.6 will need node-scoped reg
lookups"), now with real consumers: the Pi 5's UART/mailbox register
addresses (native boot, no ACPI) and the Milk-V Mars's real PLIC base
address (currently hardcoded to QEMU-virt's own value).

fdt_find_node_by_compatible() matches any entry in a node's
NUL-separated "compatible" list, first match in document order.
fdt_find_prop_in_node() scopes to that one node's own direct
properties only -- stops at the first child node or the node's own
end, per the DT spec's ordering guarantee that a node's properties
always precede its children. Same minimal, non-tree-building,
single-linear-scan-per-call style as the existing reader; no new
state, no allocation.

Compile-only verification -- no caller wired in yet, this is the
shared primitive both boards' own punch-list items will call once
built. Verified 3-arch boot to ok> (amd64/aarch64/riscv64, each in
the foreground).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 13:05:42 -04:00

307 lines
9.1 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_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;
}