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