FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

freedom-u-sdkのLinuxを立ち上げながらLinuxのブートプロセスを学ぶ(5. ブート時にコア0以外はどのような動作をするのか)

私の開発した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
f:id:msyksphinz:20200423003029p:plain

ここでMAX_HARTS > 1である場合について考えてみたい。MAX_HARTSmtrap.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以外のブートがスタートする。