Part VISA Case Studies

RISC-V Privileged Architecture

May 16, 2026·18 min read·advanced

This chapter covers the system-level architecture of RISC-V: privilege levels, control and status registers, exceptions and interrupts, virtual memory, the supervisor binary interface (SBI), the…

This chapter covers the system-level architecture of RISC-V: privilege levels, control and status registers, exceptions and interrupts, virtual memory, the supervisor binary interface (SBI), the optional hypervisor extension, and the boot sequence. It corresponds to Chapter 34 (x86-64 system architecture) and Chapter 39 (AArch64 system architecture).

The RISC-V privileged architecture is specified in a separate document from the unprivileged ISA. It defines what an OS, a hypervisor, and firmware see — the resources used to manage processes, virtual memory, traps, and the system at large. The design is, like the unprivileged ISA, more minimal than ARM's or x86's: fewer modes, simpler exception handling, and a deliberately small set of mandatory features.

01. Privilege Modes

RISC-V defines three core privilege modes:

  • U (User mode): least privileged. Applications run here.
  • S (Supervisor mode): the OS kernel.
  • M (Machine mode): most privileged. Firmware and very low-level runtime.

An optional fourth mode comes from the H extension (Hypervisor):

  • HS (Hypervisor-extended Supervisor mode): a supervisor-level mode with hypervisor capability, hosting guest supervisor mode (VS) and guest user mode (VU).

The numbering is opposite to ARM's: in RISC-V, M (machine) is the highest privilege; U is the lowest. Some implementations support only M-mode (microcontrollers); some support M+U (small embedded with simple OS); some support M+S+U (Linux-class systems); some support M+HS+VS+VU+U (virtualization).

A Linux-running RISC-V system has the layered structure:

  • U-mode: applications.
  • S-mode: Linux kernel.
  • M-mode: SBI implementation (typically OpenSBI). Provides services to S-mode and handles platform-specific details.

The OS in S-mode does not access hardware directly for many operations; it makes calls into M-mode firmware via the SBI interface. This is unusual compared to ARM (where Linux mostly runs at EL1 directly without firmware mediation for normal operations) or x86-64 (where Linux runs in ring 0 directly).

The reason: RISC-V was designed to keep S-mode platform-independent. Hardware-specific quirks — power management, secondary core startup, interrupt routing — are handled in M-mode firmware behind the SBI ABI. The Linux kernel can be platform-agnostic; the firmware adapts to specific hardware.

02. Control and Status Registers (CSRs)

RISC-V uses a unified mechanism for system-level state: Control and Status Registers (CSRs). There can be up to 4096 CSRs, accessed via dedicated instructions:

Assembly
csrr a0, satp # read CSR 'satp' into a0 csrw satp, a0 # write a0 into 'satp' csrrw a0, satp, a1 # atomic swap: a0 = old satp; satp = a1 csrrs a0, sstatus, a1 # atomic OR: a0 = old; sstatus |= a1 csrrc a0, sstatus, a1 # atomic AND-NOT: a0 = old; sstatus &= ~a1 csrrwi, csrrsi, csrrci # immediate variants (5-bit immediate)

CSRs are addressed by 12-bit numbers. The address space is partitioned by privilege required to access:

  • 0x000-0x0FF: U-mode accessible (where allowed).
  • 0x100-0x1FF: S-mode accessible.
  • 0x200-0x2FF: H-mode (hypervisor).
  • 0x300-0x3FF, 0x700-0x7BF, 0xB00-0xBFF: M-mode.
  • 0xC00-0xC7F: U-mode read-only (counters).
  • And several other regions for specific purposes.

Naming convention: a CSR's name has a prefix indicating the lowest privilege that accesses it: mstatus (M-mode), sstatus (S-mode), ustatus (U-mode, where applicable). Many CSRs come in M and S variants that are similar but reflect the corresponding mode's view.

Major CSRs by Mode

M-mode CSRs:

  • mstatus: machine-mode status (MIE, MPIE, MPP, etc. — machine interrupt enable, previous IE, previous privilege).
  • mepc: machine exception program counter (where the trap occurred).
  • mcause: machine cause register (the trap reason).
  • mtval: machine trap value (e.g., faulting address).
  • mtvec: machine trap-vector base address (where M-mode traps go).
  • mie: machine interrupt-enable.
  • mip: machine interrupt-pending.
  • mscratch: scratch register for trap handler.
  • misa: ISA description (which extensions are present).
  • mvendorid, marchid, mimpid, mhartid: implementation IDs and hart (hardware thread) ID.
  • medeleg, mideleg: exception/interrupt delegation to S-mode.
  • pmpaddrN, pmpcfgN: physical memory protection.

S-mode CSRs (parallel to M-mode):

  • sstatus, sepc, scause, stval, stvec, sie, sip, sscratch: S-mode equivalents.
  • satp: supervisor address translation and protection. Holds page-table base and ASID.
  • sip / sie: subset of mip/mie that S-mode can manipulate.

U-mode CSRs:

  • fcsr (with FP): FP rounding mode and flags.
  • cycle, time, instret: 64-bit performance counters, read-only via U-mode where enabled.

Counter Access

A nice RISC-V feature: cycle, time, and instret counters are accessible from U-mode (with M-mode permission), without a syscall:

Assembly
rdcycle a0 # pseudo-instruction; reads 'cycle' CSR
rdtime a0 # reads 'time' CSR (wall-clock)
rdinstret a0 # reads instructions-retired counter

These map to csrr a0, cycle, etc. In RV32, two reads are needed for the high and low halves; in RV64, the 64-bit values are read in one instruction.

time is wall-clock time; the frequency is platform-specific (typically a few MHz to a few hundred MHz). This is the equivalent of x86's TSC or AArch64's CNTVCT.

03. Traps

RISC-V uses a unified term trap for both synchronous exceptions (faults, syscalls) and asynchronous interrupts. When a trap occurs, the processor:

  1. Saves the current PC into xepc (mepc or sepc, depending on target mode).
  2. Saves status into xstatus (mstatus or sstatus).
  3. Saves the cause into xcause.
  4. Saves trap-specific value into xtval.
  5. Switches to the target mode (M or S).
  6. Jumps to the trap vector.

The trap vector is configured by xtvec. There are two modes: direct (all traps go to one address) or vectored (interrupts dispatch by cause to base + 4*cause).

Cause Codes

The xcause register has a high bit indicating interrupt vs. exception, and lower bits encoding the specific cause:

CauseDescription (Exception)
0Instruction address misaligned
1Instruction access fault
2Illegal instruction
3Breakpoint
4Load address misaligned
5Load access fault
6Store/AMO address misaligned
7Store/AMO access fault
8Environment call from U-mode (syscall)
9Environment call from S-mode
11Environment call from M-mode
12Instruction page fault
13Load page fault
15Store/AMO page fault
20Instruction guest-page fault (with H ext.)
21Load guest-page fault
23Store/AMO guest-page fault
CauseDescription (Interrupt)
1Supervisor software interrupt
3Machine software interrupt
5Supervisor timer interrupt
7Machine timer interrupt
9Supervisor external interrupt
11Machine external interrupt

Software interrupts are IPIs (inter-processor interrupts). Timer interrupts are from the platform timer. External interrupts are everything else routed through the platform interrupt controller (PLIC or APLIC, see below).

Trap Delegation

By default, all traps go to M-mode. But for an OS-class system, most traps should be handled by S-mode (Linux). M-mode delegates to S-mode via the medeleg and mideleg registers:

  • medeleg (machine exception delegation): bitmask of which exception causes are handled directly by S-mode.
  • mideleg (machine interrupt delegation): bitmask for interrupts.

Linux on RISC-V depends on the firmware setting up sane delegations: page faults, syscalls (ECALL from U), illegal instructions, and so on are delegated to S-mode. Only platform-management traps (machine timer, machine software interrupt, etc.) stay in M-mode.

04. System Calls

User-mode programs invoke the kernel via the ECALL (Environment Call) instruction:

Assembly
li a7, syscall_number # by Linux convention
mv a0, arg0
mv a1, arg1
...
ecall # trap into S-mode
# Result in a0; -1 for error with errno in a0 (negative)

ECALL causes a trap with cause = 8 (Environment call from U-mode). The kernel handler in S-mode reads sepc to know where to return, dispatches by syscall number (in a7 by Linux convention), and on completion does sret to return.

The convention is uniform — same instruction, same trap vector, same return path for all syscalls. There is no equivalent of x86's MSR-driven SYSCALL configuration; just the trap.

A typical S-mode trap entry:

Assembly
.balign 4 trap_entry: csrrw sp, sscratch, sp # swap sp with sscratch (kernel stack now in sp) addi sp, sp, -256 # allocate trap frame sd a0, 0(sp) sd a1, 8(sp) # ... save all registers ... csrr a0, scause csrr a1, stval csrr a2, sepc mv a3, sp # pass trap frame call handle_trap # C handler # ... restore registers ... csrrw sp, sscratch, sp sret

The csrrw sp, sscratch, sp is a clever trick: at trap entry, sscratch holds the kernel's stack pointer; the user's sp is in sp; the swap puts kernel sp in sp and user sp in sscratch (saved for restoration). This works because csrrw is atomic with respect to the trap.

Linux's S-mode trap entry is highly optimized; typical syscall path is ~80 ns on a fast core, comparable to x86 and ARM.

05. Interrupts and the PLIC / APLIC

RISC-V interrupt routing has been an evolving area. Two main systems:

PLIC (Platform-Level Interrupt Controller). The original RISC-V interrupt controller. A central PLIC connects all hardware interrupt sources to all harts. Each hart has a per-hart context with claim/complete registers. Interrupts have priorities (0-7); a hart receives the highest-priority pending interrupt.

The PLIC is memory-mapped: writes to its registers configure routing; reads claim or check pending interrupts. Linux drivers initialize the PLIC at boot based on device tree information.

APLIC (Advanced PLIC) and IMSIC (Incoming MSI Controller). Newer designs targeted at MSI-based interrupt delivery. PCIe MSIs are written directly to per-hart IMSIC registers; the APLIC handles legacy wired interrupts. Together they provide a more scalable architecture for many-hart systems.

The interrupt model also supports software interrupts (IPIs) via separate registers in the CLINT (Core-Local Interruptor) on simpler platforms or via SBI calls on more advanced ones.

Linux on RISC-V handles all this through standard drivers; the device-tree blob describes the interrupt topology.

06. Virtual Memory

RISC-V virtual memory uses page tables similar in structure to ARM and x86, but with several configurable schemes:

  • Sv32: 2-level table, 32-bit virtual addresses, 4 KiB pages. RV32 systems.
  • Sv39: 3-level table, 39-bit virtual addresses, 4 KiB pages. Most RV64 Linux systems use Sv39.
  • Sv48: 4-level table, 48-bit virtual addresses. Larger systems.
  • Sv57: 5-level table, 57-bit virtual addresses. Very large systems.

The active scheme is selected by the satp CSR's MODE field. Sv39 is by far the most common in current deployments.

Sv39 Layout

39-bit virtual address split:

Plain Text
| 9 bits | 9 bits | 9 bits | 12 bits |
VPN[2] VPN[1] VPN[0] offset

Three levels of page tables (root, mid, leaf). Each table has 512 64-bit entries. A leaf entry maps a 4 KiB page; an intermediate entry can be a megapage (2 MiB) or gigapage (1 GiB) leaf if appropriate. The hierarchy mirrors x86-64's PML4-PDPT-PD-PT, just with 3 levels for 39-bit addressing.

Page Table Entry Format

A 64-bit Sv39/Sv48/Sv57 PTE has:

  • V (valid): 1 if the entry is valid.
  • R, W, X: read, write, execute permissions.
  • U: user-accessible (1 = user; 0 = supervisor-only).
  • G: global (entry doesn't change with ASID).
  • A: accessed (set on access).
  • D: dirty (set on write).
  • PPN: physical page number (44 bits).
  • RSW: reserved for software (2 bits, OS scratch).

Notable: there is no "no-execute" bit; instead, the X bit is positive — set X to make the page executable. Combinations of R/W/X define page semantics:

  • R=0, W=0, X=0: pointer to next-level table (intermediate entry).
  • R=1, W=0, X=0: read-only data page.
  • R=1, W=1, X=0: read-write data page.
  • R=1, W=0, X=1: read-execute (typical code page).
  • R=1, W=1, X=1: read-write-execute (rare; typically a security risk).
  • R=0, W=1, X=0 or R=0, W=, X=: invalid combinations.

TLB and SFENCE.VMA

The TLB is implementation-defined; high-end RISC-V cores have multi-level TLBs similar to ARM and x86. Invalidation:

Assembly
sfence.vma # invalidate all TLB entries (full flush)
sfence.vma a0 # invalidate TLB entries for VA in a0
sfence.vma zero, a1 # invalidate all entries for ASID in a1
sfence.vma a0, a1 # invalidate VA a0 in ASID a1

SFENCE.VMA also serves as a memory barrier ordering memory accesses with respect to the TLB invalidation. The interaction is precise: any subsequent translation will see the new mapping.

In multi-core systems, TLB shootdown can be done via SFENCE.VMA in an IPI handler — similar to x86. There is no hardware-broadcast TLB invalidation in the base RISC-V spec (unlike ARM's TLBI ISH). The Svinval extension adds finer-grained barriers for performance-critical TLB management.

ASIDs

RISC-V supports Address Space Identifiers (ASIDs) in satp. Width is implementation-defined (0-16 bits). Each process gets an ASID; the kernel switches ASID on context switch instead of flushing the TLB. Equivalent in spirit to ARM's ASID and x86's PCID.

07. Physical Memory Protection (PMP)

Even without paging, RISC-V supports memory protection in M-mode via PMP (Physical Memory Protection). PMP defines up to 16 regions with R/W/X permissions, enforced by hardware.

Assembly
csrw pmpaddr0, t0 # set region 0 base/limit
csrw pmpcfg0, t1 # configure region 0 permissions

PMP is used by M-mode firmware to grant S-mode access to specific physical regions, while protecting M-mode's own data. It's a coarser mechanism than paging but enforced by hardware on every access.

For OS-running systems, PMP is usually configured once at boot (the firmware grants the kernel access to RAM and devices, then locks down its own region). Microcontrollers without an MMU rely on PMP as their primary memory protection.

08. SBI: Supervisor Binary Interface

The Supervisor Binary Interface (SBI) is the M-mode-to-S-mode ABI: a defined set of function calls that S-mode invokes to ask M-mode firmware to perform privileged or platform-specific operations.

Why SBI? RISC-V hardware is highly varied. Different boards have different timers, different IPI mechanisms, different power management. Rather than baking knowledge of every platform into Linux, the kernel calls SBI for these operations:

  • sbi_set_timer: schedule the next timer interrupt.
  • sbi_send_ipi: send an IPI to other harts.
  • sbi_remote_fence_i, sbi_remote_sfence_vma: cross-hart fences (TLB shootdown).
  • sbi_hsm_hart_start, sbi_hsm_hart_stop, sbi_hsm_hart_status: hart state management (CPU hotplug).
  • sbi_system_reset: reboot, shutdown.
  • sbi_console_putchar: legacy console output.

The most common SBI implementation is OpenSBI (BSD-2-Clause licensed). Vendors customize OpenSBI for their hardware and ship it as the M-mode firmware. RustSBI is an alternative implementation.

Calling SBI from S-mode:

Assembly
li a7, SBI_EXT_TIME # extension ID
li a6, SBI_TIME_SET_TIMER # function ID
mv a0, t0 # next timer value
ecall # trap into M-mode
# Result in a0 (error code) and a1 (return value)

ECALL from S-mode triggers cause 9 (Environment call from S-mode), trapping to M-mode where OpenSBI's handler takes over.

This indirection is the source of the comment in Chapter 42: Linux on RISC-V often calls into firmware for operations that on x86/ARM would be direct hardware access. The performance impact is small (a fast SBI call is ~100 ns), and the portability gain is large.

09. Hypervisor Extension (H)

The optional H extension adds hardware-assisted virtualization. With H enabled in misa, S-mode becomes HS-mode (Hypervisor-extended Supervisor), and additional modes appear:

  • VS-mode: virtual supervisor — guest kernel.
  • VU-mode: virtual user — guest application.

The hypervisor runs in HS-mode. Guests run in VS/VU mode, with their own page tables (stage 1) and a hypervisor-controlled second-stage translation (stage 2) that maps guest physical to host physical.

H-mode CSRs include:

  • hgatp: stage-2 page-table base.
  • hstatus: hypervisor status.
  • hideleg, hedeleg: delegate VS-mode traps directly to VS-mode where possible.
  • hvip: virtual interrupt pending.
  • htval, htinst: trap value and instruction for guest faults.

KVM on RISC-V uses the H extension. Adoption: H is in newer cores (T-Head C920, SiFive P-series, Veyron V2, AmpereOne RISC-V).

10. Performance Counters

RISC-V provides architectural performance counters:

  • cycle: 64-bit cycle counter.
  • time: 64-bit wall-clock time (frequency in mtime register on the platform).
  • instret: 64-bit instructions retired.
  • hpmcounter3 - hpmcounter31: programmable counters.

The programmable counters' events are configured via mhpmevent registers (M-mode only). Standard events include cache misses, branch mispredicts, TLB misses, etc. The exact set varies by implementation.

User-mode access is controlled by the *counteren CSRs. By default, U-mode can read cycle, time, and instret; the OS can extend this.

perf on Linux/RISC-V uses these counters via the kernel's perf events framework. ARM Streamline and similar profilers are in development for RISC-V.

11. Boot Sequence

A RISC-V system boots roughly as follows.

Reset

On hardware reset, all harts start in M-mode at a platform-defined reset vector (often physical address 0x80000000 in many SoCs). All harts start simultaneously; one is designated as the boot hart, the others spin in a holding pattern.

M-Mode Firmware

The first stage is M-mode firmware, often OpenSBI:

  1. Initializes basic platform: clocks, DRAM, low-level peripherals.
  2. Sets up M-mode CSRs: mtvec (trap vector), pmp (memory protection), delegations.
  3. Sets up S-mode environment: enables S-mode, prepares satp for the OS.
  4. Loads or finds the next-stage payload (Linux kernel + initramfs, or a more sophisticated bootloader like U-Boot).
  5. Drops to S-mode at the kernel's entry point.
  6. Stays resident in M-mode, servicing SBI calls.

S-Mode Bootloader (Optional)

For more complex boot scenarios, an S-mode bootloader (U-Boot in S-mode) loads the kernel from disk, sets up command line, and jumps to the kernel.

Linux Kernel

The kernel boots in S-mode:

  1. Sets up MMU with kernel page tables.
  2. Configures stvec, scause, sstatus, etc.
  3. Initializes drivers via device tree.
  4. Brings up other harts via SBI HSM (Hart State Management) calls.
  5. Mounts root filesystem and starts user space.

The key difference from x86 and ARM: secondary harts are brought up by SBI calls, not by direct hardware writes. This abstracts the (varied) hardware mechanisms behind a uniform interface.

Multi-Hart Bring-Up

C
for (int i = 0; i < nr_harts; i++) {
if (i == boot_hart) continue;
sbi_hart_start(i, &ap_entry, ap_context);
}

Each AP comes up at ap_entry, configures its own CSRs, registers with the scheduler, and joins the runnable set. Conceptually similar to ARM's PSCI mechanism.

12. Security Features (Emerging)

Several security features are being added to RISC-V, in various stages of ratification:

Pointer Masking (Smnpm, Ssnpm): top-byte-ignore for pointers, allowing pointer tagging similar to ARM's TBI.

Control-Flow Integrity (Zicfilp, Zicfiss): landing pads (forward-edge CFI) and shadow stacks (backward-edge CFI). Counterparts to ARM BTI and PAC.

Memory Tagging (Smmtt, Ssmmtt): in-progress, similar to ARM MTE.

Confidential VMs (CoVE — Confidential VM Extension): TEE/attestation framework, similar to AMD SEV / Intel TDX / ARM CCA. Not yet ratified.

The RISC-V security story is younger than ARM's or x86's, but is catching up rapidly. Mass-market adoption of these features is several years away.

13. Comparing to x86-64 and AArch64

Aspectx86-64AArch64RISC-V
Privilege levels4 rings4 EL3 modes (M/S/U) + H
Firmware roleUEFI, mostly stays out of OS pathATF in EL3, occasional SMCOpenSBI in M, called frequently via SBI
System reg accessRDMSR / WRMSR (privileged)MRS / MSRCSR instructions
Virtual memoryx87 page tables, 4-5 levels4-5 level, configurable granuleSv32/39/48/57
TLB shootdownIPI-basedHardware-broadcast (TLBI ISH)IPI-based or SBI
Interrupt controllerLocal APIC + I/O APICGICPLIC / APLIC + IMSIC
Hypervisor supportVMX / SVMEL2 + stage-2H extension
Memory modelTSOWeakWeak (RVWMO)

RISC-V's architecture is the most minimal: fewest privilege levels, smallest mandatory feature set, most platform-specific work pushed to firmware. ARM is more centralized with hardware-broadcast TLBI and built-in interrupt architecture. x86 carries decades of accreted features.

14. Summary

RISC-V's privileged architecture defines three mandatory privilege modes (M, S, U) plus an optional hypervisor (HS/VS/VU) layer. CSRs hold all system state, accessed via dedicated instructions; the same instruction works at all privilege levels (with different sets visible). Traps unify exceptions and interrupts; trap delegation lets S-mode handle most things directly while M-mode firmware retains overall control.

Virtual memory uses configurable schemes (Sv32, Sv39, Sv48, Sv57) with hierarchical page tables, ASIDs for cheap context switching, and SFENCE.VMA for invalidation. The PMP mechanism enforces physical-memory protection at the M-mode level. The SBI ABI lets S-mode call into M-mode firmware for platform-specific operations, providing portability across diverse hardware. The hypervisor extension supports nested translation and a virtualized supervisor mode for KVM and similar.

The minimalism of the privileged architecture pushes some complexity to firmware (OpenSBI is essential for any practical system) and to software (TLB shootdown via IPIs, more bookkeeping in some operations) but in return gives a clean, portable, well-defined platform interface. Linux on RISC-V is structurally similar to Linux on ARM, with most differences hidden behind the SBI abstraction.

The next chapter looks at RISC-V micro-architecture: how modern high-performance RISC-V cores are built, comparing them to ARM and x86-64 contemporaries.

Book mode
computer-architecturerisc-visa-case-study
Was this helpful?