前回QEMUでとりあえずprintf()
を扱うための簡単なサンプルは無いかと探していたのだが、どうやらfreedom-e-sdk
がかなり使い物になっているようで、これを試してみることにした。
$ git clone https://github.com/sifive/freedom-e-sdk.git $ cd freedom-e-sdk $ git submodule update --init --recursive # U54向け $ make PROGRAM=hello TARGET=qemu-sifive-u54 # E31向け $ make PROGRAM=hello TARGET=qemu-sifive-e31
ここではGCCとしてSiFive社が提供しているGCCバイナリを使用した。私は昔からの環境も取ってあるのでデフォルトのriscv64-unknown-elf-gcc
がGCC-7.2.0になっているのだが、もう少し新しいものを使わないと駄目なようだ。
(自分用メモ:)module switch riscv/riscv64_sifev
を適用する。
これで、それぞれ以下の場所にバイナリが生成される。
freedom-e-sdk/software/hello/debug/hello.elf
これをQEMUで実行するためにはどうするのか考えよう。まずは何も考えずにQEMUを実行してみる。
$ qemu-system-riscv64 --nographic --machine virt --bios none --kernel freedom-e-sdk/software/hello/debug/hello.elf
特に何も表示されないので、-d in_asm
でどのような命令が実行されたのか確認してみる。
$ qemu-system-riscv64 -d in_asm --nographic --machine virt --bios none \ --kernel freedom-e-sdk/software/hello/debug/hello.elf
実行直後のログ:
---------------- IN: Priv: 3; Virt: 0 0x0000000000001000: 00000297 auipc t0,0 # 0x1000 0x0000000000001004: 02828613 addi a2,t0,40 0x0000000000001008: f1402573 csrrs a0,mhartid,zero ---------------- IN: Priv: 3; Virt: 0 0x000000000000100c: 0202b583 ld a1,32(t0) 0x0000000000001010: 0182b283 ld t0,24(t0) 0x0000000000001014: 00028067 jr t0 ---------------- IN: Priv: 3; Virt: 0 0x0000000080000000: 00007197 auipc gp,28672 # 0x80007000 0x0000000080000004: d3818193 addi gp,gp,-712 0x0000000080000008: 00000297 auipc t0,0 # 0x80000008 0x000000008000000c: 01828293 addi t0,t0,24 0x0000000080000010: 30529073 csrrw zero,mtvec,t0 ---------------- IN: Priv: 3; Virt: 0 0x0000000080000014: 00100293 addi t0,zero,1 0x0000000080000018: 00028463 beqz t0,8 # 0x80000020 ----------------
0x8000_0000
の付近はhello.elf
のダンプ結果と合っているような気がする。
$ riscv64-unknown-elf-objdump -D freedom-e-sdk/software/hello/debug/hello.elf
0000000080000000 <_enter>: 80000000: 00007197 auipc gp,0x7 80000004: d3818193 addi gp,gp,-712 # 80006d38 <__global_pointer$> 80000008: 00000297 auipc t0,0x0 8000000c: 01828293 addi t0,t0,24 # 80000020 <_enter+0x20> 80000010: 30529073 csrw mtvec,t0 80000014: 00100293 li t0,1 80000018: 00028463 beqz t0,80000020 <_enter+0x20> 8000001c: 7c105073 csrwi 0x7c1,0 80000020: 00000297 auipc t0,0x0 80000024: 12428293 addi t0,t0,292 # 80000144 <early_trap_vector> 80000028: 30529073 csrw mtvec,t0 8000002c: 80000117 auipc sp,0x80000
そして、QEMUの実行はどこかで停止している。
---------------- IN: __metal_before_start Priv: 3; Virt: 0 0x0000000080000144: 342022f3 csrrs t0,mcause,zero ---------------- IN: __metal_before_start Priv: 3; Virt: 0 0x0000000080000148: 34102373 csrrs t1,mepc,zero ---------------- IN: __metal_before_start Priv: 3; Virt: 0 0x000000008000014c: 343023f3 csrrs t2,mtval,zero ---------------- IN: __metal_before_start Priv: 3; Virt: 0 0x0000000080000150: bfd5 j -12 # 0x80000144
うーん、__metal_before_start
の内部で止まってしまっている?0x80000150
と0x80000144
でループを繰り返しているようだ。
だいたいのダンプ結果を示してみると:
... IN: _write IN: _write IN: _write IN: _write IN: metal_tty_putc IN: metal_uart_putc IN: __metal_driver_sifive_uart0_putc IN: __metal_driver_sifive_uart0_control_base IN: __metal_driver_sifive_uart0_control_base IN: __metal_driver_sifive_uart0_control_base IN: __metal_driver_sifive_uart0_putc IN: __metal_driver_sifive_uart0_txready IN: __metal_driver_sifive_uart0_txready IN: __metal_before_start IN: __metal_before_start ...
なるほど、おそらくputc()
あたりで待ち状態になっているのかな?txready()
あたりのソースコードを探してみる。
freedom-e-sdk/freedom-metal/src/drivers/sifive_uart0.c
int __metal_driver_sifive_uart0_putc(struct metal_uart *uart, int c) { long control_base = __metal_driver_sifive_uart0_control_base(uart); while (__metal_driver_sifive_uart0_txready(uart) != 0) { /* wait */ } UART_REGW(METAL_SIFIVE_UART0_TXDATA) = c; return 0; }
なるほど。METAL_SIFIVE_UART0_TXDATA
への書き込みが行われているようだ。これで一応putc()
は出来ているはずだけどなあ?どうも動いていないらしい。
その後いろいろ調べて、まさかと思いQEMUのバージョンをriscv-qemu
に変えてみた。
git clone git@github.com:sifive/riscv-qemu.git -b riscv-qemu-3.1 cd riscv-qemu mkdir build cd build ../configure --target-list=riscv64-softmmu make -j4 ./riscv64-softmmu/qemu-system-riscv64 -d in_asm --nographic --machine sifive_u --bios none \ --kernel /home/msyksphinz/work/riscv/freedom-e-sdk/software/hello/debug/hello.elf
これだと動作した。つまり最新バージョンのQEMUにおけるsifive_u
は仕様が変わっている、ということか...
まずは、QEMU-5.1.0とriscv-qemu
(QEMU-3.1.0)のデバイスツリーを比較する。
- QEMU-3.1.0の場合
$ ./riscv64-softmmu/qemu-system-riscv64 --nographic --machine sifive_u,dumpdtb=riscv64-sifive_u.dtb --bios none \ --kernel /home/msyksphinz/work/riscv/freedom-e-sdk/software/hello/debug/hello.elf $ dtc riscv64-sifive_u.dtb
... soc { #address-cells = < 0x02 >; #size-cells = < 0x02 >; compatible = "simple-bus"; ranges; uart@ { interrupts = < 0x01 >; interrupt-parent = < 0x02 >; reg = < 0x00 0x10013000 0x00 0x1000 >; compatible = "sifive,uart0"; }; ...
- QEMU-5.1.0の場合
... soc { #address-cells = < 0x02 >; #size-cells = < 0x02 >; compatible = "simple-bus"; ranges; serial@10010000 { interrupts = < 0x04 >; interrupt-parent = < 0x06 >; clocks = < 0x05 0x03 >; reg = < 0x00 0x10010000 0x00 0x1000 >; compatible = "sifive,uart0"; }; ...
あーこのメモリマップの関係が違うのか。では、hello
側のメモリマップを変更してみよう。このためにはfreedom-e-sdk
内のbsp
を変更する必要があった。
diff --git a/bsp/qemu-sifive-u54/metal-platform.h b/bsp/qemu-sifive-u54/metal-platform.h index cd1e5e3..d241916 100644 --- a/bsp/qemu-sifive-u54/metal-platform.h +++ b/bsp/qemu-sifive-u54/metal-platform.h @@ -47,8 +47,8 @@ #define METAL_SIFIVE_TEST0_FINISHER_OFFSET 0UL /* From uart@10013000 */ -#define METAL_SIFIVE_UART0_10013000_BASE_ADDRESS 268513280UL -#define METAL_SIFIVE_UART0_0_BASE_ADDRESS 268513280UL +#define METAL_SIFIVE_UART0_10013000_BASE_ADDRESS 0x10010000UL +#define METAL_SIFIVE_UART0_0_BASE_ADDRESS 0x10010000UL #define METAL_SIFIVE_UART0_10013000_SIZE 4096UL #define METAL_SIFIVE_UART0_0_SIZE 4096UL
これでhello
を再ビルドしQEMUで実行すると、とりあえずprintf()
は動くようになった。ただテストパタンが終了しないのでこれは問題。QEMU-3.1.0では終了するので解析していきたいと思う。
$ ./qemu/build/riscv64-softmmu/qemu-system-riscv64 --nographic --machine sifive_u --bios none \ --kernel freedom-e-sdk/software/hello/debug/hello.elf
Hello, World! QEMU: Terminated
もう一つの確認事項は、テストパタンの終了手順だ。riscv-qemu
で確認すると最終的に__metal_driver_sifive_test0_exit
で終了しているようだった。
$ ./build/riscv64-softmmu/qemu-system-riscv64 -d in_asm --nographic --machine sifive_u --bios none --kernel /home/msyksphinz/work/riscv/freedom-e-sdk/software/hello/debug/hello.elf
---------------- IN: __metal_driver_sifive_test0_exit 0x0000000080004b56: 6795 lui a5,20480 0x0000000080004b58: 55578793 addi a5,a5,1365 0x0000000080004b5c: a021 j 8 # 0x80004b64 ---------------- IN: __metal_driver_sifive_test0_exit 0x0000000080004b64: 9fb9 addw a5,a5,a4 0x0000000080004b66: 2781 sext.w a5,a5 0x0000000080004b68: fef42223 sw a5,-28(s0) 0x0000000080004b6c: fe843783 ld a5,-24(s0) 0x0000000080004b70: fe442703 lw a4,-28(s0) 0x0000000080004b74: c398 sw a4,0(a5) 0x0000000080004b76: bfdd j -10 # 0x80004b6c
確認してみると、riscv-qemuには以下のテスト用のデバイスがマップされていた。
test@100000 { reg = < 0x00 0x100000 0x00 0x1000 >; compatible = "sifive,test0"; };
しかし、QEMU-5.1.0ではmachine=sifive_u
ではこのようなデバイスは定義していなかった。どうもmachine=virt
では定義されているようだ。
uart@10000000 { interrupts = < 0x0a >; interrupt-parent = < 0x03 >; clock-frequency = < 0x384000 >; reg = < 0x00 0x10000000 0x00 0x100 >; compatible = "ns16550a"; }; ... test@100000 { phandle = < 0x04 >; reg = < 0x00 0x100000 0x00 0x1000 >; compatible = "sifive,test1\0sifive,test0\0syscon"; };
この0x100000
というのはfreedom-e-sdk
のデフォルト値と合っているようだ。ということはuart
の位置だけを0x10000000
に切り替えるとhello
は動作してくれるだろう。
diff --git a/bsp/qemu-sifive-u54/metal-platform.h b/bsp/qemu-sifive-u54/metal-platform.h index cd1e5e3..2f5f89a 100644 --- a/bsp/qemu-sifive-u54/metal-platform.h +++ b/bsp/qemu-sifive-u54/metal-platform.h @@ -47,8 +47,8 @@ #define METAL_SIFIVE_TEST0_FINISHER_OFFSET 0UL /* From uart@10013000 */ -#define METAL_SIFIVE_UART0_10013000_BASE_ADDRESS 268513280UL -#define METAL_SIFIVE_UART0_0_BASE_ADDRESS 268513280UL +#define METAL_SIFIVE_UART0_10013000_BASE_ADDRESS 0x10000000UL +#define METAL_SIFIVE_UART0_0_BASE_ADDRESS 0x10010000UL #define METAL_SIFIVE_UART0_10013000_SIZE 4096UL #define METAL_SIFIVE_UART0_0_SIZE 4096UL
./qemu-5.1.0/build/riscv64-softmmu/qemu-system-riscv64 --nographic --machine virt --bios none --kernel /home/msyksphinz/work/riscv/freedom-e-sdk/software/hello/debug/hello.elf Hello, World!
なるほど。ちゃんと動作して終了もしてくれた。