Kernel/riscv64: Add support for handling traps from userspace

This commit also removes the unnecessary user_sp RegisterState member.
We never use the kernel stack pointer on entry, so we can simply always
store the stack pointer of the previous privilege mode in sp.

Also remove the sp member from mcontext, as RISC-V doesn't have a
dedicated stack pointer register.
sp is defined to be x2 (x[1] in our case) by the ABI.

I probably accidentally included sp while copying the struct from
aarch64.
This commit is contained in:
Sönke Holz 2024-03-05 18:22:15 +01:00 committed by Andrew Kaster
parent afe9a12412
commit 66f8d0f031
Notes: sideshowbarker 2024-07-16 22:51:10 +09:00
4 changed files with 118 additions and 61 deletions

View File

@ -18,7 +18,7 @@ VALIDATE_IS_RISCV64()
namespace Kernel {
struct RegisterState {
struct alignas(16) RegisterState {
u64 x[31];
RISCV64::CSR::SSTATUS sstatus;
@ -26,10 +26,10 @@ struct RegisterState {
RISCV64::CSR::SCAUSE scause;
u64 stval;
u64 user_sp;
FlatPtr userspace_sp() const { return user_sp; }
void set_userspace_sp(FlatPtr value) { user_sp = value; }
// x86_64 uses its additional RegisterState member "userspace_rsp" here, which is also invalid if no privilege mode change happened.
// On RISC-V, we only have one sp member, and regardless of the previous privilege mode, we always use this member here.
FlatPtr userspace_sp() const { return x[1]; }
void set_userspace_sp(FlatPtr value) { x[1] = value; }
FlatPtr ip() const { return sepc; }
void set_ip(FlatPtr value) { sepc = value; }
@ -68,7 +68,6 @@ inline void copy_kernel_registers_into_ptrace_registers(PtraceRegisters& ptrace_
for (auto i = 0; i < 31; i++)
ptrace_regs.x[i] = kernel_regs.x[i];
ptrace_regs.sp = kernel_regs.userspace_sp();
ptrace_regs.pc = kernel_regs.ip();
}
@ -77,7 +76,6 @@ inline void copy_ptrace_registers_into_kernel_registers(RegisterState& kernel_re
for (auto i = 0; i < 31; i++)
kernel_regs.x[i] = ptrace_regs.x[i];
kernel_regs.set_userspace_sp(ptrace_regs.sp);
kernel_regs.set_ip(ptrace_regs.pc);
}

View File

@ -71,4 +71,8 @@ Lclear_bss_done:
li t5, 0
li t6, 0
// The trap handler expects sscratch to be zero if we are in supervisor mode.
// sscratch contains the kernel stack pointer if we are in user mode.
csrw sscratch, zero
tail pre_init

View File

@ -14,7 +14,6 @@ extern "C" {
struct __attribute__((packed)) __mcontext {
uint64_t x[31];
uint64_t sp;
uint64_t pc;
};

View File

@ -15,23 +15,12 @@
#define SEPC_SLOT (32 * 8)
#define SCAUSE_SLOT (33 * 8)
#define STVAL_SLOT (34 * 8)
#define USERSPACE_SP_SLOT (35 * 8)
.extern trap_handler
.p2align 2
.global asm_trap_handler
asm_trap_handler:
// FIXME: Handle traps from userspace
// Save the current register state to the current stack
// and enter the C++ trap handler
// Allocate stack space for trap frame
addi sp, sp, -REGISTER_STATE_SIZE
.macro save_gpr_state_except_sp_on_stack
sd x1, 0*8(sp)
sd x2, 1*8(sp)
// sp
sd x3, 2*8(sp)
sd x4, 3*8(sp)
sd x5, 4*8(sp)
@ -61,47 +50,9 @@ asm_trap_handler:
sd x29, 28*8(sp)
sd x30, 29*8(sp)
sd x31, 30*8(sp)
.endm
// Let's save some special registers
csrr t0, sstatus
sd t0, SSTATUS_SLOT(sp)
csrr t0, sepc
sd t0, SEPC_SLOT(sp)
// We also have to save those registers as interrupts are enabled during the page fault handling code.
// A page fault exception may be reported as an interrupt in the register dump, if we wouldn't do that.
csrr t0, scause
sd t0, SCAUSE_SLOT(sp)
csrr t0, stval
sd t0, STVAL_SLOT(sp)
// TODO
sd zero, USERSPACE_SP_SLOT(sp)
// Set up TrapFrame struct on the stack
mv t0, sp
addi sp, sp, -16
sd t0, 1*8(sp)
sd zero, 0*8(sp)
// Move stack pointer into first argument register
// and jump to the C++ trap handler
mv a0, sp
call trap_handler
.global restore_context_and_sret
restore_context_and_sret:
// Remove TrapFrame from the stack
addi sp, sp, 16
// Restore special registers first
ld t0, SSTATUS_SLOT(sp)
csrw sstatus, t0
ld t0, SEPC_SLOT(sp)
csrw sepc, t0
.macro load_gpr_state_except_sp_from_stack
ld x1, 0*8(sp)
// sp
ld x3, 2*8(sp)
@ -133,6 +84,111 @@ restore_context_and_sret:
ld x29, 28*8(sp)
ld x30, 29*8(sp)
ld x31, 30*8(sp)
.endm
.p2align 2
.global asm_trap_handler
asm_trap_handler:
// We entered here from either the kernel or userland,
// so we have to find out if we came here from userland and if so, switch to the kernel stack.
// Swap the contents of sscratch and sp.
csrrw sp, sscratch, sp
// sp now contains the value of sscratch when we entered the trap handler.
// When this value is 0, we were already in supervisor (kernel) mode.
// Otherwise, the value in sp is now the kernel stack and sscratch contains the user stack pointer.
beqz sp, .Ltrap_is_from_kernel
j .Ltrap_is_from_userland
.Ltrap_is_from_kernel:
// Store 0 in sscratch and write the value inside sscratch (the kernel stack pointer) to sp.
csrrw sp, sscratch, zero
.Ltrap_is_from_userland:
// sscratch now contains the user stack pointer, or 0 if the trap was from supervisor mode.
// sp points to the kernel stack.
// Save the current register state on the kernel stack.
// Allocate stack space for a RegisterState struct.
addi sp, sp, -REGISTER_STATE_SIZE
save_gpr_state_except_sp_on_stack
// Save some CSRs to correctly handle the trap.
csrr t0, sepc
sd t0, SEPC_SLOT(sp)
csrr t0, sstatus
sd t0, SSTATUS_SLOT(sp)
// Also store these CSRs to be able to display the state of them before trap entry.
// We also might get an interrupt while handling page faults, so scause and stval would be changed by the interrupt.
csrr t0, scause
sd t0, SCAUSE_SLOT(sp)
csrr t0, stval
sd t0, STVAL_SLOT(sp)
// Read the saved stack pointer from sscratch (which is 0 if the trap is from supervisor mode)
// and set sscratch to 0, as we are currently in the kernel.
csrrw t0, sscratch, zero
// Save the user or kernel stack pointer in the RegisterState struct.
bnez t0, 1f
mv t0, sp
1:
sd t0, 1*8(sp)
// Set up a TrapFrame struct on the stack.
mv t0, sp
addi sp, sp, -16
sd zero, 0*8(sp)
sd t0, 1*8(sp)
// Move the stack pointer into the first argument register
// and jump to the C++ trap handler.
mv a0, sp
call trap_handler
.global restore_context_and_sret
restore_context_and_sret:
// Remove the TrapFrame from the stack.
addi sp, sp, 16
// Restore some CSRs first.
ld t0, SSTATUS_SLOT(sp)
csrw sstatus, t0
ld t0, SEPC_SLOT(sp)
csrw sepc, t0
// Find out to which privilege mode we have to return to.
csrr t0, sstatus
srl t0, t0, 8 // SPP (previous privilege mode)
andi t0, t0, 1
beqz t0, .Lreturn_to_user
// Return to supervisor mode.
csrw sscratch, zero
load_gpr_state_except_sp_from_stack
// Remove the RegisterState struct from the kernel stack.
addi sp, sp, REGISTER_STATE_SIZE
sret
.Lreturn_to_user:
// Store sp with the RegisterState struct removed to sscratch.
addi t0, sp, REGISTER_STATE_SIZE
csrw sscratch, t0
load_gpr_state_except_sp_from_stack
// Load the user stack pointer from the RegisterState struct on the kernel stack.
ld sp, 1*8(sp)
sret