3. Bootloader阶段(U-Boot):U-Boot在ARM64上的启动流程、设备树(DTB)的传递机制、跳转到内核前的环境准备
U-Boot(Das U-Boot)是嵌入式ARM64平台最通用的二级引导程序,负责从存储介质加载内核映像与设备树,完成硬件初始化后跳转到内核入口。本章聚焦U-Boot在ARM64上的启动流程、设备树(DTB)的传递机制以及跳转到内核前的环境准备。
3.1 U-Boot在ARM64上的启动流程
U-Boot采用两阶段启动模型:SPL(Secondary Program Loader) 与 U-Boot proper。SPL由片内ROM加载,负责初始化DDR、时钟与存储控制器,然后从存储设备加载U-Boot proper到DDR并跳转。U-Boot proper完成完整的外设初始化、环境变量加载与内核引导。
关键路径: ARM64复位入口 → SPL(arch/arm/cpu/armv8/start.S)→ DDR初始化 → 加载U-Boot proper → 重定位到高地址 → 进入board_init_r() → 执行bootcmd → 加载kernel+DTB → 跳转。
3.2 设备树(DTB)的传递机制
ARM64平台强制使用设备树(Device Tree)描述硬件拓扑。U-Boot负责将DTB从存储介质加载到内存,并将DTB物理地址通过特定寄存器传递给内核。传递路径如下:
- 加载DTB: U-Boot从FAT/EXT4分区或MTD分区读取
.dtb文件到内存(通常加载地址由fdt_addr环境变量指定)。 - DTB处理: U-Boot可对DTB进行运行时修改(如填充
chosen节点中的bootargs、initrd-start、initrd-end)。 - 寄存器传递: ARM64启动协议规定,跳转到内核时
x0寄存器必须存放DTB的有效物理地址(或0表示不使用DTB)。
ARM64启动协议约定: 内核入口时
x0 = DTB物理地址,x1 = 0,x2 = 0,x3 = 0。U-Boot通过 booti 命令自动设置这些寄存器。
| 环境变量 | 默认值示例 | 说明 |
|---|---|---|
fdt_addr | 0x48000000 | DTB加载的目标物理地址 |
fdt_high | 0xffffffffffffffff | 限制DTB重定位地址,0xffffffffffffffff 表示不重定位 |
bootargs | console=ttyAMA0,115200 root=/dev/mmcblk0p2 | 内核命令行参数,U-Boot会写入DTB的 chosen 节点 |
3.3 跳转到内核前的环境准备
U-Boot在调用内核入口点之前,必须完成以下环境准备,否则内核无法正常启动:
| 准备项 | 要求 | U-Boot实现方式 |
|---|---|---|
| MMU与Cache | MMU关闭,I-Cache/D-Cache关闭或清理 | 调用 cleanup_before_linux() 刷新Cache并关闭MMU |
| 中断状态 | 所有中断屏蔽(DAIF掩码置位) | 执行 msr daifset, #0xf |
| 寄存器约定 | x0 = DTB地址,x1-x3 = 0 | booti 命令自动设置 |
| 栈指针 | SP指向有效可写内存(非DDR末尾) | U-Boot已初始化栈,跳转前不修改 |
| 异常向量表 | 内核使用自己的向量表,U-Boot无需保留 | 不传递向量表地址 |
| 设备树完整性 | DTB必须8字节对齐,总长度正确 | U-Boot加载时校验魔数(0xd00dfeed) |
常见陷阱: 如果U-Boot跳转前未关闭D-Cache,内核早期启动时因Cache不一致导致数据异常。ARM64要求跳转前D-Cache必须处于关闭状态,I-Cache可以开启但建议清理。
U-Boot跳转内核的典型汇编序列(位于 arch/arm/lib/bootm.c 或 arch/arm/cpu/armv8/start.S):
/* 伪代码:ARM64跳转内核前的环境准备 */
cleanup_before_linux:
/* 关闭D-Cache */
mrs x0, sctlr_el2
bic x0, x0, #(1 << 2) /* 清除C位 */
msr sctlr_el2, x0
isb
/* 清理D-Cache(逐行clean & invalidate) */
bl flush_dcache_all
/* 关闭MMU */
mrs x0, sctlr_el2
bic x0, x0, #(1 << 0) /* 清除M位 */
msr sctlr_el2, x0
isb
/* 屏蔽所有中断 */
msr daifset, #0xf
/* 设置x0 = DTB地址,x1-x3 = 0 */
mov x0, x19 /* x19 保存DTB地址 */
mov x1, #0
mov x2, #0
mov x3, #0
/* 跳转到内核入口(保存在x20) */
br x20
3.4 U-Boot启动流程中的关键数据结构
U-Boot使用 bootm_headers_t 结构体管理内核映像与DTB的加载信息。该结构体在 include/image.h 中定义,关键字段如下:
| 字段 | 类型 | 说明 |
|---|---|---|
ep | ulong | 内核入口点物理地址 |
ft_addr | char * | DTB在内存中的地址(虚拟地址,U-Boot启用MMU时使用) |
ft_len | ulong | DTB的总长度(字节) |
os | image_info_t | 操作系统映像信息(类型、压缩方式、加载地址) |
initrd_start | ulong | initrd起始地址(可选) |
initrd_end | ulong | initrd结束地址(可选) |
ARM64特有差异: 与ARM32不同,ARM64的U-Boot不使用ATAGs传递参数,完全依赖设备树。所有硬件描述、内存布局、内核命令行都必须通过DTB传递。U-Boot在跳转前会调用
fdt_chosen() 将 bootargs 写入DTB的 /chosen 节点。
3.5 典型启动命令行示例
实际产品中,U-Boot环境变量通常配置为自动加载内核与DTB:
setenv loadaddr 0x40080000
setenv fdt_addr 0x48000000
setenv bootargs 'console=ttyAMA0,115200 root=/dev/mmcblk0p2 rw'
setenv bootcmd 'ext4load mmc 0:1 ${loadaddr} Image; ext4load mmc 0:1 ${fdt_addr} board.dtb; booti ${loadaddr} - ${fdt_addr}'
saveenv
boot
上述命令执行流程:
ext4load从eMMC分区0:1加载内核映像Image到0x40080000。ext4load加载设备树board.dtb到0x48000000。booti命令解析内核格式(ARM64 Linux内核映像),设置x0寄存器为DTB地址,关闭MMU与Cache,跳转到内核入口。
3.6 本章小结
- U-Boot在ARM64上采用SPL + proper两阶段启动,SPL负责最小化初始化,proper完成完整引导。
- 设备树(DTB)是ARM64平台唯一的硬件描述传递方式,U-Boot通过
x0寄存器传递DTB物理地址。 - 跳转内核前必须关闭MMU与D-Cache、屏蔽中断、清理Cache,并确保DTB 8字节对齐。
booti命令是ARM64专用引导命令,自动处理内核映像解析与DTB传递。