FPGA開発日記

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

freedom-u-sdkのLinuxを立ち上げながらLinuxのブートプロセスを学ぶ(1. シミュレータの立ち上げ方法とブートシーケンス)

私の開発したRISC-VシミュレータはLinuxを立ち上げることができる。シミュレータのデバッグ時には相当中身を読み込んだのだが、きちんと文章化していない挙句、大昔のプロジェクトなのでもう忘れかけている。

Linuxのブートの方法から各種プロセスの取り扱いまで、思い出しながらRISC-Vシミュレータを動かしていき、ちゃんと文章化しておきたいと思った。

自作RISC-VシミュレータSwimmer-RISCVでのLinuxブート方法

自作RISC-Vシミュレータでfreedom-u-sdkLinuxを立ち上げるためには以下のようにコマンドを入力する。

swimmer_riscv --binfile freedom-u-sdk/work/riscv-pk/bbl

bblというのは何かというと、Berkeley Boot Loaderというものだ。Linuxを立ち上げる際にこのプログラムが一番最初に呼ばれる。実際にはブートローダだけではなくbbl-payloadというものも含まれており、こちらがLinuxの本体に当たる。

このRISC-Vシミュレータはログ出力モードを持っており、--debugオプションを使えばすべての実行した命令のトレースを取ることができる。さらに--hier-traceオプションにより関数呼び出しのトレースを出力することができる。

f:id:msyksphinz:20200419014805p:plain

命令トレースと関数トレースは以下のような感じ。

f:id:msyksphinz:20200419014828p:plain

ブート時最初に実行されるもの

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_vectormtvecに格納する。これにより例外・割込みが発生した場合にtrap_vectorラベルにジャンプされるように設定される。
  • 各コアのスタックポインタを設定する。HartID(=コアID)が0の場合はinit_first_hartにジャンプする。

  • 複数コアの場合は、最初に有効かする例外要因はMachine Mode Software Interruptのみとする。

init_first_hartfreedom-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);
}