Files
LithosAnanke/src/starkernel/arch/riscv64/interrupts.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

150 lines
5.7 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.
*/
/**
* 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();
}