第二章:U-Boot源码结构解析
拿到U-Boot源码包,第一件事是什么?
别急着编译。我个人习惯是先逛一圈目录结构。就像搬进新家,总得先看看厨房在哪、卧室多大。源码结构搞清楚了,后面移植、调试、裁剪,心里才有底。
U-Boot的顶层目录,乍一看挺唬人,目录很多。但别慌,核心就几个:board、arch、common、drivers。我当年刚接触时,也是一个个翻过来的。今天带你走一遍,哪些是重点,哪些可以暂时忽略。
核心观点:U-Boot源码是分层设计的。硬件相关代码在arch和board,通用逻辑在common,驱动在drivers。移植时,你改得最多的就是board和arch。
2.1 顶层目录:先看门牌号
解压U-Boot源码后,ls一下,你会看到一堆目录。我挑几个必须知道的说说。
arch/—— 架构相关代码。ARM、ARM64、x86、RISC-V都在这里。你是什么CPU,就进哪个子目录。board/—— 板级代码。每个厂商、每个板子都有自己的文件夹。移植时,这里是你主要动刀的地方。common/—— 通用功能。命令行、环境变量、启动流程,都在这里。drivers/—— 驱动代码。串口、网卡、MMC、NAND Flash……各种外设驱动。include/—— 头文件。全局的配置、数据结构定义。configs/—— 默认配置文件。每个板子一个defconfig文件。tools/—— 工具链。打包、签名、镜像生成工具。doc/—— 文档。嗯,有时候有用,有时候过时了。
我的习惯:拿到一个新板子,先看configs/里有没有相近的配置,再看board/里有没有同系列的参考板。这样可以少走很多弯路。
2.2 arch/目录:CPU架构的根据地
说白了,arch/就是U-Boot里跟CPU架构绑定的那部分代码。你用的是ARM Cortex-A7,那就进arch/arm/。如果是Cortex-A53,那就是arch/arm64/。
进去之后,你会发现还有子目录:
cpu/—— CPU初始化代码。比如设置异常向量表、使能MMU、初始化Cache。lib/—— 架构相关的库函数。比如内存拷贝、字符串操作,有些会针对架构优化。include/—— 架构相关的头文件。寄存器地址、CPU特殊寄存器定义。mach-xxx/—— 具体SoC厂商的代码。比如mach-sunxi是全志的,mach-imx是NXP的。
我记得有一次移植,板子启动到一半就挂了。查了半天,发现是arch/arm/cpu/里某个Cache初始化宏没定义。嗯,这种问题最坑,因为编译不会报错,但跑起来就崩。
避坑指南:千万不要随意修改arch/下的通用代码。你改了一个文件,可能影响所有同架构的板子。我曾经为了修一个bug,改了arch/arm/lib/下的代码,结果其他同事的板子全挂了……从那以后,我改arch/之前都会先确认有没有更局部的修改方式。
2.3 board/目录:你的板子,你的地盘
这是移植工作的主战场。board/下面按厂商分类,比如board/ti/、board/freescale/、board/allwinner/。每个厂商下再按板子分。
一个典型的板级目录包含:
Kconfig—— 板级的配置选项。Makefile—— 编译规则。board.c—— 板级初始化代码。比如DDR初始化、GPIO配置、时钟设置。dts/—— 设备树文件。现在新版的U-Boot都推荐用设备树来描述硬件。
你想想看,为什么U-Boot要把板级代码单独放?因为不同板子,即使CPU一样,外设、引脚、DDR参数都可能不同。把这些差异隔离在board/里,核心代码就不用动了。
我个人习惯是,新建一个板子时,先复制一份最接近的参考板,然后改DDR参数、改引脚复用、改时钟配置。这样效率最高。
2.4 common/目录:U-Boot的“大脑”
common/里装的是U-Boot的核心逻辑。说白了,就是那些跟具体硬件无关的功能。
几个关键文件:
| 文件 | 作用 |
|---|---|
main.c |
U-Boot的主入口。启动流程、命令行循环都在这里。 |
cli.c |
命令行解释器。你输入printenv、bootm,就是它解析的。 |
command/ |
各种命令的实现。比如cmd_bootm.c、cmd_nand.c。 |
env/ |
环境变量管理。从哪里加载、保存环境变量。 |
autoboot.c |
自动启动逻辑。倒计时、默认启动命令。 |
这里有个点要注意:common/里的代码虽然通用,但很多地方用了条件编译。比如CONFIG_CMD_NAND定义了,才会编译NAND命令。所以配置裁剪时,common/是重点优化对象。
2.5 drivers/目录:外设驱动的仓库
U-Boot的驱动模型(Driver Model,简称DM)现在越来越成熟。drivers/下面按设备类型分类:
serial/—— 串口驱动。调试输出全靠它。net/—— 网卡驱动。TFTP下载、网络启动。mmc/—— SD卡、eMMC驱动。mtd/—— NAND Flash、SPI Flash驱动。gpio/—— GPIO驱动。i2c/、spi/—— 总线驱动。
每个驱动目录下,又按厂商或芯片型号分文件。比如drivers/net/下面有dwmac.c(DesignWare MAC)、fec.c(Freescale MAC)等等。
移植时,如果板子上的网卡是DesignWare的,你只需要在设备树里配好,U-Boot的DM框架会自动匹配驱动。嗯,这里要注意,设备树里的compatible字符串必须跟驱动里的一模一样,大小写都不能错。我曾经因为一个下划线没对上,折腾了两天。
2.6 其他重要目录
include/ —— 全局头文件。最核心的是include/configs/下的板级配置头文件。老版本的U-Boot用.h文件配置,新版本逐渐迁移到Kconfig + defconfig了。
configs/ —— 默认配置。每个板子一个xxx_defconfig文件。编译时make xxx_defconfig就是读取这里。
dts/ —— 设备树源文件。现在U-Boot和Linux内核共用设备树,这是个好趋势。移植时,设备树写对了,驱动基本就通了一半。
scripts/ —— 编译脚本、链接脚本。比如u-boot.lds,定义了镜像的链接布局。
2.7 我的移植经验总结
说了这么多,总结几条实战经验:
- 先看
board/和configs/—— 找最接近的参考板,复制修改。 - 设备树优先 —— 能用设备树描述的,就别写死代码。后期维护方便得多。
- 驱动别从头写 ——
drivers/里大概率有现成的,改改寄存器地址、中断号就行。 - 别动
common/和arch/的核心代码 —— 除非你很清楚后果。 - 每次只改一个模块 —— 改完DDR就编译测试,再改网卡。别一次改一堆,出了问题都不知道是哪里的锅。
一句话总结:U-Boot源码结构,说白了就是“架构分离、板级隔离、驱动模块化”。理解了这个,移植就是填空游戏。