RISC-V体系结构的U-Boot引导过程
flyfish
BootLoader
Boot是Bootstrap(鞋带)的缩写,它来自于一句谚语:“Pull oneself up by one’s own bootstraps”,直译的意思是“拽着鞋带把自己拉起来”
干这件事,得先问问牛顿行不行?牛爵爷也只是看一眼就忙着炼金去了。
早期计算机的启动,存在这样一个问题:必须先运行程序,然后计算机才能启动,但是计算机不启动就不能运行程序。为了解决这个问题人们把一小段程序先刷入ROM,通电后,第一件事就是读取它,之后计算机就能正常运行了。这个过程称为“拉鞋带”,简称为Boot。BootLoader是Boot和Loader两个单词的组合。Boot是引导,Loader是加载器。
当计算机上电时,开始的时候RAM里是没有操作系统的,因为操作系统,存放在比如硬盘、CD、DVD、Flash存储卡、U盘等
这些叫非易失性存储设备上,加电后计算机会首先执行ROM中的小程序,之后才会访问非易失性存储设备中的操作系统和数据,并将其加载到RAM中。
在嵌入式系统中整个系统的加载启动任务就完全由BootLoader来完成。BootLoader是CPU上电后运行的第一段程序,它的作用就是对嵌入式系统中的硬件进行初始化,创建内核需要的参数并将这些参数传递给内核,最终启动了操作系统内核,起到引导和加载内核的作用。PC上用的可以是BIOS这样的固件程序。可以看传递参数的一个例子。
一个 Boot Loader加上了universal就成了宇宙级的Boot Loader,全宇宙通用的Boot Loader就是U-Boot(Universal Boot Loader),U-Boot也只是BootLoader其中的一种,还是其他的例如
LILO(LInux LOader)
GRUB(GRand Unified Boot loader)
blob(blob bootloader)
RedBoot
vivi
如果看到了Bootstrap Loader或者 Bootstrap都是BootLoader的意思
RISC-V
RISC-V发音 RISC five
RISC-V是基于精简指令集计算(RISC)原理的开源指令集体系结构(ISA)。
ISA:instruction set architecture 指令集体系结构
RISC:reduced instruction set computing 精简指令集计算,有的地方写作 Reduced Instruction Set Computer
简单与复杂相对 Complex Instruction Set Computer
RISC-V的寄存器
U-Boot将使用gp保存指向全局数据的指针
x0: hard-wired zero (zero)
x1: return address (ra)
x2: stack pointer (sp)
x3: global pointer (gp)
x4: thread pointer (tp)
x5: link register (t0)
x8: frame pointer (fp)
x10-x11: arguments/return values (a0-1)
x12-x17: arguments (a2-7)
x28-31: temporaries (t3-6)
pc: program counter (pc)
U-Boot可以在M模式或S模式下运行,具体取决于它是否在提供SBI的固件初始化之前运行。固件在RISC-V引导过程中是必需的,因为它充当SEE来处理S模式U-Boot或操作系统的异常。
SBI:Supervisor Binary Interface,SBI是Supervisor(S模式操作系统)和Supervisor执行环境(SEE,Supervisor Execution Environment)之间的调用约定,其调用风格就像System call一样。OpenSBI是一个SBI实现,可以在不同的模式下与U-Boot一起使用。
OpenSBI:RISC-V Open Source Supervisor Binary Interface。
SEE:Supervisor Execution Environment
看下SBI的位置
M-mode U-Boot
<-----------( M-mode )----------><--( S-mode )-->
+----------+ +--------------+ +------------+
| U-Boot |-->| SBI firmware |--->| OS |
+----------+ +--------------+ +------------+
S-mode U-Boot
<-------------( M-mode )----------><----------( S-mode )------->
+------------+ +--------------+ +----------+ +----------+
| U-Boot SPL |-->| SBI firmware |--->| U-Boot |-->| OS |
+------------+ +--------------+ +----------+ +----------+
在引导阶段之间,hartid通过a0寄存器,设备树的起始地址通过a1寄存器。hart是RISC-V使用的术语,指的是硬件线程(RISC-V hardware thread )。hart通常在其他上下文中称为核(core)或CPU。
一般的多阶段启动
常用的多引导阶段
具有有关下一引导阶段的动态信息的OpenSBI固件
ZSBL:Zero Stage Boot Loader)
FSBL:First Stage Boot Loader,U-Boot SPL
M-mode : machine-mode
U-mode : user-mode
S-mode : supervisor-mode
u-boot.lds
buildroot/output/build/uboot-origin_master/u-boot.lds
ld就是The GNU linker
lds是The GNU linker script
ld为什么是linker呢,d哪里去了?
原文答案
意思是
Linux自带了自己的链接器,名为ld。(该名称实际上是“load”的缩写,而“loader”最初是在20世纪70年代Unix的第一个时代被称为链接器的名称.最初的操作系统中没有linker,都是操作系统loader干了所有的活。后来复杂了就有了linker,所以ld(loader)成了链接器的简写。
u-boot.lds内容
OUTPUT_FORMAT("elf64-littleriscv", "elf64-littleriscv", "elf64-littleriscv")
OUTPUT_ARCH("riscv")
ENTRY(_start)
SECTIONS
{
. = 0x42000000;
. = ALIGN(4);
.__image_copy_start : {
*(.__image_copy_start)
}
.head :
{
arch/riscv/cpu/spare_head.o (.data*)
}
.text : {
arch/riscv/cpu/start.o (.text)
}
.efi_runtime : {
__efi_runtime_start = .;
*(.text.efi_runtime*)
*(.rodata.efi_runtime*)
*(.data.efi_runtime*)
__efi_runtime_stop = .;
}
.text_rest : {
*(.text*)
}
. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
. = ALIGN(4);
.data : {
*(.data*)
}
. = ALIGN(4);
.got : {
__got_start = .;
*(.got.plt) *(.got)
__got_end = .;
}
. = ALIGN(4);
.u_boot_list : {
KEEP(*(SORT(.u_boot_list*)));
}
. = ALIGN(4);
.efi_runtime_rel : {
__efi_runtime_rel_start = .;
*(.rel*.efi_runtime)
*(.rel*.efi_runtime.*)
__efi_runtime_rel_stop = .;
}
. = ALIGN(4);
.image_copy_end : {
*(.__image_copy_end)
}
/DISCARD/ : { *(.rela.plt*) }
.rela.dyn : {
__rel_dyn_start = .;
*(.rela*)
__rel_dyn_end = .;
}
. = ALIGN(4);
.dynsym : {
__dyn_sym_start = .;
*(.dynsym)
__dyn_sym_end = .;
}
. = ALIGN(4);
ASSERT(. < 0x42000000 + 0x100000, "uboot size exceeds size limit")
_end = .;
.bss : {
__bss_start = .;
*(.bss*)
. = ALIGN(8);
__bss_end = .;
}
}
解释
OUTPUT_FORMAT("elf64-littleriscv", "elf64-littleriscv", "elf64-littleriscv")
输出可执行文件是elf格式, 64位riscv指令,小端
elf格式
大端 和 小端(Big-endian和Little-endian )
链接视图和执行视图
Section举例
.bss
.comment
.data
.data1
.debug
.dynamic
.dynstr
.dynsym
.fini
.got
.hash
.init
.interp
.line
.note
.plt
.relname
.relaname
.rodata
.rodata1
.shstrtab
.strtab
.symtab
.text
ELF文件结构
ELF Header
Program Header Table
Sections
Section Header Table
OUTPUT_ARCH("riscv")
输出可执行文件的平台为riscv
ENTRY(_start)
输出可执行文件的起始代码段为_start
. = 0x42000000;
指定可执行文件的全局入口点,地址是在0x42000000。
.__image_copy_start : {
*(.__image_copy_start)
}
u-boot将自己copy到RAM,此为需要copy的程序的start
. = ALIGN(4);
做完一件事之后,有可能4bytes不对齐了,所以看到多处ALIGN(4)
.image_copy_end : {
*(.__image_copy_end)
}
u-boot自拷贝的数据完成了,包括的section有
.__image_copy_start
.head
.text
.efi_runtime
.text_rest
.rodata
.data
.got
.u_boot_list
.efi_runtime_rel
.image_copy_end
u-boot.map
从u-boot.map中可以查找地址 例如
image_copy_start uboot copy的首地址
image_copy_end uboot copy的结束地址
rel_dyn_start .rel.dyn 段起始地址
rel_dyn_end .rel.dyn 段结束地址
bss_start .bss 段起始地址
bss_end .bss 段结束地址
image_copy_start
0x0000000042000000 . = 0x42000000
0x0000000042000000 . = ALIGN (0x4)
.__image_copy_start
0x0000000042000000 0x0
*(.__image_copy_start)
.__image_copy_start
... ...
image_copy_end
... ...
.efi_runtime_rel
0x00000000420c53a0 0x0
0x00000000420c53a0 __efi_runtime_rel_start = .
*(.rel*.efi_runtime)
*(.rel*.efi_runtime.*)
0x00000000420c53a0 __efi_runtime_rel_stop = .
0x00000000420c53a0 . = ALIGN (0x4)
.image_copy_end
*(.__image_copy_end)
查看编译之后生成的u-boot文件
buildroot/output/build/uboot-origin_master/u-boot
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
Version: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: EXEC (可执行文件)
系统架构: RISC-V
版本: 0x1
入口点地址: 0x42000640
程序头起点: 64 (bytes into file)
Start of section headers: 10029872 (bytes into file)
标志: 0x1, RVC, soft-float ABI
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 2
Size of section headers: 64 (bytes)
Number of section headers: 27
Section header string table index: 26
程序头:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000001000 0x0000000042000000 0x0000000042000000
0x00000000000d6020 0x000000000010fa20 RWE 0x1000
DYNAMIC 0x00000000000c4818 0x00000000420c3818 0x00000000420c3818
0x0000000000000110 0x0000000000000110 RW 0x8
Section to Segment mapping:
段节...
00 .head .text .text_rest .rodata .dynstr .hash .gnu.hash .data .dynamic .got .u_boot_list .rela.dyn .dynsym .bss
01 .dynamic
Dynamic section at offset 0xc4818 contains 13 entries:
标记 类型 名称/值
0x0000000000000004 (HASH) 0x420a1840
0x000000006ffffef5 (GNU_HASH) 0x420a21a0
0x0000000000000005 (STRTAB) 0x420a032c
0x0000000000000006 (SYMTAB) 0x420d40d0
0x000000000000000a (STRSZ) 5391 (bytes)
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000015 (DEBUG) 0x0
0x0000000000000007 (RELA) 0x420c53a0
0x0000000000000008 (RELASZ) 60720 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x000000006ffffffb (FLAGS_1) 标志: PIE
0x000000006ffffff9 (RELACOUNT) 2527
0x0000000000000000 (NULL) 0x0
根据链接文件u-boot.lds找到入口文件start.S
buildroot/output/build/uboot-origin_master/arch/riscv/cpu/start.S
......
#ifdef CONFIG_32BIT
#define LREG lw
#define SREG sw
#define REGBYTES 4
#define RELOC_TYPE R_RISCV_32
#define SYM_INDEX 0x8
#define SYM_SIZE 0x10
#else
#define LREG ld
#define SREG sd
#define REGBYTES 8
#define RELOC_TYPE R_RISCV_64
#define SYM_INDEX 0x20
#define SYM_SIZE 0x18
#endif
.section .data
secondary_harts_relocation_error:
.ascii "Relocation of secondary harts has failed, error %d\n"
.section .text
.globl _start
_start:
#if CONFIG_IS_ENABLED(RISCV_MMODE)
csrr a0, CSR_MHARTID
#endif
/*
* Save hart id and dtb pointer. The thread pointer register is not
* modified by C code. It is used by secondary_hart_loop.
*/
mv tp, a0
mv s1, a1
/*
* Set the global data pointer to a known value in case we get a very
* early trap. The global data pointer will be set its actual value only
* after it has been initialized.
*/
mv gp, zero
/*
* Set the trap handler. This must happen after initializing gp because
* the handler may use it.
*/
la t0, trap_entry
csrw MODE_PREFIX(tvec), t0
/*
* Mask all interrupts. Interrupts are disabled globally (in m/sstatus)
* for U-Boot, but we will need to read m/sip to determine if we get an
* IPI
*/
csrw MODE_PREFIX(ie), zero
#if CONFIG_IS_ENABLED(SMP)
/* check if hart is within range */
/* tp: hart id */
li t0, CONFIG_NR_CPUS
bge tp, t0, hart_out_of_bounds_loop
/* set xSIE bit to receive IPIs */
#if CONFIG_IS_ENABLED(RISCV_MMODE)
li t0, MIE_MSIE
#else
li t0, SIE_SSIE
#endif
csrs MODE_PREFIX(ie), t0
#endif
......
命令
bootelf - Boot from an ELF image in memory
bootp - boot image via network using BOOTP/TFTP protocol
dhcp - boot image via network using DHCP/TFTP protocol
diskboot - boot from ide device
nboot - boot from NAND device
nfs - boot image via network using NFS protocol
rarpboot - boot image via network using RARP/TFTP protocol
scsiboot - boot from SCSI device
tftpboot - boot image via network using TFTP protocol
usbboot - boot from USB device
参考
部分图片来自Western Digital Corporation or its affiliates.
https://www.thegoodpenguin.co.uk/blog/an-overview-of-opensbi/
https://github.com/riscv-software-src/opensbi
https://github.com/u-boot
https://linux-sunxi.org/U-Boot
https://u-boot.readthedocs.io/en/latest/