Qemu + virt(RISC-V)のカーネルエントリポイントを確認した
Writing an OS in 1,000 Linesを写経した。 これはエナガ本の副読本でRISC-Vに向けたOpenSBI依存のOSを書くというオンラインテキスト。写経ではコピペをしないようにすることとわからなかった仕様は確認することを意識してやった。
エナガ本では説明用の自作OSとしてHinaOSが使われている。1000LとHinaOSは同一ターゲット(qemu-system-riscv32 (RV32I) の virt 仮想マシン)に向けた実装だが開始アドレスが異なる。
HinaOSの開始アドレスは0x80000000で1000Lでは0x80200000になっていた。このちがいについて調べた。
結論
HinaOSはQemuで実行する際に-bios=noneを渡しており、Zeroth Stage Boot Loader(ZSBL)から呼ばれる。
そのため virt マシンのメモリマップの VIRT_DRAMの先頭がエントリーポイントとなる。
一方で 1000L-OS は OpenSBIに依存しており VIRT_DRAM 先頭には OpenSBI が置かれる。そのためカーネルの開始アドレスは OpenSBI と virt マシンの2つが協力して決定する。そのため異なるアドレスになる。 具体的にどのように決まるかは後述する。
詳細
OpenSBIはファームウェアでRISC-V SBI v2.0のリファレンス実装。 qemu-system-riscv32 のデフォルトBIOSとして使われる。
RISC-V SBI v2.0は S-modeやVS-modeで動くブートローダ, OS, ハイパーバイザに向けたインタフェースでシステムコール風にハードウェアへのアクセスなどを提供する。実装はSupervisor Execution Environment(SEE)と呼ばれる。wiki.riscv.orgに置かれている。
OpenSBIはファームウェアビルドを提供していて、ビルドオプションで処理後の次のステージにどう繋げるか3方式の中から選択できる。
- FW_DYNAMIC
- Next Address をマシンに渡してもらう方式
- FW_JUMP
- Next Address をビルド時に指定する方式
- FW_PAYLOAD
- 次のステージ用プログラムをOpenSBIのバイナリに含める方式
- ボードがFDTファイルを提供しない
- ボードが複数バイナリをロードしてくれない
- 次のステージ用プログラムをOpenSBIのバイナリに含める方式
qemu-system-riscv32 のデフォルトBIOSは FW_DYNAMIC としてビルドされたもの。
virt でのbootの流れ
- リセットベクタにジャンプする
- ZSBL が動く
a2レジスタにstruct fw_dynamic_infoへのポインタを格納する: OpenSBIの要求(仕様箇所を見つけられてない)- DRAMの先頭にジャンプしてOpenSBIが動く
- OpenSBIがカーネルに飛ばす
実際に流れを確認する。まず QemuのデバッグコンソールでROM情報をみる。0x1000 にリセットベクタが0x1028 に mrom.finfo が置かれている。
|
|
次にリセットベクタの処理を確認すると下のように a2 レジスタに 0x1028 (0x1000 + 40) が格納されたのちになんかジャンプ(jr)してる。
|
|
QEMUのRISC-Vのブートを読むと当たり前だけど一致している。
ここで0x1028 をみると下のようになっている。あやしい 0x80200000 がみえる。
|
|
そこでstruct fw_dynamic_infoの定義を確認すると3要素目(+8byte)にunsigned long next_addrがいることがわかる。
|
|
何かの仕様すくなくともOpenSBIの要求に従って次に読み込むアドレスがa2レジスタ経由で渡されることがわかった。
次に Qemu + virt 環境で実際の値がどこからきてるか確認する。
mrom.finfoはriscv/boot.c#riscv_rom_copy_firmware_info()で読み込んでいた。そして直前に cpu_to_le32(kernel_entry)をnext_addrに格納している。
この関数はriscv_setup_rom_reset_vec()で呼ばれていて、
そしてvirt_machine_done()に呼ばれている。
virt_machine_done()の中では下のように kernel_entryが決定されている。
|
|
BIOS設定、PFlash、カーネルファイルの指定が判断材料に使われている。
PFlashが設定されてない場合、riscv_load_kernel()の中で ELFファイルを読み込むときに設定される値と一致する。
一方で、PFlashが設定されていると VIRT_FLASH 先頭が利用される。
というわけで QEMU + virt + デフォルトBIOS(OpenSBI w/ FW_DYNAMIC) では、カーネル定義を ELF ファイルで渡すと OpenSBI が初期化処理後にカーネルのエントリポイントにジャンプしてくれることがわかった。