riscv64: real trap entry with save/restore and SRET return

Punch list §25 item 0.2 complete.

Verified: riscv64 builds clean and boots to the ok> prompt with no regression;
dict_hash 0x3d4e1daf289da94f, unchanged from item 0.1's baseline. Disassembly
confirms the 320-byte frame, all 16 integer caller-saved registers, the FS
check, and SRET on exit; riscv64_trap_entry lands at 0x414fa8, 4-byte aligned
as stvec direct mode requires.

Not verified, and the item says so: neither new path was exercised. No timer is
armed until 0.3, so riscv64_interrupt_handler never ran, and no exception
occurred during boot, so the fatal path was not observed -- it is preserved
structurally, same branch to the same unchanged handler. This is why C2
rewrote the acceptance to no-regression rather than to having taken and
returned from a trap.

Register set is the LP64D psABI caller-saved list, not this document's summary:
integer ra/t0-t6/a0-a7 (16), FP ft0-ft11/fa0-fa7 (20) plus fcsr, and sepc +
sstatus. Callee-saved registers are the C handler's responsibility.

The FP half is conditional on sstatus.FS != Off, which the item did not
anticipate. Nothing in boot.S or kernel_entry.S programs FS, so its value is
whatever firmware leaves; touching an f-register with FS == Off raises an
illegal-instruction trap, and doing that inside the trap handler would be
unrecoverable. Omitting the FP save is not an option either -- the built
riscv64 image contains 530 FP instructions (fld, fmul.d, fcvt.lu.d among them),
confirming B2's finding against the binary rather than the build flags alone.
So the save is conditional, and sstatus is restored after the f-registers.

Dispatch: scause bit 63 routes to riscv64_interrupt_handler with scause in a0;
cause 5 (supervisor timer) calls heartbeat_tick(). Other causes are ignored
rather than fatal -- none are enabled to arrive. Everything else still falls
through to riscv64_exception_handler, unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-03 12:52:34 -04:00
co-authored by Claude Opus 5
parent c3e4fc282c
commit f3821ed686
6 changed files with 10674 additions and 7 deletions
+35
View File
@@ -11,11 +11,46 @@
#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);
/* 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) is routed to @c heartbeat_tick(). No timer is
* armed yet: arming via the SBI TIME extension, enabling @c sie.STIE, and the
* mandatory per-tick re-arm are punch-list item 0.3. Until then this path is
* unreachable, which is why item 0.2 accepts on "boots with no regression"
* rather than on having observed an interrupt.
*
* 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) {
heartbeat_tick();
}
}
/**
* @brief Print a 64-bit value as "0xNNNNNNNNNNNNNNNN" to the kernel console.
*
+168 -5
View File
@@ -15,13 +15,176 @@
.hidden riscv64_trap_entry
.hidden riscv64_install_vectors
/* Direct-mode trap entry: stvec[1:0] = 0 means all traps branch here */
/* ---------------------------------------------------------------------------
* Trap frame layout (offsets from sp), 320 bytes, 16-byte aligned.
*
* Only the LP64D psABI's *caller-saved* registers are saved: a C handler
* preserves callee-saved registers itself, and the interrupted code already
* expects caller-saved registers to be clobbered across a call.
*
* integer caller-saved (16): ra, t0-t6, a0-a7
* FP caller-saved (20): ft0-ft11, fa0-fa7 + fcsr
* supervisor CSRs (2): sepc, sstatus
*
* The FP half is saved only when sstatus.FS != Off. Nothing in boot.S or
* kernel_entry.S programs FS, so its state is whatever firmware left; touching
* an f-register while FS == Off raises an illegal-instruction trap, which
* inside the trap handler would be unrecoverable. The kernel image does
* contain real FP code (fld/fmul.d/fcvt on the Q48.16 and statistics paths),
* so the save cannot simply be omitted -- it has to be conditional.
* ------------------------------------------------------------------------- */
.equ TF_RA, 0
.equ TF_T0, 8
.equ TF_T1, 16
.equ TF_T2, 24
.equ TF_T3, 32
.equ TF_T4, 40
.equ TF_T5, 48
.equ TF_T6, 56
.equ TF_A0, 64
.equ TF_A1, 72
.equ TF_A2, 80
.equ TF_A3, 88
.equ TF_A4, 96
.equ TF_A5, 104
.equ TF_A6, 112
.equ TF_A7, 120
.equ TF_SEPC, 128
.equ TF_SSTATUS,136
.equ TF_FCSR, 144
.equ TF_F0, 152 /* ft0-ft7 : 152 .. 208 */
.equ TF_FA0, 216 /* fa0-fa7 : 216 .. 272 */
.equ TF_FT8, 280 /* ft8-ft11 : 280 .. 304 */
.equ TF_SIZE, 320
/* sstatus.FS occupies bits [14:13]; 0 = Off */
.equ SSTATUS_FS_SHIFT, 13
.equ SSTATUS_FS_MASK, 3
/* Direct-mode trap entry: stvec[1:0] = 0 means all traps branch here.
* stvec requires 4-byte alignment. */
.balign 4
.global riscv64_trap_entry
riscv64_trap_entry:
call riscv64_exception_handler
/* Should not return; if it does, spin */
1: wfi
j 1b
addi sp, sp, -TF_SIZE
sd ra, TF_RA(sp)
sd t0, TF_T0(sp)
sd t1, TF_T1(sp)
sd t2, TF_T2(sp)
sd t3, TF_T3(sp)
sd t4, TF_T4(sp)
sd t5, TF_T5(sp)
sd t6, TF_T6(sp)
sd a0, TF_A0(sp)
sd a1, TF_A1(sp)
sd a2, TF_A2(sp)
sd a3, TF_A3(sp)
sd a4, TF_A4(sp)
sd a5, TF_A5(sp)
sd a6, TF_A6(sp)
sd a7, TF_A7(sp)
csrr t0, sepc
sd t0, TF_SEPC(sp)
csrr t0, sstatus
sd t0, TF_SSTATUS(sp)
/* Save FP only if FS != Off */
srli t1, t0, SSTATUS_FS_SHIFT
andi t1, t1, SSTATUS_FS_MASK
beqz t1, 1f
frcsr t2
sd t2, TF_FCSR(sp)
fsd ft0, TF_F0+0(sp)
fsd ft1, TF_F0+8(sp)
fsd ft2, TF_F0+16(sp)
fsd ft3, TF_F0+24(sp)
fsd ft4, TF_F0+32(sp)
fsd ft5, TF_F0+40(sp)
fsd ft6, TF_F0+48(sp)
fsd ft7, TF_F0+56(sp)
fsd fa0, TF_FA0+0(sp)
fsd fa1, TF_FA0+8(sp)
fsd fa2, TF_FA0+16(sp)
fsd fa3, TF_FA0+24(sp)
fsd fa4, TF_FA0+32(sp)
fsd fa5, TF_FA0+40(sp)
fsd fa6, TF_FA0+48(sp)
fsd fa7, TF_FA0+56(sp)
fsd ft8, TF_FT8+0(sp)
fsd ft9, TF_FT8+8(sp)
fsd ft10, TF_FT8+16(sp)
fsd ft11, TF_FT8+24(sp)
1:
/* scause bit 63 set => interrupt (negative when read as signed). */
csrr a0, scause
bltz a0, 2f
/* Synchronous exception: fatal, does not return. */
call riscv64_exception_handler
9: wfi
j 9b
2: /* Interrupt: a0 already holds scause for the C dispatcher. */
call riscv64_interrupt_handler
/* Restore. Reload the saved sstatus first -- it decides whether the FP
* half was written, and it must be restored *after* the f-registers so
* that FS is back to the interrupted context's value on exit. */
ld t0, TF_SSTATUS(sp)
srli t1, t0, SSTATUS_FS_SHIFT
andi t1, t1, SSTATUS_FS_MASK
beqz t1, 3f
ld t2, TF_FCSR(sp)
fscsr t2
fld ft0, TF_F0+0(sp)
fld ft1, TF_F0+8(sp)
fld ft2, TF_F0+16(sp)
fld ft3, TF_F0+24(sp)
fld ft4, TF_F0+32(sp)
fld ft5, TF_F0+40(sp)
fld ft6, TF_F0+48(sp)
fld ft7, TF_F0+56(sp)
fld fa0, TF_FA0+0(sp)
fld fa1, TF_FA0+8(sp)
fld fa2, TF_FA0+16(sp)
fld fa3, TF_FA0+24(sp)
fld fa4, TF_FA0+32(sp)
fld fa5, TF_FA0+40(sp)
fld fa6, TF_FA0+48(sp)
fld fa7, TF_FA0+56(sp)
fld ft8, TF_FT8+0(sp)
fld ft9, TF_FT8+8(sp)
fld ft10, TF_FT8+16(sp)
fld ft11, TF_FT8+24(sp)
3:
csrw sstatus, t0
ld t0, TF_SEPC(sp)
csrw sepc, t0
ld ra, TF_RA(sp)
ld t0, TF_T0(sp)
ld t1, TF_T1(sp)
ld t2, TF_T2(sp)
ld t3, TF_T3(sp)
ld t4, TF_T4(sp)
ld t5, TF_T5(sp)
ld t6, TF_T6(sp)
ld a0, TF_A0(sp)
ld a1, TF_A1(sp)
ld a2, TF_A2(sp)
ld a3, TF_A3(sp)
ld a4, TF_A4(sp)
ld a5, TF_A5(sp)
ld a6, TF_A6(sp)
ld a7, TF_A7(sp)
addi sp, sp, TF_SIZE
sret
.global riscv64_install_vectors
riscv64_install_vectors: