Appearance
trap.c
#include "types.h"
#include "param.h"
#include "memlayout.h"
#include "riscv.h"
#include "spinlock.h"
#include "proc.h"
#include "defs.h"
struct spinlock tickslock;
uint ticks;
extern char trampoline[], uservec[], userret[];
TIP
in kernelvec.S, calls kerneltrap().
void kernelvec();
extern int devintr();
void
trapinit(void)
{
initlock(&tickslock, "time");
}
TIP
set up to take exceptions and traps while in the kernel.
void
trapinithart(void)
{
w_stvec((uint64)kernelvec);
}
TIP
handle an interrupt, exception, or system call from user space. called from trampoline.S
void
usertrap(void)
{
int which_dev = 0;
if((r_sstatus() & SSTATUS_SPP) != 0)
panic("usertrap: not from user mode");
TIP
send interrupts and exceptions to kerneltrap(), since we're now in the kernel.
w_stvec((uint64)kernelvec);
struct proc *p = myproc();
TIP
save user program counter.
p->trapframe->epc = r_sepc();
if(r_scause() == 8){
TIP
system call
if(killed(p))
exit(-1);
TIP
sepc points to the ecall instruction, but we want to return to the next instruction.
p->trapframe->epc += 4;
TIP
an interrupt will change sepc, scause, and sstatus, so enable only now that we're done with those registers.
intr_on();
syscall();
} else if((which_dev = devintr()) != 0){
TIP
ok
} else {
printf("usertrap(): unexpected scause 0x%lx pid=%d\n", r_scause(), p->pid);
printf(" sepc=0x%lx stval=0x%lx\n", r_sepc(), r_stval());
setkilled(p);
}
if(killed(p))
exit(-1);
TIP
give up the CPU if this is a timer interrupt.
if(which_dev == 2)
yield();
usertrapret();
}
TIP
return to user space
void
usertrapret(void)
{
struct proc *p = myproc();
TIP
we're about to switch the destination of traps from kerneltrap() to usertrap(), so turn off interrupts until we're back in user space, where usertrap() is correct.
intr_off();
TIP
send syscalls, interrupts, and exceptions to uservec in trampoline.S
uint64 trampoline_uservec = TRAMPOLINE + (uservec - trampoline);
w_stvec(trampoline_uservec);
TIP
set up trapframe values that uservec will need when the process next traps into the kernel.
p->trapframe->kernel_satp = r_satp();
p->trapframe->kernel_sp = p->kstack + PGSIZE;
p->trapframe->kernel_trap = (uint64)usertrap;
p->trapframe->kernel_hartid = r_tp();
TIP
set up the registers that trampoline.S's sret will use to get to user space.
TIP
set S Previous Privilege mode to User.
unsigned long x = r_sstatus();
x &= ~SSTATUS_SPP;
x |= SSTATUS_SPIE;
w_sstatus(x);
TIP
set S Exception Program Counter to the saved user pc.
w_sepc(p->trapframe->epc);
TIP
tell trampoline.S the user page table to switch to.
uint64 satp = MAKE_SATP(p->pagetable);
TIP
jump to userret in trampoline.S at the top of memory, which switches to the user page table, restores user registers, and switches to user mode with sret.
uint64 trampoline_userret = TRAMPOLINE + (userret - trampoline);
((void (*)(uint64))trampoline_userret)(satp);
}
TIP
interrupts and exceptions from kernel code go here via kernelvec, on whatever the current kernel stack is.
void
kerneltrap()
{
int which_dev = 0;
uint64 sepc = r_sepc();
uint64 sstatus = r_sstatus();
uint64 scause = r_scause();
if((sstatus & SSTATUS_SPP) == 0)
panic("kerneltrap: not from supervisor mode");
if(intr_get() != 0)
panic("kerneltrap: interrupts enabled");
if((which_dev = devintr()) == 0){
TIP
interrupt or trap from an unknown source
printf("scause=0x%lx sepc=0x%lx stval=0x%lx\n", scause, r_sepc(), r_stval());
panic("kerneltrap");
}
TIP
give up the CPU if this is a timer interrupt.
if(which_dev == 2 && myproc() != 0)
yield();
TIP
the yield() may have caused some traps to occur, so restore trap registers for use by kernelvec.S's sepc instruction.
w_sepc(sepc);
w_sstatus(sstatus);
}
void
clockintr()
{
if(cpuid() == 0){
acquire(&tickslock);
ticks++;
wakeup(&ticks);
release(&tickslock);
}
TIP
ask for the next timer interrupt. this also clears the interrupt request. 1000000 is about a tenth of a second.
w_stimecmp(r_time() + 1000000);
}
TIP
check if it's an external interrupt or software interrupt, and handle it. returns 2 if timer interrupt, 1 if other device, 0 if not recognized.
int
devintr()
{
uint64 scause = r_scause();
if(scause == 0x8000000000000009L){
TIP
this is a supervisor external interrupt, via PLIC.
TIP
irq indicates which device interrupted.
int irq = plic_claim();
if(irq == UART0_IRQ){
uartintr();
} else if(irq == VIRTIO0_IRQ){
virtio_disk_intr();
} else if(irq){
printf("unexpected interrupt irq=%d\n", irq);
}
TIP
the PLIC allows each device to raise at most one interrupt at a time; tell the PLIC the device is now allowed to interrupt again.
if(irq)
plic_complete(irq);
return 1;
} else if(scause == 0x8000000000000005L){
TIP
timer interrupt.
clockintr();
return 2;
} else {
return 0;
}
}