2. U-Boot源码结构:顶层目录、板级支持包(BSP)、驱动模型(DM)与配置系统(Kconfig/defconfig)
好,咱们正式开始啃U-Boot源码。很多新手一拉下来U-Boot代码,看到满屏目录就头大。我当年第一次看的时候也懵——这玩意儿怎么比Linux内核还乱?其实不然,它的结构非常有章法。今天我就带你把它拆开揉碎,讲清楚四个核心部分:顶层目录、板级支持包、驱动模型和配置系统。
一句话总结:U-Boot源码 = 通用框架 + 板级定制 + 驱动抽象 + 灵活配置。搞懂这四块,你就能在任意板子上玩转U-Boot。
2.1 顶层目录:U-Boot的骨架
先看顶层。你拉下代码后,ls一下,会看到大概二十几个目录。别慌,我按功能给你分个类。
| 目录 | 作用 | 我的经验 |
|---|---|---|
arch/ |
CPU架构相关代码(ARM、RISC-V、x86等) | 移植新板子时,这里改得最多 |
board/ |
板级初始化、外设配置 | 每个板子一个子目录,定制化核心 |
common/ |
通用命令和框架代码 | 比如cmd/里的命令实现 |
drivers/ |
设备驱动(DM模型的核心) | 网络、存储、串口都在这里 |
include/ |
头文件、配置宏、板级配置 | configs/里放defconfig文件 |
net/ |
网络协议栈(TFTP、NFS等) | OTA部署重点关注 |
tools/ |
编译工具、打包脚本 | 生成镜像时经常用到 |
doc/ |
文档和README | 遇到问题先翻这里,别急着问人 |
你看,顶层目录其实就三块:架构相关(arch/)、板级相关(board/)、通用代码(剩下的)。这种分层设计,说白了就是为了方便移植——你换CPU架构改arch/,换板子改board/,其他基本不动。
我的习惯:拿到一个新板子,我第一件事就是看board/和include/configs/下的文件。这两个目录藏着板子的所有秘密。
2.2 板级支持包(BSP):让U-Boot认识你的板子
BSP是什么?就是让U-Boot能在你板子上跑起来的那层胶水代码。它主要分布在两个地方:
board/<厂商>/<板子名>/:板级初始化代码,比如DDR初始化、时钟配置、GPIO设置。include/configs/<板子名>.h:板级配置头文件,定义内存大小、环境变量位置、外设地址等。
举个例子,我做过一个i.MX6ULL的项目,它的BSP路径是这样的:
board/freescale/mx6ullevk/ # 板级代码
include/configs/mx6ullevk.h # 板级配置
在mx6ullevk.c里,你会看到board_init_f()、board_init_r()这些函数。它们负责在U-Boot启动早期把硬件初始化好。我曾经踩过一个坑——DDR时序参数配错了,U-Boot死活跑不起来。后来查了三天,才发现是board/下的一个寄存器值写错了。嗯,从那以后我改BSP代码都格外小心。
注意:BSP代码是板子特有的,不能通用。你从别的板子抄代码时,一定要核对原理图和芯片手册。别问我怎么知道的……
2.3 驱动模型(DM):U-Boot的设备驱动框架
DM是U-Boot近几年的重大改进。它把驱动抽象成三层:
- UCLASS(驱动类):比如
UCLASS_ETH代表以太网设备,UCLASS_GPIO代表GPIO。 - UDRV(驱动):具体的驱动实现,比如
fec_mxc驱动。 - UDEVICE(设备实例):板子上的具体设备,比如“eth0”对应某个PHY。
为什么要搞这么复杂?我举个例子。以前写驱动,每个板子都要自己注册设备、绑定中断。现在有了DM,你只需要在设备树里描述硬件,驱动框架自动帮你搞定。说白了,就是把硬件描述和驱动代码解耦。
下面这张图帮你理清DM的层次关系:
你看,从UCLASS到UDEVICE,一层层往下,越来越具体。这种设计的好处是:驱动代码可以复用。比如你换了一个板子,但用的还是FEC网卡,那UDRV不用改,只需要在设备树里改一下寄存器地址就行。
避坑指南:我曾经在移植时忘了在设备树里加compatible属性,结果DM框架死活匹配不上驱动。记住,设备树里的compatible字符串必须和驱动里的.compatible完全一致,一个字符都不能差。
2.4 配置系统:Kconfig + defconfig
U-Boot的配置系统是从Linux内核学来的。它由两部分组成:
- Kconfig:定义配置选项的菜单结构,存在各个目录的
Kconfig文件里。 - defconfig:板子的默认配置,存在
configs/目录下,比如mx6ullevk_defconfig。
编译时,你执行make mx6ullevk_defconfig,系统就会读取这个文件,生成.config。然后make的时候,所有#ifdef CONFIG_XXX的代码会根据.config决定是否编译。
我举个例子。你想开启网络功能,需要在defconfig里加一行:
CONFIG_CMD_NET=y
CONFIG_CMD_TFTP=y
CONFIG_CMD_PING=y
这样编译出来的U-Boot才有网络命令。如果你忘了加CONFIG_CMD_NET,那tftp、ping这些命令根本不会编译进去。我刚开始做OTA时,就因为这个折腾了半天——明明代码没问题,但就是找不到tftp命令。后来才发现是defconfig里没开。
核心原则:defconfig决定了U-Boot的功能集。做OTA部署时,一定要确保网络相关的配置都打开了。我建议你对照doc/README.*检查一遍。
最后,我把这四个部分的关系总结一下:
| 部分 | 核心文件/目录 | 作用 | OTA部署中的角色 |
|---|---|---|---|
| 顶层目录 | arch/, common/, net/ |
通用框架和架构代码 | 网络协议栈、命令框架 |
| BSP | board/, include/configs/ |
板级初始化和配置 | 网卡、存储设备初始化 |
| DM | drivers/, 设备树 |
驱动抽象和复用 | 网卡驱动、MMC驱动 |
| 配置系统 | Kconfig, configs/*_defconfig |
功能裁剪和定制 | 开启网络、TFTP、环境变量支持 |
嗯,这一章的内容就到这儿。你把这四个部分搞清楚了,后面讲OTA部署时,你就知道该改哪里、该配什么了。记住,源码结构是基础,基础不牢,地动山摇。