🐶

RISC-VのOpenSBIでHelloWorld

2023/08/10に公開

前回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_putcharsbi_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に置きました。
https://github.com/tetsu-koba/hello_sbi

ビルド

$ 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のプロンプトに戻ってきました。

関連

https://zenn.dev/tetsu_koba/articles/554cad6a46149e
https://zenn.dev/tetsu_koba/articles/866fc7b4d459bd

脚注
  1. 実際にはボード依存のリセットの方法とSBI callを使う方法の両方が実装されています。 ↩︎

Discussion