2. 嵌入式启动基础

拿到一份U-Boot源码,第一件事是什么?我个人的习惯是——先看目录结构。别急着编译,也别急着改代码。先把家底摸清楚,后面移植的时候才能少走弯路。

U-Boot的源码目录,说白了就是一套分层解耦的设计。顶层负责调度,arch管CPU架构,board管具体板子,drivers管外设。每一层各司其职,互不干扰。你想想看,如果所有代码都堆在一个目录里,那移植一个新板子得改多少地方?

好,咱们一层一层拆开看。

2.1 顶层目录:U-Boot的“总指挥部”

顶层目录下的文件和文件夹,是整个U-Boot的骨架。我列个表,你对照着源码看会更清楚。

目录/文件 作用 我的备注
arch/ CPU架构相关代码(ARM、RISC-V、x86等) 移植时最常改的地方之一
board/ 具体板级支持代码 每个板子一个子目录
common/ 通用核心函数(命令解析、环境变量等) 一般不动,但得知道它在哪
drivers/ 外设驱动(串口、网卡、MMC、USB等) 驱动适配的主战场
include/ 全局头文件和配置文件 configs/ 子目录放板级配置
lib/ 通用库函数(字符串、压缩、加密等) 基本不用改
net/ 网络协议栈(TFTP、NFS、DHCP等) 调试网络启动时重点关注
fs/ 文件系统支持(ext4、FAT、UBIFS等) 按需开启即可
tools/ 主机端工具(mkimage、env工具等) 打包镜像时离不开它
Makefile 顶层构建脚本 入口文件,定义了编译流程
Kconfig 图形化配置系统入口 类似Linux内核的menuconfig
小技巧: 我刚开始移植时,总喜欢在顶层目录里乱翻。后来发现,只要盯住 arch/、board/、include/configs/ 这三个目录,90%的移植工作就搞定了。别被其他目录分散精力。

2.2 arch/ 目录:CPU架构的“方言区”

不同的CPU架构,启动流程、寄存器布局、指令集都不一样。U-Boot用 arch/ 目录把这些差异隔离起来。

以ARM为例,进去看看:

arch/arm/
├── cpu/          # 具体CPU核的代码(armv7、armv8、cortex-a系列等)
├── dts/          # 设备树源文件(.dts/.dtsi)
├── include/      # 架构相关的头文件
├── lib/          # 架构通用函数(cache、MMU设置等)
├── mach-*/       # 厂商/SoC相关代码(mach-sunxi、mach-rockchip等)
└── config.mk     # 架构编译规则

这里有个关键点:mach-*/ 目录。每个SoC厂商都有自己的初始化逻辑。比如全志的芯片在 mach-sunxi 里,瑞芯微的在 mach-rockchip 里。移植时,你得先找到对应厂商的目录。

注意: 我曾经犯过一个低级错误——在移植一个新SoC时,直接复制了另一个厂商的 mach-* 目录,结果编译出来的U-Boot连串口都不打印。后来才发现,两个SoC的时钟树完全不一样。所以,不要偷懒,每个SoC的底层代码都得重新适配。

2.3 board/ 目录:板级定制的“身份证”

board/ 目录存放的是具体电路板的代码。一个板子一个子目录,命名规则通常是 board/<厂商>/<板子名>/。

举个例子:

board/ti/am335x/          # TI AM335x系列板子
board/freescale/mx6q/     # 飞思卡尔i.MX6Q板子
board/rockchip/evb_rk3399/# 瑞芯微RK3399 EVB板

每个板子目录下通常包含:

  • board.c:板级初始化函数(时钟、DDR、GPIO等)
  • Kconfig:板级配置选项
  • MAINTAINERS:维护者信息
  • Makefile:编译规则

说白了,board/ 就是你的板子在U-Boot世界里的“身份证”。移植的第一步,往往就是在这里新建一个目录。

2.4 common/ 目录:U-Boot的“通用工具箱”

common/ 目录里装的是U-Boot的核心逻辑。比如:

  • 命令处理:cmd/ 子目录,每个命令一个文件(cmd_bootm.c、cmd_nand.c 等)
  • 环境变量:env/ 子目录,管理环境变量的存储和读取
  • 启动流程:main.c、board_r.c、board_f.c 等
  • 控制台:串口输入输出、命令行解析

这个目录我一般不动。但有个例外——如果你需要自定义一个启动命令,或者修改环境变量的存储方式,就得来这里翻一翻。

核心原则: common/ 里的代码是“通用”的,不要为了一个板子的特殊需求去修改它。正确的做法是:通过配置选项(Kconfig)或板级回调函数来实现定制。

2.5 drivers/ 目录:外设驱动的“大本营”

U-Boot的驱动模型(Driver Model,简称DM)是从2014年之后逐步引入的。现在的 drivers/ 目录结构非常清晰:

drivers/
├── serial/       # 串口驱动
├── net/          # 网卡驱动(设计以太网、PHY等)
├── mmc/          # MMC/SD卡驱动
├── spi/          # SPI总线及设备驱动
├── i2c/          # I2C总线及设备驱动
├── gpio/         # GPIO驱动
├── usb/          # USB主机/设备驱动
├── mtd/          # NAND Flash、SPI Flash等存储驱动
├── video/        # 显示驱动(LCD、HDMI等)
└── ...           # 其他外设

每个驱动目录下,又按厂商或芯片系列细分。比如串口驱动:

drivers/serial/
├── serial_ns16550.c   # 标准16550 UART
├── serial_s3c24x0.c   # 三星S3C24x0
├── serial_pl01x.c     # ARM PrimeCell PL011
└── ...                # 其他

移植时,如果你的板子用的是标准IP核(比如NS16550串口),那基本不用改驱动,配好寄存器地址和时钟就行。但如果是厂商自研的IP,那就得自己写驱动了。

我的经验: 调试新板子,第一件事就是让串口能打印。所以 drivers/serial/ 是我移植时第一个要搞定的目录。串口通了,后面的一切才有意义。

2.6 其他关键目录速览

除了上面几个大头,还有几个目录也值得了解:

目录 一句话说明
include/configs/ 板级配置头文件,定义宏开关和参数
dts/ 设备树源文件,描述硬件拓扑
scripts/ 构建脚本和辅助工具
test/ 单元测试和自动化测试用例
doc/ 文档(README、移植指南等)

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

说了这么多,不如一张图来得直观。下面这张SVG图,展示了U-Boot源码的核心层次关系:

U-Boot源码核心层次结构 U-Boot 顶层目录 arch/ board/ common/ drivers/ include/ cpu/ dts/ mach-*/ board.c Kconfig cmd/ env/ main.c serial/ net/ mmc/ configs/ dts/ CPU架构差异隔离 移植时选对mach-* 板级定制代码 新建目录开始移植 通用核心逻辑 尽量不改 外设驱动大本营 串口驱动最先调 头文件和配置 configs/ 是关键 核心原则:分层解耦,各司其职 移植时只改 arch/、board/、include/configs/

2.8 移植时,我重点关注哪些目录?

好了,目录结构讲完了。最后总结一下,如果你要移植U-Boot到一个新板子,按这个顺序来:

  1. arch/arm/cpu/:确认CPU核类型,选对编译选项
  2. arch/arm/mach-*/:找到SoC厂商的初始化代码
  3. board/<厂商>/<板子名>/:新建板级目录,写 board.c
  4. include/configs/<板子名>.h:配置DDR参数、时钟、外设地址
  5. drivers/serial/:搞定串口,让U-Boot能打印
  6. drivers/net/:网卡驱动,用于网络启动
  7. drivers/mmc/:存储驱动,用于加载内核和设备树

嗯,这个顺序我踩过不少坑。尤其是DDR参数,如果配错了,U-Boot根本跑不起来。所以,先确认DDR初始化代码是否正确,再往下走。

避坑指南: 我曾经在移植一块新板子时,DDR参数直接从参考板复制过来,结果U-Boot启动到一半就卡死了。查了两天才发现,是DDR的时序参数差了0.5ns。所以,DDR参数一定要对照芯片手册和板子原理图来配,别偷懒。

好了,这一章的内容就到这里。U-Boot的源码结构,说白了就是一套分层设计。你只要记住:arch/ 管CPU,board/ 管板子,drivers/ 管外设,include/configs/ 管配置。剩下的,都是辅助。