*-nemu,cte: support stack switching from user to kernel
* To support stack switching from user to kernel with a software-based method, we need the following (1) something to identify whether the trap comes from user or kernel space (2) the kernel stack to switch to (3) something to identify whether the trap returns to user or kernel space (4) the user stack to resume to * In mips32, + We use $k0 to achieve (1) + (2). When a trap is taken, first examine $k0. If it is 0, the trap comes from kernel space and there is no need to perform stack switching. If it is not 0, it must store the kernel stack to switch to. Then we set $k0 to 0 to support nested traps. + We use $sp's saving slot in the context structure to achive (3) + (4). If it is 0, the trap is going to return to kernel and there is no need to perform stack switching. Also there is no need to update $k0 since it is still 0. If it is not 0, it must store the user stack to switch to. Before switching back to user stack, we should also set $k0 to the current $sp as the kernel stack. At the next time a trap is taken, we will switch to such $sp. + The remaining is to correct initialize $k0 and $sp's saving slot. * riscv32 is similar to mips32, except that we use sscratch to achieve (1) + (2) * A real x86 uses DPL + TSS to achieve (1) + (2), and iret to achieve (3) + (4). But this is complicated for students. Therefore we use a nearly software-based solution. + We use a global variable __am_ksp to achieve (1) + (2), and a new member `usp` which next to `eip` in the context structure to achieve (3) + (4). + We will still push part of context on the user stack before we switch to the kernel stack when a trap is taken. There is no way to avoid this, since x86 leverages hardware stack. We should move the values pushed on the user stack to the kernel stack after we switch to it, and store the stack pointer right before the trap is taken to `usp`, pretending the whole context is pushed totally on the kernel stack. + Trouble comes when we are going to return to user space. Switching back to user stack and popping eflags/cs/eip should be performed atomically. We can never achieve this with any software-based solution. Therefore we modify the behavior of the iret instruction to a customized version: temp <- pop(); standard_iret(); if (temp != 0) esp <- temp; This is why we require the new `usp` member should be next to `eip` in the context structure. + Note that the customized version of iret still does not write memory. Thus it will not hurt the process of DiffTest in NEMU by using `difftest_skip_ref()` to let QEMU skip the iret instruciton and perform register synchronization. But we need to synchronize eflags, too.
This commit is contained in:
parent
955e903bd4
commit
bddf8239be
|
@ -6,6 +6,7 @@ struct _Context {
|
|||
uintptr_t edi, esi, ebp, esp;
|
||||
uintptr_t ebx, edx, ecx, eax; // Register saved by pushal
|
||||
int irq; // # of irq
|
||||
uintptr_t usp;
|
||||
uintptr_t eip, cs, eflags; // Execution state before trap
|
||||
};
|
||||
|
||||
|
|
|
@ -52,6 +52,8 @@ int _cte_init(_Context*(*handler)(_Event, _Context*)) {
|
|||
*(uint32_t *)0x80000000 = instr; // TLB refill exception
|
||||
*(uint32_t *)(0x80000000 + 4) = 0; // delay slot
|
||||
|
||||
asm volatile("move $k0, $zero");
|
||||
|
||||
// register event handler
|
||||
user_handler = handler;
|
||||
|
||||
|
@ -62,6 +64,7 @@ _Context *_kcontext(_Area kstack, void (*entry)(void *), void *arg) {
|
|||
_Context *c = (_Context*)kstack.end - 1;
|
||||
c->epc = (uintptr_t)entry;
|
||||
c->GPR2 = (uintptr_t)arg; // a0
|
||||
c->gpr[29] = 0; // sp slot, used as usp
|
||||
return c;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,15 +22,22 @@ f(30) f(31)
|
|||
#define CP0_CAUSE 13
|
||||
#define CP0_EPC 14
|
||||
|
||||
#define SWAP(r1, r2, t) move $t, $r1; move $r1, $r2; move $r2, $t;
|
||||
|
||||
.set noat
|
||||
.globl __am_asm_trap
|
||||
__am_asm_trap:
|
||||
move $k0, $sp
|
||||
beqz $k0, in_kernel
|
||||
SWAP(k0, sp, k1)
|
||||
|
||||
in_kernel:
|
||||
addiu $sp, $sp, -CONTEXT_SIZE
|
||||
|
||||
MAP(REGS, PUSH)
|
||||
|
||||
sw $k0, OFFSET_SP($sp)
|
||||
sw $k0, OFFSET_SP($sp) # k0 = (from user ? usp : 0)
|
||||
move $k0, $zero
|
||||
|
||||
mflo $t0
|
||||
mfhi $t1
|
||||
mfc0 $t2, $CP0_CAUSE
|
||||
|
@ -68,6 +75,12 @@ __am_asm_trap:
|
|||
|
||||
MAP(REGS, POP)
|
||||
|
||||
addiu $sp, $sp, CONTEXT_SIZE
|
||||
lw $k0, OFFSET_SP($sp) # k0 = (from user ? usp : 0)
|
||||
addiu $sp, $sp, CONTEXT_SIZE # ksp
|
||||
|
||||
beqz $k0, return
|
||||
# return to user
|
||||
SWAP(k0, sp, k1)
|
||||
|
||||
return:
|
||||
eret
|
||||
|
|
|
@ -68,6 +68,7 @@ _Context *_ucontext(_AddressSpace *as, _Area kstack, void *entry) {
|
|||
c->as = as;
|
||||
c->epc = (uintptr_t)entry;
|
||||
c->status = 0x1;
|
||||
c->gpr[29] = 1; // sp slot, used as usp, non-zero
|
||||
return c;
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,8 @@ int _cte_init(_Context*(*handler)(_Event, _Context*)) {
|
|||
// initialize exception entry
|
||||
asm volatile("csrw stvec, %0" : : "r"(__am_asm_trap));
|
||||
|
||||
asm volatile("csrw sscratch, zero");
|
||||
|
||||
// register event handler
|
||||
user_handler = handler;
|
||||
|
||||
|
@ -51,6 +53,7 @@ _Context *_kcontext(_Area kstack, void (*entry)(void *), void *arg) {
|
|||
c->epc = (uintptr_t)entry;
|
||||
c->GPR2 = (uintptr_t)arg;
|
||||
c->status = 0x000c0100;
|
||||
c->gpr[2] = 0; // sp slot, used as usp
|
||||
return c;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ f(30) f(31)
|
|||
#define PUSH(n) sw concat(x, n), (n * 4)(sp);
|
||||
#define POP(n) lw concat(x, n), (n * 4)(sp);
|
||||
|
||||
#define CONTEXT_SIZE ((31 + 4) * 4)
|
||||
#define CONTEXT_SIZE ((32 + 3) * 4)
|
||||
#define OFFSET_SP ( 2 * 4)
|
||||
#define OFFSET_CAUSE (32 * 4)
|
||||
#define OFFSET_STATUS (33 * 4)
|
||||
|
@ -20,12 +20,17 @@ f(30) f(31)
|
|||
|
||||
.globl __am_asm_trap
|
||||
__am_asm_trap:
|
||||
csrrw sp, sscratch, sp
|
||||
bnez sp, save_context
|
||||
# trap from kernel, restore the original sp
|
||||
csrrw sp, sscratch, sp
|
||||
|
||||
save_context:
|
||||
addi sp, sp, -CONTEXT_SIZE
|
||||
|
||||
MAP(REGS, PUSH)
|
||||
|
||||
mv t0, sp
|
||||
addi t0, t0, CONTEXT_SIZE
|
||||
csrrw t0, sscratch, x0 # t0 = (from user ? usp : 0)
|
||||
sw t0, OFFSET_SP(sp)
|
||||
|
||||
csrr t0, scause
|
||||
|
@ -50,4 +55,12 @@ __am_asm_trap:
|
|||
|
||||
addi sp, sp, CONTEXT_SIZE
|
||||
|
||||
csrw sscratch, sp # ksp
|
||||
lw sp, (OFFSET_SP - CONTEXT_SIZE)(sp) # sp = (from user ? usp : 0)
|
||||
|
||||
bnez sp, return
|
||||
# return to kernel
|
||||
csrrw sp, sscratch, sp
|
||||
|
||||
return:
|
||||
sret
|
||||
|
|
|
@ -95,5 +95,6 @@ _Context *_ucontext(_AddressSpace *as, _Area kstack, void *entry) {
|
|||
c->as = as;
|
||||
c->epc = (uintptr_t)entry;
|
||||
c->status = 0x000c0120;
|
||||
c->gpr[2] = 1; // sp slot, used as usp, non-zero is ok
|
||||
return c;
|
||||
}
|
||||
|
|
|
@ -17,9 +17,18 @@ void __am_vecnull();
|
|||
void __am_get_cur_as(_Context *c);
|
||||
void __am_switch(_Context *c);
|
||||
|
||||
uintptr_t __am_ksp = 0;
|
||||
|
||||
_Context* __am_irq_handle(_Context *c) {
|
||||
__am_get_cur_as(c);
|
||||
|
||||
if (__am_ksp != 0) {
|
||||
// trap from user
|
||||
memcpy(&c->irq, (void *)__am_ksp, 5 * sizeof(uintptr_t));
|
||||
c->usp = __am_ksp + 5 * sizeof(uintptr_t);
|
||||
__am_ksp = 0;
|
||||
}
|
||||
|
||||
_Context *next = c;
|
||||
if (user_handler) {
|
||||
_Event ev = {0};
|
||||
|
@ -38,6 +47,11 @@ _Context* __am_irq_handle(_Context *c) {
|
|||
|
||||
__am_switch(next);
|
||||
|
||||
if (next->usp != 0) {
|
||||
// return to user, set ksp for the next use
|
||||
__am_ksp = (uintptr_t)(next + 1);
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
|
@ -70,6 +84,7 @@ _Context* _kcontext(_Area kstack, void (*entry)(void *), void *arg) {
|
|||
c->cs = 0x8;
|
||||
c->eip = (uintptr_t)__am_kcontext_start;
|
||||
c->eflags = 0x2 | FL_IF;
|
||||
c->usp = 0;
|
||||
c->GPR1 = (uintptr_t)arg;
|
||||
c->GPR2 = (uintptr_t)entry;
|
||||
return c;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#----|------------entry------------|---irq id---|-----handler-----|
|
||||
.globl __am_vecsys; __am_vecsys: pushl $0x80; jmp __am_asm_trap
|
||||
.globl __am_vectrap; __am_vectrap: pushl $0x81; jmp __am_asm_trap
|
||||
.globl __am_irq0; __am_irq0: pushl $32; jmp __am_asm_trap
|
||||
.globl __am_vecnull; __am_vecnull: pushl $-1; jmp __am_asm_trap
|
||||
#----|------------entry------------|---usp---|---irq id---|-----handler-----|
|
||||
.globl __am_vecsys; __am_vecsys: pushl $0; pushl $0x80; jmp __am_asm_trap
|
||||
.globl __am_vectrap; __am_vectrap: pushl $0; pushl $0x81; jmp __am_asm_trap
|
||||
.globl __am_irq0; __am_irq0: pushl $0; pushl $32; jmp __am_asm_trap
|
||||
.globl __am_vecnull; __am_vecnull: pushl $0; pushl $-1; jmp __am_asm_trap
|
||||
|
||||
.globl __am_kcontext_start
|
||||
__am_kcontext_start:
|
||||
|
@ -10,6 +10,14 @@ __am_kcontext_start:
|
|||
call *%ebx
|
||||
|
||||
__am_asm_trap:
|
||||
cmpl $0, __am_ksp
|
||||
je in_kernel
|
||||
|
||||
# switch to kernel stack
|
||||
xchg %esp, __am_ksp
|
||||
subl $20, %esp
|
||||
|
||||
in_kernel:
|
||||
pushal
|
||||
|
||||
pushl $0
|
||||
|
@ -23,4 +31,4 @@ __am_asm_trap:
|
|||
popal
|
||||
addl $4, %esp
|
||||
|
||||
iret
|
||||
iret # customized with usp
|
||||
|
|
|
@ -107,5 +107,6 @@ _Context* _ucontext(_AddressSpace *as, _Area kstack, void *entry) {
|
|||
c->cs = 0x8;
|
||||
c->eip = (uintptr_t)entry;
|
||||
c->eflags = 0x2 | FL_IF;
|
||||
c->usp = (uintptr_t)kstack.end; // non-zero but should be used to safely construct context
|
||||
return c;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue