diff options
Diffstat (limited to 'arch/riscv/trap')
| -rw-r--r-- | arch/riscv/trap/extable.cc | 92 | ||||
| -rw-r--r-- | arch/riscv/trap/fpu.S | 103 | ||||
| -rw-r--r-- | arch/riscv/trap/swtch.S | 96 | ||||
| -rw-r--r-- | arch/riscv/trap/trap_md.cc | 241 | ||||
| -rw-r--r-- | arch/riscv/trap/trap_vector.S | 283 |
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 |
