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
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
bffd87615d
commit
5e46f18fd9
+14
-10
@@ -319,13 +319,14 @@ blocker for free.
|
||||
3. **Mailbox-property-interface framebuffer driver** — genuinely new code (§IV.1's own
|
||||
assessment), populating `BootInfo.framebuffer` the same shape UEFI GOP currently does, so
|
||||
`console.c`/`vt100.c`/`framebuffer.c` need no changes at all downstream.
|
||||
4. **`fdt.c`/`fdt.h` extension**: today's reader only supports "first match anywhere in the
|
||||
tree" (`fdt_find_prop`'s own doc comment) — sufficient for the single global properties
|
||||
used so far (riscv64's `timebase-frequency`), **not** sufficient for node-scoped lookups
|
||||
(a specific peripheral's own `reg` address, e.g. UART or the mailbox registers) once more
|
||||
than one node could plausibly define a same-named property. `fdt.h`'s own header comment
|
||||
already flagged this as a known future need ("item 0.6 will need node-scoped `reg`
|
||||
lookups... may extend this") — this is that extension, now with a real consumer.
|
||||
4. **`fdt.c`/`fdt.h` extension — DONE 2026-09-04.** Added `fdt_find_node_by_compatible()`
|
||||
(matches any entry in a node's NUL-separated `compatible` list, first match in document
|
||||
order) and `fdt_find_prop_in_node()` (scoped to that one node's own direct properties only
|
||||
— stops at the first child node or the node's own end, never descends or continues into a
|
||||
sibling). Same minimal, non-tree-building style as the existing reader — no new state, no
|
||||
allocation, one linear scan per call. Verified 3-arch boot to `ok>` (compile-only — no
|
||||
caller wired in yet; this is the shared primitive items 1/3 above and §V.3 item 3 below
|
||||
will each call once built).
|
||||
5. **`pci_init()` DTB path**: a devicetree-based alternative for RP1 discovery, since
|
||||
`boot_info->acpi_table` will be `NULL` on this path and RP1 is PCIe-attached, not directly
|
||||
memory-mapped.
|
||||
@@ -426,9 +427,12 @@ it doesn't. This is a real punch-list item, not a hypothetical.
|
||||
pointer, `acpi_table`=`NULL`, memory map from DTB `/memory`+`/reserved-memory`, `args` from
|
||||
`/chosen`/`bootargs`.
|
||||
3. **PLIC base address: make it DTB-discovered**, not the current QEMU-virt-specific
|
||||
constant — the one concrete, already-flagged risk above. Uses the same `fdt.c` node-scoped
|
||||
lookup extension §IV.3 already scopes for the Pi 5's UART/mailbox addresses — one extension,
|
||||
two consumers.
|
||||
constant — the one concrete, already-flagged risk above. `fdt.c`'s node-scoped lookup
|
||||
extension (§IV.3 item 4, **DONE 2026-09-04**, shared with the Pi 5's UART/mailbox
|
||||
addresses) is the primitive this calls
|
||||
(`fdt_find_node_by_compatible(fdt, "sifive,plic-1.0.0")` → `fdt_find_prop_in_node(..., "reg", ...)`,
|
||||
plausible compatible string, not yet confirmed against the Mars's real DTB) — the actual
|
||||
PLIC-init call site update is still open, only the primitive it needs now exists.
|
||||
4. **Framebuffer for HDMI output**: JH7110's display path is genuinely unresearched this
|
||||
pass — unlike the Pi 5's mailbox interface (well-documented, reused across many Pi bare-
|
||||
metal projects), no equivalent research done yet for JH7110's own display controller.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Capsule Block Manifest — Auto-generated
|
||||
<!-- Generated by mkcapsule --manifest 2026-09-04T15:51:18Z -->
|
||||
<!-- Generated by mkcapsule --manifest 2026-09-04T17:03:08Z -->
|
||||
<!-- 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.
@@ -8,13 +8,20 @@
|
||||
* fdt.h - Minimal flattened-devicetree reader
|
||||
*
|
||||
* Just enough of the Devicetree Specification v0.4 §5 to pull values out of
|
||||
* the blob the UEFI firmware publishes under EFI_DTB_TABLE_GUID. Read-only,
|
||||
* no allocation, no tree construction — it walks the structure block each
|
||||
* call, which is fine for the handful of boot-time lookups the kernel needs.
|
||||
* the blob the UEFI firmware publishes under EFI_DTB_TABLE_GUID, or that a
|
||||
* native (non-UEFI) boot entry passes directly. Read-only, no allocation, no
|
||||
* tree construction — it walks the structure block each call, which is fine
|
||||
* for the handful of boot-time lookups the kernel needs.
|
||||
*
|
||||
* Deliberately not a general devicetree library. Added for punch-list item
|
||||
* 0.3 (riscv64 timebase-frequency); item 0.6 will need node-scoped `reg`
|
||||
* lookups for the aarch64 GIC and may extend this.
|
||||
* 0.3 (riscv64 timebase-frequency); extended (FABRIC-3.md §IV.3/§V.3,
|
||||
* 2026-09-04) with node-scoped lookup, for exactly the case this header
|
||||
* originally flagged as a future need (item 0.6's aarch64 GIC) plus its
|
||||
* real, concrete consumers as of this pass: the Raspberry 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,
|
||||
* `arch/riscv64/plic.c`'s own doc comment already warned this isn't
|
||||
* assumed stable across configurations).
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_FDT_H
|
||||
@@ -62,4 +69,44 @@ const void* fdt_find_prop(const void* fdt, const char* name, uint32_t* len_out);
|
||||
*/
|
||||
int fdt_prop_u32(const void* fdt, const char* name, uint32_t* out);
|
||||
|
||||
/**
|
||||
* @brief Find the first node whose "compatible" property matches @p compatible.
|
||||
*
|
||||
* "compatible" is a NUL-separated list of strings (DT spec §2.3.1) — matches
|
||||
* if @p compatible equals any one entry in the list, not just the whole
|
||||
* property verbatim. 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 compatible Compatible string to match, NUL-terminated.
|
||||
* @return An opaque handle to the matched node, for use with
|
||||
* @c fdt_find_prop_in_node() only (not a raw offset or a pointer
|
||||
* to anything else meaningful) — or NULL if no node matches.
|
||||
*/
|
||||
const void* fdt_find_node_by_compatible(const void* fdt, const char* compatible);
|
||||
|
||||
/**
|
||||
* @brief Find a property by name, scoped to one node.
|
||||
*
|
||||
* Like @c fdt_find_prop(), but scans only @p node's own direct properties
|
||||
* (as returned by @c fdt_find_node_by_compatible()) — stops at the first
|
||||
* child node or the end of @p node's property list, never descends into
|
||||
* children, never continues into a sibling. This is the difference that
|
||||
* matters for a property name like "reg", which is not unique across the
|
||||
* tree the way "timebase-frequency" (the whole reason @c fdt_find_prop()
|
||||
* was originally sufficient) happens to be.
|
||||
*
|
||||
* @param fdt Blob, already checked with @c fdt_valid().
|
||||
* @param node Handle from @c fdt_find_node_by_compatible(); NULL is
|
||||
* safe and returns NULL (propagates a failed node lookup
|
||||
* without a separate caller-side check).
|
||||
* @param name Property name, NUL-terminated.
|
||||
* @param len_out Receives the property length in bytes; may be NULL.
|
||||
* @return Pointer to the property value inside @p fdt, or NULL if not
|
||||
* found (or if @p node is NULL). The value is big-endian as
|
||||
* stored in the blob.
|
||||
*/
|
||||
const void* fdt_find_prop_in_node(const void* fdt, const void* node,
|
||||
const char* name, uint32_t* len_out);
|
||||
|
||||
#endif /* STARKERNEL_FDT_H */
|
||||
|
||||
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
@@ -156,3 +156,151 @@ int fdt_prop_u32(const void* fdt, const char* name, uint32_t* out)
|
||||
*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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user