/* 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 #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(); }