FPGA開発日記

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

freedom-u-sdkのLinuxを立ち上げながらLinuxのブートプロセスを学ぶ(6. CPU0が実行する`boot_loader(dtb)`の流れ)

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

さて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のブートを開始する。いよいよここからだ。