找回密码
立即注册
搜索
发回帖 发新帖

6538

积分

0

好友

827

主题
发表于 前天 07:12 | 查看: 29| 回复: 0

U-Boot源码目录结构详解封面

四个学习目标:

第一,想修改 DDR 参数应该去哪个目录?添加一个新的 U-Boot 命令呢?

第二,arch/arm/mach-rockchip/ 和 board/rockchip/evb_rk3506/ 分别管什么?

第三,U-Boot 的 DM(Driver Model) 驱动框架和 Linux 的设备模型有什么异同?

第四,configs/ 目录下的 defconfig 是如何通过 Kconfig 继承关系展开成 .config 的?

这四个问题分别对应第四节的 4.1/4.2、4.3、4.4 以及第五节的定位指南。

一、概述

U-Boot 源码树拥有 3700+ 文件、300+ 目录,初看令人望而生畏。但理解了它的分层架构后,你会发现它高度模块化——芯片相关、板级相关、驱动、命令、文件系统各归其位。本章按“修改某功能 → 去哪个目录”的思路带你拆解。

学完本章,你将能回答上面四个问题。

二、形象比喻:国际化连锁酒店的部门分工

展开目录之前,先给一个比喻建立直觉。把 U-Boot 想象成一家国际化连锁酒店:arch/ 是各地分店的建筑结构——楼层、水电,换一个城市就要换一套;board/ 是每家分店的个性化装修;drivers/ 是酒店设备供应商,不管哪家分店空调电视都是同一批供应商;cmd/ 是前台服务菜单;fs/ 是仓库管理系统;common/ 是总部管理流程;include/ 是规章制度手册;configs/ 是每家分店的运营手册。

U-Boot 目录 酒店类比 职责
arch/ 各地分店的建筑结构(楼层、水电) CPU 架构相关代码
board/ 每家分店的个性化装修 特定板卡配置
drivers/ 酒店设备供应商 各类外设驱动
cmd/ 前台服务菜单 U-Boot 命令行命令
fs/ 仓库管理系统 文件系统(ext4/fat/ubifs)
common/ 总部管理流程 通用功能和框架
include/ 规章制度手册 头文件和配置宏
configs/ 每家分店的运营手册 defconfig 配置文件

重点说三层关系。arch/ 是“建筑结构”——换一个城市,也就是换 CPU 架构,就要换一套,这是最底层的;board/ 是“个性化装修”——同一个城市不同分店,也就是同 SoC 不同板卡,装修不同但建筑结构一样;drivers/ 是“设备供应商”——不管哪家分店,空调电视都是同一批供应商提供,驱动是跨板卡复用的。记住这个“建筑-装修-设备”的三层比喻,后面看目录树就不会迷路。

设问一下:换了一颗 DDR 颗粒,属于换建筑、换装修、还是换设备?答案是接近“换装修”——DDR 参数属于板级配置,归 board/ 和 arch/.../sdram.c,不是架构级也不是外设驱动级。用这个互动把比喻坐实,印象会深很多。

三、U-Boot 源码顶层全景

有了比喻打底,看 U-Boot 源码的顶层全景树。这棵树是本章的核心,逐层带读。放到比喻里,这棵树就是这家酒店的“总平面图”。讲的时候按“优先级”讲,不按字母顺序——先讲带 ⭐ 标记的五个目录:arch/、board/、cmd/、configs/、drivers/,这五个是移植时 90% 的修改都集中的地方。剩下的 common/、fs/、include/、lib/、scripts/、tools/ 一带而过,知道在哪就行,前期不用深入。

先看整棵树:

u-boot/
├── arch/              # ⭐ CPU 架构代码(最重要)
│   ├── arm/
│   │   ├── cpu/       # ARM 通用 CPU 代码
│   │   ├── dts/       # U-Boot 自己的设备树
│   │   ├── include/   # 架构相关头文件
│   │   ├── lib/       # 架构相关库函数
│   │   ├── mach-rockchip/  # ⭐ Rockchip 平台
│   │   │   ├── board.c     # 板级通用初始化 (1553行)
│   │   │   ├── spl.c       # SPL 启动流程 (655行)
│   │   │   ├── fit.c       # FIT 镜像处理
│   │   │   ├── sdram.c     # DDR 初始化封装
│   │   │   ├── rk3506/     # RK3506 专属代码
│   │   │   └── Makefile
│   │   └── mach-*/    # 其他 SoC 平台
│   └── ...
│
├── board/             # ⭐ 板卡级代码
│   └── rockchip/
│       └── evb_rk3506/    # RK3506 评估板
│           ├── evb_rk3506.c  # 板级初始化
│           ├── Kconfig
│           └── Makefile
│
├── cmd/               # ⭐ 命令实现
│   ├── bootm.c        # bootm 命令(引导内核)
│   ├── booti.c        # booti 命令(ARM64 Image)
│   ├── tftpboot.c     # tftp 下载命令
│   ├── mii.c          # 网络 PHY 操作
│   └── ...
│
├── common/            # 通用功能
│   ├── main.c         # 主循环
│   ├── cli.c          # 命令行解析
│   ├── image.c        # 内核镜像处理
│   ├── spl/           # SPL 通用框架
│   └── ...
│
├── configs/           # ⭐ defconfig 配置文件
│   ├── rk3506_defconfig      # RK3506 默认配置
│   ├── rk3506-rt.config      # 实时配置
│   └── rk3506b.config        # RK3506B 配置
│
├── drivers/           # ⭐ 驱动框架
│   ├── clk/rockchip/  # 时钟驱动
│   ├── gpio/          # GPIO 驱动
│   ├── i2c/           # I2C 驱动
│   ├── mmc/           # eMMC/SD 驱动
│   ├── mtd/           # NAND/SPI Flash 驱动
│   ├── net/           # 以太网驱动
│   ├── pinctrl/       # 引脚控制
│   ├── ram/rockchip/  # DDR 控制器驱动
│   ├── serial/        # 串口驱动
│   ├── spi/           # SPI 驱动
│   └── ...
│
├── dts/               # U-Boot 设备树
│   └── rk3506-evb.dts   # 从内核 DTS 同步
│
├── fs/                # 文件系统支持
│   ├── ext4/
│   ├── fat/
│   └── ubifs/
│
├── include/           # 头文件
│   ├── configs/       # 板级头文件
│   ├── dm/            # 驱动模型头文件
│   └── linux/         # 兼容 Linux 的头文件
│
├── lib/               # 通用库函数
│   ├── fdt/           # Flattened Device Tree
│   ├── lzma/          # LZMA 解压
│   └── ...
│
├── scripts/           # 编译辅助脚本
│   ├── Makefile.build
│   └── Kconfig
│
├── tools/             # 主机端工具
│   ├── mkimage        # 制作 U-Boot 镜像
│   └── ...
│
├── Makefile           # 顶层 Makefile
├── Kconfig            # 顶层 Kconfig
└── .config            # 当前配置(编译时生成)

带读时重点强调两个“容易混”的点。

第一,arch/arm/mach-rockchip/ 和 board/rockchip/ 都带 rockchip,学员常问“这俩有啥区别?”——这里先卖个关子,下一节 4.1 和 4.2 会专门对比。先记住:arch/ 下管 SoC 级、board/ 下管板级。

第二,configs/ 里那个 .config 是编译时才生成的,源码树里默认没有,别去找它——这个下一章 Kconfig 会详讲。

四、关键目录深度解析

顶层树有了全局印象,接下来挑三个最常打交道的目录深入看:4.1 讲 arch/arm/mach-rockchip/,4.2 讲 board/rockchip/evb_rk3506/,4.3 讲 drivers/ 的 DM 框架,4.4 讲 configs/ 的 defconfig 层级。

4.1 arch/arm/mach-rockchip/ —— Rockchip 平台核心

这是移植时最常修改的目录,先看它里面有哪些关键文件:

文件 行数 核心内容
board.c 1553 板级初始化、Android boot、AVB、充电、DVFS
spl.c 655 SPL 启动、DDR 初始化、热键检测、boot模式
fit.c 515 FIT 镜像加载和验证
sdram.c 214 DDR 初始化参数配置
rk3506/ - RK3506 专属 syscon/clock 配置
make_fit_atf.sh - 制作 ATF(ARM Trusted Firmware)的 FIT 镜像
make_fit_optee.sh - 制作 OP-TEE 的 FIT 镜像

重点说三个文件。board.c 有 1553 行,是这堆里最大的,因为它要管的事最多——板级初始化、Android 的 AVB 安全校验、充电逻辑、DVFS 动态调频,全塞在这一个文件里。spl.c 是 SPL 阶段的主流程,655 行,第 11 章会专门拆它。sdram.c 只有 214 行,但它管 DDR 初始化参数——这个文件虽然短,却是移植 DDR 时最关键的文件之一,新手容易因为它短而忽略它。

一个易错点:sdram.c 在 arch/arm/mach-rockchip/ 下,但 DDR 控制器的“驱动”在 drivers/ram/rockchip/ 下。这俩是配合关系——sdram.c 是参数配置和封装层,drivers/ram/ 是底层控制器驱动。新手想修改 DDR 参数却去 drivers/ram/ 里翻,翻半天找不到。

放到比喻里,sdram.c 是分店报给总部的“场地参数表”——这家店几路进水、电压多少,由店面情况决定;drivers/ram/ 是水泵厂商的控制板,所有分店通用。参数表填错,再好的水泵也调不准水压;而换水泵这种事,轮不到分店说了算。

一句话:改参数去 mach-rockchip/sdram.c,改驱动逻辑才去 drivers/ram/。

表里还有两个脚本 make_fit_atf.sh 和 make_fit_optee.sh,是用来打包安全启动镜像的。ATF 是 ARM Trusted Firmware,OP-TEE 是可信执行环境,后面有专章详讲,这里先知道名字就行,不展开。

4.2 board/rockchip/evb_rk3506/ —— 评估板代码

看完 SoC 级的 mach-rockchip/,对比着看板级的 board/rockchip/evb_rk3506/。这里有一段典型的板级代码,是一个 GPIO 看门狗喂狗的实现,逐行看:

/* 文件路径:u-boot/board/rockchip/evb_rk3506/evb_rk3506.c */
#include <common.h>
#include <watchdog.h>
#include <asm/io.h>
#include <asm/gpio.h>

/* GPIO 看门狗喂狗引脚:GPIO3_B5 = 109 */
#define GPIO_WATCHDOG 109

/* 硬件看门狗喂狗(GPIO 翻转) */
void hw_feed_wdg(void)
{
    if (gpio_get_value(GPIO_WATCHDOG))       /* 读取当前电平 */
        gpio_set_value(GPIO_WATCHDOG, 0);    /* 高->低 */
    else
        gpio_set_value(GPIO_WATCHDOG, 1);    /* 低->高 */
}

/* 初始化硬件看门狗 */
void hw_watchdog_reset(void)
{
    gpio_request(GPIO_WATCHDOG, "gpio_watchdog");  /* 申请 GPIO */
    gpio_direction_output(GPIO_WATCHDOG, 0);        /* 设为输出,初始低 */
    hw_feed_wdg();                                   /* 首次喂狗 */
}

先看那个宏 #define GPIO_WATCHDOG 109——GPIO3_B5 对应编号 109,这是这块评估板硬件原理图决定的,换一块板子引脚就不同,所以它放在 board/ 下而不是 arch/ 下。放到比喻里,这行宏就是分店装修图上的一个标注:这家店把监控接在三号配电箱 B5 回路,隔壁分店可能完全不同;但全城分店的水电总图(arch/)是一套。

这就是“板级”和“SoC 级”的分界:和具体板子硬件绑死的放 board/,和 SoC 本身绑死的放 arch/。

讲到喂狗逻辑设问一下:为什么要用 GPIO 翻转,而不是固定电平?因为硬件看门狗芯片是检测电平“跳变”的,一直保持高或低它会认为主控死机了,必须不停翻转才能“喂狗”告诉它“我还活着”。这个细节体会一下底层代码的精巧。

另外提醒一个新手常犯的错:gpio_request 申请 GPIO 后要 gpio_direction_output 设方向,这个顺序不能反——先设方向再申请会失败。

这里和 4.1 做个对比回收,回到刚才卖的关子。arch/arm/mach-rockchip/ 管 SoC 级,也就是所有 RK3506 板子共用的逻辑;board/rockchip/evb_rk3506/ 管板级,也就是这块评估板特有的逻辑,比如哪个 GPIO 接了看门狗。

如果你自己做一块 RK3506 的板子,arch/ 下的代码基本不用动,但要新加一个 board/rockchip/你的板子名/ 目录,把看门狗引脚、LED 引脚这些板级特有配置放进去。这就是“移植一块新板子”的核心工作。

4.3 drivers/ —— 驱动模型(DM)

第三站看 drivers/ 目录,这里涉及 U-Boot 的 DM 驱动框架。U-Boot 使用 DM(Driver Model)驱动框架,类似于 Linux 的设备模型。每个 U-Boot 驱动都要定义 U_BOOT_DRIVER 宏,下面是一个 Rockchip GPIO 驱动声明的示例:

/* DM 驱动核心数据结构 */
/* 每个 U-Boot 驱动都要定义 U_BOOT_DRIVER 宏 */

/* 示例:Rockchip GPIO 驱动声明 */
U_BOOT_DRIVER(rockchip_gpio) = {
    .name       = "rockchip_gpio",      /* 驱动名 */
    .id         = UCLASS_GPIO,          /* 所属 uclass */
    .of_match   = rockchip_gpio_ids,    /* 设备树匹配表 */
    .probe      = rockchip_gpio_probe,  /* probe 函数 */
    .ops        = &gpio_rockchip_ops,   /* 操作函数集 */
};

这段代码是 U-Boot 驱动框架的灵魂,逐字段讲。

.name 是驱动名,要唯一;.id 是它属于哪个 uclass(驱动类),GPIO 驱动就归 UCLASS_GPIO,串口归 UCLASS_SERIAL,这个分类很关键;.of_match 是设备树匹配表,U-Boot 启动时拿设备树里的 compatible 字符串和这张表比对,匹配上了才 probe;.probe 是探测函数,匹配成功后调用它来初始化硬件;.ops 是操作函数集,上层通过它调用具体操作,比如 GPIO 的设值、读值。

重点讲 DM 和 Linux 设备模型的异同,这正是开头的问题之一。

相同点:都用“驱动 + 设备 + 总线”的模型,都有 probe/remove 机制,都用设备树匹配。

不同点:U-Boot 的 DM 更精简,没有 Linux 那么多子系统,比如没有完整的电源管理、没有 runtime PM;probe 时机也不同——U-Boot 是在启动时一次性 probe 所有用到的设备,Linux 是按需 probe。另外 U-Boot 的 DM 是可选的,早期 U-Boot 没有,后来才引入,现在新代码都要求用 DM。

讲这个对比时,如果有 Linux 驱动基础,可以联想 platform_driver,理解会很快。

一个易错点:新手常把 U_BOOT_DRIVER 和 Linux 的 module_init 搞混。U-Boot 没有“模块”概念,U_BOOT_DRIVER 声明的驱动是在链接时就被收集到一个专门的段里,启动时由 DM 框架统一遍历、匹配、probe。所以它不是“注册”出去的,而是“被扫描”出来的。这个心智模型一定要建立对,否则后面学驱动移植会处处卡壳。

4.4 configs/ —— defconfig 层级

最后看 configs/ 目录,它涉及 defconfig 的继承层级。RK3506 的 defconfig 继承关系是这样的:

rk3506_defconfig (主配置)
├── 包含: arch/Kconfig -> ARM -> Rockchip -> RK3506
├── 包含: board/rockchip/evb_rk3506/Kconfig
├── 包含: drivers/*/Kconfig
└── 包含: cmd/Kconfig, fs/Kconfig ...

rk3506_defconfig 不是孤立的一个文件,它是“入口”,真正展开成 .config 要靠 Kconfig 系统把各处分散的配置项汇集起来。四条分支分别对应四个层面:

arch/Kconfig 决定架构级选项,也就是 ARM、Rockchip、RK3506 这一脉;board/.../Kconfig 决定板级选项;drivers/*/Kconfig 决定编哪些驱动;cmd/Kconfig 和 fs/Kconfig 决定支持哪些命令和文件系统。

这里只点到为止:具体的 defconfig 怎么读、怎么改、Kconfig 语法是什么,下一章会专门讲,本章只需要知道 configs/ 目录是配置入口即可。不要在本章展开 Kconfig 语法,否则会和下一章重复、节奏散掉。

这里正好回收开头的问题之一:configs/ 目录下的 defconfig 是如何通过 Kconfig 继承关系展开成 .config 的?答案就是这棵树——defconfig 是入口,Kconfig 系统沿四条分支把分散在各目录的配置项汇拢,最终生成完整的 .config,详细过程下一章演示。

五、快速定位指南

讲完四个关键目录,给大家一张“快速定位指南”,这是本章最实用的一张表,建议截图保存。以后移植时想知道“改某功能去哪个文件”,直接查这张表:

你想做什么 去哪个文件
修改默认 bootcmd configs/rk3506_defconfig 中 CONFIG_BOOTCOMMAND
修改启动延时 configs/rk3506_defconfig 中 CONFIG_BOOTDELAY
修改 DDR 参数 arch/arm/mach-rockchip/sdram.c
添加新的 U-Boot 命令 cmd/ 目录下新增文件 + Makefile
适配新的板卡 board/rockchip/ 下新增目录
修改设备树 dts/rk3506-evb.dts
修改串口波特率 configs/rk3506_defconfig 中 CONFIG_BAUDRATE
修改环境变量存储位置 include/configs/ 或 defconfig 中 ENV_ 配置

重点强调三行。第一行“修改 bootcmd”——这是移植后最常见的调整,bootcmd 决定 U-Boot 怎么加载内核,几乎每块板子都要改。第三行“修改 DDR 参数”对应 4.1 讲的 sdram.c,再次提醒:参数在 sdram.c,不是 drivers/ram。第五行“适配新板卡”对应 4.2,提醒:新板子要在 board/rockchip/ 下加目录。

一个现场演示建议:可以现场带大家改一个 CONFIG_BOOTDELAY,从默认值改成 0,重新编译烧写,让大家看到启动不再等待。这个改动小、见效快、风险低,非常适合课堂演示。改完引导大家去这张表里找到对应行,体会“定位指南”的用法。

注意一个易错点:改 defconfig 后要重新 make rk3506_defconfig 让改动生效,直接改 .config 会被下次 defconfig 覆盖——这个坑要提前提醒。

把这张表放回第二节的酒店里对一遍:改 bootcmd、波特率,等于改“运营手册”,对应 configs/;改 DDR 参数,等于改“建筑结构”里和地基相关的部分,对应 arch/;加命令,等于往“前台菜单”加菜,对应 cmd/;适配新板卡,等于给新分店做“个性化装修”,对应 board/。日常改得最多的是运营手册那一档,动得最少的是建筑结构——动手册出错顶多是流程不顺,动承重墙是要出人命的,这个轻重关系也适用于这几个目录。

📝 本章小结

到这里把 U-Boot 的源码目录结构走了一遍。一句话概括:U-Boot 源码树按“架构 → 板卡 → 功能 → 基础”四层金字塔组织,arch/arm/mach-rockchip/ 管 SoC 级初始化,board/rockchip/evb_rk3506/ 管板级适配,drivers/ 管外设驱动,cmd/ 管命令行命令。

再强调一个新手最大的认知误区,一定要讲透:U-Boot 不使用 Linux 的设备树来初始化自己的硬件,而是用 DM 驱动模型。 虽然 U-Boot 也会加载设备树,但那是用于传给内核的;它自己的硬件初始化依赖 U_BOOT_DRIVER 宏声明和 probe 函数。这个区别讲不透,后面学驱动移植会处处卡壳。

开头四个问题逐条对账。

问题一:想修改 DDR 参数应该去哪个目录?添加一个新的 U-Boot 命令呢?

改 DDR 参数去 arch/arm/mach-rockchip/sdram.c,不是 drivers/ram/;加命令去 cmd/ 目录下新增文件加 Makefile。(答案在 4.1 和第五节)

问题二:arch/arm/mach-rockchip/ 和 board/rockchip/evb_rk3506/ 分别管什么?

前者管 SoC 级共用逻辑,所有 RK3506 板子通用;后者管板级特有配置,比如看门狗接在哪个 GPIO。做新板子时 arch/ 基本不动,新加一个 board/rockchip/你的板子名/ 目录。(答案在 4.1 和 4.2)

问题三:U-Boot 的 DM 驱动框架和 Linux 的设备模型有什么异同?

都基于“驱动+设备+总线+probe”,都用设备树匹配;但 DM 更精简,没有完整电源管理和 runtime PM,而且是启动时一次性 probe,Linux 按需 probe。(答案在 4.3)

问题四:defconfig 是如何通过 Kconfig 继承关系展开成 .config 的?

defconfig 是入口,Kconfig 系统沿 arch、board、drivers、cmd/fs 四条分支把分散在各目录的配置项汇拢,最终生成完整的 .config。(答案在 4.4)

🏠 课后练习

光听不练不行,留三道练习题。

1. 必做: 在 U-Boot 源码中找到一个你熟悉的驱动(如串口 drivers/serial/),画出它的 U_BOOT_DRIVER 声明包含哪些关键字段,对应 4.3 讲的五个字段 name、id、of_match、probe、ops。建议用 grep -rn "U_BOOT_DRIVER" drivers/serial/ 快速定位。

2. 选做: 修改 configs/rk3506_defconfig 中的 CONFIG_BOOTDELAY 为 0,重新编译 U-Boot 并烧写,观察启动是否还等待按键——这就是第五节演示的那个改动,独立做一遍。

3. 思考: U-Boot 源码目录结构和 Linux 内核源码树有哪些相似之处?为什么两者都选择类似的分层架构?(提示:对比 arch/、drivers/、fs/)

答案方向是:两者相似是因为都要支持多架构、多板卡、多外设,分层架构能把“平台相关”和“平台无关”代码隔离,最大化代码复用——这是大型嵌入式项目的通用组织哲学。

下一章进入 U-Boot 的配置系统,讲 Kconfig 和 defconfig 的语法与展开过程,把本章 4.4 里只点到为止的“defconfig 怎么展开成 .config”这个问题彻底讲透。




上一篇:GPT审计PHP源码挖出超危漏洞:无需登录远程执行代码
下一篇:GPT、Claude、Gemini机器人操控实测:RoboColiseum多维能力对比
您需要登录后才可以回帖 登录 | 立即注册

关闭

站长推荐上一条 /1 下一条

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-10-27 04:13 , Processed in 0.081266 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表