updated: 2024-02-07 Wed 00:00

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 ?

RISC-V Open Source Supervisor Binary Interface (OpenSBI).

You may check - OpenSBI Deep Dive.

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:

https://embeddedinn.com/articles/tutorial/RISCV-Uncovering-the-Mysteries-of-Linux-Boot-on-RISC-V-QEMU-Machines/


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