summaryrefslogtreecommitdiff
path: root/arch/riscv/trap
diff options
context:
space:
mode:
Diffstat (limited to 'arch/riscv/trap')
-rw-r--r--arch/riscv/trap/extable.cc92
-rw-r--r--arch/riscv/trap/fpu.S103
-rw-r--r--arch/riscv/trap/swtch.S96
-rw-r--r--arch/riscv/trap/trap_md.cc241
-rw-r--r--arch/riscv/trap/trap_vector.S283
5 files changed, 815 insertions, 0 deletions
diff --git a/arch/riscv/trap/extable.cc b/arch/riscv/trap/extable.cc
new file mode 100644
index 0000000..57a3dc1
--- /dev/null
+++ b/arch/riscv/trap/extable.cc
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#include <arch/extable.h>
+#include <lume/types.h>
+
+extern struct ExceptionTableEntry __extable_start[];
+extern struct ExceptionTableEntry __extable_end[];
+
+static void swap_extable_entries(struct ExceptionTableEntry *a, struct ExceptionTableEntry *b) {
+ uint64 a_insn = reinterpret_cast<uint64>(&a->insn) + a->insn;
+ uint64 a_fixup = reinterpret_cast<uint64>(&a->fixup) + a->fixup;
+ uint64 b_insn = reinterpret_cast<uint64>(&b->insn) + b->insn;
+ uint64 b_fixup = reinterpret_cast<uint64>(&b->fixup) + b->fixup;
+
+ a->insn = static_cast<int32>(b_insn - reinterpret_cast<uint64>(&a->insn));
+ a->fixup = static_cast<int32>(b_fixup - reinterpret_cast<uint64>(&a->fixup));
+ b->insn = static_cast<int32>(a_insn - reinterpret_cast<uint64>(&b->insn));
+ b->fixup = static_cast<int32>(a_fixup - reinterpret_cast<uint64>(&b->fixup));
+}
+
+static void sort_extable(size_t left, size_t right) {
+ if (left >= right) return;
+
+ size_t mid_idx = left + (right - left) / 2;
+ struct ExceptionTableEntry *pivot = &__extable_start[mid_idx];
+ uint64 pivot_val = reinterpret_cast<uint64>(&pivot->insn) + pivot->insn;
+
+ size_t i = left;
+ size_t j = right;
+
+ while (i <= j) {
+ while ((reinterpret_cast<uint64>(&__extable_start[i].insn) + __extable_start[i].insn) < pivot_val) {
+ i++;
+ }
+ while ((reinterpret_cast<uint64>(&__extable_start[j].insn) + __extable_start[j].insn) > pivot_val) {
+ if (j == 0) {
+ break;
+ }
+ j--;
+ }
+ if (i <= j) {
+ swap_extable_entries(&__extable_start[i], &__extable_start[j]);
+ i++;
+ if (j == 0) {
+ break;
+ }
+ j--;
+ }
+ }
+
+ if (j > left) {
+ sort_extable(left, j);
+ }
+ if (i < right) {
+ sort_extable(i, right);
+ }
+}
+
+void extable_init() {
+ size_t count = __extable_end - __extable_start;
+ if (count <= 1) return;
+ sort_extable(0, count - 1);
+}
+
+uint64 search_extable(uint64 pc) {
+ size_t count = __extable_end - __extable_start;
+ if (count == 0) return 0;
+
+ size_t left = 0;
+ size_t right = count - 1;
+
+ /* Binary search */
+ while (left <= right) {
+ size_t mid = left + (right - left) / 2;
+ const struct ExceptionTableEntry *entry = &__extable_start[mid];
+ uint64 mid_pc = reinterpret_cast<uint64>(&entry->insn) + entry->insn;
+
+ if (mid_pc == pc) {
+ return reinterpret_cast<uint64>(&entry->fixup) + entry->fixup;
+ }
+
+ if (mid_pc < pc) {
+ left = mid + 1;
+ } else {
+ if (mid == 0) break;
+ right = mid - 1;
+ }
+ }
+
+ return 0;
+}
diff --git a/arch/riscv/trap/fpu.S b/arch/riscv/trap/fpu.S
new file mode 100644
index 0000000..90d96d9
--- /dev/null
+++ b/arch/riscv/trap/fpu.S
@@ -0,0 +1,103 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+/*
+ * FPU Save / Restore (RISC-V D extension)
+ *
+ * void fpu_save(FpState* state);
+ * Saves f0-f31 and fcsr to the FpState structure.
+ *
+ * void fpu_restore(FpState* state);
+ * Restores f0-f31 and fcsr from the FpState structure.
+ *
+ * FpState layout: f[0..31] at offset 0 (each 8 bytes), fcsr at offset 256.
+ *
+ * Called from schedule() when switching between tasks that have
+ * used the FPU (sstatus.FS == Dirty on save path).
+ */
+
+ .section .text
+
+/* ======================================================================
+ * void fpu_save(FpState* state); // a0 = state pointer
+ * ====================================================================== */
+ .globl fpu_save
+ .align 2
+fpu_save:
+ fsd f0, 0(a0)
+ fsd f1, 8(a0)
+ fsd f2, 16(a0)
+ fsd f3, 24(a0)
+ fsd f4, 32(a0)
+ fsd f5, 40(a0)
+ fsd f6, 48(a0)
+ fsd f7, 56(a0)
+ fsd f8, 64(a0)
+ fsd f9, 72(a0)
+ fsd f10, 80(a0)
+ fsd f11, 88(a0)
+ fsd f12, 96(a0)
+ fsd f13, 104(a0)
+ fsd f14, 112(a0)
+ fsd f15, 120(a0)
+ fsd f16, 128(a0)
+ fsd f17, 136(a0)
+ fsd f18, 144(a0)
+ fsd f19, 152(a0)
+ fsd f20, 160(a0)
+ fsd f21, 168(a0)
+ fsd f22, 176(a0)
+ fsd f23, 184(a0)
+ fsd f24, 192(a0)
+ fsd f25, 200(a0)
+ fsd f26, 208(a0)
+ fsd f27, 216(a0)
+ fsd f28, 224(a0)
+ fsd f29, 232(a0)
+ fsd f30, 240(a0)
+ fsd f31, 248(a0)
+ frcsr t0
+ sw t0, 256(a0)
+ ret
+
+/* ======================================================================
+ * void fpu_restore(FpState* state); // a0 = state pointer
+ * ====================================================================== */
+ .globl fpu_restore
+ .align 2
+fpu_restore:
+ fld f0, 0(a0)
+ fld f1, 8(a0)
+ fld f2, 16(a0)
+ fld f3, 24(a0)
+ fld f4, 32(a0)
+ fld f5, 40(a0)
+ fld f6, 48(a0)
+ fld f7, 56(a0)
+ fld f8, 64(a0)
+ fld f9, 72(a0)
+ fld f10, 80(a0)
+ fld f11, 88(a0)
+ fld f12, 96(a0)
+ fld f13, 104(a0)
+ fld f14, 112(a0)
+ fld f15, 120(a0)
+ fld f16, 128(a0)
+ fld f17, 136(a0)
+ fld f18, 144(a0)
+ fld f19, 152(a0)
+ fld f20, 160(a0)
+ fld f21, 168(a0)
+ fld f22, 176(a0)
+ fld f23, 184(a0)
+ fld f24, 192(a0)
+ fld f25, 200(a0)
+ fld f26, 208(a0)
+ fld f27, 216(a0)
+ fld f28, 224(a0)
+ fld f29, 232(a0)
+ fld f30, 240(a0)
+ fld f31, 248(a0)
+ lw t0, 256(a0)
+ fscsr t0
+ ret
diff --git a/arch/riscv/trap/swtch.S b/arch/riscv/trap/swtch.S
new file mode 100644
index 0000000..dbae43d
--- /dev/null
+++ b/arch/riscv/trap/swtch.S
@@ -0,0 +1,96 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+/*
+ * Context Switch & Kernel Thread Trampoline (RISC-V 64)
+ *
+ * context_switch: saves/restores callee-saved registers.
+ * kthread_entry_trampoline: entry point for newly created kernel threads.
+ * After context_switch loads the new Context, execution lands here via
+ * Context.ra = kthread_entry_trampoline. The entry function and argument
+ * are in s0 and s1 respectively, set by sched_init/create_tcb.
+ *
+ * The trampoline calls the MI-layer kthread_entry(entry, arg).
+ *
+ * Reference: docs/specs/scheduler.md §6
+ */
+
+ .section .text
+
+/* ========================================================================
+ * void context_switch(Context* old_ctx, Context* next_ctx);
+ * a0 = old_ctx_ptr, a1 = next_ctx_ptr
+ * ======================================================================== */
+ .globl context_switch
+ .align 2
+context_switch:
+ sd ra, 0(a0)
+ sd sp, 8(a0)
+ sd s0, 16(a0)
+ sd s1, 24(a0)
+ sd s2, 32(a0)
+ sd s3, 40(a0)
+ sd s4, 48(a0)
+ sd s5, 56(a0)
+ sd s6, 64(a0)
+ sd s7, 72(a0)
+ sd s8, 80(a0)
+ sd s9, 88(a0)
+ sd s10, 96(a0)
+ sd s11, 104(a0)
+
+ ld ra, 0(a1)
+ ld sp, 8(a1)
+ ld s0, 16(a1)
+ ld s1, 24(a1)
+ ld s2, 32(a1)
+ ld s3, 40(a1)
+ ld s4, 48(a1)
+ ld s5, 56(a1)
+ ld s6, 64(a1)
+ ld s7, 72(a1)
+ ld s8, 80(a1)
+ ld s9, 88(a1)
+ ld s10, 96(a1)
+ ld s11, 104(a1)
+
+ ret
+
+/* ========================================================================
+ * kthread_entry_trampoline
+ *
+ * Reached via context_switch when a new kthread is first scheduled.
+ * Context was set up by create_tcb():
+ * s0 = entry function pointer
+ * s1 = argument pointer
+ *
+ * Calls the MI-layer C++ function:
+ * void kthread_entry(void (*entry)(void*), void* arg);
+ * ======================================================================== */
+ .globl kthread_entry_trampoline
+ .align 2
+kthread_entry_trampoline:
+ mv a0, s0 /* a0 = entry function */
+ mv a1, s1 /* a1 = argument */
+ call kthread_entry /* MI-layer C++ function */
+
+ /* kthread_entry should never return, but if it does: */
+ wfi
+ j kthread_entry_trampoline + 12 /* loop on wfi */
+
+/* ========================================================================
+ * ret_from_fork
+ *
+ * Trampoline for newly forked processes.
+ * Context is set up by fork() so that s0 contains the TrapFrame pointer.
+ *
+ * Calls the MI-layer C++ function:
+ * void fork_child_entry(TrapFrame* tf);
+ * ======================================================================== */
+ .globl ret_from_fork
+ .align 2
+ret_from_fork:
+ mv a0, s0 /* a0 = TrapFrame pointer */
+ call fork_child_entry /* does not return, eventually calls userret */
+ wfi
+ j ret_from_fork + 8 \ No newline at end of file
diff --git a/arch/riscv/trap/trap_md.cc b/arch/riscv/trap/trap_md.cc
new file mode 100644
index 0000000..e30aea6
--- /dev/null
+++ b/arch/riscv/trap/trap_md.cc
@@ -0,0 +1,241 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+/*
+ * Trap MD Layer (RISC-V 64)
+ *
+ * All architecture-specific trap logic lives here:
+ * - scause/stval reading (CSR access)
+ * - scause → MI TrapCause translation
+ * - RISC-V specific fixups (ecall epc += 4, ebreak epc += 2)
+ * - stvec setup (trap_init / trap_init_ap)
+ * - kernel_trap_handler (traps from S-mode)
+ *
+ * MI layer (kernel/trap.cc) contains ZERO arch-specific code.
+ *
+ * Reference: docs/specs/trap.md §7
+ */
+
+#include <lume/trap.h>
+#include <lume/irq.h>
+#include <lume/klog.h>
+#include <lume/console.h>
+#include <lume/kprintf.h>
+#include <lume/sched.h>
+#include <lume/task.h>
+#include <arch/trap.h>
+#include <arch/trapframe.h>
+#include <arch/cpu.h>
+#include <arch/extable.h>
+
+/* PLIC interface (drivers/plic.cc) */
+uint32 plic_claim();
+void plic_complete(uint32 irq);
+
+/* trap_vector.S: assembly entry point */
+extern "C" void trap_vector();
+
+/* ========================================================================
+ * scause → TrapCause translation (RISC-V specific)
+ * ======================================================================== */
+
+static TrapCause scause_to_trap_cause(uint64 scause)
+{
+ if (arch::trap::is_interrupt(scause)) {
+ switch (arch::trap::cause_code(scause)) {
+ case IRQ_S_TIMER: return TrapCause::IrqTimer;
+ case IRQ_S_SOFTWARE: return TrapCause::IrqSoftware;
+ case IRQ_S_EXTERNAL: return TrapCause::IrqExternal;
+ default: return TrapCause::Unknown;
+ }
+ }
+
+ /* Exception (synchronous) */
+ switch (scause) {
+ case EXC_INST_PAGE_FAULT: return TrapCause::PageFaultExec;
+ case EXC_LOAD_PAGE_FAULT: return TrapCause::PageFaultLoad;
+ case EXC_STORE_PAGE_FAULT: return TrapCause::PageFaultStore;
+ case EXC_ECALL_FROM_U: return TrapCause::Syscall;
+ case EXC_ILLEGAL_INST: return TrapCause::IllegalInst;
+ case EXC_BREAKPOINT: return TrapCause::Breakpoint;
+
+ case EXC_INST_ACCESS_FAULT:
+ case EXC_LOAD_ACCESS_FAULT:
+ case EXC_STORE_ACCESS_FAULT:
+ return TrapCause::AccessFault;
+
+ case EXC_INST_MISALIGNED:
+ case EXC_LOAD_MISALIGNED:
+ case EXC_STORE_MISALIGNED:
+ return TrapCause::AlignFault;
+
+ default:
+ return TrapCause::Unknown;
+ }
+}
+
+TrapCause trap_translate_scause(uint64 scause)
+{
+ return scause_to_trap_cause(scause);
+}
+
+static TrapOrigin origin_from_sstatus(uint64 sstatus)
+{
+ constexpr uint64 kSstatusSPP = (1ULL << 8);
+ return (sstatus & kSstatusSPP) ? TrapOrigin::FromKernel
+ : TrapOrigin::FromUser;
+}
+
+static TrapClass classify_trap(uint64 scause, TrapCause cause)
+{
+ if (arch::trap::is_interrupt(scause)) {
+ return TrapClass::Interrupt;
+ }
+ if (cause == TrapCause::Syscall) {
+ return TrapClass::Syscall;
+ }
+ if (cause == TrapCause::Unknown) {
+ return TrapClass::Unknown;
+ }
+ return TrapClass::Exception;
+}
+
+static TrapInfo build_trap_info(arch::TrapFrame* tf,
+ uint64 scause,
+ uint64 stval,
+ TrapOrigin origin)
+{
+ TrapInfo info{};
+ info.cause = scause_to_trap_cause(scause);
+ info.cls = classify_trap(scause, info.cause);
+ info.origin = origin;
+ info.val = stval;
+ info.epc = tf ? tf->epc : 0;
+ info.scause = scause;
+ return info;
+}
+
+/* ========================================================================
+ * trap_dispatch — user-mode trap entry (called from trap_vector.S)
+ *
+ * Reads RISC-V CSRs, translates to MI TrapCause, applies arch-specific
+ * fixups, then calls MI trap_handle().
+ *
+ * After trap_handle returns, we must return to user mode via userret().
+ * The assembly jalr t3 that called us has NO return-to-user code after it,
+ * so trap_dispatch must never return normally.
+ * ======================================================================== */
+
+extern "C" void userret(arch::TrapFrame* tf);
+
+extern "C" [[noreturn]] void trap_dispatch(arch::TrapFrame* tf)
+{
+ /* Switch stvec to kernelvec immediately so that any traps during
+ * kernel execution (timer IRQs, page faults from copy_from_user)
+ * go through the kernel trap entry, not the user trap entry.
+ * This matches Linux which sets stvec to handle_exception (kernel)
+ * at the top of the user trap path. */
+ extern void kernelvec();
+ arch::trap::set_vector(reinterpret_cast<uint64>(&kernelvec));
+
+ /* Enable SUM so S-mode can access user pages (PTE_U).
+ * Without this, any dereference of user-space pointers in syscall
+ * handlers (sys_write buf, sys_read buf, etc.) would page fault. */
+ arch::trap::enable_sum();
+
+ uint64 scause = arch::trap::get_cause();
+ uint64 stval = arch::trap::get_value();
+
+ TrapOrigin origin = origin_from_sstatus(tf->sstatus);
+ TrapInfo info = build_trap_info(tf, scause, stval, origin);
+
+ /* Per-trap trace from user mode. Stored at KLOG_DEBUG / KLOG_SUBSYS_TRAP
+ * so it lives in the ring buffer for postmortem but stays off the console
+ * by default. No more in-band 20-trap window — flip console_level or
+ * dump the ring on demand instead. */
+ if (origin == TrapOrigin::FromUser) {
+ pr_debug_s(KLOG_SUBSYS_TRAP,
+ "trap: cause=%u epc=%llx val=%llx\n",
+ static_cast<unsigned>(info.cause), info.epc, info.val);
+ }
+
+ /* Save trapframe in TCB for syscall handlers (like fork) */
+ TaskControlBlock* curr = current_task();
+ if (curr) {
+ curr->trapframe = tf;
+ }
+
+ /* RISC-V ecall fix: ecall does not increment sepc.
+ * Advance epc so sret returns to the instruction AFTER ecall. */
+ if (info.cause == TrapCause::Syscall) {
+ tf->epc += 4;
+ info.epc = tf->epc;
+ }
+
+ /* RISC-V ebreak fix: advance past the C.EBREAK instruction (2 bytes).
+ * This is arch-specific because instruction size depends on the ISA. */
+ if (info.cause == TrapCause::Breakpoint) {
+ tf->epc += 2;
+ info.epc = tf->epc;
+ }
+
+ /* Call MI-layer dispatcher (zero inline assembly from here on) */
+ trap_handle(tf, info);
+
+ /* Return to user mode.
+ * Set stvec back to trap_vector for the next user-mode trap,
+ * then jump to userret which restores registers and does sret. */
+ arch::trap::set_vector(reinterpret_cast<uint64>(&trap_vector));
+ userret(tf);
+ __builtin_unreachable();
+}
+
+/* ========================================================================
+ * kernel_trap_handler — traps taken while already in S-mode
+ *
+ * For Phase 2, only timer and external interrupts are expected.
+ * Exceptions in S-mode are always fatal.
+ *
+ * This belongs in MD because it reads scause directly.
+ * ======================================================================== */
+
+extern "C" void kernel_trap_handler(arch::TrapFrame* tf)
+{
+ uint64 scause = arch::trap::get_cause();
+ uint64 stval = arch::trap::get_value();
+
+ if (scause == EXC_LOAD_PAGE_FAULT || scause == EXC_STORE_PAGE_FAULT) {
+ uint64 fixup = search_extable(tf->epc);
+ if (fixup != 0) {
+ /* Exception table entry found: redirect execution to fixup code */
+ tf->epc = fixup;
+ return;
+ }
+ }
+
+ TrapInfo info = build_trap_info(tf, scause, stval, TrapOrigin::FromKernel);
+ trap_handle(tf, info);
+}
+
+/* kernelvec assembly is in trap_vector.S */
+extern "C" void kernelvec();
+
+/* ========================================================================
+ * trap_init() — BSP: set stvec, init IrqManager
+ * ======================================================================== */
+
+void trap_init()
+{
+ g_irq_manager.init();
+ arch::trap::set_vector(reinterpret_cast<uint64>(&kernelvec));
+}
+
+/* ========================================================================
+ * trap_init_ap() — AP: set this CPU's stvec
+ * ======================================================================== */
+
+void trap_init_ap()
+{
+ arch::trap::set_vector(reinterpret_cast<uint64>(&kernelvec));
+}
+
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