3. Bootloader移植(U-Boot):U-Boot源码结构、板级目录创建、设备树(DTS)配置、网络与存储驱动使能
Bootloader 移植,说白了就是让 U-Boot 在你的板子上先跑起来。我做了这么多年嵌入式,发现很多人一上来就改代码,结果连板子都起不来。其实,搞懂 U-Boot 的源码结构,比什么都重要。
3.1 U-Boot 源码结构概览
U-Boot 的源码目录,初看挺吓人,但核心就几个。我个人习惯先看顶层 Makefile,它决定了整个编译流程。然后重点关注这几个目录:
| 目录 | 作用 | 我关注的点 |
|---|---|---|
arch/ |
CPU 架构相关代码(ARM、RISC-V 等) | 启动流程、异常向量表 |
board/ |
板级特定代码 | 这里就是要新建的目录 |
configs/ |
板级默认配置文件 | xxx_defconfig 就在这里 |
drivers/ |
各种驱动(网卡、存储、串口) | 使能驱动时经常改这里 |
dts/ |
设备树源文件 | 硬件描述的核心 |
include/ |
头文件、配置宏 | 板级配置头文件 |
嗯,这里要注意:board/ 和 configs/ 是移植时改动最频繁的地方。你想想看,U-Boot 要适配成千上万的板子,靠的就是这种分层结构。
3.2 创建板级目录
假设我们的 Modbus 网关主控是 imx6ull,芯片厂商是 mycompany,板子叫 modbus-gw。我会这样操作:
- 复制参考板:找一块芯片相同的官方板,比如
board/freescale/mx6ullevk,整个复制过来。 - 重命名目录:改成
board/mycompany/modbus-gw/。 - 修改文件:把里面的
mx6ullevk.c、Kconfig、MAINTAINERS里的名字全换掉。 - 创建 defconfig:在
configs/下新建modbus_gw_defconfig,内容参考官方板,但去掉不需要的功能。
我曾经犯过一个错:直接从头写板级文件,结果漏了内存初始化,卡在 lowlevel_init 三天。后来学乖了,永远从参考板开始。
3.3 设备树(DTS)配置
设备树是 U-Boot 和 Linux 共享硬件信息的桥梁。对于 Modbus 网关,我们需要描述:CPU、内存、串口(用于 Modbus RTU)、SPI(用于无线模块)、以太网(用于 Modbus TCP)。
我会在 arch/arm/dts/ 下创建 imx6ull-modbus-gw.dts。核心片段如下:
#include "imx6ull.dtsi"
/ {
model = "MyCompany Modbus Gateway";
compatible = "mycompany,modbus-gw", "fsl,imx6ull";
chosen {
stdout-path = &uart1;
};
memory {
reg = <0x80000000 0x10000000>; // 256MB DDR3
};
};
&uart1 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart1>;
};
&fec1 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet1>;
phy-mode = "rmii";
phy-handle = <ðphy0>;
};
这里有个坑:memory 节点的大小必须和硬件实际 DDR 容量一致。我见过有人写 512MB,结果板子只有 256MB,U-Boot 启动到一半就崩了。
3.4 网络与存储驱动使能
Modbus 网关离不开网络和存储。U-Boot 里使能驱动,主要通过 defconfig 里的宏开关。
网络驱动(FEC 以太网)
对于 i.MX6ULL,内置的 FEC 控制器。在 modbus_gw_defconfig 里加上:
CONFIG_FEC_MXC=y
CONFIG_PHYLIB=y
CONFIG_PHY_MICREL=y
然后检查 drivers/net/fec_mxc.c 里的 fec_mxc_probe 函数,确保它调用了你的板级初始化。我一般会在 board_eth_init 里加调试打印,确认 PHY 地址对不对。
fec_mxc.c 里加了 printf("PHY addr: %d\n", addr);,才发现是地址偏移了一位。
存储驱动(eMMC/SD 卡)
存储是烧录固件的关键。使能 MMC 驱动:
CONFIG_MMC=y
CONFIG_FSL_ESDHC=y
CONFIG_CMD_MMC=y
然后配置分区表。我习惯在 U-Boot 环境变量里设置:
setenv mmcdev 0
setenv mmcpart 1
setenv bootargs console=ttymxc0,115200 root=/dev/mmcblk0p2 rootwait
嗯,这里要注意:mmcblk0p2 对应的是第二个分区(rootfs),第一个分区通常放内核和设备树。
3.5 核心流程总结
为了让思路更清晰,我画了一张图,展示 U-Boot 移植的核心流程:
3.6 常见问题与排查
移植过程中,我总结了几类高频问题:
| 现象 | 可能原因 | 我的排查方法 |
|---|---|---|
| U-Boot 无任何输出 | 串口引脚配置错误、时钟不对 | 检查 pinctrl 和 clock 节点 |
| 网卡无法 ping 通 | PHY 地址不对、MDIO 时序问题 | 加 printf 打印 PHY 寄存器 |
| eMMC 无法识别 | 时钟频率过高、电压不匹配 | 降低 CONFIG_FSL_ESDHC_CLK |
启动卡在 Starting kernel... |
设备树与内核不匹配 | 用 fdt addr 检查 DTB |
printf 是最快的);2. 对比参考板(看哪里不一样);3. 单步执行(用 JTAG 或硬件断点)。
说白了,U-Boot 移植就是个「复制-修改-验证」的循环。别怕出错,我每次移植新板子,至少会重烧 50 次以上。但只要流程对了,总能跑起来。