3、Yocto中U-Boot的集成方式:meta层结构、u-boot_%.bbappend文件编写、配置U-Boot的defconfig与设备树
好,咱们进入正题。前面两章我们把U-Boot的启动流程和Yocto的基础概念过了一遍。这一章,我要带你亲手把U-Boot塞进Yocto的构建体系里。说白了,就是搞清楚三件事:meta层怎么搭、bbappend文件怎么写、defconfig和设备树怎么配。
我在项目里见过不少新手,直接把U-Boot源码扔进meta层,然后改几个变量就以为完事了。结果编译出来要么启动卡死,要么设备树对不上。嗯,这里面的坑,咱们一个一个填。
3.1 meta层的标准结构
先看一个典型的meta层长什么样。你打开任何一个成熟的BSP(板级支持包),目录结构基本都是这个套路:
meta-custom-bsp/
├── conf/
│ ├── layer.conf
│ └── machine/
│ └── myboard.conf
├── recipes-bsp/
│ └── u-boot/
│ ├── u-boot_%.bbappend
│ ├── files/
│ │ ├── myboard_defconfig
│ │ └── myboard.dts
│ └── patches/
│ └── 0001-fix-sdmmc-timing.patch
├── recipes-kernel/
│ └── linux/
│ └── linux-mainline_%.bbappend
└── README
这里有几个关键点:
- conf/layer.conf:声明这个层的优先级和依赖关系。优先级数字越大,越优先被采用。
- conf/machine/:定义你的板子型号,比如CPU架构、存储分区、串口参数等。
- recipes-bsp/u-boot/:U-Boot的配方目录,核心战场。
- files/:存放你自定义的defconfig、设备树源文件(.dts)、以及一些杂项配置文件。
- patches/:补丁文件,用于修复bug或添加新功能。
3.2 u-boot_%.bbappend 文件编写
bbappend文件是Yocto里最灵活的东西。它不会覆盖原始的bb文件,而是在其基础上追加或修改内容。这个%是通配符,匹配任何版本的U-Boot配方。
来看一个我实际用过的例子:
# u-boot_%.bbappend
FILESEXTRAPATHS:prepend := "${THISDIR}/files:"
SRC_URI += " \
file://myboard_defconfig \
file://myboard.dts \
file://0001-fix-sdmmc-timing.patch \
"
do_configure:append() {
# 强制使用我们自己的defconfig
cp ${WORKDIR}/myboard_defconfig ${S}/configs/
}
do_compile:prepend() {
# 编译前打个标记,方便调试
bbnote "Building U-Boot for ${MACHINE} with custom defconfig"
}
这里有几个变量你得搞明白:
| 变量/函数 | 作用 | 我踩过的坑 |
|---|---|---|
FILESEXTRAPATHS |
告诉BitBake去哪里找额外的文件 | 路径写错,导致defconfig找不到,编译报错 |
SRC_URI |
列出所有需要下载或本地引入的文件 | 补丁顺序不对,打不上 |
do_configure:append |
在配置阶段之后执行自定义操作 | 忘记加${WORKDIR},文件路径不对 |
do_compile:prepend |
在编译阶段之前执行 | 用来加调试日志,别乱改编译参数 |
FILESEXTRAPATHS的赋值方式一定要用:=(立即赋值)或者+=,并且要prepend。否则BitBake可能找不到你的文件。我曾经因为这个折腾了一下午,最后发现是赋值时机的问题。
3.3 配置U-Boot的defconfig
defconfig是U-Boot的配置蓝图。它决定了哪些功能被编译进U-Boot,哪些被砍掉。Yocto里,你通常会在files/目录下放一个自定义的defconfig。
生成defconfig的方法很简单:
- 先下载U-Boot源码,用
make <厂商默认配置>_defconfig生成一个基础配置。 - 然后用
make menuconfig调整你需要的选项。 - 最后用
make savedefconfig导出精简版的defconfig。
举个例子,假设你要为一块基于i.MX8M的板子配置U-Boot:
# myboard_defconfig
CONFIG_ARM=y
CONFIG_ARCH_IMX8M=y
CONFIG_SYS_TEXT_BASE=0x40200000
CONFIG_NR_DRAM_BANKS=1
CONFIG_SYS_SDRAM_BASE=0x40000000
CONFIG_DM_MMC=y
CONFIG_MMC_IMX_ESDHC=y
CONFIG_DM_ETH=y
CONFIG_FEC_MXC=y
CONFIG_CMD_BOOTZ=y
CONFIG_CMD_EXT4=y
CONFIG_CMD_FAT=y
CONFIG_OF_CONTROL=y
CONFIG_DEFAULT_DEVICE_TREE="myboard"
注意最后一行CONFIG_DEFAULT_DEVICE_TREE,它指定了U-Boot默认加载的设备树文件名。这个必须跟你后面提供的.dts文件名一致。
3.4 设备树的集成
设备树(Device Tree)是U-Boot和内核之间的硬件描述语言。U-Boot本身也需要设备树来初始化硬件,比如DDR、MMC、网卡等。
在Yocto里集成设备树,通常有两种方式:
- 方式一: 把.dts文件放在
files/目录下,通过bbappend引入。适合小改动。 - 方式二: 在meta层里创建一个独立的
recipes-kernel/linux/配方,专门管理设备树。适合复杂项目。
我个人更推荐方式一,因为简单直接。来看一个.dts文件的片段:
// myboard.dts
/dts-v1/;
#include "imx8mm.dtsi"
/ {
model = "My Custom Board";
compatible = "my,myboard", "fsl,imx8mm";
chosen {
stdout-path = &uart1;
};
memory@40000000 {
device_type = "memory";
reg = <0x0 0x40000000 0 0x80000000>; // 2GB DDR
};
};
&uart1 {
status = "okay";
};
&usdhc2 {
status = "okay";
bus-width = <4>;
non-removable;
};
这个文件定义了板子的内存大小、串口、SD卡等外设。U-Boot在启动时会解析这个设备树,然后初始化对应的硬件。
reg属性写错了,导致U-Boot只能识别1GB内存,但板子上焊了2GB。排查了整整两天,最后用bdinfo命令才发现内存大小不对。所以,设备树里的地址和大小一定要跟硬件原理图对一遍。
3.5 整体集成流程
把上面这些串起来,整个集成流程就是:
- 创建meta层,配置
layer.conf和myboard.conf。 - 在
recipes-bsp/u-boot/下编写u-boot_%.bbappend。 - 在
files/下放置自定义的defconfig和设备树源文件。 - 运行
bitbake u-boot,观察编译输出。 - 如果编译失败,检查
FILESEXTRAPATHS和SRC_URI的路径。 - 如果编译成功,用
flash.bin或u-boot.bin烧录到板子,验证启动。
下面这张图展示了整个集成流程的核心逻辑:
这张图把整个流程串起来了。你从创建meta层开始,然后编写bbappend,接着并行配置defconfig和设备树,最后通过bitbake编译并烧录验证。每一步都有对应的文件操作和变量设置,缺一不可。