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>
150 lines
5.7 KiB
C
150 lines
5.7 KiB
C
/*
|
||
StarForth — Steady-State Virtual Machine Runtime
|
||
Copyright (c) 2023–2025 Robert A. James. All rights reserved.
|
||
Licensed under the StarForth License, Version 1.0.
|
||
*/
|
||
|
||
/**
|
||
* interrupts.c (riscv64) - Trap vector setup and common exception handler
|
||
*/
|
||
|
||
#include <stdint.h>
|
||
#include "arch.h"
|
||
#include "console.h"
|
||
#include "starkernel/timer.h"
|
||
|
||
volatile const char *g_sk_fault_word = (void *)0;
|
||
|
||
extern void riscv64_install_vectors(void);
|
||
|
||
/* Defined in arch/riscv64/apic.c. Declared here rather than in the shared
|
||
* starkernel/apic.h because re-arming is specific to the one-shot SBI timer
|
||
* and has no meaning for the amd64 periodic APIC timer. Same extern-in-place
|
||
* convention as riscv64_install_vectors above. */
|
||
extern void riscv64_timer_rearm(void);
|
||
|
||
/* scause cause codes for supervisor-mode interrupts (RISC-V Privileged Spec
|
||
* §4.1.9, Table "Supervisor cause register values"). Only the timer is used;
|
||
* software (1) and external (9) interrupts are not enabled. */
|
||
#define SCAUSE_INTERRUPT_BIT (1ULL << 63)
|
||
#define SCAUSE_S_TIMER 5ULL
|
||
|
||
/**
|
||
* @brief Dispatch an asynchronous supervisor interrupt.
|
||
*
|
||
* Called from @c riscv64_trap_entry in @c isr.S when @c scause has bit 63 set.
|
||
* Unlike @c riscv64_exception_handler() this **returns** — the trap entry
|
||
* restores the caller-saved register set and issues @c SRET.
|
||
*
|
||
* Supervisor timer (cause 5) re-arms the one-shot SBI timer and then accounts
|
||
* the tick. **Re-arm comes first**: the SBI timer fires once per programmed
|
||
* deadline, so any return path that skips the re-arm stops the heartbeat
|
||
* permanently and silently. Ordering it ahead of @c heartbeat_tick() means a
|
||
* fault in the bookkeeping cannot also cost the next tick.
|
||
*
|
||
* Any other cause is ignored rather than fatal: an unexpected-but-harmless
|
||
* asynchronous interrupt should not take the kernel down, and none are
|
||
* currently enabled to arrive.
|
||
*
|
||
* @param scause Raw @c scause value, interrupt bit still set.
|
||
*/
|
||
void riscv64_interrupt_handler(uint64_t scause)
|
||
{
|
||
uint64_t cause = scause & ~SCAUSE_INTERRUPT_BIT;
|
||
|
||
if (cause == SCAUSE_S_TIMER) {
|
||
riscv64_timer_rearm();
|
||
heartbeat_tick();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief Print a 64-bit value as "0xNNNNNNNNNNNNNNNN" to the kernel console.
|
||
*
|
||
* Formats @p val as a 16-nibble lowercase hex string preceded by "0x" into
|
||
* a 19-byte stack buffer (including null terminator) and emits it via
|
||
* @c console_puts(). Used by @c riscv64_exception_handler() to print
|
||
* @c scause, @c sepc, and @c stval in the exception diagnostic dump.
|
||
* No heap allocation or libc dependency.
|
||
*
|
||
* @param val 64-bit value to format.
|
||
*/
|
||
static void print_hex(uint64_t val)
|
||
{
|
||
char buf[19];
|
||
buf[0] = '0'; buf[1] = 'x';
|
||
for (int i = 15; i >= 0; i--) {
|
||
int nibble = (int)(val & 0xF);
|
||
buf[2 + i] = (char)(nibble < 10 ? '0' + nibble : 'a' + nibble - 10);
|
||
val >>= 4;
|
||
}
|
||
buf[18] = '\0';
|
||
console_puts(buf);
|
||
}
|
||
|
||
/**
|
||
* @brief RISC-V common trap handler — print diagnostic and spin.
|
||
*
|
||
* Called from the @c isr.S trap vector for all synchronous exceptions
|
||
* and asynchronous interrupts that are not handled by a specific driver.
|
||
* Reads three supervisor-mode CSRs directly:
|
||
*
|
||
* - @c scause — Supervisor Cause Register. Bit 63 distinguishes
|
||
* interrupts (1) from exceptions (0); bits [62:0] encode the cause
|
||
* code (e.g., 0 = instruction address misaligned, 2 = illegal
|
||
* instruction, 5 = load access fault, 13 = store page fault,
|
||
* 8 = ecall from U-mode). See RISC-V Privileged Spec §4.1.9.
|
||
* - @c sepc — Supervisor Exception Program Counter. The PC of the
|
||
* instruction that caused the trap (or the return address for
|
||
* interrupt handlers); execution resumes from @c sepc on SRET.
|
||
* - @c stval — Supervisor Trap Value. For load/store page faults this
|
||
* holds the faulting virtual address; for illegal instruction faults
|
||
* it holds the faulting instruction encoding; zero for other causes.
|
||
*
|
||
* Prints all three values to the kernel console then enters an infinite
|
||
* @c WFI loop to halt the core permanently. All exceptions that reach
|
||
* this handler are fatal in the current single-hart kernel.
|
||
*
|
||
* @c g_sk_fault_word is set by the VM FORTH dispatcher and names the
|
||
* word executing at the time of the trap, aiding post-mortem serial
|
||
* log analysis.
|
||
*/
|
||
void riscv64_exception_handler(void)
|
||
{
|
||
uint64_t scause, sepc, stval;
|
||
__asm__ volatile ("csrr %0, scause" : "=r"(scause));
|
||
__asm__ volatile ("csrr %0, sepc" : "=r"(sepc));
|
||
__asm__ volatile ("csrr %0, stval" : "=r"(stval));
|
||
|
||
console_println("*** EXCEPTION (riscv64) ***");
|
||
console_puts(" scause = "); print_hex(scause); console_puts("\n");
|
||
console_puts(" sepc = "); print_hex(sepc); console_puts("\n");
|
||
console_puts(" stval = "); print_hex(stval); console_puts("\n");
|
||
|
||
for (;;) {
|
||
__asm__ volatile ("wfi" ::: "memory");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief Install the RISC-V supervisor trap vector table (M4 milestone).
|
||
*
|
||
* Calls @c riscv64_install_vectors() (defined in @c isr.S) which writes
|
||
* the trap vector base address into @c stvec (Supervisor Trap Vector Base
|
||
* Address Register, CSR 0x105). The vector mode field (bits [1:0]) is
|
||
* set to @c 0 (Direct mode) so all traps dispatch to the single base
|
||
* address, or @c 1 (Vectored mode) so async interrupts dispatch to
|
||
* @c stvec + 4*cause.
|
||
*
|
||
* On RISC-V there is no IDT and no legacy PIC; the PLIC replaces both.
|
||
* The @c arch_interrupts_init() name is shared across all three ISAs so
|
||
* that @c kernel_main() calls the same symbol regardless of target.
|
||
*
|
||
* Must be called after the supervisor stack (@c sp) is established and
|
||
* before @c arch_enable_interrupts().
|
||
*/
|
||
void arch_interrupts_init(void)
|
||
{
|
||
riscv64_install_vectors();
|
||
}
|