🐶
RISC-VのOpenSBIでHelloWorld
前回U-BootからHelloWorldしたときには、コンソールの入出力はU-Boot内部へのジャンプテーブル経由で行いました。今回はOpenSBIのシステムコール(SBI call)を使ってみます。
SBI(Supervisor Binary Interface)
RISC-VではS(Supervisor)モード間のインタフェースを規定しています。それがSBI(Supervisor Binary Interface)で、そのオープンソースの実装がOpenSBIです。
この図はこちらのスライドから引用しました。
VisionFive2のボードにもOpenSBIが載っていて、ブート時のログでそのことがわかります。
U-Boot SPL 2021.10 (Jun 21 2023 - 13:42:04 +0800)
DDR version: dc2e84f0.
Trying to boot from SPI
OpenSBI v1.2
____ _____ ____ _____
/ __ \ / ____| _ \_ _|
| | | |_ __ ___ _ __ | (___ | |_) || |
| | | | '_ \ / _ \ '_ \ \___ \| _ < | |
| |__| | |_) | __/ | | |____) | |_) || |_
\____/| .__/ \___|_| |_|_____/|___^@/_____|
| |
|_|
Platform Name : StarFive VisionFive V2
Platform Features : medeleg
Platform HART Count : 5
Platform IPI Device : aclint-mswi
Platform Timer Device : aclint-mtimer @ 4000000Hz
Platform Console Device : uart8250
Platform HSM Device : ---
Platform PMU Device : ---
Platform Reboot Device : pm-reset
Platform Shutdown Device : pm-reset
Platform Suspend Device : ---
Firmware Base : 0x40000000
Firmware Size : 392 KB
Firmware RW Offset : 0x40000
Runtime SBI Version : 1.0
Domain0 Name : root
Domain0 Boot HART : 1
Domain0 HARTs : 0*,1*,2*,3*,4*
Domain0 Region00 : 0x0000000002000000-0x000000000200ffff M: (I,R,W) S/U: ()
Domain0 Region01 : 0x0000000040000000-0x000000004003ffff M: (R,X) S/U: ()
Domain0 Region02 : 0x0000000040040000-0x000000004007ffff M: (R,W) S/U: ()
Domain0 Region03 : 0x0000000000000000-0xffffffffffffffff M: (R,W,X) S/U: (R,W,X)
Domain0 Next Address : 0x0000000040200000
Domain0 Next Arg1 : 0x0000000042200000
Domain0 Next Mode : S-mode
Domain0 SysReset : yes
Domain0 SysSuspend : yes
Boot HART ID : 1
Boot HART Domain : root
Boot HART Priv Version : v1.11
Boot HART Base ISA : rv64imafdcbx
Boot HART ISA Extensions : none
Boot HART PMP Count : 8
Boot HART PMP Granularity : 4096
Boot HART PMP Address Bits: 34
Boot HART MHPM Count : 2
Boot HART MIDELEG : 0x0000000000000222
Boot HART MEDELEG : 0x000000000000b109
...
DRAMは0x40000000 から載っていますが、Linuxカーネルで使用するRAMは0x40200000 からになっています。
どうやら最初の2MBはOpenSBIのためにリザーブされているようです。
$ sudo cat /proc/iomem
...
40200000-c010ffff : System RAM
40202000-419b1b9c : Kernel image
40202000-40c65a21 : Kernel code
41200000-415fffff : Kernel rodata
41800000-4190adff : Kernel data
4190b000-419b1b9c : Kernel bss
c0110000-c01fffff : Reserved
c0200000-23fffffff : System RAM
...
SBI call
U-Bootのソースコードを調べると、リセットをするときにSBI callを利用していることがわかりました。[1]
再起動のためのリセットの具体的な方法はSoCやボードに依存していますが、SBI call経由にすることでその違いを隠蔽することができます。
今回はU-Bootのコードを参考にして、sbi_console_putchar
とsbi_console_getchar
の2つだけを使用できるようにしました。
sbi.c
#include "sbi.h"
struct sbiret sbi_ecall(int ext, int fid, unsigned long arg0,
unsigned long arg1, unsigned long arg2,
unsigned long arg3, unsigned long arg4,
unsigned long arg5)
{
struct sbiret ret;
register unsigned long a0 asm ("a0") = (unsigned long)(arg0);
register unsigned long a1 asm ("a1") = (unsigned long)(arg1);
register unsigned long a2 asm ("a2") = (unsigned long)(arg2);
register unsigned long a3 asm ("a3") = (unsigned long)(arg3);
register unsigned long a4 asm ("a4") = (unsigned long)(arg4);
register unsigned long a5 asm ("a5") = (unsigned long)(arg5);
register unsigned long a6 asm ("a6") = (unsigned long)(fid);
register unsigned long a7 asm ("a7") = (unsigned long)(ext);
asm volatile ("ecall"
: "+r" (a0), "+r" (a1)
: "r" (a2), "r" (a3), "r" (a4), "r" (a5), "r" (a6), "r" (a7)
: "memory");
ret.error = a0;
ret.value = a1;
return ret;
}
void sbi_console_putchar(int ch)
{
sbi_ecall(SBI_EXT_0_1_CONSOLE_PUTCHAR, 0, ch, 0, 0, 0, 0, 0);
}
int sbi_console_getchar(void)
{
struct sbiret ret;
ret = sbi_ecall(SBI_EXT_0_1_CONSOLE_GETCHAR, 0, 0, 0, 0, 0, 0, 0);
return ret.error;
}
HelloWorld
hello.c
#include "sbi.h"
void sbi_puts(char *s)
{
while (*s) {
sbi_console_putchar(*s++);
}
sbi_console_putchar('\n');
}
int main(int argc, char** argv)
{
sbi_puts("Hello from SBI");
return 0;
}
必要なファイルはGitHubに置きました。
ビルド
$ make
riscv64-unknown-elf-gcc -Os -ffunction-sections -fdata-sections -c -o crt.o crt.c
riscv64-unknown-elf-gcc -Os -ffunction-sections -fdata-sections -c -o hello.o hello.c
riscv64-unknown-elf-gcc -Os -ffunction-sections -fdata-sections -c -o sbi.o sbi.c
riscv64-unknown-elf-gcc -nostdlib -T hello_sbi.lds -Wl,--gc-sections crt.o hello.o sbi.o -o hello_sbi
riscv64-unknown-elf-objcopy -O binary hello_sbi hello_sbi.bin
シンボルは以下の通り。
$ riscv64-unknown-elf-nm hello_sbi |sort
0000000041000000 T _start
000000004100001e T sbi_puts
000000004100003e T main
0000000041000056 T sbi_ecall
0000000041000074 T sbi_console_putchar
0000000041000097 B __bss_start
0000000041000098 B __bss_end
0000000041000098 B _end
実行結果
StarFive # ls mmc 1:3
4415441 System.map-5.15.0-starfive
199013 config-5.15.0-starfive
extlinux/
9272081 initrd.img-5.15.0-starfive
406 uEnv.txt
8574915 vmlinuz-5.15.0-starfive
dtbs/
4856 hello_world.bin
151 hello_sbi.bin
7 file(s), 2 dir(s)
StarFive # fatload mmc 1:3 0x41000000 hello_sbi.bin
151 bytes read in 5 ms (29.3 KiB/s)
StarFive # go 0x41000000
## Starting application at 0x41000000 ...
Hello from SBI
## Application terminated, rc = 0x0
StarFive #
想定通り、Hello from SBI
が表示されて、U-Bootのプロンプトに戻ってきました。
関連
-
実際にはボード依存のリセットの方法とSBI callを使う方法の両方が実装されています。 ↩︎
Discussion