RISC-V privileged architecture
Contract
| Field | Bound contract |
|---|---|
| Trigger | A kernel, hypervisor, or firmware for RISC-V needs privilege modes, CSR access, a trap entry, timer or external interrupts, OpenSBI integration, virtual memory, or a QEMU virt boot and GDB session. |
| Authority | Reversible local: writes only the kernel or firmware sources in the project directory the user names and the OpenSBI build output under its own build/; rollback is reverting the sources and deleting build/. No remote mutation. |
| Side effect | New or edited sources, an OpenSBI firmware image, and QEMU processes started for testing. |
| Done | The image boots on qemu-system-riscv64 -machine virt, an ecall from U-mode reaches the trap handler with scause = 8, the timer interrupt fires through SBI, a PLIC source is claimed and completed, and the kernel runs with satp pointing at a page table it built. |
Inputs
- Which modes the target implements: M, S, and U (Linux-class), or M and U only (embedded).
- XLEN (RV32 or RV64) and the ISA string the toolchain targets (
-march=rv64gc). - Whether OpenSBI runs below the kernel (S-mode kernel) or the kernel owns M-mode.
- Toolchain:
riscv64-unknown-elf-gccor a Linux-target cross GCC, and a GDB that knows RISC-V (gdb-multiarch, or the toolchain's own).
Procedure
Fix the mode layout. M-mode runs firmware (OpenSBI) and has every right; S-mode runs the kernel; U-mode runs applications. An embedded part with no S-mode runs M and U only, and the "kernel" is the M-mode firmware. Done when: the project states which mode each component runs in and which CSR set (
m*ors*) it therefore touches.Access CSRs through inline assembly wrappers. Done when: every CSR read and write goes through one wrapper per register, so a mode change is one edit.
CSR Mode Holds mstatus,sstatusM, S Global interrupt enable, previous privilege, FS and XS state mtvec,stvecM, S Trap vector base and mode (direct or vectored) mepc,sepcM, S PC at the trap mcause,scauseM, S Trap cause; top bit set means interrupt mtval,stvalM, S Faulting address or instruction bits mie,sieM, S Per-source interrupt enable mip,sipM, S Per-source interrupt pending satpS Translation mode, ASID, root page table PPN static inline uint64_t read_satp(void) { uint64_t v; asm volatile("csrr %0, satp" : "=r"(v)); return v; } static inline void write_stvec(const void *handler) { asm volatile("csrw stvec, %0" :: "r"(handler)); }Decode traps.
scausebit 63 (RV64) distinguishes interrupts from exceptions; the low bits give the code. Done when: the handler dispatches the codes below and panics on any other withscause,sepc, andstvalprinted.scauseKind Meaning interrupt, code 1 Interrupt Supervisor software interrupt interrupt, code 5 Interrupt Supervisor timer interrupt interrupt, code 9 Interrupt Supervisor external interrupt (PLIC) 8 Exception ecallfrom U-mode12, 13, 15 Exception Instruction, load, store page fault 2 Exception Illegal instruction void handle_trap(uint64_t scause, uint64_t sepc, uint64_t stval) { if (scause >> 63) { switch (scause & 0xff) { case 5: timer_interrupt(); break; case 9: external_interrupt(); break; default: panic("interrupt %lu", scause & 0xff); } return; } switch (scause) { case 8: handle_syscall(); break; /* sepc += 4 before sret */ case 12: case 13: case 15: handle_page_fault(stval, scause); break; default: panic("exception %lu sepc=%lx stval=%lx", scause, sepc, stval); } }Write the trap entry. In direct mode every trap lands at
stvec; the entry swaps to the kernel stack throughsscratch, saves the registers, calls the C handler, restores, and executessret. The spec requiresBASEto be 4-byte aligned, and vectored mode (MODE = 1) jumps toBASE + 4 * causefor interrupts. Done when: anecallfrom U-mode arrives inhandle_trapwithscause = 8and returns to the instruction after it..section .text.trap .globl trap_entry .align 4 trap_entry: csrrw sp, sscratch, sp # switch to the kernel stack # push the caller-saved and callee-saved registers csrr a0, scause csrr a1, sepc csrr a2, stval call handle_trap # pop the registers csrrw sp, sscratch, sp sretwrite_stvec(trap_entry); /* low two bits 0: direct mode */Wire the interrupt controllers. On QEMU
virtthe CLINT (or ACLINT with-machine virt,aclint=on) provides per-hart timer (mtime,mtimecmp) and software (msip) interrupts, and the PLIC routes device interrupts (UART, virtio) with per-source priority, per-context enable, and a claim and complete register pair. An S-mode kernel sets timers through SBI rather than touchingmtimecmp, which is M-mode memory. Done when: a PLIC source (the UART) is claimed and completed once per interrupt and the timer fires at the programmed interval.uint32_t irq = plic_claim(hart_context); /* read claim register */ handle_device_irq(irq); plic_complete(hart_context, irq); /* write the same value back */Build OpenSBI with the kernel as payload when the kernel is S-mode.
FW_PAYLOAD_PATHtakes the image file of the next stage (a flat binary, not the ELF);FW_PAYLOAD_OFFSETis the payload offset from the OpenSBI load address, and it must equal where the kernel is linked to run. Done when:build/platform/generic/firmware/fw_payload.elfexists and boots the kernel on QEMU.git clone https://github.com/riscv-software-src/opensbi make -C opensbi PLATFORM=generic FW_PAYLOAD=y \ FW_PAYLOAD_PATH=../kernel.bin FW_PAYLOAD_OFFSET=0x200000 # QEMU virt loads OpenSBI at 0x80000000, so 0x200000 puts the kernel at 0x80200000SBI calls are
ecallfrom S-mode with the extension ID ina7and the function ID ina6; the TIME extension ID is0x54494D45andsbi_set_timeris function 0. Base, TIME, IPI, RFENCE, and HSM are the extensions a kernel uses first.struct sbiret sbi_set_timer(uint64_t stime) { return sbi_ecall(0x54494D45, 0, stime, 0, 0, 0, 0, 0); }Build page tables. Sv39 translates 39-bit virtual addresses through three levels; Sv48 uses four levels and 48 bits. On RV64
satpholdsMODEin bits 63 to 60 (8 for Sv39, 9 for Sv48), a 16-bit ASID, and the 44-bit root PPN. Map the kernel's own code and data before writingsatp, or the instruction after the write faults. Done when: the kernel runs withsatpenabled and a user page mapped withPTE_Uis readable from U-mode and faults from S-mode withoutSUMset.#define PTE_V (1UL << 0) #define PTE_R (1UL << 1) #define PTE_W (1UL << 2) #define PTE_X (1UL << 3) #define PTE_U (1UL << 4) uint64_t *walk_create(uint64_t *root, uint64_t va, int alloc); void map_page(uint64_t *root, uint64_t va, uint64_t pa, int perm);Issue
sfence.vmaafter changing a mapping the hart may have cached.Boot and debug on QEMU
virt.-bios defaultloads QEMU's bundled OpenSBI and jumps to-kernelin S-mode;-bios noneruns the kernel in M-mode at0x80000000.-s -Sopens a GDB stub on port 1234 and waits. Done when: GDB stops at the kernel entry aftertarget remote :1234.qemu-system-riscv64 -machine virt -cpu rv64 -m 128M \ -kernel kernel.elf -bios default \ -serial mon:stdio -display none -no-reboot qemu-system-riscv64 -machine virt -m 128M \ -kernel opensbi/build/platform/generic/firmware/fw_payload.elf \ -bios none -nographic qemu-system-riscv64 -machine virt -m 128M -kernel kernel.elf -bios default -nographic -s -S & gdb-multiarch kernel.elf -ex "target remote :1234"Read xv6-riscv when a reference is needed.
kernel/start.c(M-mode to S-mode handoff),kernel/trap.c,kernel/vm.c, andkernel/plic.care small and map onto steps 3 to 7. Done when: each step above has been compared to its xv6 counterpart.git clone https://github.com/mit-pdos/xv6-riscv make -C xv6-riscv qemu
Failure and recovery
| Symptom | Cause | Fix |
|---|---|---|
Trap loop right after stvec is set |
stvec base misaligned or the handler itself faults |
Align the entry to 4 bytes; check the handler's first instructions with GDB. |
Fault on the instruction after csrw satp |
Kernel code not mapped in the new table | Identity-map the kernel before enabling translation. |
| Timer never fires | S-mode kernel writes mtimecmp directly, or sie.STIE clear |
Call sbi_set_timer; set STIE in sie and SIE in sstatus. |
| PLIC source never interrupts | Priority 0, enable bit clear, or threshold too high | Set priority above 0, set the enable bit for the hart context, set threshold 0. |
| OpenSBI prints its banner then hangs | FW_PAYLOAD_OFFSET does not match the kernel's link address |
Make the offset plus 0x80000000 equal the linked entry. |
| Illegal instruction in the kernel | Compressed or other extension missing from the CPU model | Match -march to the QEMU CPU, or pass -cpu rv64,c=true. |
Output
Kernel or firmware sources in the named directory that boot on QEMU virt with a working trap entry, timer and PLIC interrupt paths, and an enabled page table, plus the QEMU and GDB command lines used and, when OpenSBI is built, the payload offset that matched the link address.