3. U-Boot源码结构:从顶层布局到板级添加

做嵌入式的人,几乎都绕不过U-Boot。我刚开始接触它的时候,第一反应是——这代码量也太大了。但其实它的顶层结构非常清晰,说白了就是一套「架构分离、板级独立、配置驱动」的框架。今天我们就把它拆开看看。

3.1 顶层目录布局:一眼看懂U-Boot的家底

U-Boot的顶层目录,每个文件夹都有明确的职责。我习惯把它们分成三类:核心代码、板级相关、构建工具。

目录 职责 我的理解
arch/ CPU架构相关代码(ARM、RISC-V、x86等) 芯片底层的「方言」
board/ 具体板卡的初始化代码 每块板子的「身份证」
configs/ 默认配置文件(defconfig) 板级功能的「开关清单」
dts/ 设备树源文件(.dts / .dtsi) 硬件拓扑的「说明书」
drivers/ 各类外设驱动 功能模块的「积木盒」
include/ 全局头文件、配置宏 整个项目的「公共词典」
common/ 通用命令和框架代码 U-Boot的「骨架」
lib/ 通用库函数(crc、sha、lzma等) 谁都能用的「工具箱」
net/ 网络协议栈 网络功能的「引擎」
fs/ 文件系统支持 读写存储的「翻译官」

关键认知:U-Boot的目录设计遵循「关注点分离」。你想改CPU初始化,去arch/;想改板级外设,去board/;想改引脚定义,去dts/。别在错误的地方找答案。

3.2 arch/ 目录:CPU架构的「方言区」

进入arch/,你会看到arm/、riscv/、x86/等子目录。每个架构下又按SoC厂商细分。以ARM为例:

arch/arm/
├── cpu/          # CPU核心初始化(如armv7、armv8)
├── dts/          # 架构级设备树(.dtsi)
├── include/      # 架构相关头文件
├── lib/          # 架构通用函数(如cache、mmu)
├── mach-*/       # 各SoC厂商代码(mach-sunxi、mach-rockchip等)
└── config.mk     # 架构编译规则

我记得第一次移植时,在arch/arm/cpu/armv8/start.S里找异常向量表,找了半天。后来才明白——启动流程是架构相关的,板级差异只体现在外设初始化上。这个认知帮我省了很多时间。

3.3 board/ 目录:板级代码的「身份证」

board/目录下,每个板卡厂商一个子目录。比如:

board/ti/
├── am335x/       # BeagleBone Black等
├── dra7xx/       # DRA7系列
└── common/       # TI通用板级代码

每个板级目录通常包含:

  • board.c — 板级初始化函数(board_init_f、board_init_r)
  • Kconfig — 板级配置选项
  • Makefile — 编译规则
  • MAINTAINERS — 维护者信息

我的习惯:添加新板卡时,先找一个硬件相似的现有板级目录,直接复制一份再改。比如你的板子用全志H3,就从board/sunxi/下手。别从零开始写,那是自找麻烦。

3.4 configs/ 目录:板级功能的「开关清单」

每个板卡在configs/下都有一个xxx_defconfig文件。它本质上是Kconfig的默认配置快照。举个例子:

# configs/sandbox_defconfig
CONFIG_SYS_TEXT_BASE=0x00000000
CONFIG_SANDBOX=y
CONFIG_SANDBOX_SDL=y
CONFIG_CMD_BOOTM=y
CONFIG_CMD_GPIO=y

这里每一行都是一个开关。你想想看,U-Boot的构建系统就是靠这个文件,决定哪些功能编译进去,哪些不编译。我见过不少新手直接改.config,结果make distclean后全丢了——永远只改defconfig文件。

3.5 dts/ 目录:硬件拓扑的「说明书」

设备树是U-Boot和Linux共享的硬件描述语言。在dts/目录下:

dts/
├── arch/         # 按架构分(arch/arm/dts/)
│   └── arm/
│       ├── am335x-boneblack.dts
│       ├── am33xx.dtsi          # SoC级通用定义
│       └── am335x.dtsi          # 系列通用定义
└── Makefile      # 设备树编译规则

设备树的层级关系:

  • .dtsi — SoC级或系列级通用定义(如时钟、中断控制器)
  • .dts — 板级具体定义(如GPIO引脚、外设地址)

我曾经踩过的坑:在.dts里重复定义了.dtsi中已有的节点,导致编译报错。记住——.dtsi是「基础模板」,.dts只做「增量覆盖」。别重复造轮子。

3.6 Kconfig与Makefile构建系统:U-Boot的「大脑」

U-Boot使用Kconfig管理配置,用Makefile控制编译。这套组合拳,说白了就是:你选什么功能,它就编译什么代码。

3.6.1 Kconfig配置体系

Kconfig文件分布在各个目录,顶层Kconfig通过source语句引入子目录配置:

# 顶层 Kconfig
source "arch/Kconfig"
source "board/Kconfig"
source "drivers/Kconfig"
source "fs/Kconfig"
source "net/Kconfig"
source "dts/Kconfig"

每个子目录的Kconfig定义自己的配置项。比如:

# drivers/gpio/Kconfig
config GPIO_SUNXI
    bool "Sunxi GPIO driver"
    depends on ARCH_SUNXI
    help
      Say Y here to support GPIO on Allwinner Sunxi SoCs.

配置流程:

  1. make xxx_defconfig — 加载默认配置
  2. make menuconfig — 图形化调整(可选)
  3. make — 根据.config编译

3.6.2 Makefile编译规则

U-Boot的Makefile体系分三层:

层级 文件 作用
顶层 Makefile 入口,定义全局规则、链接脚本
架构层 arch/arm/config.mk 架构编译选项(如-march=armv8-a)
板级层 board/xxx/Makefile 板级源文件列表

一个典型的板级Makefile:

# board/ti/am335x/Makefile
obj-y += board.o
obj-y += mux.o
obj-$(CONFIG_SPL_BUILD) += spl.o

这里obj-y表示总是编译,obj-$(CONFIG_xxx)表示条件编译。嗯,这套规则和Linux内核完全一致,如果你做过内核驱动,上手会很快。

3.7 如何添加一个新的板级支持:实战五步法

现在,假设我们要为一块新板卡myboard(基于全志H3 SoC)添加U-Boot支持。我会按这五步走:

Step 1:创建板级目录

mkdir -p board/sunxi/myboard
cp board/sunxi/board.c board/sunxi/myboard/
# 修改board.c中的板级初始化函数

Step 2:创建设备树

cp dts/arch/arm/sun8i-h3-orangepi-pc.dts dts/arch/arm/sun8i-h3-myboard.dts
# 修改model、memory、regulator等节点

Step 3:创建默认配置文件

cp configs/orangepi_pc_defconfig configs/myboard_defconfig
# 修改CONFIG_DEFAULT_DEVICE_TREE指向新dts
# 调整CONFIG_SYS_TEXT_BASE等板级参数

Step 4:注册到Kconfig

在board/sunxi/Kconfig中添加:

config TARGET_MYBOARD
    bool "Support myboard"
    select ARCH_SUNXI
    help
      Support for the myboard board based on H3.

同时在顶层arch/arm/Kconfig的source链中确保能引用到。

Step 5:验证编译

make myboard_defconfig
make -j4
# 检查u-boot.bin和u-boot-sunxi-with-spl.bin是否生成

核心原则:添加板级支持,本质上是「复制-修改-注册」三部曲。别想着重写框架,U-Boot的框架已经很成熟了,你只需要填好自己的那块拼图。

3.8 知识结构总览

下面这张图,把U-Boot的源码结构和板级添加流程串起来了。我建议你把它存下来,每次移植前看一眼。

U-Boot源码结构与板级添加流程 U-Boot 源码结构与板级添加流程 U-Boot 顶层目录 arch/ — CPU架构 board/ — 板级代码 configs/ — 默认配置 dts/ — 设备树 drivers/ — 驱动 include/ — 头文件 构建系统 Kconfig — 配置选项 Makefile — 编译规则 defconfig — 配置快照 板级添加五步法 ① 创建 board/ 目录 ② 创建设备树 .dts ③ 创建 defconfig ④ 注册 Kconfig ⑤ 编译验证 💡 核心思想:复制 → 修改 → 注册。不要从零开始,站在巨人肩膀上。 每个新板卡 = 1个board目录 + 1个dts文件 + 1个defconfig + 1个Kconfig条目

3.9 避坑指南

我曾经犯过的错:

  • 设备树节点名不匹配:在.dts中定义的节点名,和驱动中compatible字符串对不上。U-Boot不会报错,但驱动就是加载不了。检查compatible是第一步。
  • defconfig遗漏关键选项:比如忘了开CONFIG_SPL,结果SPL根本没编译。建议对照参考板卡的defconfig逐行核对。
  • Kconfig依赖链断裂:新板卡依赖某个架构选项,但Kconfig里没写select。编译时一堆未定义宏。用make menuconfig检查依赖是否满足。

我的调试技巧:添加新板卡后,先别急着加功能。用make V=1编译一次,看编译了哪些文件。如果板级代码没被编译,八成是Kconfig或Makefile没配好。这个习惯帮我省了无数排查时间。