私の開発したRISC-VシミュレータはLinuxを立ち上げることができる。シミュレータのデバッグ時には相当中身を読み込んだのだが、きちんと文章化していない挙句、大昔のプロジェクトなのでもう忘れかけている。
Linuxのブートの方法から各種プロセスの取り扱いまで、思い出しながらRISC-Vシミュレータを動かしていき、ちゃんと文章化しておきたいと思った。
自作RISC-VシミュレータSwimmer-RISCVでのLinuxブート方法
自作RISC-Vシミュレータでfreedom-u-sdkのLinuxを立ち上げるためには以下のようにコマンドを入力する。
swimmer_riscv --binfile freedom-u-sdk/work/riscv-pk/bbl
bbl
というのは何かというと、Berkeley Boot Loaderというものだ。Linuxを立ち上げる際にこのプログラムが一番最初に呼ばれる。実際にはブートローダだけではなくbbl-payload
というものも含まれており、こちらがLinuxの本体に当たる。
このRISC-Vシミュレータはログ出力モードを持っており、--debug
オプションを使えばすべての実行した命令のトレースを取ることができる。さらに--hier-trace
オプションにより関数呼び出しのトレースを出力することができる。
命令トレースと関数トレースは以下のような感じ。
ブート時最初に実行されるもの
SiFiveのシミュレータやCPU実装をベースにしているのだが、最初にブートROMが実行される。ブートROMからリセットシーケンスに飛ぶ。ジャンプ先はbfdのエントリーポイントにジャンプしている。
- RISC-VのブートROMに格納されたブートシーケンス
StoreToBus<Word_t> (0x00001000, 0x00000297 ); // auipc t0,0x0 StoreToBus<Word_t> (0x00001004, 0x28593 + (reset_vec_size * 4 << 20)); // addi a1, t0, &dtb StoreToBus<Word_t> (0x00001008, static_cast<Word_t>(0xf1402573)); // csrr a0, mhartid if (m_bit_mode == RiscvBitMode_t::Bit32) { StoreToBus<Word_t> (0x0000100c, 0x0182a283 ); // lw t0,24(t0) } else { StoreToBus<Word_t> (0x0000100c, 0x0182b283 ); // ld t0,24(t0) } StoreToBus<Word_t> (0x00001010, 0x00028067 ); // jr t0 StoreToBus<Word_t> (0x00001014, 0x00000000 ); StoreToBus<Word_t> (0x00001018, static_cast<Word_t>(entry_address & 0xffffffff)); StoreToBus<Word_t> (0x0000101c, static_cast<Word_t>(entry_address >> 32 ));
ジャンプ先のdo_reset
では、ハードウェアのレジスタ初期化を行う。この初期化ルーチンはfreedom-u-sdk/riscv-pk/machine/mentry.S
に格納されている。pkはProxy-Kernelで、シンプルなカーネルと考えて問題ない。様々なOSに必要ルーチンを格納している。
freedom-u-sdk/riscv-pk/machine/mentry.S
do_reset: li x1, 0 li x2, 0 li x3, 0 li x4, 0 li x5, 0 li x6, 0 ... li x30, 0 li x31, 0 csrw mscratch, x0 # write mtvec and make sure it sticks la t0, trap_vector csrw mtvec, t0 csrr t1, mtvec 1:bne t0, t1, 1b la sp, stacks + RISCV_PGSIZE - MENTRY_FRAME_SIZE csrr a3, mhartid slli a2, a3, RISCV_PGSHIFT add sp, sp, a2 # Boot on the first hart beqz a3, init_first_hart # set MSIE bit to receive IPI li a2, MIP_MSIP csrw mie, a2
必要な処理は以下の通りだ。
- トラップベクタとして
trap_vector
をmtvec
に格納する。これにより例外・割込みが発生した場合にtrap_vector
ラベルにジャンプされるように設定される。 各コアのスタックポインタを設定する。HartID(=コアID)が0の場合は
init_first_hart
にジャンプする。複数コアの場合は、最初に有効かする例外要因はMachine Mode Software Interruptのみとする。
init_first_hart
はfreedom-u-sdk/riscv-pk/machine/minit.c
に格納してある。これらの動作について見ていこう。
void init_first_hart(uintptr_t hartid, uintptr_t dtb) { // Confirm console as early as possible query_uart(dtb); query_uart16550(dtb); query_htif(dtb); printm("bbl loader\r\n"); hart_init(); hls_init(0); // this might get called again from parse_config_string // Find the power button early as well so die() works query_finisher(dtb); query_mem(dtb); query_harts(dtb); query_clint(dtb); query_plic(dtb); wake_harts(); plic_init(); hart_plic_init(); //prci_test(); memory_init(); boot_loader(dtb); }