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>
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user