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:
@@ -12,10 +12,127 @@
|
||||
|
||||
#include "apic.h"
|
||||
#include "uefi.h"
|
||||
#include "console.h"
|
||||
#include "timer.h"
|
||||
#include <stdint.h>
|
||||
|
||||
static uint64_t s_timer_period_tsc = 0;
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* SBI (Supervisor Binary Interface)
|
||||
*
|
||||
* RISC-V S-mode cannot program the timer directly: the CLINT's mtimecmp is an
|
||||
* M-mode register. The timer is armed by asking the SEE (OpenSBI, running in
|
||||
* M-mode beneath EDK2) via ECALL.
|
||||
*
|
||||
* Calling convention, SBI v0.2+ (SBI spec §3): a7 = EID, a6 = FID,
|
||||
* a0.. = arguments; returns a0 = error, a1 = value.
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#define SBI_EXT_BASE 0x10UL
|
||||
#define SBI_BASE_FID_PROBE_EXT 3UL
|
||||
|
||||
#define SBI_EXT_TIME 0x54494D45UL /* "TIME" */
|
||||
#define SBI_TIME_FID_SET_TIMER 0UL
|
||||
|
||||
#define SBI_SUCCESS 0L
|
||||
|
||||
/* sie.STIE — supervisor timer interrupt enable (Privileged Spec §4.1.3) */
|
||||
#define SIE_STIE (1UL << 5)
|
||||
|
||||
typedef struct
|
||||
{
|
||||
long error;
|
||||
long value;
|
||||
} sbiret_t;
|
||||
|
||||
/** @brief Issue an SBI ECALL with one argument. */
|
||||
static sbiret_t sbi_call1(unsigned long eid, unsigned long fid,
|
||||
unsigned long arg0)
|
||||
{
|
||||
register unsigned long r_a0 __asm__("a0") = arg0;
|
||||
register unsigned long r_a1 __asm__("a1") = 0;
|
||||
register unsigned long r_a6 __asm__("a6") = fid;
|
||||
register unsigned long r_a7 __asm__("a7") = eid;
|
||||
sbiret_t ret;
|
||||
|
||||
__asm__ volatile (
|
||||
"ecall"
|
||||
: "+r"(r_a0), "+r"(r_a1)
|
||||
: "r"(r_a6), "r"(r_a7)
|
||||
: "memory");
|
||||
|
||||
ret.error = (long)r_a0;
|
||||
ret.value = (long)r_a1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read the RISC-V @c time CSR.
|
||||
*
|
||||
* Deadlines handed to @c sbi_set_timer() are absolute values on this counter.
|
||||
* @c arch/riscv64/timer.c keeps its own copy of this accessor; duplicating
|
||||
* four instructions is preferable to widening @c timer.h with an
|
||||
* architecture-specific accessor that only these two files can use.
|
||||
*/
|
||||
static inline uint64_t rdtime(void)
|
||||
{
|
||||
uint64_t val;
|
||||
__asm__ volatile (
|
||||
"rdtime %0" : "=r"(val));
|
||||
return val;
|
||||
}
|
||||
|
||||
/* Set once in apic_timer_start(): 1 when the TIME extension probed present,
|
||||
* 0 when the timer could not be armed at all. */
|
||||
static int s_sbi_time_ok = 0;
|
||||
/* Absolute `time` value of the next expected interrupt. Advanced by period
|
||||
* rather than recomputed from "now" so that a late tick does not push the
|
||||
* whole schedule out; see riscv64_timer_rearm(). */
|
||||
static uint64_t s_next_deadline = 0;
|
||||
|
||||
/**
|
||||
* @brief Arm the SBI timer for @p deadline.
|
||||
* @return 1 on success, 0 if the SEE rejected the call.
|
||||
*/
|
||||
static int sbi_set_timer(uint64_t deadline)
|
||||
{
|
||||
sbiret_t r = sbi_call1(SBI_EXT_TIME, SBI_TIME_FID_SET_TIMER,
|
||||
(unsigned long)deadline);
|
||||
return r.error == SBI_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Re-arm the one-shot SBI timer and account the tick.
|
||||
*
|
||||
* **The SBI timer is one-shot by nature.** Servicing a timer interrupt without
|
||||
* programming the next deadline leaves the heartbeat stopped permanently, with
|
||||
* no error anywhere — the single most likely silent failure of this driver.
|
||||
* Every path out of a timer interrupt must reach this function.
|
||||
*
|
||||
* Called from @c riscv64_interrupt_handler() in @c interrupts.c on
|
||||
* @c scause cause 5.
|
||||
*/
|
||||
void riscv64_timer_rearm(void)
|
||||
{
|
||||
uint64_t now;
|
||||
|
||||
if (!s_sbi_time_ok) return;
|
||||
|
||||
s_next_deadline += s_timer_period_tsc;
|
||||
|
||||
/* If servicing ran long enough that the next deadline is already behind
|
||||
* us, resynchronise rather than burn through a backlog of instant
|
||||
* interrupts. */
|
||||
now = rdtime();
|
||||
if (s_next_deadline <= now)
|
||||
{
|
||||
s_next_deadline = now + s_timer_period_tsc;
|
||||
}
|
||||
|
||||
sbi_set_timer(s_next_deadline);
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Initialise the interrupt controller (RISC-V PLIC stub).
|
||||
*
|
||||
@@ -70,20 +187,62 @@ int apic_timer_init(uint64_t tsc_hz, uint32_t tick_hz)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Start periodic timer delivery (RISC-V stub).
|
||||
* @brief Start timer delivery via the SBI TIME extension.
|
||||
*
|
||||
* On x86-64 this unmasks the APIC timer. On RISC-V a periodic timer would
|
||||
* be armed via CLINT or SBI here; the driver is deferred. No-op stub.
|
||||
* Probes for the TIME extension first. If the SEE does not provide it the
|
||||
* timer is **not** armed and the condition is reported loudly rather than
|
||||
* papered over with the legacy EID 0x00 call: a heartbeat that silently never
|
||||
* ticks is far worse to diagnose than one that says why at boot.
|
||||
*
|
||||
* On success: computes the first absolute deadline, arms it, and sets
|
||||
* @c sie.STIE. Global delivery is gated separately by @c sstatus.SIE, which
|
||||
* @c arch_enable_interrupts() sets.
|
||||
*/
|
||||
void apic_timer_start(void) { }
|
||||
void apic_timer_start(void)
|
||||
{
|
||||
sbiret_t probe;
|
||||
|
||||
probe = sbi_call1(SBI_EXT_BASE, SBI_BASE_FID_PROBE_EXT, SBI_EXT_TIME);
|
||||
if (probe.error != SBI_SUCCESS || probe.value == 0)
|
||||
{
|
||||
console_println("SBI: TIME extension ABSENT - timer NOT armed, "
|
||||
"heartbeat will not tick");
|
||||
s_sbi_time_ok = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
s_sbi_time_ok = 1;
|
||||
s_next_deadline = rdtime() + s_timer_period_tsc;
|
||||
|
||||
if (!sbi_set_timer(s_next_deadline))
|
||||
{
|
||||
console_println("SBI: set_timer REJECTED - timer NOT armed");
|
||||
s_sbi_time_ok = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
__asm__ volatile (
|
||||
"csrs sie, %0"
|
||||
::
|
||||
"r"(SIE_STIE) : "memory");
|
||||
console_println("SBI: timer armed (TIME extension)");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Stop periodic timer delivery (RISC-V stub).
|
||||
* @brief Stop timer delivery by masking @c sie.STIE.
|
||||
*
|
||||
* On x86-64 this masks the APIC timer. On RISC-V a periodic timer would
|
||||
* be disarmed via CLINT or SBI here; the driver is deferred. No-op stub.
|
||||
* The SBI timer cannot be cancelled outright — masking the enable bit is the
|
||||
* supported way to stop delivery. Any deadline already programmed simply goes
|
||||
* unserviced.
|
||||
*/
|
||||
void apic_timer_stop(void) { }
|
||||
void apic_timer_stop(void)
|
||||
{
|
||||
__asm__ volatile (
|
||||
"csrc sie, %0"
|
||||
::
|
||||
"r"(SIE_STIE) : "memory");
|
||||
s_sbi_time_ok = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the expected cycle-counter ticks per heartbeat period.
|
||||
|
||||
Reference in New Issue
Block a user