/* 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; }