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 → 跳转。
U-Boot ARM64 两阶段启动流程 ROM Code SPL 加载 DDR 初始化 加载 U-Boot proper 重定位到高地址 board_init_r() bootcmd 执行 跳转内核 SPL 阶段:最小化初始化,代码量通常 < 32KB,运行在 SRAM 中 U-Boot proper 阶段:完整驱动模型,支持文件系统、网络、环境变量

3.2 设备树(DTB)的传递机制

ARM64平台强制使用设备树(Device Tree)描述硬件拓扑。U-Boot负责将DTB从存储介质加载到内存,并将DTB物理地址通过特定寄存器传递给内核。传递路径如下:

  1. 加载DTB: U-Boot从FAT/EXT4分区或MTD分区读取 .dtb 文件到内存(通常加载地址由 fdt_addr 环境变量指定)。
  2. DTB处理: U-Boot可对DTB进行运行时修改(如填充 chosen 节点中的 bootargs、initrd-start、initrd-end)。
  3. 寄存器传递: ARM64启动协议规定,跳转到内核时 x0 寄存器必须存放DTB的有效物理地址(或0表示不使用DTB)。
ARM64启动协议约定: 内核入口时 x0 = DTB物理地址,x1 = 0,x2 = 0,x3 = 0。U-Boot通过 booti 命令自动设置这些寄存器。
环境变量默认值示例说明
fdt_addr0x48000000DTB加载的目标物理地址
fdt_high0xffffffffffffffff限制DTB重定位地址,0xffffffffffffffff 表示不重定位
bootargsconsole=ttyAMA0,115200 root=/dev/mmcblk0p2内核命令行参数,U-Boot会写入DTB的 chosen 节点
DTB 传递机制:从存储到内核 存储介质 (eMMC/SD/NAND) U-Boot 读取 DTB (fdt_addr 指定地址) 运行时修改 DTB (填充 chosen 节点) x0 = DTB 物理地址 (ARM64 启动协议) Linux 内核 解析 DTB U-Boot 通过 fdt 命令手动处理 DTB:fdt addr、fdt set、fdt chosen booti 命令自动完成 DTB 加载、修改与寄存器设置

3.3 跳转到内核前的环境准备

U-Boot在调用内核入口点之前,必须完成以下环境准备,否则内核无法正常启动:

准备项要求U-Boot实现方式
MMU与CacheMMU关闭,I-Cache/D-Cache关闭或清理调用 cleanup_before_linux() 刷新Cache并关闭MMU
中断状态所有中断屏蔽(DAIF掩码置位)执行 msr daifset, #0xf
寄存器约定x0 = DTB地址,x1-x3 = 0booti 命令自动设置
栈指针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 中定义,关键字段如下:

字段类型说明
epulong内核入口点物理地址
ft_addrchar *DTB在内存中的地址(虚拟地址,U-Boot启用MMU时使用)
ft_lenulongDTB的总长度(字节)
osimage_info_t操作系统映像信息(类型、压缩方式、加载地址)
initrd_startulonginitrd起始地址(可选)
initrd_endulonginitrd结束地址(可选)
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

上述命令执行流程:

  1. ext4load 从eMMC分区0:1加载内核映像 Image 到 0x40080000。
  2. ext4load 加载设备树 board.dtb 到 0x48000000。
  3. booti 命令解析内核格式(ARM64 Linux内核映像),设置x0寄存器为DTB地址,关闭MMU与Cache,跳转到内核入口。
booti 命令内部执行序列 解析 Image 头 校验 DTB 魔数 填充 chosen 节点 关闭 MMU/Cache br x20 跳转 booti 是 ARM64 专用引导命令,与 bootm 的区别: booti 直接解析未压缩的 Image 格式,不依赖 uImage 头;自动处理 DTB 地址传递

3.6 本章小结

  • U-Boot在ARM64上采用SPL + proper两阶段启动,SPL负责最小化初始化,proper完成完整引导。
  • 设备树(DTB)是ARM64平台唯一的硬件描述传递方式,U-Boot通过 x0 寄存器传递DTB物理地址。
  • 跳转内核前必须关闭MMU与D-Cache、屏蔽中断、清理Cache,并确保DTB 8字节对齐。
  • booti 命令是ARM64专用引导命令,自动处理内核映像解析与DTB传递。