コグノスケ


link 未来から過去へ表示(*)  link 過去から未来へ表示

link もっと前
2024年8月3日 >>> 2024年7月25日
link もっと後

2024年8月2日

Debian on RISC-V

目次: 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)を使用しています。

  • NFS Server: Debian Testing (Trixie)
  • NFS: nfs-common 2.6.4-5
  • QEMU: 9.0.2
  • RISC-V Linux: 6.9.0

今回の実験環境はこんな感じです。

(※)例えばlibcbor0.10はRISC-V版が存在しません。openssh-client -> libfido2-1 -> libcbor0.10の依存を持つ、OpenSSHクライアントがインストールできません。

Debianルートファイルシステムの作成

Debianを動作させるにはDebianのルートファイルシステムが必要です。debootstrapというコマンドで作成できて超便利です。

debootstrapによるRISC-V Debian rootfsの構築
$ 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を指定しているなど、セキュリティ完全無視の設定なので実験が終わったら設定を消してください。

NFSの設定

#### /etc/exports

/path/to/debian_rootfs    *(rw,no_root_squash,no_subtree_check,insecure)

設定を反映するにはexportfsコマンドを使います。何も出力されませんが、エラーがでなければ良いです。

NFSの設定反映
# apt-get install nfs-kernel-server nfs-common

# exportfs -a

手動でマウントする場合はこんな感じです。

NFSマウントの例
# sudo mount -t nfs -o nolock 192.168.1.1:/path/to/debian_rootfs rootfs

オプションnolockを付けている理由は、portmapやnfslockのようなデーモンなしでもマウントできるようにするためです。busyboxからマウントするときは便利です。

NFS rootfsでの動作確認

QEMUの起動引数はたくさんありますがまずはネットワークデバイスの設定をします。netdevでQEMUの外とのネットワーク接続を定義し、deviceでQEMUの中のネットワークデバイスを定義します。カーネル引数はappendに指定します。rootfsをNFSでマウントするためのオプションは、下記の3つです。

  • root=...: rootfsをNFSでマウントする
  • nfsroot=...,v3: NFSでマウントするサーバーとディレクトリの指定、NFS v3を使う
  • ip=...: IPを自動設定

NFS v4でも動きそうに思うんですけど、私の環境はなぜかNFS v3じゃないと動作しませんでした。これに気づくまでかなり時間を浪費しました……。

QEMU上でRISC-V Debian GNU/Linuxが起動する様子
$ 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のログを確認してください。

network backend〜エラー、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と再ビルドをしてください。

編集者:すずき(2024/08/09 02:59)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2024年7月29日

OpenSBIを調べる - デバイスツリーの扱い(実機想定)

目次: Linux

OpenSBIのブート部分を調べます。OpenSBIはいくつか動作モードがあるのですが、payloadを持ったタイプを調べます。対象のファイル名はbuild/platform/generic/firmware/fw_payload.binもしくは.elfです。

今回はデバイスツリーについて調べます。対象はOpenSBI 1.5です。

QEMUの場合は、QEMU起動時にデバイスツリーがロードされ、ブートコードによってレジスタa1にアドレス(-m 128Mの場合0x87e00000、メモリ量オプションによって変わる)が設定されます。実機はこのような動作はしないので、メモリがまっさらな状態からOpenSBIとデバイスツリーをどう配置すれば動くか、その方法についても調べます。

QEMUの動作を復習

QEMU向けの場合はfw_platform_init()がレジスタa1を参照しますので、ブート時にa1の設定が必要です。

OpenSBIのブート部分

//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で既存のデバイスツリーを無視して、手動でロードする実験をしたい場合は、

  • OpenSBIのバイナリをロードする
  • pcをOpenSBIのロードアドレス兼エントリアドレス(0x80000000)に設定する
  • デバイスツリーをロードする
  • a1をデバイスツリーのアドレスに設定する
  • 上記をすべてのhartに対して実行する

このような設定をすると実験できます。コマンドを手動で実行するのは辛いので、スクリプトファイルを用意してsource (スクリプトファイル)コマンドで実行したほうが楽でしょう。

デバイスツリーとOpenSBIをロードし、レジスタを設定するGDBスクリプト
# 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を用意します。

model名だけ変更したデバイスツリー

/* 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側もログが出ます。例えばこんな感じです。

先ほどロードしたOpenSBIのログ

$ ./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がロードされない実機だとしても、デバッガー経由でロードして起動することができそうです。

編集者:すずき(2024/08/09 12:18)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2024年7月25日

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)です。

Linuxにデバイスツリーの場所を伝えるもう一つの方法

以前(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を下記のように改変します。

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番目の処理が選択されます。

デバイスツリーの先頭アドレスを求めるコード(Makefile改造後)

//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の値をずっと維持し続けるため実装を工夫しています。

レジスタ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が生成&配置したデバイスツリーの先頭アドレスが渡されます。メモリマップはこんな感じです。


デバイスツリーに関連するQEMUのメモリマップ(改造版)

動作ログも確認しましょう。

OpenSBI 1.5の起動ログ(抜粋、改造版)
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/07/26 02:08)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link もっと前
2024年8月3日 >>> 2024年7月25日
link もっと後

管理用メニュー

link 記事を新規作成

<2024>
<<<08>>>
----123
45678910
11121314151617
18192021222324
25262728293031

最近のコメント5件

  • link 14年6月13日
    2048player...さん (09/26 01:04)
    「最後に、この式を出すのに紙4枚(A4)も...」
  • link 14年6月13日
    2048playerさん (09/26 01:00)
    「今のところ最も簡略化した式です。\n--...」
  • link 14年6月13日
    2048playerさん (09/16 01:00)
    「返信ありがとうございます。\nコメントが...」
  • link 14年6月13日
    すずきさん (09/12 21:19)
    「コメントありがとうございます。同じ結果に...」
  • link 14年6月13日
    2048playerさん (09/08 17:30)
    「私も2048の最高スコアを求めたのですが...」

最近の記事3件

  • link 24年9月14日
    すずき (09/22 11:23)
    「[OpenSBIを調べる - scratch領域の詳細] 目次: Linux今回はOpenSBIのコード内に頻出するscrat...」
  • link 21年8月11日
    すずき (09/22 00:15)
    「[Kindle - まとめリンク] 目次: Kindle初代Kindle Fire HDの話。Kindle Fire HDのカ...」
  • link 24年8月11日
    すずき (09/22 00:14)
    「[Amazonマイリストへの問い合わせの返事がきた] 目次: Kindle先日(2024年8月4日の日記参照)Amazonへ問...」
link もっとみる

こんてんつ

open/close wiki
open/close Linux JM
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 2021年
open/close 2022年
open/close 2023年
open/close 2024年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報

合計:  counter total
本日:  counter today

link About www.katsuster.net
RDFファイル RSS 1.0

最終更新: 09/26 01:04