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近几年的重大改进。它把驱动抽象成三层:

  1. UCLASS(驱动类):比如UCLASS_ETH代表以太网设备,UCLASS_GPIO代表GPIO。
  2. UDRV(驱动):具体的驱动实现,比如fec_mxc驱动。
  3. UDEVICE(设备实例):板子上的具体设备,比如“eth0”对应某个PHY。

为什么要搞这么复杂?我举个例子。以前写驱动,每个板子都要自己注册设备、绑定中断。现在有了DM,你只需要在设备树里描述硬件,驱动框架自动帮你搞定。说白了,就是把硬件描述和驱动代码解耦。

下面这张图帮你理清DM的层次关系:

U-Boot驱动模型(DM)层次结构 U-Boot 驱动模型 (DM) 层次结构 UCLASS(驱动类) UCLASS_ETH | UCLASS_GPIO | UCLASS_SERIAL | UCLASS_MMC ... UDRV(驱动实现) fec_mxc | gpio_mxc | serial_ns16550 | fsl_esdhc ... UDEVICE(设备实例) eth0 (FEC) | gpio0 (GPIO1) | serial0 (UART1) | mmc0 (eMMC) ... 说明: 设备树(.dts)描述硬件 → DM框架自动匹配UCLASS和UDRV → 生成UDEVICE实例 抽象层 实现层 实例层

你看,从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部署时,你就知道该改哪里、该配什么了。记住,源码结构是基础,基础不牢,地动山摇。