Skip to content

U-Boot 引导程序

U-Boot (Universal Bootloader) 是嵌入式系统中最常用的引导程序,最初由 Wolfgang Denk 在 1999 年创建,现在是开源项目中应用最广泛的 Bootloader 之一。U-Boot 支持多种架构(ARM、x86、MIPS、RISC-V 等)和多种文件系统,能够从不同的存储设备加载操作系统内核。

嵌入式系统的引导特点

通用 PC 的 BIOS 与嵌入式系统的 Bootloader 有着本质的区别。这种差异反映了通用计算与嵌入式计算在哲学层面的根本分歧:PC 厂商提供"黑盒标准协议",而嵌入式厂商采用"白盒结构描述"。

BIOS/ACPI vs UBoot/DTS

在 PC 主板上,硬件极其复杂且品牌众多。为了让 Windows 或 Linux 能在数万种主板上运行,厂商建立了 BIOS/ACPI 中间层。BIOS 把主板上复杂的电压调节、引脚复用、时钟树全部屏蔽,内核启动时读取 ACPI 表来获取硬件信息。内核问"这块板子怎么关机",ACPI 回"调用这个特定的方法,我来帮你操作寄存器"。这种机制导致 BIOS 异常臃肿且闭源,开发者很难介入底层的电信号控制。

而在嵌入式平台上,每一分钱的成本和每一毫安的功耗都要计算。厂商无法预知用户会如何使用这块板子,如果不需要 PCIe,用户可以把那几个引脚配置成 GPIO 来控制继电器。因此嵌入式厂商不再提供一个昂贵的 BIOS 来"翻译"硬件,而是直接把芯片手册和设备树 (DTS) 扔给开发者。设备树就是硬件物理结构的数字化映射,它告诉内核"在 0xfe150000 这个物理地址,躺着一个 PCIe 控制器","它的复位信号在 GPIO0_B4,请你自己去拉低它"。

U-Boot 的分级引导

嵌入式系统的启动采用分级引导加载程序,这是因为内存 (DDR) 在刚开机时是不可用的。以 RK3588 为例,启动过程分为三个阶段。

ROM Code 阶段

ROM Code 固化在芯片内部的一小块 Mask ROM 中,厂家出厂就烧死了,开发者无法修改。ROM Code 负责检查启动介质(eMMC、TF 卡还是 USB),它会从存储介质中读取第一阶段的引导代码,并将其搬运到 CPU 内部的 SRAM(静态随机存储器,不需要初始化就能用)中执行。

SRAM 的容量非常有限,通常只有几十到几百 KB,因此这一阶段的代码必须尽可能小,只完成最基本的硬件初始化和代码搬运工作。

SPL/TPL 阶段

SPL (Secondary Program Loader) 是 U-Boot 的第一阶段引导程序,它的主要任务是初始化 DDR 内存。DDR 初始化是最复杂的一步,需要训练 DDR 时序、配置电压、设置频率等。这部分代码通常由芯片厂商提供,因为涉及许多不公开的寄存器配置。

有些平台还会引入 TPL (Tertiary Program Loader),在 SPL 和最终 U-Boot 之间增加一个中间层,用于处理更复杂的初始化逻辑。一旦 DDR 初始化完成,系统就拥有了海量的 RAM 空间,可以加载完整的 U-Boot。

U-Boot 阶段

U-Boot 运行时 DDR 已经可用,因此可以提供丰富的功能。这就是嵌入式的"半个 BIOS",它支持简单的命令行界面,可以读取脚本、加载内核镜像和设备树 (DTB)。

U-Boot 的主要功能包括硬件探测、环境变量管理、网络支持、文件系统访问、内核加载等。用户可以通过串口或网络与 U-Boot 交互,修改启动参数、更新固件、调试硬件问题。

U-Boot 与设备树

U-Boot 使用设备树 (Device Tree) 来描述硬件结构。设备树是一种数据结构,用树状格式描述硬件设备的拓扑关系。设备树源文件 (DTS) 经过编译后生成二进制文件 (DTB),U-Boot 将 DTB 传递给内核。

设备树的优势在于将硬件描述与内核代码解耦。传统的内核包含大量硬编码的硬件信息,每增加一个平台就要修改内核代码。使用设备树后,内核只需要保持设备树格式的兼容性,不同平台的硬件差异通过不同的 DTB 文件来描述。

从工程实践来看,设备树让硬件移植变得简单。开发者只需要修改 DTB 文件,就可以让同一个内核在不同的硬件平台上运行。这对于芯片厂商和板卡厂商来说大大降低了维护成本。

U-Boot 的常用功能

U-Boot 提供了丰富的命令集,可以完成各种系统管理任务。环境变量是 U-Boot 的核心机制,用户可以设置 bootargs(内核启动参数)、bootcmd(自动启动命令)、ipaddr(网络地址)等变量。这些变量可以保存在存储介质中,下次启动时自动加载。

网络功能是 U-Boot 的另一个重要特性。U-Boot 支持 TFTP、NFS 等网络协议,可以从网络服务器加载内核和根文件系统。这在开发调试时非常有用,开发者不需要频繁烧录存储介质,只需更新网络服务器上的文件即可。

U-Boot 还支持脚本执行功能,用户可以编写启动脚本,实现复杂的启动逻辑。例如,可以先检查某个 GPIO 引脚的状态,根据结果选择不同的内核或根文件系统。这种灵活性在产品更新和现场维护时非常实用。