The RISC-V Linux User's Manual
Quan Nguyen
March 21, 2013
This document is out-of-date and was never authoritative. This is not an official source for RISC-V information. It is here only historical purposes. Please consult riscv.org for the most up-to-date information.
Proposed cover of the RISC-V Linux User's Manual
The purpose of this page is to document the assumptions made during the development of the RISC-V port of the Linux kernel.
A project with a duration such as this requires adequate documentation to support future development and maintenance. This document is created with the hope of being useful; however, its accuracy is not guaranteed.
This document is currently under development. This document is copyrighted by Quan Nguyen, © 2013. All rights reserved. I do intend, however, to release this document to the public freely upon its satisfactory completion.
A full description of the history can be found in Volume I of the RISC-V Instruction Set Manual. While the architecture intends to be a clean break, its implementation contains many remnants of predecessor architectures. In particular, the port of gcc and glibc derive from MIPS; as a result, architectural decisions that were not explicitly made by the RISC-V ISA have been inherited from MIPS. The RISC-V port of Linux itself is also inspired by parts of the Linux-MIPS codebase; however, careful considerations were made to rely as little on MIPS conventions as possible.
Therefore, when this document is unclear, referring to the MIPS specification for the corresponding topic may yield insight.
The RISC-V ISA makes visible:
For 64-bit versions of RISC-V, the XPRs are 64-bits wide, and for 32-bit versions of RISC-V, the XPRs are 32-bits wide. The privileged control registers may have different widths, depending on the nature of the register.
The RISC-V port of the Linux kernel uses the following conventions for its registers.
Register | Canonical Name | RISC-V Linux |
x0 | zero | Zero (hard-wired) |
x1 | ra | Return address |
x2 | v0 | Function return value Syscall number |
x3 | v1 | Function return value (pipe2 only) |
x4 | a0 | Function argument register |
x5 | a1 | Function argument register |
x6 | a2 | Function argument register |
x7 | a3 | Function argument register Syscall error code |
x8 | a4 | Function argument register |
x9 | a5 | Function argument register |
x10 | a6 | Function argument register |
x11 | a7 | Function argument register |
x12 | t0 | Temporary register |
x13 | t1 | Temporary register |
x14 | t2 | Temporary register |
x15 | t3 | Temporary register |
x16 | t4 | Temporary register |
x17 | t5 | Temporary register |
x18 | t6 | Temporary register |
x19 | t7 | Temporary register |
x20 | s0 | Caller-save register |
x21 | s1 | Caller-save register |
x22 | s2 | Caller-save register |
x23 | s3 | Caller-save register |
x24 | s4 | Caller-save register |
x25 | s5 | Caller-save register |
x26 | s6 | Caller-save register |
x27 | s7 | Caller-save register |
x28 | s8 / gp | Caller-save register Global pointer |
x29 | s9 / fp | Caller-save register Frame pointer |
x30 | sp | Stack pointer (user and kernel) |
x31 | tp | Thread pointer (for TLS) |
MIPS convention note: The pipe2 system call is specially handled by MIPS implementations, but the RISC-V port of glibc has been patched to remove this behavior.
The RISC-V port of the Linux kernel does not use any
floating-point hardware. There are no assumptions made about the values of the
floating-point registers, or the status register fsr
.
The privileged control registers (PCRs) are used by the processor in Supervisor mode to complete tasks such as memory management and exception handling. These are not accessible from userspace.
Privileged Control Register | Canonical Name | RISC-V Linux |
pcr0 | status | Processor Status Register |
pcr1 | epc | Exception Program Counter Bad Address (Instruction faults) |
pcr2 | badvaddr | Bad Virtual Address (Stores/Loads) |
pcr3 | evec | Exception Handler Address |
pcr4 | count | Timer counter value |
pcr5 | compare | Timer compare value |
pcr6 | cause | Exception cause register |
pcr7 | ptbr | Page table base register |
pcr8 | Unused by RISC-V Linux | |
pcr9 | Unused by RISC-V Linux | |
pcr10 | Unused by RISC-V Linux | |
pcr11 | Unused by RISC-V Linux | |
pcr12 | k0 | Kernel scratch register current task pointer |
pcr13 | k1 | Kernel scratch register |
pcr14 | Usused by RISC-V Linux | |
pcr15 | Usused by RISC-V Linux | |
pcr16 | Usused by RISC-V Linux | |
pcr17 | Usused by RISC-V Linux | |
pcr18 | Usused by RISC-V Linux | |
pcr19 | Usused by RISC-V Linux | |
pcr20 | Unused by RISC-V Linux | |
pcr21 | Unused by RISC-V Linux | |
pcr22 | Unused by RISC-V Linux | |
pcr23 | Unused by RISC-V Linux | |
pcr24 | Unused by RISC-V Linux | |
pcr25 | Unused by RISC-V Linux | |
pcr26 | tosim | To-Simulator Register |
pcr27 | fromsim | From-Simulator Register |
pcr28 | Unused by RISC-V Linux | |
pcr29 | Unused by RISC-V Linux | |
pcr30 | tohost | HTIF To-Host Communication Register |
pcr31 | fromhost | HTIF From-Host Register |
Simulator implementation note:
The tosim
and fromsim
registers are magical
PCRs that do special commands based on the packet whose physical
address is written to these registers.
status
PCRThe status register has been subject to many changes in the RISC-V ISA. However, the RISC-V port of Linux uses the following configuration:
Field name | IP | IM | 0 | VM | S64 | U64 | S | PS | EC | EV | EF | ET | ||||||||||||||||||||
Bit position | 3124 | 2316 | 159 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | ||||||||||||||||||||
Initial value | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ? | ? | ? | ? | ? | ? | ? | ? | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | ? | ? | X | X | 0 |
Shown are the values that are known at processor startup. Other than the obvious 0 and 1, a ? is undefined and X as "don't care."
Simulator implementation note: The simulator sets the the interrupt mask (IM) to all 1, previous-supervisor (PS) to 0, and all EC/EV/EF to 0.
All interrupts (and exceptions) enter through one exception handler, the address of which is located in the evec PCR.
RISC-V provides eight interrupt lines for timers and other devices. They are level triggered, and masked with the IM field in the status PCR. The interrupts in use in the RISC-V port of Linux are as follows:
Interrupt Number | Description |
0 | Unused |
1 | Unused |
2 | Unused |
3 | Unused |
4 | Unused |
5 | Inter-processor |
6 | Host/Devices |
7 | Timer |
Interrupt priority decreases as the interrupt number increases; in this case, the timer interrupt is always taken last. Each interrupt is associated with an exception number, to be placed in the cause PCR.
Simulator implementation note: The ISA functional simulator differs from this behavior by setting the topmost bit of the cause register if it is an interrupt.
Exceptions are taken as the faulting instruction is executed. The behavior of each exception is different, particularly in the semantics of how error values are saved. The list is not comprehensive, but covers the most useful registers.
If an exception is not handled by Linux, the kernel will panic.
Code ( cause ) | Description | Value of other registers | Handled in RISC-V Linux? |
0 | Instruction address misaligned | epc = misaligned instruction address badvaddr = misaligned instruction address | No |
1 | Instruction access fault | epc = faulting instruction address | Yes |
2 | Illegal instruction | epc = illegal instruction address | No |
3 | Privileged instruction | epc = privileged instruction address | No |
4 | Floating point disabled | epc = floating point instruction address | No |
5 | Reserved | (Reserved) | No |
6 | System call | epc = system call instruction address v0 = system call number (See "System Call conventions") | Yes |
7 | Breakpoint | epc = break instruction address | No |
8 | Load address misaligned | epc = misaligned load instruction address badvaddr = misaligned load address | No |
9 | Store address misaligned | epc = misaligned store instruction address badvaddr = misaligned store address | No |
10 | Load access fault | epc = faulting load instruction address badvaddr = faulting load address | Yes |
11 | Store access fault | epc = faulting store instruction address badvaddr = faulting store address | Yes |
12-15 | Unused | (Not applicable) | No |
16-23 | Interrupts | cause not equal to (interrupt number + 16)(See "Interrupts and Exceptions") | Some |
24-31 | Unused | (Not applicable) | No |
ISA oddity note:
Most exceptions that can be handled by Linux return to exactly
epc
to repeat the instruction. However, this behavior is not
desirable for system calls or breakpoints. The functional simulator does not distinguish system
calls or breakpoints from other exceptions, and any use of the eret
instruction will repeatedly jump on the syscall
or
break
instructions. Therefore, for these two exceptions, the Linux exception handler will
manually increment epc
by one instruction forward (epc <-
epc + 0x4
) before returning to the program that triggered the exception.
MIPS convention note: The RISC-V ABI, in both its forms, RV32 and RV64, share many commonalites with their respective MIPS counterparts; MIPS O32 and MIPS64. Yunsup notes that RISC-V also takes cues from the MIPS Embedded ABI (EABI). However, it is the conclusion of the author that some ABI decisions made by MIPS developers were brain-damaged unsuitable for RISC-V to make a "clean break" from previous architectures.
The number of the syscall can be found in
arch/riscv/asm/include/unistd.h
, but it includes the file
include/linux/asm-generic/unistd.h
, so users may refer to that
instead.
v0
.Syscalls currently may have up to six arguments to be used.
RISC-V provides eight argument registers, so all arguments may be passed via
registers a0
through a7
.
a0
through a7
.
syscall
instruction.This invokes the kernel handler for syscalls.
Linux returns the result of the syscall in register
v0
.
MIPS covention note: MIPS convention
also specifies that register a3
contains the error status of the syscall: 0 if successful and less than zero
otherwise. This error status is set by the architecture-dependent portion of the
kernel; however, glibc seems to expect register a3
to be clobbered.
(Why a3
? MIPS has only four argument registers, but this notion was
not preserved when RISC-V was designed with eight argument registers.)
To recover the result,
v0
a3
Again, this stipulates that a3
is clobbered.
As with MIPS, the following registers must be saved by the caller:
And these registers must be saved by the callee:
The author presumes that twiddling with sp or tp will be very bad ideas.
Various things provide error codes for failed system calls or kernel functions.
The field below provides a lookup table for the error values for the RISC-V
port of Linux. The file can also be found at include/asm-generic/errno-base.h
and include/asm-generic/errno.h
.
When the processor enters kernel mode, it must save the entire state of
the user process. The struct task_struct
pointer of the current
process is always located in the k0
PCR. When the processor needs
to save the user process's state, it saves the following registers, in this
order:
The registers are saved into the kernel-mode stack for the process, located at
current->stack
, where current
also refers to the
k0
PCR.