4.1 RISC-V trap machinery

Each RISC-V CPU has a set of hardware control registers that the kernel writes to tell the CPU how to handle traps, and that the kernel can read to find out about a trap that has occurred. The RISC-V documents contain the full story [riscv:priv]. riscv.h (0500) contains definitions that xv6 uses. Here’s an outline of the most important registers:

  • stvec: The kernel writes the address of its trap handler code here; the RISC-V jumps to the address in stvec to handle a trap.

  • sepc: When a trap occurs, RISC-V saves the program counter here (since the pc is then overwritten with the value in stvec). The sret (return from trap) instruction copies sepc to the pc. The kernel can write sepc to control where sret goes.

  • scause: RISC-V puts a number here that describes the reason for the trap.

  • sscratch: The kernel trap handler code uses sscratch to help it avoid overwriting user registers before saving them.

  • sstatus: The SIE bit in sstatus controls whether device interrupts are enabled. If the kernel clears SIE, the RISC-V will defer device interrupts until the kernel sets SIE. The SPP bit indicates whether a trap came from user mode or supervisor mode, and controls to what mode sret returns.

The above registers can only be accessed in supervisor mode (i.e., by the kernel); the CPU prevents user code from reading or writing them.

Each CPU on a multi-core chip has its own set of these registers, and more than one CPU may be handling a trap at any given time.

When it forces a trap, the RISC-V hardware does the following:

  1. 1.

    If the trap is a device interrupt, and the sstatus SIE bit is clear, don’t do any of the following.

  2. 2.

    Disable interrupts by clearing the SIE bit in sstatus.

  3. 3.

    Copy the pc to sepc.

  4. 4.

    Save the current mode (user or supervisor) in the SPP bit in sstatus.

  5. 5.

    Set scause to a number indicating the trap’s cause.

  6. 6.

    Set the mode to supervisor.

  7. 7.

    Copy stvec to the pc.

  8. 8.

    Start executing at the new pc.

The CPU doesn’t switch to the kernel page table, doesn’t switch to a stack in the kernel, and doesn’t save any registers other than the pc. Kernel software must perform these tasks. One reason that the CPU does minimal work during a trap is to provide flexibility to software; for example, some operating systems omit a page table switch in some situations to increase trap performance.

It’s worth thinking about whether any of the steps listed above could be omitted, perhaps in search of faster traps. Though there are situations in which a simpler sequence can work, many of the steps would be dangerous to omit in general. For example, suppose that the CPU didn’t switch program counters. Then a trap from user space could switch to supervisor mode while still running user instructions. Those user instructions could break user/kernel isolation, for example by modifying the satp register to point to a page table that allowed accessing all of physical memory. It is thus important that the CPU switch to a kernel-specified instruction address, namely stvec.