Skip to content

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