3. U-Boot 源码结构:顶层目录与关键模块

说实话,我第一次打开 U-Boot 源码树的时候,也有点懵。目录太多了,不知道从哪看起。但干这行久了,你会发现它的结构其实很有章法。今天我就带你捋一遍,哪些目录是核心,哪些是辅助,以及我平时调试时最常翻哪些地方。

3.1 顶层目录:一眼看穿骨架

你解压 U-Boot 后,第一眼看到的顶层目录大概有二十多个。别慌,我帮你分成几类:

类别目录名一句话说明
核心代码arch/CPU 架构相关,启动最关键的汇编代码在这
核心代码board/板级初始化,你的板子独有逻辑
核心代码common/通用命令和核心框架
核心代码drivers/外设驱动,网口、串口、存储都在这里
构建系统configs/默认配置文件,defconfig 文件全在这
构建系统scripts/Kconfig 和构建脚本
构建系统Makefile顶层 Makefile,入口
辅助工具tools/mkimage 等主机端工具
辅助工具doc/文档,但说实话我很少看,不如读代码
辅助工具include/头文件,全局宏定义
我的习惯:拿到一个新板子,我第一件事就是去 board/ 和 configs/ 找对应目录。如果找不到,八成得自己从头写板级文件。

3.2 arch/ 目录:CPU 架构的起点

这是 U-Boot 启动的第一站。说白了,arch/ 里放的是跟 CPU 内核绑定的代码。比如 ARM 的异常向量表、x86 的实模式切换、RISC-V 的 M 模式初始化。

我拿 ARM 举例,你进去会看到:

arch/arm/
├── cpu/          # 具体 CPU 型号,如 armv7、armv8
├── include/      # 架构级头文件
├── lib/          # 架构公共函数,如 cache 操作
├── mach-*/       # SoC 厂商目录,如 mach-sunxi、mach-imx
└── start.S       # 汇编入口,第一条指令

嗯,这里有个重点:start.S。它是 U-Boot 的入口点。CPU 上电后第一条指令就落在这里。我在项目中遇到过好几次,因为 start.S 里关中断的时机不对,导致调试器连不上。后来我习惯在 start.S 开头加一段 GPIO 点灯,确认 CPU 确实跑起来了。

避坑指南:我曾经在移植新 CPU 时,只改了 arch/arm/cpu/ 下的文件,忘了更新 arch/arm/mach-xxx/ 里的时钟配置。结果串口输出全是乱码。记住,mach- 目录管的是 SoC 级别的外设,不是 CPU 核。

3.3 board/ 目录:你的板子,你的代码

board/ 是板级代码的集中营。每个板子一个子目录,比如:

board/ti/am335x/          # TI AM335x 系列
board/freescale/mx6q/     # NXP i.MX6Q
board/samsung/smdk2440/   # 三星 SMDK2440

这里面通常有:

  • board.c — 板级初始化函数,比如 DDR 参数、GPIO 复用
  • Kconfig — 板级配置选项
  • MAINTAINERS — 谁维护这块板子

你想想看,U-Boot 为什么要把 board/ 和 arch/ 分开?因为 CPU 架构是通用的,但板子上的 DDR 型号、网卡 PHY 地址、LED 引脚都是独一无二的。我见过有人把板级代码塞进 arch/ 里,结果换了个板子,整个架构目录都得改。千万别这么干。

3.4 common/ 目录:命令与核心逻辑

common/ 是 U-Boot 的“大脑”。它包含了:

  • 命令处理框架 — command.c、main.c
  • 通用命令实现 — cmd/ 子目录,比如 cmd/bootm.c、cmd/nand.c
  • 环境变量管理 — env/ 子目录
  • 启动逻辑 — bootm.c、bootstage.c

我个人最常翻的是 common/cmd/。比如你想知道 bootm 命令到底怎么解析镜像头,直接看 cmd/bootm.c 就行。代码写得挺清楚,就是函数调用链有点深。

注意:别在 common/ 里加板级相关的 ifdef。我见过有人直接在 common/main.c 里写 #ifdef CONFIG_MY_BOARD,结果代码越来越乱。正确的做法是在 board/ 里实现钩子函数,通过 board_late_init() 等接口回调。

3.5 drivers/ 目录:外设驱动大本营

U-Boot 的驱动模型(Driver Model,DM)现在很成熟了。drivers/ 下面按设备类型分目录:

子目录驱动类型常见文件
serial/串口驱动ns16550.c、pl011.c
net/网卡驱动designware.c、fec_mxc.c
mmc/SD/eMMC 驱动sdhci.c、dwc_mmc.c
spi/SPI 控制器驱动spi_flash.c、mxc_spi.c
i2c/I2C 控制器驱动i2c_core.c、mv_i2c.c
gpio/GPIO 驱动gpio-uclass.c、mxc_gpio.c

为什么要单独说 drivers/?因为你在移植 U-Boot 时,80% 的时间都在调驱动。我记得有一次调网卡驱动,死活 ping 不通。最后发现是 drivers/net/ 里的 PHY 驱动初始化顺序不对,先配了 MAC 再配 PHY,导致自动协商失败。调了两天,就改了一行代码。

3.6 其他关键目录速览

  • include/ — 全局头文件。include/configs/ 里是板级配置头文件,老版本 U-Boot 的核心配置都在这里。新版本虽然用 Kconfig 了,但有些板子还在用。
  • configs/ — 默认配置文件。每个板子一个 xxx_defconfig,编译时用 make xxx_defconfig 加载。
  • tools/ — 主机工具。比如 mkimage 用来给内核镜像加 U-Boot 头,fdtgrep 用来裁剪设备树。
  • scripts/ — Kconfig 和构建脚本。如果你要加新的配置选项,得改这里。

3.7 一张图看懂 U-Boot 源码结构

下面这张 SVG 图,是我自己梳理的 U-Boot 源码层级关系。从顶层到底层,你编译和启动时,代码是怎么流动的,一目了然。

U-Boot 源码结构层级图 U-Boot 源码结构层级关系 U-Boot 顶层目录 arch/ board/ common/ drivers/ cpu/ mach-xxx/ start.S board.c Kconfig cmd/ env/ bootm.c serial/ net/ mmc/ 编译时:configs/ → arch/ + board/ → common/ + drivers/ → 链接生成 u-boot.bin 启动时:start.S → board_init_f() → board_init_r() → main_loop()

3.8 总结一下

U-Boot 的源码结构,说白了就是“分层解耦”。arch/ 管 CPU,board/ 管板子,common/ 管逻辑,drivers/ 管外设。你只要记住这个原则,找代码就不会迷路。

我个人的工作流是这样的:拿到一个新板子,先去 configs/ 找 defconfig,然后去 board/ 看板级初始化,最后去 drivers/ 调外设。如果启动卡住了,90% 的问题出在 arch/arm/cpu/ 的 lowlevel_init 或者 board/ 的 DDR 配置上。

一个小技巧:用 git grep 在源码里搜你的板子名,能快速定位所有相关文件。比如 git grep -l "myboard",比手动翻目录快多了。