Files
StarForth/src/starkernel/hal/fdt.c
T
Robert Allan James accd79fc70 riscv64: integrate minimal flattened devicetree reader and switch timer to time CSR
Punch list §25 item 0.3 NOT complete.

- Added `starkernel/fdt.h` and `fdt.c` for minimal read-only devicetree parsing: sufficient for boot-time lookups such as `timebase-frequency`.
- Bootloader now captures the devicetree blob (DTB) from `EFI_DTB_TABLE_GUID` into `BootInfo::dtb`.
- RISC-V timer subsystem now uses the `time` CSR as the primary timestamp source, abandoning the hardcoded `cycle` frequency assumption.
- Timer rate is read from `timebase-frequency` in the DTB when accessible; otherwise, a fallback value is used with a RELATIVE trust level.
- Integrated the SBI TIME extension for one-shot timer deadlines, ensuring re-arming occurs after each tick to avoid missing heartbeats.

Verified: riscv64 builds clean, boots to the ok> prompt with no regression; `riscv64/timer.c` reports accurate frequencies on QEMU's default firmware.
Signed-off-by: Robert Allan James <robert.allan.james@gmail.com>
2026-08-03 13:10:17 -04:00

159 lines
4.4 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;
}