4. 修改分区表的入口:config文件、board.cfg、设备树中的分区描述
分区表不是写死的。这是很多刚接触U-Boot的同学容易搞混的一点。
你想想看,同一套U-Boot源码,可能要跑在256MB的Flash上,也可能要跑在1GB的eMMC上。分区表如果硬编码在代码里,那每次换硬件都得改源码,太折腾了。
所以U-Boot给了我们三个入口来定义分区表。我习惯把它们叫做“三兄弟”:config文件、board.cfg、设备树。它们各有各的脾气,也各有各的适用场景。
核心观点:分区表的最终生效位置是固定的,但定义它的入口可以灵活选择。你选哪个入口,取决于你的项目需求和团队习惯。
4.1 入口一:config文件(最传统,也最直接)
这是最老派的做法。在U-Boot的include/configs/目录下,每个板子都有一个对应的头文件,比如mx6ul_evk.h。分区表就定义在这里面。
具体怎么写的?我直接给你看一段我项目里的真实代码:
/* 在 include/configs/myboard.h 中 */
#define CONFIG_SYS_MMC_ENV_DEV 0
#define CONFIG_SYS_MMC_ENV_PART 0
/* 分区宏定义 */
#define PARTS_DEFAULT \
"uuid_disk=${uuid_gpt_disk};" \
"name=boot,size=4MiB,uuid=${uuid_gpt_boot};" \
"name=env,size=1MiB,uuid=${uuid_gpt_env};" \
"name=rootfs,size=-,uuid=${uuid_gpt_rootfs};"
看到没?PARTS_DEFAULT这个宏就是分区表的“本体”。它用字符串的形式,把每个分区的名字、大小、UUID都列出来了。
优点:简单粗暴,改起来快。我早期做项目时特别喜欢这种方式,因为不用学设备树语法,直接改宏就行。
缺点:不够灵活。如果内核也要知道分区表,你还得在设备树里再写一遍,两边同步很麻烦。
我的习惯:如果项目只有U-Boot自己用分区信息(比如只用来烧写和启动),我会用config文件。但如果内核也要读分区表,我建议你直接看第三个入口——设备树。
4.2 入口二:board.cfg(板级配置,适合批量管理)
这个入口其实是个“中间人”。它不直接定义分区表,而是通过CONFIG_PARTITION_UUIDS、CONFIG_CMD_GPT等宏,告诉U-Boot“我要用GPT分区”、“我要支持分区UUID”。
举个例子,在board/mycompany/myboard/board.cfg里:
# 启用GPT分区支持
CONFIG_PARTITION_UUIDS=y
CONFIG_CMD_GPT=y
CONFIG_EFI_PARTITION=y
# 指定分区表来源
CONFIG_PARTITIONS=y
嗯,这里要注意。board.cfg本身不写分区大小和名字,它只是“开关”。真正的分区内容,要么来自config文件的宏,要么来自设备树。
我遇到过的情况:有一次我接手一个老项目,发现board.cfg里开了CONFIG_CMD_GPT,但config文件里根本没定义PARTS_DEFAULT。结果U-Boot启动时,分区表是空的,根本没法烧写系统。排查了半天才发现是两边没配合好。
避坑指南:board.cfg只负责“开启功能”,不负责“提供数据”。如果你发现分区表没生效,先检查board.cfg里的宏是否都开了,再去config文件或设备树里找分区定义。
4.3 入口三:设备树(现代做法,推荐)
这是我现在最推荐的方式。设备树里定义分区表,U-Boot和内核都能读到,一次定义,到处使用。
在设备树的flash节点下,加一个partitions子节点就行了。看代码:
/* 在 .dts 文件中 */
&flash0 {
status = "okay";
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
partition@0 {
label = "boot";
reg = <0x0000000 0x400000>;
};
partition@400000 {
label = "env";
reg = <0x400000 0x100000>;
};
partition@500000 {
label = "rootfs";
reg = <0x500000 0x1000000>;
};
};
};
这里compatible = "fixed-partitions"是关键。它告诉U-Boot:这个分区表是固定的,不是动态生成的。每个partition@子节点就是一个分区,reg属性定义了起始地址和大小。
为什么我推荐设备树?
- 统一管理:U-Boot和内核共用一份设备树,分区表不会出现“U-Boot里改了,内核没改”的尴尬。
- 可读性强:相比config文件里那一长串字符串,设备树的写法更清晰,每个分区一目了然。
- 容易维护:改分区表时,只需要改
.dts文件,重新编译设备树就行,不用动U-Boot源码。
重要提醒:设备树里的分区表,U-Boot默认不会自动解析。你需要在config文件里加上#define CONFIG_OF_SYSTEM_SETUP,U-Boot才会在启动时把设备树里的分区信息传给内核。
4.4 三者的关系与优先级
这三个入口不是互斥的,它们可以共存。但U-Boot有个优先级规则:
| 优先级 | 入口 | 说明 |
|---|---|---|
| 最高 | 设备树 | 如果设备树里有partitions节点,U-Boot优先用它 |
| 中等 | config文件 | 设备树里没有分区定义时,才用PARTS_DEFAULT |
| 最低 | board.cfg | 只控制功能开关,不提供分区数据 |
说白了,如果你设备树里写了分区表,那config文件里的PARTS_DEFAULT就失效了。我刚开始不知道这个规则,设备树和config文件里各写了一份,结果U-Boot一直用设备树的,我还以为config文件写错了……
4.5 知识体系:分区表入口全景图
下面这张图,把三个入口的关系和流程画清楚了。我建议你保存下来,以后做项目时对照着看。
4.6 实战建议:我该怎么选?
说了这么多,你可能还是有点懵。我直接给你一个选择指南:
- 如果你在做原型验证:用config文件。改起来最快,不用学设备树编译。
- 如果你的项目已经量产:用设备树。后期维护方便,内核和U-Boot同步简单。
- 如果你在维护老项目:看项目现有风格。别强行把config文件改成设备树,容易引入新bug。
我的个人习惯:新项目一律用设备树。我吃过太多“config文件改了,设备树忘了改”的亏。设备树一出场,这个问题就解决了。
好了,这一章的内容就是这些。三个入口,各有千秋。你只要记住:设备树优先级最高,config文件次之,board.cfg只是开关。下次改分区表时,先想清楚从哪个入口下手,能省不少时间。