私の開発したRISC-VシミュレータはLinuxを立ち上げることができる。シミュレータのデバッグ時には相当中身を読み込んだのだが、きちんと文章化していない挙句、大昔のプロジェクトなのでもう忘れかけている。
Linuxのブートの方法から各種プロセスの取り扱いまで、思い出しながらRISC-Vシミュレータを動かしていき、ちゃんと文章化しておきたいと思った。
ブート時にコア0以外はどのような動作をするのか
これまでの初期化プログラムの動きは、基本的にコア0(初期化動作を行うコア)の動きのみを眺めてきた。ここではコア0以外のコアがブート時にどのような動作をするのかを確認していこう。
自身のコア番号を識別するためには、RISC-Vにはmhartid
というレジスタが定義されており、これにより自分のコア番号を取得できる。自分がコア0ではない場合、上記で解説したinit_first_hart()
にはジャンプせず、コア0からの割り込みを待つための待機状態に入る。
freedom-u-sdk/riscv-pk/machine/mentry.S
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 ...
mhartid
が0出なかった場合、mie
レジスタ(Machine Interrupt Enable)レジスタのMIP_MSIP
(Machine Mode Interrupt Pending, Machine Software Interrupt)ビットを有効化し、マシンモードでのソフトウェア割込みを受け付けて待機状態に入るという仕組みだ。
.LmultiHart: #if MAX_HARTS > 1 # wait for an IPI to signal that it's safe to boot wfi # masked harts never start la a4, disabled_hart_mask LOAD a4, 0(a4) srl a4, a4, a3 andi a4, a4, 1 bnez a4, .LmultiHart # only start if mip is set csrr a2, mip andi a2, a2, MIP_MSIP beqz a2, .LmultiHart # make sure our hart id is within a valid range fence li a2, MAX_HARTS bltu a3, a2, init_other_hart #endif wfi j .LmultiHart
ここでMAX_HARTS > 1
である場合について考えてみたい。MAX_HARTS
はmtrap.h
で定義され取り、アトミック命令がサポートされていれば基本的に8個までのHARTを持つことができる。
freedom-u-sdk/riscv-pk/machine/mtrap.h
#ifdef __riscv_atomic # define MAX_HARTS 8 // arbitrary #else # define MAX_HARTS 1 #endif
.LmultiHart
ラベル以下の動作を見ていこう。まずはコア0以外のCPUはwfi
(wait for interrupt)に入りコア0からの割り込み要求っを待つ。コア0はinit_first_hart()
にて自分以外のコアをwake_harts()
で呼び起こすが、これを待っている。
wfi
が解除されると、割り込みルーチンに飛ぶ。割り込みルーチンはあらかじめmtvec
レジスタに格納しているのでそこにジャンプする。ジャンプ先はtrap_vector
だ。trap_vector
を見てみよう。
freedom-u-sdk/riscv-pk/machine/mentry.S
trap_vector: csrrw sp, mscratch, sp beqz sp, .Ltrap_from_machine_mode STORE a0, 10*REGBYTES(sp) STORE a1, 11*REGBYTES(sp) csrr a1, mcause bgez a1, .Lhandle_trap_in_machine_mode # This is an interrupt. Discard the mcause MSB and decode the rest. sll a1, a1, 1
一つずつ見ていこう。まず確認しているのは割り込み要因だ。これはmcause
レジスタの中身をチェックすることで確認できる。mcause
レジスタの値をa1
に読み込むと、最上位ビットを落とす。最上位ビットは割り込みなのか例外なのかを示すためのビットで、割り込みならば1が設定されているがとりあえずこれを切り落とす。
割り込み要因がタイマ割り込みかどうかをチェックする。そうでないのでとりあえずここは飛ばそう。
# Is it a machine timer interrupt? li a0, IRQ_M_TIMER * 2 bne a0, a1, 1f # Yes. Simply clear MSIE and raise SSIP. li a0, MIP_MTIP csrc mie, a0 li a0, MIP_STIP csrs mip, a0
次はIPI(コア間割込み)かどうかをチェックする。これは該当するので、処理を続けよう。まずはIPIビットをクリアする。このビットはCLINT内に定義されており、自分のIPI用のCLINTのビット位置はMENTRY_IPI_OFFSET
離れた場所にある値をロードすることで得られる(というか自分でその様に予め値を書き込んでいる)。
freedom-u-sdk/riscv-pk/machine/mtrap.h
#define MENTRY_HLS_OFFSET (INTEGER_CONTEXT_SIZE + SOFT_FLOAT_CONTEXT_SIZE) #define MENTRY_FRAME_SIZE (MENTRY_HLS_OFFSET + HLS_SIZE) #define MENTRY_IPI_OFFSET (MENTRY_HLS_OFFSET)
しかし今回は、コア0からのIPI要求ではMENTRY_IPI_OFFSET
の場所に何も書き込まれていはいない。従ってIPIの割り込み要因をチェックするコードはすべて実行されず、単純に元に戻ってくるだけである。
freedom-u-sdk/riscv-pk/machine/mtrap.h
typedef struct { volatile uint32_t* ipi; volatile int mipi_pending; // IPIではこの場所に何かしら要因が書いていなければならないのだが、コア0は何も書き込んでいないので以下のテストはすべてスルーされる。 volatile uint64_t* timecmp; volatile uint32_t* plic_m_thresh; volatile uintptr_t* plic_m_ie; volatile uint32_t* plic_s_thresh; volatile uintptr_t* plic_s_ie; } hls_t;
実際のテスト部分。beqz
による比較はすべてTrueで、結局何もせず.Lmret
に戻ってくる。
# Now, decode the cause(s). #ifdef __riscv_atomic addi a0, sp, MENTRY_IPI_PENDING_OFFSET amoswap.w a0, x0, (a0) #else lw a0, MENTRY_IPI_PENDING_OFFSET(a0) sw x0, MENTRY_IPI_PENDING_OFFSET(a0) #endif and a1, a0, IPI_SOFT beqz a1, 1f csrs mip, MIP_SSIP 1: andi a1, a0, IPI_FENCE_I beqz a1, 1f fence.i 1: andi a1, a0, IPI_SFENCE_VMA beqz a1, 1f sfence.vma 1: andi a1, a0, IPI_HALT beqz a1, 1f wfi j 1b 1: j .Lmret
.Lmret: # Go back whence we came. LOAD a0, 10*REGBYTES(sp) LOAD a1, 11*REGBYTES(sp) csrrw sp, mscratch, sp mret
mret
の戻る先はwfi
の直後であるため、結局何もせずにIPI割り込みであることだけを確認して戻ってくる。
wfi
が解除されると、diasbled_hart_mask
変数をチェックする。これはコア0により設定されている変数で、RISC-V以外のコアなど立ち上げる必要のないコアをはじくためのマスクとなっている。自分のコアのビット位置に1が設定されてあればブートできるが、そうでなければ再び.L_multiHart
に入り待機状態になる。次のmip
レジスタ(Machine Interrupt Pending、自分に割り込みが入っているかどうかをチェックする)も確認し、MSIPが有効であることを確認する。
最後にinit_other_harts()
にジャンプすることで、コア0以外のブート処理が始まる。
freedom-u-sdk/riscv-pk/machine/minit.c
void init_other_hart(uintptr_t hartid, uintptr_t dtb) { hart_init(); hart_plic_init(); boot_other_hart(dtb); }
hart_init()
とhart_plic_init()
は解説したとおりだ。boot_other_hart()
によりコア0以外のブートがスタートする。