*-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:
Zihao Yu 2020-02-12 00:52:33 +08:00
parent 955e903bd4
commit bddf8239be
10 changed files with 71 additions and 12 deletions

View File

@ -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
};

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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;

View File

@ -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

View File

@ -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;
}