私の開発したRISC-VシミュレータはLinuxを立ち上げることができる。シミュレータのデバッグ時には相当中身を読み込んだのだが、きちんと文章化していない挙句、大昔のプロジェクトなのでもう忘れかけている。
Linuxのブートの方法から各種プロセスの取り扱いまで、思い出しながらRISC-Vシミュレータを動かしていき、ちゃんと文章化しておきたいと思った。
CPU0が実行するboot_loader(dtb)
の流れ
init_first_hart()
も最後の関数、boot_loader(dtb)
に移る。boot_loader()
の本体はfreedom-u-sdk/riscv-pk/bbl/bbl.c
に格納されている。
freedom-u-sdk/riscv-pk/bbl/bbl.c
void boot_loader(uintptr_t dtb) { extern char _payload_start; filter_dtb(dtb); #ifdef PK_ENABLE_LOGO print_logo(); #endif #ifdef PK_PRINT_DEVICE_TREE fdt_print(dtb_output()); #endif mb(); entry_point = &_payload_start; boot_other_hart(0); }
print_logo
はRISC-Vのロゴを表示するためのルーチンで、誰もが見たことのあるこのロゴを表示する。
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv vvvvvvvvvvvvvvvvvvvvvvvvvvvv rrrrrrrrrrrrr vvvvvvvvvvvvvvvvvvvvvvvvvv rrrrrrrrrrrrrrrr vvvvvvvvvvvvvvvvvvvvvvvv rrrrrrrrrrrrrrrrrr vvvvvvvvvvvvvvvvvvvvvvvv rrrrrrrrrrrrrrrrrr vvvvvvvvvvvvvvvvvvvvvvvv rrrrrrrrrrrrrrrrrr vvvvvvvvvvvvvvvvvvvvvvvv rrrrrrrrrrrrrrrr vvvvvvvvvvvvvvvvvvvvvv rrrrrrrrrrrrr vvvvvvvvvvvvvvvvvvvvvv rr vvvvvvvvvvvvvvvvvvvvvv rr vvvvvvvvvvvvvvvvvvvvvvvv rr rrrr vvvvvvvvvvvvvvvvvvvvvvvvvv rrrr rrrrrr vvvvvvvvvvvvvvvvvvvvvv rrrrrr rrrrrrrr vvvvvvvvvvvvvvvvvv rrrrrrrr rrrrrrrrrr vvvvvvvvvvvvvv rrrrrrrrrr rrrrrrrrrrrr vvvvvvvvvv rrrrrrrrrrrr rrrrrrrrrrrrrr vvvvvv rrrrrrrrrrrrrr rrrrrrrrrrrrrrrr vv rrrrrrrrrrrrrrrr rrrrrrrrrrrrrrrrrr rrrrrrrrrrrrrrrrrr rrrrrrrrrrrrrrrrrrrr rrrrrrrrrrrrrrrrrrrr rrrrrrrrrrrrrrrrrrrrrr rrrrrrrrrrrrrrrrrrrrrr INSTRUCTION SETS WANT TO BE FREE
次のmb()
はMemory Blockのことで、実際にはfence
命令によってメモリ操作を確定させている。
freedom-u-sdk/riscv-pk/machine/atomic.h
#define mb() asm volatile ("fence" ::: "memory")
最後にLinuxのペイロードにジャンプする。entry_point
というグローバル変数にペイロードの場所を格納しておき、boot_other_hart()
にジャンプする。_payload_start
は実際にはLinuxのブートのためのコードであり、bblを作成する際にLinuxのバイナリを接続することで作成している。
freedom-u-sdk/riscv-pk/bbl/payload.S
#include "encoding.h" .section ".payload","a",@progbits .align RISCV_PGSHIFT + RISCV_PGLEVEL_BITS .globl _payload_start, _payload_end _payload_start: .incbin BBL_PAYLOAD _payload_end:
このBBL_PAYLOAD
の実体はwork/riscv-pk/bbl_payload
で、objcopy
コマンドを使ってELFファイルからバイナリに変換し埋め込んでいる。
freedom-u-sdk/work/riscv-pk/bbl.mk
bbl_payload: $(BBL_PAYLOAD) if $(READELF) -h $< 2> /dev/null > /dev/null; then $(OBJCOPY) -O binary $< $@; else cp $< $@; fi
freedom-u-sdk/work/riscv-pk/Makefile
BBL_PAYLOAD := /home/msyksphinz/work/riscv/freedom-u-sdk/work/linux/vmlinux-stripped
さてboot_other_hart()
に戻ってくるが、すでにboot_other_hart
に入る前にentry_point
にBBLペイロードの場所を格納しているのですぐにwhile文を抜ける。
freedom-u-sdk/riscv-pk/bbl/bbl.c
void boot_other_hart(uintptr_t unused __attribute__((unused))) { const void* entry; do { entry = entry_point; mb(); } while (!entry); ...
最後にスーパバイザモードにジャンプする。ジャンプ先はペイロードだ。
... enter_supervisor_mode(entry, hartid, dtb_output()); }
entry_supervisor_mode()
ではLinuxのブートに向けて最終的な調整を行う。
まずはPMP(Physical Memory Protection)の設定だ。基本的にどの領域へのアクセスも許可する。PMPをサポートしていないアーキテクチャでPMP設定をしてしまった場合のために、一時的にmtvecの場所を変更してトラップを無視するようにしている。
void enter_supervisor_mode(void (*fn)(uintptr_t), uintptr_t arg0, uintptr_t arg1) { // Set up a PMP to permit access to all of memory. // Ignore the illegal-instruction trap if PMPs aren't supported. uintptr_t pmpc = PMP_NAPOT | PMP_R | PMP_W | PMP_X; asm volatile ("la t0, 1f\n\t" "csrrw t0, mtvec, t0\n\t" "csrw pmpaddr0, %1\n\t" "csrw pmpcfg0, %0\n\t" ".align 2\n\t" "1: csrw mtvec, t0" : : "r" (pmpc), "r" (-1UL) : "t0");
最後にmstatus
を設定してLinuxのブートに備える。
mstatus.MPP
レジスタの設定。MPPはMachine Mode Previous Privilegeで、一つ前の権限モードを格納している。mret
命令ではMPP
レジスタフィールドの値を読み取って過去の動作モードに戻る。ここではPRV_S
を設定するため、mret
命令によりスーパバイザモードに戻って欲しい。mstatus.MPIE
レジスタフィールドの設定。MPIE
はMachine Mode Previous Interrupt Enableで、一つ前の権限モードでの割込み許可モードを記憶している。このビットフィールドを0に設定することで、mret
命令でスーパバイザモードに戻った時に割り込みを受け入れてしまうことを防いでいる。mscratch
レジスタの設定。一般的にこのレジスタはマシンモードにおいてHART固有のストレージ領域のポインタを格納しておくためのレジスタである。mepc
の設定。mret
命令はmepc
に格納されているアドレスに向かってジャンプする。引数から受け取ったentry
、つまりbbl_payload
の先頭を設定する。
そしてmret
でいよいよスーパバイザモードに遷移し、Linuxのブートを開始する。いよいよここからだ。