2. U-Boot源码结构:顶层目录、板级目录、驱动目录、通用代码目录解析

拿到一份U-Boot源码,第一眼看到几十个文件夹,很多人会懵。我刚开始接触U-Boot时也是这样,对着目录列表发呆——到底该从哪里看起?

其实U-Boot的目录结构设计得很清晰。它遵循一个核心原则:把硬件相关的代码和硬件无关的代码分开。你想想看,U-Boot要支持几百种开发板,如果所有代码都混在一起,那维护成本得多高?

下面这张图能帮你快速建立全局认知:

U-Boot 源码顶层目录结构 U-Boot 根目录 board/ 板级目录 drivers/ 驱动目录 common/ 通用代码目录 其他顶层目录 arch/include/net/... board/ti/am335x/ board/freescale/imx6/ board/samsung/exynos/ drivers/mmc/ drivers/net/ drivers/spi/ common/main.c common/command.c common/console.c arch/arm/ include/configs/ net/ 核心设计思想:分层与解耦 • board/:硬件板级差异,每个板子一个子目录 • drivers/:外设驱动,按设备类型分类 • common/:命令解析、控制台、环境变量等通用逻辑 • arch/:CPU架构相关代码(ARM、RISC-V、x86等)

2.1 顶层目录——从根上理解U-Boot

U-Boot根目录下大概有30多个子目录和文件。别被数量吓到,真正核心的就那么几个。我按重要程度给你排个序:

目录/文件 作用 调试时关注度
board/ 板级初始化代码,每个开发板一个子目录 ⭐⭐⭐⭐⭐
drivers/ 外设驱动,MMC、网络、串口等 ⭐⭐⭐⭐
common/ 命令处理、控制台、环境变量 ⭐⭐⭐⭐
arch/ CPU架构相关,ARM/RISC-V/x86 ⭐⭐⭐
include/ 头文件,特别是configs/下的板级配置 ⭐⭐⭐⭐⭐
net/ 网络协议栈,TFTP/NFS核心 ⭐⭐⭐
fs/ 文件系统支持,ext4/fat等 ⭐⭐
lib/ 通用库函数,字符串、加密等 ⭐⭐
tools/ 主机端工具,mkimage等 ⭐
我的习惯:调试一个新板子时,我通常先看 board/ 和 include/configs/ 这两个目录。前者告诉我硬件怎么初始化,后者告诉我功能怎么配置。这两个搞清楚了,80%的问题就能定位。

2.2 板级目录 board/——硬件差异的集中营

board/ 目录下按厂商分类,每个厂商下面再按芯片型号分。比如:

board/
├── ti/
│   ├── am335x/        # BeagleBone Black 等
│   ├── omap5/         # OMAP5 系列
│   └── k2hk/          # Keystone II
├── freescale/
│   ├── imx6/          # i.MX6 系列
│   ├── imx8/          # i.MX8 系列
│   └── ls1046a/       # Layerscape
├── samsung/
│   ├── exynos/        # Exynos 系列
│   └── s5p/           # S5P 系列
└── rockchip/
    ├── rk3288/        # RK3288
    └── rk3399/        # RK3399

每个板级目录里通常包含这些文件:

  • board.c:板级初始化入口,board_init_f() 和 board_init_r() 就在这里
  • Kconfig:板级配置菜单
  • MAINTAINERS:维护者信息,出bug了知道找谁
  • Makefile:编译规则
我曾经踩过的坑:有一次移植U-Boot到新板子,我直接复制了另一个板子的 board.c,以为改改引脚配置就行。结果板子死活起不来。后来发现 board_init_f() 里有个 DDR 时序参数完全不一样——那个板子用的是 DDR3,我的是 DDR4。所以,板级目录里的代码一定要逐行审查,特别是内存初始化部分。

2.3 驱动目录 drivers/——外设的抽象层

drivers/ 目录按设备类型分类,每个子目录管理一类外设。U-Boot的驱动模型(Driver Model)让这部分代码变得很规整:

drivers/
├── mmc/          # SD卡、eMMC驱动
│   ├── mmc.c     # 通用MMC协议层
│   ├── omap_hsmmc.c  # TI平台MMC
│   └── fsl_esdhc.c   # NXP平台MMC
├── net/          # 以太网驱动
│   ├── eth.c     # 通用网络接口
│   ├── fec_mxc.c # NXP FEC控制器
│   └── designware.c # Synopsys DW MAC
├── spi/          # SPI总线驱动
├── i2c/          # I2C总线驱动
├── serial/       # 串口驱动(调试输出关键)
├── gpio/         # GPIO驱动
├── usb/          # USB主机/设备驱动
└── mtd/          # NAND/SPI Flash驱动

驱动目录的调试要点:

  • 串口驱动——如果串口没输出,你连U-Boot启动了没都不知道。我建议先确认串口驱动是否正常
  • MMC驱动——内核镜像通常存在SD卡或eMMC里,MMC驱动挂了,内核就加载不了
  • 网络驱动——TFTP下载内核时,网卡驱动必须工作

驱动调试口诀:

串口先通看输出,MMC再通读内核。
网络最后传文件,逐层验证不出错。

2.4 通用代码目录 common/——U-Boot的大脑

common/ 目录存放的是与硬件无关的通用逻辑。这里面的代码,你在任何板子上都能看到:

文件 功能 调试价值
main.c U-Boot主循环,命令解析入口 ⭐⭐⭐⭐⭐
command.c 命令注册与查找 ⭐⭐⭐⭐
console.c 控制台输入输出 ⭐⭐⭐⭐
env_common.c 环境变量管理 ⭐⭐⭐
bootm.c 内核启动核心逻辑 ⭐⭐⭐⭐⭐
autoboot.c 自动启动逻辑 ⭐⭐⭐

这里我想特别说说 main.c。U-Boot启动后,最终会进入 main_loop() 函数。这个函数就做两件事:

  1. 检查是否满足自动启动条件(比如倒计时内没按键)
  2. 进入命令行,等待用户输入

你看,整个U-Boot的交互逻辑都浓缩在这个文件里。调试启动流程时,我经常在 main_loop() 里加打印,看看到底是卡在自动启动还是命令处理上。

2.5 其他重要目录

除了上面三个核心目录,还有几个目录也值得了解:

  • arch/:CPU架构相关代码。ARM、RISC-V、x86等各占一个子目录。这里面有中断向量表、MMU初始化、异常处理等底层代码。调试内核启动时,arch/arm/lib/bootm.c 里的 do_bootm_linux() 是重点
  • include/configs/:板级配置头文件。每个板子对应一个 .h 文件,比如 am335x_evm.h。这里面定义了内存大小、外设地址、环境变量默认值等。我调试时第一个看的就是这个文件
  • net/:网络协议栈。TFTP、NFS、DHCP等协议都在这里。网络启动内核时,这里出问题的概率很高
我的调试路径:遇到启动问题,我一般按这个顺序查代码:
1. include/configs/xxx.h —— 配置对不对
2. board/xxx/board.c —— 初始化流程对不对
3. common/bootm.c —— 内核启动参数对不对
4. drivers/xxx/ —— 外设驱动对不对
这个顺序能覆盖90%的问题。

2.6 目录结构背后的设计哲学

U-Boot的目录结构,说白了就是分层隔离。你想想看:

  • 板级差异 → board/ 目录
  • CPU架构差异 → arch/ 目录
  • 外设差异 → drivers/ 目录
  • 通用逻辑 → common/ 目录

这种设计的好处很明显:你要移植U-Boot到新板子,只需要改 board/ 和 include/configs/ 下的文件,最多再改改 drivers/ 里对应的外设驱动。通用代码基本不用动。

我记得有一次帮客户调试,他们的板子用的是定制的FPGA,挂了一个奇怪的网络控制器。我只需要在 drivers/net/ 下加一个新驱动文件,然后在 board/ 目录里注册一下,其他代码完全不用改。这就是分层设计的力量。

注意:虽然目录结构是分层的,但实际运行时这些代码是链接在一起的。你改了一个驱动文件,重新编译后整个U-Boot都会重新链接。所以调试时,一定要确认你改的是哪个目录下的文件,别改错了地方。

好了,目录结构就讲到这里。记住一个原则:硬件相关的去 board/ 和 drivers/ 找,逻辑相关的去 common/ 找,配置相关的去 include/configs/ 找。按这个思路,你很快就能在U-Boot源码里找到你想要的东西。