☰
RK SDK U-Boot 启动流程分析
2026/10/3 7:39:38 网站建设 项目流程

RK SDK U-Boot 启动流程分析

1. U-Boot 多段式启动概述

目前多数 U-Boot 启动方案使用多段式加载启动 U-Boot:

BootROM --> Pre-Loader (TPL/SPL) --> U-Boot

启动阶段每个过程讲解如下:

1.1 BootROM

这是芯片制造时固化在片内 ROM 中的代码,在芯片上电时首先被执行,负责根据启动配置查找启动介质并加载其中的 Pre-Loader 程序;BootROM 阶段还没有初始化 DRAM,只能使用片内 SRAM 作为运行内存。片内 SRAM 速度快但容量较小,通常无法容纳完整的 U-Boot,因此系统采用多段式加载启动。

1.2 Pre-Loader

Pre-Loader 由 BootROM 从存储设备中加载并执行,负责DDR初始化,建立最小运行环境,选启动介质,定位并读取下一级镜像等功能;(不同芯片厂商的Pre-Loader设计也不同,比如RK又将Pre-Loader分为TPL和SPL两部分,RK的TPL运行在SRAM只负责DRAM初始化,其他任务则由SPL负责,SPL运行在DRAM中)

1.3 U-Boot proper

U-Boot 由 SPL 加载到 DRAM 并运行。U-Boot 会完成重定位,初始化更多外设,并加载 Kernel、设备树以及可选的 initramfs;普通 ext4、UBIFS 或 NFS 根文件系统通常由 Linux 内核在启动过程中挂载,而不是由 U-Boot 直接加载。

2. ARM Trusted Firmware(TF-A/ATF)

ARM Trusted Firmware 是 Arm 处理器的可信启动与安全运行时固件框架。完整的 TF-A 参考启动架构可以包含负责加载后续镜像的 BL2,以及常驻 EL3 的 BL31;在当前 RK SDK 中,后续镜像实际由 DDR bin 和 U-Boot SPL 加载,Rockchip BL31 主要负责建立 EL3 运行环境、提供运行时服务并进入普通世界。

2.1 TrustZone、安全世界与普通世界

Arm 把系统分成了安全世界与普通世界两个执行环境:

  • 安全世界(Secure World):用于运行 OP-TEE 等安全操作系统,处理密钥、加解密、安全存储和身份认证等敏感任务。
  • 普通世界(Non-secure World):用于运行 U-Boot、Linux 和普通应用程序,负责设备管理、文件系统、网络通信等常规任务。

2.2 BL 阶段与异常级

启动阶段异常级所属世界说明
BL1EL3安全世界最早期可信启动固件
BL2S-EL1 或 EL3安全世界平台初始化和镜像加载阶段,实际异常级由平台配置决定;
BL31EL3安全世界常驻运行时固件,提供电源管理、安全调用处理,并管理安全世界与普通世界之间的切换。
BL32S-EL1安全世界【可选】可信操作系统,例如 OP-TEE。
BL33NS-EL2 或 NS-EL1普通世界普通世界引导程序,例如 U-Boot、UEFI。

2.3 TF-A 通用启动流程

2.3.1 BL1:BootROM

芯片复位后,CPU 从 BootROM 开始执行。不同平台装载 BL2 的方式并不完全相同;在当前 RK U-Boot流程中,BootROM 先运行承担 DDR 初始化职责的 DDR bin,DDR 初始化完成并返回 BootROM 后,再由 BootROM 加载 SPL。

2.3.2 BL2:平台初始化

在 TF-A 参考架构中,BL2 完成 DRAM 等必要硬件的初始化,并按平台要求配置内存的安全访问权限,随后将 BL31、可选的 BL32 和 BL33 加载到内存中

准备完成后,BL2 将后续镜像的入口等信息传给 BL31,并跳转到 BL31 执行。此时 BL32 和 BL33 虽然已经加载到内存,但尚未开始运行。

2.3.3 BL31:EL3 运行时固件

BL31 设置 EL3 异常向量表,初始化安全监控和电源管理服务,为后续的 CPU 启动、休眠、复位以及安全世界切换做好准备。

如果存在 BL32,BL31 先进入 BL32,使安全操作系统完成初始化,完成后返回 BL31;如果没有 BL32,则直接准备进入 BL33。

2.3.4 BL32:可信操作系统(可选)

BL32 通常是 OP-TEE,负责初始化安全内存、页表、异常处理和可信服务等运行环境。

OP-TEE 是运行在安全世界中的小型安全操作系统,在 U-Boot 启动前初始化,并在 Linux 运行期间常驻,主要为可信应用提供隔离执行、密钥管理、密码学运算和安全存储等能力。Linux 应用通过libteec打开可信应用会话并提交命令,请求经/dev/tee*和内核 OP-TEE 驱动,通过共享内存传递参数、执行 SMC 指令进入 BL31,再由 BL31 切换到 OP-TEE 执行对应可信应用,完成后沿原路径返回结果。

2.3.5 BL33:普通世界引导程序

BL33 通常是 U-Boot 或 UEFI,负责进一步初始化设备、加载内核和设备树等启动文件,最终启动操作系统。

BL33 的异常级由平台配置决定,可以是 NS-EL2 或 NS-EL1;

3. Rockchip 启动路线与镜像组织

3.1 Rockchip 文档中的启动阶段

RK U-Boot启动方案也使用多段式加载启动U-Boot,与主线uboot不同的是,RK将Pre-Loader分为TPL和SPL两部分,以下是瑞芯微的说明文档:

+--------+----------------+----------+-------------+---------+ | Boot | Terminology #1 | Actual | Rockchip | Image | | stage | | program | Image | Location| | number | | name | Name | (sector)| +--------+----------------+----------+-------------+---------+ | 1 | Primary | ROM code | BootRom | | | | Program | | | | | | Loader | | | | | | | | | | | 2 | Secondary | U-Boot |idbloader.img| 0x40 | pre-loader | | Program | TPL/SPL | | | | | Loader (SPL) | | | | | | | | | | | 3 | - | U-Boot | u-boot.itb | 0x4000 | including u-boot and atf | | | | uboot.img | | only used with miniloader | | | | | | | | | ATF/TEE | trust.img | 0x6000 | only used with miniloader | | | | | | | 4 | - | kernel | boot.img | 0x8000 | | | | | | | | 5 | - | rootfs | rootfs.img | 0x40000 | +--------+----------------+----------+-------------+---------+

3.2 Miniloader 与 U-Boot SPL 两种路线

Rockchip 常见的两种路线主要区别在于 DDR 初始化之后由谁继续加载后续固件:闭源 Miniloader 路线使用 Rockchip 提供的闭源 DDR bin 和 Miniloader,分别完成 DDR 初始化和后续固件加载;U-Boot SPL 路线使用闭源 DDR bin 完成 DDR 初始化,再由源码构建的 U-Boot SPL 加载后续镜像,便于自行编译、调试和修改。两条路线中的 DDR 初始化组件通常都不是开源的。两者后续进入 BL31、初始化可选的 BL32,再运行 BL33 的流程基本一致。下文使用“闭源 DDR bin + 开源 U-Boot SPL”路线分析 RK SDK U-Boot 启动流程。

4. RK U-Boot 的 BL1~BL33 映射

阶段对应固件主要任务完成后的去向
BL1Rockchip BootROM从启动介质加载早期程序进入 DDR bin,并在其返回后加载 SPL
BL2DDR bin + U-Boot SPL初始化 DDR,加载 BL31、BL32、BL33,并按当前安全配置执行完整性或签名校验,准备交接参数进入 BL31
BL31Rockchip TF-A建立 EL3 运行环境、安全监控和电源管理服务先初始化 BL32,再进入 BL33
BL32OP-TEE建立可信运行环境,提供安全服务初始化后返回 BL31,并保持常驻
BL33U-Boot proper完成设备初始化,加载内核和设备树启动 Linux

4.1 BL1:BootROM

芯片上电或复位后,CPU 首先执行片内 BootROM。此时 DDR 尚未初始化,BootROM 从启动介质识别并读取早期引导镜像,将承担 TPL 功能角色的闭源 DDR bin 加载到片内 SRAM 中执行。DDR bin 完成 DRAM 初始化后返回 BootROM,BootROM 再将 U-Boot SPL 加载到 DRAM 中执行。

4.2 BL2:DDR bin + SPL

在本文对 BL 阶段的功能映射中,BL2 对应闭源 DDR bin 与源码构建的 U-Boot SPL。DDR bin 先完成 DRAM 初始化并返回 BootROM,随后 BootROM 加载并运行 SPL。

SPL 解析uboot.imgFIT,将 BL31、BL32 和 BL33 加载到各自的目标地址,并检查镜像完整性

4.3 BL31:EL3 安全监控环境

RK BL31使用闭源固件,BL31接收 SPL 提供的入口参数,建立 EL3 异常处理、上下文和运行时服务。如果存在 BL32 固件,则将执行控制权传递给可信操作系统 (OP-TEE) 镜像 BL32,BL32在初始化完成OP-TEE之后会返回BL31阶段,BL31再进入非安全世界的 BL33/U-Boot,如果没有
BL32 固件,则BL31直接进入非安全世界的 BL33/U-Boot。

4.4 BL32:OP-TEE

当前 RK SDK 使用预编译的 BL32/OP-TEE 镜像。BL32 本身就是安全载荷 OP-TEE,它会初始化自己的安全内存、页表、异常处理和可信服务环境,初始化完成后返回 BL31。

BL31 → BL32 初始化 → 返回 BL31 → BL33

BL32 并不直接跳转到 U-Boot安全世界到普通世界的切换由 BL31 完成。

4.5 BL33:U-Boot proper

BL33 运行完整 U-Boot,完成设备初始化并启动 Linux。

5. BL33:U-Boot proper 详细流程

5.1 U-Boot 汇编入口

U-Boot 从_start开始执行,经过 ARMv8 早期汇编初始化后进入_main。

_main的主要工作是:

  1. 设置临时栈。
  2. 为全局数据结构gd分配空间。
  3. 初始化临时gd。
  4. 调用board_init_f()。
  5. 重定位 U-Boot。
  6. 切换到新的栈和gd。
  7. 清空BSS。
  8. 调用board_init_r()。

核心调用关系:

_start └─ reset └─ _main ├─ board_init_f_alloc_reserve() ├─ board_init_f_init_reserve() ├─ board_init_f() ├─ relocate_code() ├─ clear BSS └─ board_init_r()

5.2board_init_f()

board_init_f()是重定位前的初始化阶段。

这一阶段运行时:

  • U-Boot 还没有完成正式重定位。
  • 可用堆空间有限。
  • 使用临时栈和临时gd。

主要流程可以整理为:

board_init_f() │ ├─ fdtdec_setup() │ └─ 定位 U-Boot 自带 DTB │ ├─ arch_cpu_init() │ └─ ARM/SoC 早期初始化 │ ├─ mach_cpu_init() │ └─ Rockchip 平台初始化 │ ├─ initf_dm() │ └─ 建立早期 Driver Model │ ├─ timer_init() ├─ env_init() ├─ serial_init() ├─ console_init_f() │ └─ 串口开始输出日志 │ ├─ dram_init() │ └─ 获取 DRAM 容量和布局 │ └─ 规划重定位空间
5.2.1 重定位内存规划

U-Boot 从 DDR 顶部向下预留空间,包括:

  • MMU 页表。
  • Framebuffer。
  • U-Boot 重定位后的代码。
  • malloc 堆。
  • bd结构。
  • 新的gd。
  • 设备树副本。
  • 新栈。

关键过程:

setup_dest_addr reserve_mmu reserve_video reserve_uboot reserve_malloc reserve_board reserve_global_data reserve_fdt reserve_stacks setup_reloc

5.3 U-Boot 重定位

board_init_f()返回后,汇编代码调用:

relocate_code(gd->relocaddr)

将 U-Boot 从最初的加载位置复制到规划好的 DDR 高地址。

重定位完成后:

  1. 修正代码和数据中的重定位项。
  2. 切换到新的栈。
  3. 切换到新的gd。
  4. 清空重定位后映像的BSS。
  5. 进入board_init_r()。
初始 U-Boot 映像 │ │ relocate_code() ▼ DDR 高地址中的正式 U-Boot │ ├─ 新 gd ├─ 新栈 └─ 清空 BSS

5.4board_init_r()

board_init_r()是 BL33 最核心的初始化阶段。它依次执行init_sequence_r[]中的函数,大致分为以下几组。

5.4.1 建立完整运行环境
initr_reloc initr_caches initr_reloc_global_data initr_malloc sysmem_initr interrupt_init

主要完成:

  • 启用和配置缓存。
  • 更新重定位后的全局数据。
  • 建立完整堆空间。
  • 初始化 U-Boot 内存区域管理。
  • 初始化中断相关环境。
5.4.2 建立初始 live tree 和驱动模型
initr_of_live() initr_dm()

此时建立的是基于 U-Boot 自带 DTB 的 live tree 和 DM 设备。

5.4.3 建立早期最小环境变量

SDK 执行:

initr_env_nowhere()

建立一个最小临时环境,其中的主要地址(以RK3576为例)为:

scriptaddr = 0x40500000 pxefile_addr_r= 0x40600000 fdt_addr_r = 0x48300000 kernel_addr_r = 0x40400000 kernel_addr_c = 0x45480000 ramdisk_addr_r= 0x4a200000

5.5 板级初始化与 Kernel DTB V2

board_init_r()随后调用平台的board_init():

RK SDK 中的核心顺序是:

board_init() │ ├─ board_debug_init() ├─ optee_client_init() ├─ init_kernel_dtb() ├─ early_download() ├─ clks_probe() ├─ regulators_enable_boot_on() ├─ io_domain_init() ├─ set_armclk_rate() ├─ dvfs_init() └─ rk_board_init()
5.5.1 初始化 OP-TEE 客户端

BL32/OP-TEE 已经在 BL33 之前启动。

这里的:

optee_client_init();

不是重新加载 OP-TEE,而是让 U-Boot 建立与已经运行的 OP-TEE 之间的通信能力。

后续 U-Boot 可以通过:

U-Boot → SMC → BL31 → OP-TEE

请求安全服务,例如:

  • 读取安全启动状态。
  • 读取 OEM 解锁状态。
  • 访问安全存储。
  • 配合 AVB 验证。
  • 通知 OP-TEE U-Boot 阶段即将结束。
5.5.2 提前加载 Kernel DTB

init_kernel_dtb()会在进入命令行、执行bootcmd之前读取 Kernel DTB。

当前正常读取链路是:

boot 分区 └─ boot.img(FIT) └─ resource 子镜像 └─ rk-kernel.dtb │ └─ 加载到 fdt_addr_r=0x48300000

随后执行:

  • 检查 FDT 头。
  • 检查 DTB hash(存在 hash 时)。
  • 执行板级 early fixup。
  • 在存在有效 DTBO 且满足条件时应用 DT overlay。
  • 构建 Kernel DTB live tree。
  • 扫描 Kernel DTB 设备。

这里还需要注意两个源码边界:当前dtb_check_ok()在比较根节点compatible之前直接返回成功,因此平台兼容性检查实际上没有执行;同时board_init()没有检查init_kernel_dtb()的返回值,所以 Kernel DTB 读取失败不一定会在此处立即停止 U-Boot。若读取在切换前失败,U-Boot 可能继续保留原来的 U-Boot DTB,后续能否正常启动取决于具体驱动和启动路径。

5.5.3 切换到存储环境变量

Kernel DTB 和启动介质初始化完成后,执行:

initr_env_switch()

该函数先导出早期最小环境,然后销毁临时环境并从 eMMC 等介质导入持久化环境,接着将早期环境追加到存储环境中,部分同名变量会覆盖存储环境;bootargs另外使用追加逻辑处理。因此这里不是两个环境的简单无条件合并。

随后继续初始化:

  • 串口。
  • 控制台。
  • 显示。
  • USB。
  • MMC。
  • 网络。
  • 存储设备。
  • 命令系统。
  • 其他驱动。

5.6main_loop()

board_init_r()的最后调用:

run_main_loop() └─ main_loop()

main_loop()的流程是:

main_loop() │ ├─ cli_init() ├─ 执行 preboot ├─ bootdelay_process() ├─ autoboot_command() │ └─ 执行 bootcmd │ └─ 如果自动启动失败或被打断 └─ cli_loop(),进入 U-Boot 命令行

执行默认bootcmd命令加载启动内核, 如果自动启动失败或被打断,则进入 U-Boot 命令行

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询