The RISC-V Linux User's Manual
March 21, 2013
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|
|x2||v0||Function return value|
|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|
|x28||s8 / gp||Caller-save register|
|x29||s9 / fp||Caller-save register|
|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
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|
|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:
fromsim registers are magical
PCRs that do special commands based on the packet whose physical
address is written to these registers.
The status register has been subject to many changes in the RISC-V ISA. However, the RISC-V port of Linux uses the following configuration:
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 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.
|Description||Value of other registers||Handled in |
|0||Instruction address misaligned||No|
|1||Instruction access fault||Yes|
|4||Floating point disabled||No|
(See "System Call conventions")
|8||Load address misaligned||No|
|9||Store address misaligned||No|
|10||Load access fault||Yes|
|11||Store access fault||Yes|
(See "Interrupts and Exceptions")
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
instruction will repeatedly jump on the
break instructions. Therefore, for these two exceptions, the Linux exception handler will
epc by one instruction forward (
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
Syscalls currently may have up to six arguments to be used.
RISC-V provides eight argument registers, so all arguments may be passed via
This invokes the kernel handler for syscalls.
Linux returns the result of the syscall in register
MIPS covention note: MIPS convention
also specifies that register
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.
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,
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
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
The registers are saved into the kernel-mode stack for the process, located at
current also refers to the