4. U-Boot架构与源码组织

U-Boot 不是一个“一个目录装所有东西”的工程,而是一套按“通用核心 + 架构 + 厂商 + 板级”四层组织的可裁剪源码树。理解这套组织方式,是后续修改 SPL、适配 TI Sitara 板卡、定位 defconfig 与 DTS 对应关系的前提。本节从目录结构入手,再讲清 SPL 与 U-Boot proper 的编译分离,最后落到 TI Sitara 的 board/ti、defconfig 与 DTS 三者的对应关系。

一、U-Boot 源码树的四层结构

顶层目录可以按职责分成四类。第一类是构建与配置入口:Makefile、Kconfig、configs/、scripts/。第二类是通用核心代码:common/(启动流程、命令行)、cmd/(各条 U-Boot 命令)、drivers/(外设驱动)、lib/、net/、fs/。第三类是架构与厂商适配:arch/arm/ 下按 CPU 家族分目录,TI Sitara 属于 arch/arm/mach-omap2/(AM335x/AM437x)与 arch/arm/mach-k3/(AM62x/AM64x 等 K3 器件)。第四类是板级与设备树:board/ 下按厂商分目录,arch/arm/dts/ 存放 DTS,include/configs/ 存放板级头文件。

关键点在于:同一颗 SoC 的通用初始化放在 mach-* 目录,具体板子的差异放在 board/ 目录,而“这块板子用哪些驱动、开哪些命令”由 configs/ 下的 defconfig 决定。三者分工明确,改动时不要放错位置。

U-Boot 源码树四层组织与 TI Sitara 对应关系 ① 构建与配置入口 Makefile · Kconfig · configs/(defconfig)· scripts/ 决定“编译什么” ② 通用核心代码 common/ · cmd/ · drivers/ · lib/ · net/ · fs/ 与 SoC 无关 ③ 架构与厂商适配 arch/arm/ · mach-omap2/(AM335x/AM437x)· mach-k3/(AM62x/AM64x) SoC 级初始化 ④ 板级与设备树 board/ti/ · arch/arm/dts/ · include/configs/ 板级差异 对应关系示例(AM335x BeagleBone Black) defconfig:configs/am335x_evm_defconfig 板级目录:board/ti/am335x/  DTS:arch/arm/dts/am335x-boneblack.dts 三者必须同名对应

二、SPL 与 U-Boot proper 的编译分离

在 TI Sitara 这类需要从 MMC、SPI、NAND 等介质启动的平台上,U-Boot 被拆成两段:SPL(Secondary Program Loader)和 U-Boot proper。SPL 极小,运行在片上 SRAM(AM335x 约 128KB),只做最必要的事:初始化 DDR、时钟、串口,然后把 U-Boot proper 从存储介质搬到 DDR 并跳转。U-Boot proper 才是功能完整的引导程序,负责命令行、网络、文件系统、加载内核。

编译分离体现在构建产物上。执行 make am335x_evm_defconfig && make -j4 后,会在源码根目录生成 spl/u-boot-spl.bin、spl/u-boot-spl.map,以及 u-boot.bin、u-boot.img、u-boot.map。SPL 的链接脚本是 arch/arm/cpu/armv7/omap-common/u-boot-spl.lds(OMAP 系列),它把代码段限制在 SRAM 地址范围内;U-Boot proper 使用 arch/arm/cpu/u-boot.lds,链接到 DDR 地址。两者共用同一份源码,但通过 CONFIG_SPL_BUILD 宏在编译期区分:同一个 .c 文件在 SPL 阶段只编译被 #ifdef CONFIG_SPL_BUILD 包裹的部分。

这意味着修改代码时要判断“这段逻辑属于 SPL 还是 proper”。例如 DDR 初始化参数在 SPL 阶段生效,而 bootcmd、网络命令只在 proper 阶段存在。若在 SPL 中误用 proper 才有的驱动,链接会因符号缺失而失败,或运行时因 SRAM 溢出而卡死。

三、board/ti 目录与 defconfig、DTS 的对应关系

TI Sitara 的板级代码集中在 board/ti/ 下,按板卡系列分子目录:board/ti/am335x/、board/ti/am43xx/、board/ti/am62x/、board/ti/am64x/ 等。每个目录通常包含 board.c(板级初始化、DDR 参数、引脚复用)、mux.c(pinmux 配置)、Makefile 和 Kconfig。Kconfig 中会定义 CONFIG_TARGET_AM335X_EVM 之类的目标选项,供 defconfig 选中。

defconfig 位于 configs/,命名规则是“板卡名_defconfig”,例如 am335x_evm_defconfig、am62x_evm_a53_defconfig。它是一份最小配置集合,通过 CONFIG_TARGET_* 选中板级目录,通过 CONFIG_DEFAULT_DEVICE_TREE 指定默认 DTS 文件名。DTS 位于 arch/arm/dts/,命名与 defconfig 中的 CONFIG_DEFAULT_DEVICE_TREE 值一致,例如 am335x-boneblack.dts。编译时,构建系统根据该配置把对应 DTS 编译成 u-boot.dtb,并追加到 U-Boot proper 镜像尾部。

三者的对应链条是:defconfig 选中 TARGET 和 DEFAULT_DEVICE_TREE → TARGET 决定编译哪个 board/ti 子目录 → DEFAULT_DEVICE_TREE 决定编译哪个 DTS。如果 defconfig 里写的 DTS 文件名在 arch/arm/dts 下不存在,编译会直接报错;如果 TARGET 与 board 目录不匹配,板级初始化函数不会被链接进来,表现为启动后串口无输出或 DDR 未初始化。

排查板级问题时,先确认三件事:grep CONFIG_TARGET configs/xxx_defconfig 看目标;grep DEFAULT_DEVICE_TREE configs/xxx_defconfig 看 DTS;ls board/ti/ 看目录是否存在。三者一致,编译链才完整。

四、实操:从 defconfig 反查板级与设备树

以 AM335x EVM 为例,执行以下命令可以快速建立对应关系:

# 1. 查看 defconfig 中的关键配置
grep -E "CONFIG_TARGET|DEFAULT_DEVICE_TREE" configs/am335x_evm_defconfig

# 输出示例:
# CONFIG_TARGET_AM335X_EVM=y
# CONFIG_DEFAULT_DEVICE_TREE="am335x-evm"

# 2. 确认板级目录
ls board/ti/am335x/

# 3. 确认设备树文件
ls arch/arm/dts/am335x-evm.dts

# 4. 编译后检查产物
make am335x_evm_defconfig
make -j4
ls -lh spl/u-boot-spl.bin u-boot.img u-boot.dtb

编译完成后,u-boot.dtb 就是由 am335x-evm.dts 编译而来。可以用 fdtdump u-boot.dtb | head -40 查看根节点 compatible 字段,确认它确实是目标板卡。若 compatible 与硬件不符,说明 DEFAULT_DEVICE_TREE 选错了,需要回到 defconfig 修改。

五、常见误区与判断依据

第一个误区是把板级代码写到 mach-omap2 里。mach-* 只放 SoC 通用逻辑,板级差异必须放 board/ti。第二个误区是改 defconfig 后忘记重新执行 make xxx_defconfig,导致 .config 未更新,编译仍用旧配置。第三个误区是 SPL 与 proper 共用变量时未加 CONFIG_SPL_BUILD 保护,造成 SPL 体积超限。判断 SPL 是否超限,可查看 spl/u-boot-spl.map 末尾的地址范围,或编译时观察 SPL image size exceeds limit 警告。

掌握这套组织方式后,后续无论是新增一块 TI Sitara 板卡,还是裁剪 SPL 功能,都能快速定位到正确的目录和配置文件,而不是在庞大的源码树中盲目搜索。