summaryrefslogtreecommitdiff
path: root/arch/riscv/trap/trap_vector.S
diff options
context:
space:
mode:
Diffstat (limited to 'arch/riscv/trap/trap_vector.S')
-rw-r--r--arch/riscv/trap/trap_vector.S283
1 files changed, 283 insertions, 0 deletions
diff --git a/arch/riscv/trap/trap_vector.S b/arch/riscv/trap/trap_vector.S
new file mode 100644
index 0000000..8e45cae
--- /dev/null
+++ b/arch/riscv/trap/trap_vector.S
@@ -0,0 +1,283 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+/*
+ * Trap Vector Entry / Exit (RISC-V 64, S-mode)
+ *
+ * On trap entry (interrupt / exception / ecall):
+ * sscratch holds the TrapFrame pointer (set up by kernel before sret).
+ * We swap sscratch <-> tp, save all 31 GPRs + sstatus + sepc into TrapFrame,
+ * then load kernel metadata (sp, satp, tp, trap handler) and jump to C++.
+ *
+ * On trap return (userret):
+ * Restore all GPRs + sstatus + sepc from TrapFrame, swap sscratch <-> tp,
+ * then sret back to user mode.
+ *
+ * Layout must match arch::TrapFrame in arch/riscv/include/arch/trapframe.h
+ * Reference: docs/specs/arch_abstraction.md §7.2
+ */
+
+#include <arch/trapframe.h>
+
+ .section .text
+ .globl trap_vector
+ .globl userret
+ .align 4
+
+/*
+ * trap_vector — unified S-mode trap entry point (Direct mode)
+ *
+ * Precondition: sscratch = pointer to current task's TrapFrame
+ */
+trap_vector:
+ /* Step 1: swap sscratch <-> tp. Now tp = TrapFrame*, sscratch = old tp */
+ csrrw tp, sscratch, tp
+
+ /* Step 2: save all 31 general-purpose registers into TrapFrame */
+ sd ra, TF_RA(tp)
+ sd sp, TF_SP(tp)
+ sd gp, TF_GP(tp)
+ /* tp (x4) is in sscratch — save it later */
+ sd t0, TF_T0(tp)
+ sd t1, TF_T1(tp)
+ sd t2, TF_T2(tp)
+ sd s0, TF_S0(tp)
+ sd s1, TF_S1(tp)
+ sd a0, TF_A0(tp)
+ sd a1, TF_A1(tp)
+ sd a2, TF_A2(tp)
+ sd a3, TF_A3(tp)
+ sd a4, TF_A4(tp)
+ sd a5, TF_A5(tp)
+ sd a6, TF_A6(tp)
+ sd a7, TF_A7(tp)
+ sd s2, TF_S2(tp)
+ sd s3, TF_S3(tp)
+ sd s4, TF_S4(tp)
+ sd s5, TF_S5(tp)
+ sd s6, TF_S6(tp)
+ sd s7, TF_S7(tp)
+ sd s8, TF_S8(tp)
+ sd s9, TF_S9(tp)
+ sd s10, TF_S10(tp)
+ sd s11, TF_S11(tp)
+ sd t3, TF_T3(tp)
+ sd t4, TF_T4(tp)
+ sd t5, TF_T5(tp)
+ sd t6, TF_T6(tp)
+
+ /* Save the real tp (user's tp) which is now in sscratch */
+ csrr t0, sscratch
+ sd t0, TF_TP(tp)
+
+ /* Step 3: save sstatus */
+ csrr t0, sstatus
+ sd t0, TF_SSTATUS(tp)
+
+ /* Step 4: save sepc (interrupted PC) */
+ csrr t0, sepc
+ sd t0, TF_EPC(tp)
+
+ /* Step 5: load kernel context from TrapFrame metadata */
+ ld sp, TF_KERNEL_SP(tp) /* switch to kernel stack */
+ ld t1, TF_KERNEL_SATP(tp) /* kernel page table */
+ ld t2, TF_KERNEL_CPUID(tp) /* CPU ID for tp */
+ ld t3, TF_KERNEL_TRAP(tp) /* C++ trap_handler address */
+
+ /* Switch to kernel page table */
+ csrw satp, t1
+
+ /* Step 6: flush TLB after page table switch */
+ sfence.vma zero, zero
+
+ /* Step 6.5: restore kernel gp (global pointer).
+ * The compiler uses gp-relative addressing for global variable access
+ * (e.g. ld reg, offset(gp)). User mode may have set gp to anything,
+ * so we MUST reload it before entering any C++ code. */
+.option push
+.option norelax
+ la gp, __global_pointer$
+.option pop
+
+ /* Pass TrapFrame* to C++ handler (a0 = tf) */
+ mv a0, tp
+
+ /* Set tp = CPU ID (kernel convention) */
+ mv tp, t2
+
+ /* Step 7: jump to C++ trap_handler */
+ jalr t3
+
+
+/* ========================================================================
+ * kernelvec — unified S-mode trap entry point for traps FROM S-MODE
+ *
+ * Traps from S-mode already have a valid kernel stack pointer in sp.
+ * We just allocate a TrapFrame on the stack and save registers.
+ * ======================================================================== */
+ .globl kernelvec
+ .align 4
+kernelvec:
+ /* Allocate TrapFrame on stack */
+ addi sp, sp, -296
+
+ /* Save all 31 general-purpose registers into TrapFrame */
+ sd ra, TF_RA(sp)
+ /* Save the OLD sp (before we subtracted 296) */
+ addi t0, sp, 296
+ sd t0, TF_SP(sp)
+ sd gp, TF_GP(sp)
+ sd tp, TF_TP(sp)
+ sd t0, TF_T0(sp)
+ sd t1, TF_T1(sp)
+ sd t2, TF_T2(sp)
+ sd s0, TF_S0(sp)
+ sd s1, TF_S1(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)
+ sd s2, TF_S2(sp)
+ sd s3, TF_S3(sp)
+ sd s4, TF_S4(sp)
+ sd s5, TF_S5(sp)
+ sd s6, TF_S6(sp)
+ sd s7, TF_S7(sp)
+ sd s8, TF_S8(sp)
+ sd s9, TF_S9(sp)
+ sd s10, TF_S10(sp)
+ sd s11, TF_S11(sp)
+ sd t3, TF_T3(sp)
+ sd t4, TF_T4(sp)
+ sd t5, TF_T5(sp)
+ sd t6, TF_T6(sp)
+
+ /* Save sstatus and sepc */
+ csrr t0, sstatus
+ sd t0, TF_SSTATUS(sp)
+ csrr t0, sepc
+ sd t0, TF_EPC(sp)
+
+ /* Call C++ kernel_trap_handler(TrapFrame* tf) */
+ mv a0, sp
+ call kernel_trap_handler
+
+ /* Restore sstatus and sepc */
+ ld t0, TF_SSTATUS(sp)
+ csrw sstatus, t0
+ ld t0, TF_EPC(sp)
+ csrw sepc, t0
+
+ /* Restore all 31 general-purpose registers */
+ ld ra, TF_RA(sp)
+ ld gp, TF_GP(sp)
+ ld tp, TF_TP(sp)
+ ld t0, TF_T0(sp)
+ ld t1, TF_T1(sp)
+ ld t2, TF_T2(sp)
+ ld s0, TF_S0(sp)
+ ld s1, TF_S1(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)
+ ld s2, TF_S2(sp)
+ ld s3, TF_S3(sp)
+ ld s4, TF_S4(sp)
+ ld s5, TF_S5(sp)
+ ld s6, TF_S6(sp)
+ ld s7, TF_S7(sp)
+ ld s8, TF_S8(sp)
+ ld s9, TF_S9(sp)
+ ld s10, TF_S10(sp)
+ ld s11, TF_S11(sp)
+ ld t3, TF_T3(sp)
+ ld t4, TF_T4(sp)
+ ld t5, TF_T5(sp)
+ ld t6, TF_T6(sp)
+
+ /* Restore sp last */
+ ld sp, TF_SP(sp)
+
+ sret
+
+/*
+ * userret — return to user mode
+ *
+ * Called from C++ as: userret(TrapFrame* tf)
+ * a0 = pointer to TrapFrame (with user satp already written by caller)
+ *
+ * Precondition: interrupts must be disabled by caller.
+ */
+userret:
+ /* a0 = TrapFrame pointer, use it as base */
+
+ /* Step 1: write back epc -> sepc */
+ ld t0, TF_EPC(a0)
+ csrw sepc, t0
+
+ /* Step 2: write back sstatus */
+ ld t0, TF_SSTATUS(a0)
+ csrw sstatus, t0
+
+ /* Step 3: caller is responsible for switching satp to user page table
+ * and flushing TLB before calling userret. */
+
+ /* Store TrapFrame address into sscratch for next trap entry */
+ csrw sscratch, a0
+
+ /* Use a0 as base to restore all registers */
+ mv tp, a0
+
+ /* Step 4: restore all 31 general-purpose registers */
+ ld ra, TF_RA(tp)
+ ld sp, TF_SP(tp)
+ ld gp, TF_GP(tp)
+ /* defer tp restore — we're using it as base */
+ ld t0, TF_T0(tp)
+ ld t1, TF_T1(tp)
+ ld t2, TF_T2(tp)
+ ld s0, TF_S0(tp)
+ ld s1, TF_S1(tp)
+ ld a0, TF_A0(tp)
+ ld a1, TF_A1(tp)
+ ld a2, TF_A2(tp)
+ ld a3, TF_A3(tp)
+ ld a4, TF_A4(tp)
+ ld a5, TF_A5(tp)
+ ld a6, TF_A6(tp)
+ ld a7, TF_A7(tp)
+ ld s2, TF_S2(tp)
+ ld s3, TF_S3(tp)
+ ld s4, TF_S4(tp)
+ ld s5, TF_S5(tp)
+ ld s6, TF_S6(tp)
+ ld s7, TF_S7(tp)
+ ld s8, TF_S8(tp)
+ ld s9, TF_S9(tp)
+ ld s10, TF_S10(tp)
+ ld s11, TF_S11(tp)
+ ld t3, TF_T3(tp)
+ ld t4, TF_T4(tp)
+ ld t5, TF_T5(tp)
+ ld t6, TF_T6(tp)
+
+ /* Step 5: restore user tp (swap sscratch <-> tp) */
+ /* sscratch currently = TrapFrame address, tp = TrapFrame address */
+ /* We need: tp = user's tp, sscratch = TrapFrame address */
+ /* Load user tp from TrapFrame first, but we can't use any temp reg now.
+ * Use the sscratch swap trick: */
+ ld tp, TF_TP(tp)
+
+ /* sscratch already holds TrapFrame address (set above with csrw) */
+
+ /* Step 6: sret — return to user mode */
+ sret