はじめに
Zynq MPSoCで、コンピュータを学ぼう にて、「Xilinx社のZynqMP SoCのブートシーケンス」を説明しました。 また、まとめでは、RISC-Vでもほぼ同じですよ。と書きました。
RISC-VのBootについては、ちゃんと確認していなかったので、確認しました。
RISC-VのBoot
下記のサイトの内容から確認しました。
- 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
start.S 内でいろいろやって、main 関数をコールします。
call main tail exit
main.c の main 関数は、下記のような引数を持ちます。第一引数は mhartidの id、第二引数は dtbがストアされている アドレスです。しかしながら、この id は、main 関数内では使われていません。また、dtb も引数の値は使っていません。
int main(int id, unsigned long dtb)
ここで、
最後の方で、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 に 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については、
にて、まとめたのでこちらを覗いてみてください。