目次: Linux
OpenSBI + Linuxの環境まで動いたので、次はLinuxのディストリビューションに挑みます。ディストリビューションはDebianにします。以前はUnofficialでしたがDebian 13(Trixie)から正式にRISC-Vをサポートする(riscv64 is now an official architecture - debian-riscv)そうです。良いですね。
DebianのドキュメントにStarFive VisionFive V1用のインストール手順がある(Installing Debian On StarFive VisionFive V1 - Debian Wiki)ので、非常に参考になります。
RISC-Vサポート予定とされているDebian 13(Trixie)は現時点だとTesting版のはずですが、RISC-V向けのパッケージが存在しないものがあって(※)動かなさそうだったので、今回はUnstable版(sid)を使用しています。
今回の実験環境はこんな感じです。
(※)例えばlibcbor0.10はRISC-V版が存在しません。openssh-client -> libfido2-1 -> libcbor0.10の依存を持つ、OpenSSHクライアントがインストールできません。
Debianを動作させるにはDebianのルートファイルシステムが必要です。debootstrapというコマンドで作成できて超便利です。
$ apt-get install qemu-user-static binfmt-support debootstrap systemd-container rsync wget $ sudo debootstrap --arch=riscv64 unstable /path/to/riscv-chroot https://deb.debian.org/debian I: Target architecture can be executed I: Retrieving InRelease I: Checking Release signature I: Valid Release signature (key id 4CB50190207B4758A3F73A796ED0E7B82643E131) I: Retrieving Packages I: Validating Packages I: Resolving dependencies of required packages... I: Resolving dependencies of base packages... I: Checking component main on https://deb.debian.org/debian... I: Retrieving adduser 3.137 I: Validating adduser 3.137 I: Retrieving apt 2.9.7 I: Validating apt 2.9.7 ... I: Configuring tasksel-data... I: Configuring libc-bin... I: Configuring ca-certificates... I: Base system installed successfully.
このとき依存パッケージ、特にQEMU(qemu-user-static)をインストールし忘れていると、E: Unable to execute target architectureとメッセージが出て止まります。
作成したDebian rootfsをQEMUからNFSでマウントします。サーバー側の設定例はこんな感じです。insecureを指定しているなど、セキュリティ完全無視の設定なので実験が終わったら設定を消してください。
#### /etc/exports
/path/to/debian_rootfs *(rw,no_root_squash,no_subtree_check,insecure)
設定を反映するにはexportfsコマンドを使います。何も出力されませんが、エラーがでなければ良いです。
# apt-get install nfs-kernel-server nfs-common # exportfs -a
手動でマウントする場合はこんな感じです。
# sudo mount -t nfs -o nolock 192.168.1.1:/path/to/debian_rootfs rootfs
オプションnolockを付けている理由は、portmapやnfslockのようなデーモンなしでもマウントできるようにするためです。busyboxからマウントするときは便利です。
QEMUの起動引数はたくさんありますがまずはネットワークデバイスの設定をします。netdevでQEMUの外とのネットワーク接続を定義し、deviceでQEMUの中のネットワークデバイスを定義します。カーネル引数はappendに指定します。rootfsをNFSでマウントするためのオプションは、下記の3つです。
NFS v4でも動きそうに思うんですけど、私の環境はなぜかNFS v3じゃないと動作しませんでした。これに気づくまでかなり時間を浪費しました……。
$ qemu-system-riscv64 \ -machine virt \ -bios none \ -nographic \ -chardev stdio,id=con,mux=on \ -serial chardev:con \ -mon chardev=con,mode=readline \ -netdev user,id=netdev0 \ -device virtio-net-device,netdev=netdev0 \ -kernel ../../opensbi/build/platform/generic/firmware/fw_payload.bin \ -append "root=/dev/nfs nfsroot=192.168.1.1:/path/to/debian_rootfs,v3 ip=on nfsrootdebug rw" \ -m 2g \ -smp 4 \ -s \ ... [ 2.312617] ALSA device list: [ 2.313060] No soundcards found. [ 2.534757] VFS: Mounted root (nfs filesystem) readonly on device 0:20. [ 2.541726] devtmpfs: mounted [ 2.708101] Freeing unused kernel image (initmem) memory: 2260K [ 2.710000] Run /sbin/init as init process [ 4.808617] systemd[1]: systemd 256.4-2 running in system mode (+PAM +AUDIT +SELINUX +APPARMOR +IMA +SMACK +SECCOMP +GCRYPT -GNUTLS +OPENSSL +ACL +BLKID +CURL +ELFUTILS +FIDO2 +IDN2 -IDN +IPTC +KMOD +LIBCRYPTSETUP +LIBCRYPTSETUP_PLUGINS +LIBFDISK +PCRE2 +PWQUALITY +P11KIT +QRENCODE +TPM2 +BZIP2 +LZ4 +XZ +ZLIB +ZSTD +BPF_FRAMEWORK -XKBCOMMON +UTMP +SYSVINIT +LIBARCHIVE) [ 4.811613] systemd[1]: Detected virtualization qemu. [ 4.812465] systemd[1]: Detected architecture riscv64. Welcome to Debian GNU/Linux trixie/sid! [ 4.857724] systemd[1]: Hostname set to <qemu>. ... Debian GNU/Linux trixie/sid qemu ttyS0 qemu login:
うまくいけばログイン画面が拝めるはずです。
ログインユーザーを作成する方法が良くわからなかったため、Debianのrootfsを別のinitramfsからマウントして無理やりuseraddしてユーザーを追加しました。本来どうするものなんでしょうね……?
自分でQEMUをビルドしていると、netdev user,...に対してnetwork backend 'user' is not compiledエラーが出る場合があります。その時はQEMUビルド時のconfigureのログを確認してください。
$ ./qemu-system-riscv64 -netdev user,id=a qemu-system-riscv64: -netdev user,id=a: network backend 'user' is not compiled into this binary #### configureのログ(NGパターン) Network backends AF_XDP support : NO slirp support : NO ## ★slirpが使えない★ vde support : NO netmap support : NO #### configureのログ(OKパターン) Network backends AF_XDP support : NO slirp support : YES 4.8.0 ## ★slirpが使える★ vde support : NO netmap support : NO
QEMUはnetdev user,...の実装にslirpを使っているようで、slirp supportがNOになっているとnetdev user,...が機能しません。libslirp-devをインストール(apt-get install libslirp-dev)してから、QEMUのconfigureと再ビルドをしてください。
目次: Linux
OpenSBIのブート部分を調べます。OpenSBIはいくつか動作モードがあるのですが、payloadを持ったタイプを調べます。対象のファイル名はbuild/platform/generic/firmware/fw_payload.binもしくは.elfです。
今回はデバイスツリーについて調べます。対象はOpenSBI 1.5です。
QEMUの場合は、QEMU起動時にデバイスツリーがロードされ、ブートコードによってレジスタa1にアドレス(-m 128Mの場合0x87e00000、メモリ量オプションによって変わる)が設定されます。実機はこのような動作はしないので、メモリがまっさらな状態からOpenSBIとデバイスツリーをどう配置すれば動くか、その方法についても調べます。
QEMU向けの場合はfw_platform_init()がレジスタa1を参照しますので、ブート時にa1の設定が必要です。
//opensbi/firmware/fw_base.S
_bss_zero:
REG_S zero, (s4)
add s4, s4, __SIZEOF_POINTER__
blt s4, s5, _bss_zero
/* Setup temporary trap handler */
lla s4, _start_hang
csrw CSR_MTVEC, s4
/* Setup temporary stack */
lla s4, _fw_end
li s5, (SBI_SCRATCH_SIZE * 2)
add sp, s4, s5
/* Allow main firmware to save info */
MOV_5R s0, a0, s1, a1, s2, a2, s3, a3, s4, a4
call fw_save_info
MOV_5R a0, s0, a1, s1, a2, s2, a3, s3, a4, s4
#ifdef FW_FDT_PATH
/* Override previous arg1 */
lla a1, fw_fdt_bin
#endif
/*
* Initialize platform
* Note: The a0 to a4 registers passed to the
* firmware are parameters to this function.
*/
MOV_5R s0, a0, s1, a1, s2, a2, s3, a3, s4, a4
call fw_platform_init /* ★この関数がa1を参照する★ */
add t0, a0, zero
MOV_5R a0, s0, a1, s1, a2, s2, a3, s3, a4, s4
add a1, t0, zero
//opensbi/platform/generic/platform.c
/*
* The fw_platform_init() function is called very early on the boot HART
* OpenSBI reference firmwares so that platform specific code get chance
* to update "platform" instance before it is used.
*
* The arguments passed to fw_platform_init() function are boot time state
* of A0 to A4 register. The "arg0" will be boot HART id and "arg1" will
* be address of FDT passed by previous booting stage.
*
* The return value of fw_platform_init() function is the FDT location. If
* FDT is unchanged (or FDT is modified in-place) then fw_platform_init()
* can always return the original FDT location (i.e. 'arg1') unmodified.
*/
unsigned long fw_platform_init(unsigned long arg0, unsigned long arg1,
unsigned long arg2, unsigned long arg3,
unsigned long arg4)
{
const char *model;
void *fdt = (void *)arg1; //★ここでレジスタa1の値を使っている★
u32 hartid, hart_count = 0;
int rc, root_offset, cpus_offset, cpu_offset, len;
...
関数fw_platform_init()は_bss_zeroの後に呼ばれます。fw_next_arg1()を呼び出す前なのでFW_PAYLOAD_FDT_ADDRやFW_PAYLOAD_FDT_OFFSETを定義しても参照されません。
もしQEMUで既存のデバイスツリーを無視して、手動でロードする実験をしたい場合は、
このような設定をすると実験できます。コマンドを手動で実行するのは辛いので、スクリプトファイルを用意してsource (スクリプトファイル)コマンドで実行したほうが楽でしょう。
# load_dtb_opensbi.gdb restore virt.dtb binary 0x87f00000 thread 1 set $a1=0x87f00000 thread 2 set $a1=0x87f00000 thread 3 set $a1=0x87f00000 thread 4 set $a1=0x87f00000 restore build/platform/generic/firmware/fw_payload.bin binary 0x80000000 thread 1 set $pc=0x80000000 thread 2 set $pc=0x80000000 thread 3 set $pc=0x80000000 thread 4 set $pc=0x80000000
デバイスツリーを0x87f00000にロードして実行する例です。restoreコマンドでロードしているデバイスツリーのバイナリをQEMUから得る方法は前回(2024年月日の日記参照)の説明をご参照ください。
QEMUが起動時に用意するデバイスツリーとは異なるデバイスツリーをロードできることを確認するため、QEMUのデバイスツリーのmodelを少し書き換えたデバイスツリーvirt_mod.dtbを用意します。
/* virt.dts */
/dts-v1/;
/ {
#address-cells = <0x02>;
#size-cells = <0x02>;
compatible = "riscv-virtio";
model = "riscv-virtio,qemo"; /* ★★qemu → qemoに変更★★ */
DTSからDTBにするとき(その逆もできます)はデバイスツリーコンパイラdtcを使用します。
$ dtc -O dtb virt.dts > virt.dtb
QEMUを起動した後にデバッガーでQEMUにアタッチし、先ほど作成したGDBスクリプトにてロードとレジスタの設定を行います。
$ riscv64-unknown-linux-gnu-gdb (gdb) target remote :1234 Remote debugging using :1234 warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x0000000000001000 in ?? () (gdb) source load_dtb_opensbi.gdb Restoring binary file ../qemu/build/virt_mod.dtb into memory (0x87f00000 to 0x87f01c20) [Switching to thread 1 (Thread 1.1)] #0 0x0000000000001000 in ?? () [Switching to thread 2 (Thread 1.2)] #0 0x0000000000001000 in ?? () [Switching to thread 3 (Thread 1.3)] #0 0x0000000000001000 in ?? () [Switching to thread 4 (Thread 1.4)] #0 0x0000000000001000 in ?? () Restoring binary file build/platform/generic/firmware/fw_payload.bin into memory (0x80000000 to 0x81f51608) [Switching to thread 1 (Thread 1.1)] #0 0x0000000000001000 in ?? () [Switching to thread 2 (Thread 1.2)] #0 0x0000000000001000 in ?? () [Switching to thread 3 (Thread 1.3)] #0 0x0000000000001000 in ?? () [Switching to thread 4 (Thread 1.4)] #0 0x0000000000001000 in ?? () (gdb) continue Continuing.
最後のcontinueを実行するとQEMU側もログが出ます。例えばこんな感じです。
$ ./qemu-system-riscv64 -machine virt \ -bios none \ -nographic \ -chardev stdio,id=con,mux=on \ -serial chardev:con \ -mon chardev=con,mode=readline \ -smp 4 \ -s \ -S OpenSBI v1.5 ____ _____ ____ _____ / __ \ / ____| _ \_ _| | | | |_ __ ___ _ __ | (___ | |_) || | | | | | '_ \ / _ \ '_ \ \___ \| _ < | | | |__| | |_) | __/ | | |____) | |_) || |_ \____/| .__/ \___|_| |_|_____/|____/_____| | | |_| Platform Name : riscv-virtio,qemo ★★改造したmodel名になっている★★ Platform Features : medeleg Platform HART Count : 4 ...
OpenSBIがPlatform Nameを出しているログを見れば、先程デバイスツリーを改造したときに付けたmodel名が出力されているはずです。メモリ上にデバイスツリーやOpenSBIがロードされない実機だとしても、デバッガー経由でロードして起動することができそうです。
目次: Linux
OpenSBIのブート部分を調べます。OpenSBIはいくつか動作モードがあるのですが、payloadを持ったタイプを調べます。対象のファイル名はbuild/platform/generic/firmware/fw_payload.binもしくは.elfです。
今回はOpenSBIのデバイスツリー処理について調べます。対象はOpenSBI 1.5 PLATFORM=genericの改造版、実行環境はQEMU RV64 virt machine(qemu-system-riscv64 -machine virt)です。
以前(2024年7月24日の日記参照)説明したデバイスツリーの先頭アドレスを取得する処理の概要は下記のとおりです。
_scratch_init //scratch領域を確保する、先頭アドレスはtpレジスタに格納される fw_next_arg1() // firmware/fw_payload.S // FW_PAYLOAD_FDT_ADDRが定義済みならFW_PAYLOAD_FDT_ADDRを返す // FW_PAYLOAD_FDT_OFFSETが定義済みなら_fw_start + FW_PAYLOAD_FDT_OFFSETを返す // いずれも未定義ならばa1レジスタの値を返す ★改造してこの実装を使ってみる★ // 結果はscratch->next_arg1に格納
プラットフォームgenericを選択すると選択肢2番目の処理が使われます。が、MakefileからFW_PAYLOAD_FDT_OFFSETの定義を削除すると、3番目の処理を選択できます。
無理やり改造しているので全てが完璧に動作するかわからないですが、少なくともLinuxのブート部分まで到達しますから、OpenSBIの動作を調べるには十分でしょう。Makefileを下記のように改変します。
# opensbi/platform/generic/objects.mk
ifeq ($(PLATFORM_RISCV_XLEN), 32)
# This needs to be 4MB aligned for 32-bit system
FW_PAYLOAD_OFFSET=0x400000
else
# This needs to be 2MB aligned for 64-bit system
FW_PAYLOAD_OFFSET=0x200000
endif
FW_PAYLOAD_FDT_OFFSET=$(FW_JUMP_FDT_OFFSET) ★この行を削除する★
FW_PAYLOAD_FDT_ADDRもFW_PAYLOAD_FDT_OFFSETも未定義にしたので、3番目の処理が選択されます。
//opensbi/firmware/fw_payload.S
fw_next_arg1:
#ifdef FW_PAYLOAD_FDT_ADDR
li a0, FW_PAYLOAD_FDT_ADDR
#elif defined(FW_PAYLOAD_FDT_OFFSET)
lla a0, _fw_start
li a1, FW_PAYLOAD_FDT_OFFSET
add a0, a0, a1
#else
add a0, a1, zero //★a1レジスタの値を返すだけ★
#endif
ret
なぜa1レジスタにデバイスツリーの先頭アドレスが格納されるか?については、以前の(2024年7月23日の日記参照)QEMUのデバイスツリーの説明をご参照ください。
レジスタa1の値はすぐに壊れてしまいそうに思えますが、OpenSBIはレジスタa1の値をずっと維持し続けるため実装を工夫しています。
// opensbi/firmware/fw_base.S
/* Store next address in scratch space */
MOV_3R s0, a0, s1, a1, s2, a2 //★a0, a1, a2の値をs0, s1, s2に退避★
call fw_next_addr //★C言語の関数呼び出しでa0, a1, a2レジスタが壊れる★
REG_S a0, SBI_SCRATCH_NEXT_ADDR_OFFSET(tp)
MOV_3R a0, s0, a1, s1, a2, s2 //★a0, a1, a2の値を復帰★
C言語で実装した関数を呼び出すなど、レジスタa1の値が壊れることがわかっている場合は壊れる前に別レジスタに値を退避し、関数から戻ってきた後に復帰処理を行います。
この改造を行うとデバイスツリーのコピーは行われず、ペイロードにQEMUが生成&配置したデバイスツリーの先頭アドレスが渡されます。メモリマップはこんな感じです。
動作ログも確認しましょう。
Domain0 Name : root Domain0 Boot HART : 3 Domain0 HARTs : 0*,1*,2*,3* 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 : 0x0000000080200000 Domain0 Next Arg1 : 0x0000000087e00000 ★0x82200000 → 0x87e00000に変化した★ Domain0 Next Mode : S-mode Domain0 SysReset : yes Domain0 SysSuspend : yes
QEMUが渡してきたアドレス0x87e00000がそのまま使われていることがわかります。
デバイスツリーのもう1つの扱い方についてわかりました。しかしデバイスツリーを起動時に生成&配置できるのはQEMUのようなエミュレータだけです。実機ではそんなことはできませんから、何か別の方法が採用されているはずです。次回は実機でのデバイスツリーの扱いについて調べたいと思います。
< | 2024 | > | ||||
<< | < | 08 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | - | 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
合計:
本日: