Vengineerの妄想

人生を妄想しています。

RISC-VのBoot

はじめに

Zynq MPSoCで、コンピュータを学ぼう にて、「Xilinx社のZynqMP SoCのブートシーケンス」を説明しました。 また、まとめでは、RISC-Vでもほぼ同じですよ。と書きました。

vengineer.hatenablog.com

RISC-VのBootについては、ちゃんと確認していなかったので、確認しました。

RISC-VのBoot

下記のサイトの内容から確認しました。

github.com

  • Zero Stage Boot Loader : SoCの中にあるROM
  • First Stage Boot Loader : ここは、U-Boot SPL など
  • Second Stage Boot Loader : OpenSBI => U-Boot
  • Linux

OpenSBI は、ZynqMP の CPUコア、Armv8 の Arm Trusted Firmware に相当するものです。OpenSBIとLinuxについては、

にて、まとめたのでこちらを覗いてみてください。

OpenSBIより前はどうなっているのか?

上記の記事の中では、

のURLが貼っていましたので、そこを探ってみます。

Zero Stage Boot Loader

Zero Stage Boot Loader のソースコードは、こちら

start.S が最初に実行するコードです。prog_start から始まり、start に続きます。

  .section .text.init
  .globl _prog_start
_prog_start:
  .globl _start
_start:
  la t0, trap_entry
  csrw mtvec, t0

  smp_pause(s1, s2)

ずーと続いて、main を call します。

  smp_resume(s1, s2)

  // Allocate 4 KiB stack for each hart
  csrr t0, mhartid
  slli t0, t0, 12
  la sp, _sp
  sub sp, sp, t0

  call main

  li t0, CCACHE_SIDEBAND_ADDR
  csrr a0, mhartid
  la a1, _dtb
  jr t0

  .align 2
trap_entry:
  call handle_trap

  .section .rodata
_dtb:
  .incbin "zsbl/ux00_zsbl.dtb"
  //.incbin DEVICE_TREE

main.c の main関数の中で、ux00boot_load_gpt_partition にてCCACHE_SIDEBAND_ADDRにロードしています。

int main()
{
  if (read_csr(mhartid) == NONSMP_HART) {
    unsigned int peripheral_input_khz;
    if (UX00PRCI_REG(UX00PRCI_CLKMUXSTATUSREG) & CLKMUX_STATUS_TLCLKSEL) {
      peripheral_input_khz = CORE_CLK_KHZ; // perpheral_clk = tlclk
    } else {
      peripheral_input_khz = (CORE_CLK_KHZ / 2);
    }
    init_uart(peripheral_input_khz);
    ux00boot_load_gpt_partition((void*) CCACHE_SIDEBAND_ADDR, &gpt_guid_sifive_fsbl, peripheral_input_khz);
  }

  Barrier_Wait(&barrier, NUM_CORES);

  return 0;
}

ux00boot_load_gpt_partition は、ux00boot/ux00boot.c の中で次のように定義しています。

void ux00boot_load_gpt_partition(void* dst, const gpt_guid* partition_type_guid, unsigned int peripheral_input_khz)
{
  uint32_t mode_select = *((volatile uint32_t*) MODESELECT_MEM_ADDR);

  spi_ctrl* spictrl = NULL;
  void* spimem = NULL;

  int spi_device = get_boot_spi_device(mode_select);
  ux00boot_routine boot_routine = get_boot_routine(mode_select);

  switch (spi_device)
  {
    case 0:
      spictrl = (spi_ctrl*) SPI0_CTRL_ADDR;
      spimem = (void*) SPI0_MEM_ADDR;
      break;
    case 1:
      spictrl = (spi_ctrl*) SPI1_CTRL_ADDR;
      spimem = (void*) SPI1_MEM_ADDR;
      break;
    case 2:
      spictrl = (spi_ctrl*) SPI2_CTRL_ADDR;
      break;
    case 3:
      // MODESELECT_LOOP. Don't try to find spi device in debug mode.
      break;
    default:
      ux00boot_fail(ERROR_CODE_UNHANDLED_SPI_DEVICE, 0);
      break;
  }

  unsigned int error = 0;

  switch (boot_routine)
  {
    case UX00BOOT_ROUTINE_FLASH:
      error = initialize_spi_flash_direct(spictrl, peripheral_input_khz);
      if (!error) error = load_spiflash_gpt_partition(spictrl, dst, partition_type_guid);
      break;
    case UX00BOOT_ROUTINE_MMAP:
      error = initialize_spi_flash_mmap_single(spictrl, peripheral_input_khz);
      if (!error) error = load_mmap_gpt_partition(spimem, dst, partition_type_guid);
      break;
    case UX00BOOT_ROUTINE_MMAP_QUAD:
      error = initialize_spi_flash_mmap_quad(spictrl, peripheral_input_khz);
      if (!error) error = load_mmap_gpt_partition(spimem, dst, partition_type_guid);
      break;
    case UX00BOOT_ROUTINE_SDCARD:
    case UX00BOOT_ROUTINE_SDCARD_NO_INIT:
      {
        int skip_sd_init_commands = (boot_routine == UX00BOOT_ROUTINE_SDCARD) ? 0 : 1;
        error = initialize_sd(spictrl, peripheral_input_khz, skip_sd_init_commands);
        if (!error) error = load_sd_gpt_partition(spictrl, dst, partition_type_guid);
      }
      break;
    case UX00BOOT_ROUTINE_LOOP:
      error = 0;
      /**
       * Control transfer back to debugger.
       * Debugger can load next stage to PAYLOAD_DEST.
       */
      asm volatile ("ebreak");
      break;
    default:
      error = ERROR_CODE_UNHANDLED_BOOT_ROUTINE;
      break;
  }

  if (error) {
    ux00boot_fail(error, 0);
  }
}

SPI または SDCard から First Stage Boot Loader をロードしています。

start.S に戻って、ロードしたアドレス (CCACHE_SIDEBAND_ADDR) を t0、mhartid を a0、_dtb を a1 に設定して、t0 (CCACHE_SIDEBAND_ADDR) にジャンプします。これで、Zero Stage Boot Loader から First Stage Boot Loader に移行します。

  call main

  li t0, CCACHE_SIDEBAND_ADDR
  csrr a0, mhartid
  la a1, _dtb
  jr t0

ここによると、

x5   t0  一時レジスタ・リンクレジスタ  呼び出し側
x10-x11 a0-a1   引数・戻り値レジスタ  呼び出し側
x12-x17 a2-a7   引数レジスタ  呼び出し側

にあるように、t0 をリンクレジスタ、a0/a1 を引数レジスタとして使っています。

mhardid とは、Hart ID Register でリードオンリーなレジスタのようです。

The mhartid CSR is an MXLEN-bit read-only register containing the integer ID of the hardware thread running the code.

アドレスは、ux00_zsbl.lds にて、設定しています。

First Stage Boot Loader

FSBL のソースコードは、ここにあります。

start.S 内でいろいろやって、main 関数をコールします。

  call main
  tail exit

main.c の main 関数は、下記のような引数を持ちます。第一引数は mhartidの id、第二引数は dtbがストアされている アドレスです。しかしながら、この id は、main 関数内では使われていません。また、dtb も引数の値は使っていません。

int main(int id, unsigned long dtb)

ここで、

  • DDR の初期化
  • GEMGXL の初期化
  • dtbDRAM にコピーする

最後の方で、PAYLOAD_DEST に Second Boot Loader をダウンロードして、slave_main 関数を実行します。

  puts("Loading boot payload");
  ux00boot_load_gpt_partition((void*) PAYLOAD_DEST, &gpt_guid_sifive_bare_metal, peripheral_input_khz);

  puts("\r\n\n");
  slave_main(0, dtb);

slave_main 関数の中で

  • mtvec <= PAYLOAD_DEST
  • a0 <= id (値は 0)
  • a1 <= dtb or dtb_target

にして、mtvec に jump

int slave_main(int id, unsigned long dtb)
{
#ifdef BOARD_SETUP
  while (1)
    ;
#else
  // Wait for the DTB location to become known
  while (!dtb_target) {}

  //wait on barrier, disable sideband then trap to payload at PAYLOAD_DEST
  write_csr(mtvec,PAYLOAD_DEST);

  register int a0 asm("a0") = id;
#ifdef SKIP_DTB_DDR_RANGE
  register unsigned long a1 asm("a1") = dtb;
#else
  register unsigned long a1 asm("a1") = dtb_target;
#endif
  // These next two guys must get inlined and not spill a0+a1 or it is broken!
  Barrier_Wait(&barrier, NUM_CORES);
  ccache_enable_ways(CCACHE_CTRL_ADDR,14);
  asm volatile ("unimp" : : "r"(a0), "r"(a1));
#endif

  return 0;
}

おわりに

ZBSL => FSBL => SBL (Second Bootloader) までの流れがわかりました。

ちなみに、SBLは、OpenSBIになります。

再度、OpenSBI は、ZynqMP の CPUコア、Armv8 の Arm Trusted Firmware に相当するものです。OpenSBIとLinuxについては、

にて、まとめたのでこちらを覗いてみてください。