前回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!
なるほど。ちゃんと動作して終了もしてくれた。