4.1 RISC-V trap machinery

Each RISC-V CPU has a set of 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 [16]. riscv.h (kernel/riscv.h:1) contains definitions that xv6 uses. Here’s an outline of the most important registers:

  • stvec: The kernel writes the address of its trap handler 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 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 relate to traps handled in supervisor mode, and they cannot be read or written in user mode.

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 needs to force a trap, the RISC-V hardware does the following for all trap types:

  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 reflect 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.

Note that 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.