RISC-V bare metal
Table of Contents
1. RISC-V bare metal (without OpenSBI)
You may follow osdev wiki pages.
2. RISC-V bare metal with OpenSBI
2.1. What is OpenSBI ?
2.2. setup OpenSBI
Debian 12's opensbi packages has older version and may not work as expected. Install from source:
git clone https://github.com/riscv-software-src/opensbi.git cd opensbi make ARCH=riscv CROSS_COMPILE=riscv64-unknown-elf- PLATFORM=generic
2.3. get device tree
You may need to install device-tree-compiler package.
$ qemu-system-riscv64 -machine virt -machine dumpdtb=riscv64-virt.dtb $ dtc -I dtb -O dts -o riscv64-virt.dts riscv64-virt.dtb
open riscv64-virt.dts
...
memory@80000000 {
device_type = "memory";
reg = <0x00 0x80000000 0x00 0x8000000>;
};
...
soc {
#address-cells = <0x02>;
#size-cells = <0x02>;
compatible = "simple-bus";
ranges;
...
serial@10000000 {
interrupts = <0x0a>;
interrupt-parent = <0x03>;
clock-frequency = "\08@";
reg = <0x00 0x10000000 0x00 0x100>;
compatible = "ns16550a";
};
...
}
...
ns16550a - https://en.wikipedia.org/wiki/16550_UART
2.4. example code
startup.S
.global start
.section .startup
start:
/* setup stack */
la sp, stack_top
call kmain
.end
linker.ld
OpenSBI will be loaded at 0x80000000 by QEMU.
(note: Therefore, in case of bare metal without OpenSBI, we need to load kernel at 0x80000000.)
We are using OpenSBI, so we will try to load our kernel at 0x80100000 (or later address).
. = 0x80100000;
SECTIONS {
.text : ALIGN(4K) {
startup.o(.startup)
*(.init);
*(.text);
}
.bss : ALIGN(4K) {
PROVIDE(bss_start = .);
*(.bss);
. += 4096;
PROVIDE(stack_top = .);
. += 4096;
PROVIDE(global_pointer = .);
PROVIDE(bss_end = .);
}
.rodata : ALIGN(4K) {
*(.rodata);
}
.data : ALIGN(4K) {
*(.data);
}
}
kernel.c
UART address from device tree - "serial@10000000"
The following example based on - https://wiki.osdev.org/RISC-V_Bare_Bones
#include <stdint.h>
unsigned char *uart = (unsigned char *)0x10000000;
void putchar_uart(char c) {
*uart = c;
return;
}
void print_uart(const char * str) {
while(*str != '\0') {
putchar_uart(*str);
++str;
}
return;
}
void kmain(void) {
print_uart("Hello world!\n");
while(1) {
// Read input from the UART
putchar_uart(*uart);
}
return;
}
Makefile
CROSS_COMPILE=riscv64-unknown-elf-
startup_kernel: startup.o linker.ld kernel.o
${CROSS_COMPILE}ld -T linker.ld --no-dynamic-linker -static -nostdlib -s -o startup startup.o kernel.o
startup.o: startup.S
${CROSS_COMPILE}as -o startup.o -c startup.S
kernel.o: kernel.c
${CROSS_COMPILE}gcc -Wall -Wextra -c -mcmodel=medany -o kernel.o -c kernel.c -ffreestanding
all:
startup_kernel
clean:
rm -f *.o
rm -f startup
2.5. test kernel
$ qemu-system-riscv64 -M virt -m 128M -nographic -bios PATH_TO_opensbi/build/platform/generic/firmware/fw_dynamic.bin \
-kernel startup
OpenSBI v1.4-8-gbb90a9e
____ _____ ____ _____
/ __ \ / ____| _ \_ _|
| | | |_ __ ___ _ __ | (___ | |_) || |
| | | | '_ \ / _ \ '_ \ \___ \| _ < | |
| |__| | |_) | __/ | | |____) | |_) || |_
\____/| .__/ \___|_| |_|_____/|____/_____|
| |
|_|
Platform Name : riscv-virtio,qemu
Platform Features : medeleg
Platform HART Count : 1
Platform IPI Device : aclint-mswi
Platform Timer Device : aclint-mtimer @ 10000000Hz
Platform Console Device : uart8250
Platform HSM Device : ---
Platform PMU Device : ---
Platform Reboot Device : syscon-reboot
Platform Shutdown Device : syscon-poweroff
Platform Suspend Device : ---
Platform CPPC Device : ---
Firmware Base : 0x80000000
Firmware Size : 323 KB
Firmware RW Offset : 0x40000
Firmware RW Size : 67 KB
Firmware Heap Offset : 0x48000
Firmware Heap Size : 35 KB (total), 2 KB (reserved), 10 KB (used), 22 KB (free)
Firmware Scratch Size : 4096 B (total), 336 B (used), 3760 B (free)
Runtime SBI Version : 2.0
Domain0 Name : root
Domain0 Boot HART : 0
Domain0 HARTs : 0*
Domain0 Region00 : 0x0000000000100000-0x0000000000100fff M: (I,R,W) S/U: (R,W)
Domain0 Region01 : 0x0000000010000000-0x0000000010000fff M: (I,R,W) S/U: (R,W)
Domain0 Region02 : 0x0000000002000000-0x000000000200ffff M: (I,R,W) S/U: ()
Domain0 Region03 : 0x0000000080040000-0x000000008005ffff M: (R,W) S/U: ()
Domain0 Region04 : 0x0000000080000000-0x000000008003ffff M: (R,X) S/U: ()
Domain0 Region05 : 0x000000000c400000-0x000000000c5fffff M: (I,R,W) S/U: (R,W)
Domain0 Region06 : 0x000000000c000000-0x000000000c3fffff M: (I,R,W) S/U: (R,W)
Domain0 Region07 : 0x0000000000000000-0xffffffffffffffff M: () S/U: (R,W,X)
Domain0 Next Address : 0x0000000080100000
Domain0 Next Arg1 : 0x0000000087e00000
Domain0 Next Mode : S-mode
Domain0 SysReset : yes
Domain0 SysSuspend : yes
Boot HART ID : 0
Boot HART Domain : root
Boot HART Priv Version : v1.12
Boot HART Base ISA : rv64imafdch
Boot HART ISA Extensions : sstc,zicntr,zihpm,sdtrig
Boot HART PMP Count : 16
Boot HART PMP Granularity : 2 bits
Boot HART PMP Address Bits: 54
Boot HART MHPM Info : 16 (0x0007fff8)
Boot HART Debug Triggers : 2 triggers
Boot HART MIDELEG : 0x0000000000001666
Boot HART MEDELEG : 0x0000000000f0b509
Hello world!
ctrl+a x to exit
3. get default RISC-V linker
for further experiments
$ riscv64-unknown-linux-gnu-ld --verbose > riscv64-virt.ld
4. Debugging
$ qemu-system-riscv64 -M virt -m 128M -nographic -bios PATH_TO_opensbi/build/platform/generic/firmware/fw_dynamic.bin \
-kernel startup -s -S
In another shell
$ riscv64-unknown-linux-gnu-gdb startup (gdb) gdb-multiarch (gdb) set architecture riscv:rv64 (gdb) set disassemble-next-line on (gdb) target remote :1234 Press c to continue booting
5. useful links
sifive examples:
https://github.com/sifive/freedom-e-sdk.git
OSDEV:
https://wiki.osdev.org/RISC-V_Bare_Bones
https://wiki.osdev.org/RISC-V_Meaty_Skeleton_with_QEMU_virt_board
Bare metal programming RISC-V:
https://popovicu.com/posts/bare-metal-programming-risc-v/
https://popovicu.com/posts/risc-v-sbi-and-full-boot-process/
https://github.com/popovicu/risc-v-bare-metal-fake-bios
RISC-V from scratch:
https://github.com/twilco/riscv-from-scratch
Uncovering the Mysteries of Linux Boot on RISC-V QEMU Machines - A Deep Dive into the Boot Process:
interrupt:
https://github.com/s094392/riscv-bare-metal
https://mullerlee.cyou/2020/07/09/riscv-exception-interrupt/
RISC-V Assembler Reference
https://michaeljclark.github.io/asm.html
GNU linker ld (GNU Binutils)
https://sourceware.org/binutils/docs/ld.html
Debug Console Extension (EID #0x4442434E "DBCN")
https://github.com/riscv-non-isa/riscv-sbi-doc/blob/master/src/ext-debug-console.adoc
RISC-V – A Baremetal Introduction using C++
https://riscv.org/news/2021/08/risc-v-a-baremetal-introduction-using-c-phil-mulholland/
sifive.com blog
https://www.sifive.com/blog/all-aboard-part-1-compiler-args
https://www.sifive.com/blog/all-aboard-part-2-relocations
https://www.sifive.com/blog/all-aboard-part-3-linker-relaxation-in-riscv-toolchain
https://www.sifive.com/blog/all-aboard-part-4-risc-v-code-models
https://www.sifive.com/blog/all-aboard-part-5-risc-v-multilib
https://www.sifive.com/blog/all-aboard-part-6-booting-a-risc-v-linux-kernel
https://www.sifive.com/blog/all-aboard-part-7-entering-and-exiting-the-linux-kernel-on-risc-v
https://www.sifive.com/blog/all-aboard-part-8-the-risc-v-linux-port-is-upstream
https://www.sifive.com/blog/all-aboard-part-9-paging-and-mmu-in-risc-v-linux-kernel
https://www.sifive.com/blog/all-aboard-part-10-how-to-contribute-to-the-risc-v-software-ecosystem
https://www.sifive.com/blog/all-aboard-part-11-risc-v-hackathon-presented-by-sifive