2. Bootloader移植实战:U-Boot源码结构分析、板级配置修改、DDR初始化与网络驱动移植
Bootloader 是嵌入式系统上电后运行的第一段代码。说白了,它负责把内核从 Flash 或网络里搬进内存,然后跳转过去。U-Boot 是目前最主流的 Bootloader,几乎支持所有主流架构。这一章,我带你从源码结构入手,一步步完成板级配置、DDR 初始化和网络驱动的移植。
2.1 U-Boot 源码结构:先摸清家底
拿到 U-Boot 源码,别急着改。先看看它长什么样。我个人习惯先看顶层目录,心里有个全局图。
| 目录/文件 | 作用 |
|---|---|
arch/ |
架构相关代码,比如 arm、riscv、x86。我们最常用的是 arch/arm/ |
board/ |
板级代码,每个板子一个子目录。移植的核心战场 |
configs/ |
默认配置文件,make xxx_defconfig 用的就是这里 |
include/configs/ |
板级头文件,定义宏、地址、外设参数 |
drivers/ |
驱动框架,串口、网卡、MMC、NAND 都在这里 |
dts/ |
设备树源文件,新版本 U-Boot 越来越依赖设备树 |
common/ |
通用命令和框架代码,比如 cmd/ 下的命令实现 |
嗯,这里要注意:U-Boot 的启动流程大致是:start.S(汇编)→ board_init_f → relocate → board_init_r。移植时,你主要关心 board/ 和 include/configs/ 下的文件。
核心原则:不要从头写板级代码。找一个与你的板子最接近的参考板,复制一份,然后修改。我在项目中遇到过,有人非要自己从头写,结果 DDR 参数调了两周还没跑起来。
2.2 板级配置修改:从参考板开始
假设你的板子用的是 NXP i.MX6ULL 芯片,参考板是 mx6ull_14x14_evk。我会这样做:
- 复制板级目录:
cp -r board/freescale/mx6ull_14x14_evk board/freescale/myboard - 修改 Kconfig 和 Makefile:把里面的名字改成
myboard - 创建默认配置:
cp configs/mx6ull_14x14_evk_defconfig configs/myboard_defconfig - 修改板级头文件:
include/configs/myboard.h,重点改 DDR 参数、网络配置、环境变量地址
举个例子,头文件里你需要定义这些宏:
/* 串口 */
#define CONFIG_MXC_UART_BASE UART1_BASE
/* 网络 */
#define CONFIG_FEC_MXC_PHYADDR 0
#define CONFIG_ETHPRIME "FEC"
/* 环境变量存放地址 */
#define CONFIG_ENV_OFFSET 0x80000
#define CONFIG_ENV_SIZE 0x20000
/* DDR 参数 */
#define PHYS_SDRAM 0x80000000
#define PHYS_SDRAM_SIZE (512 * 1024 * 1024) /* 512MB */
我的经验:头文件里的宏定义,能少改就少改。只改与硬件相关的部分。比如时钟频率、GPIO 复用、PHY 地址。其他保持参考板的默认值,等系统跑起来再逐步优化。
2.3 DDR 初始化:最头疼的一步
DDR 初始化是移植中最容易翻车的地方。我曾经在一个项目里,因为一个时序参数没调对,板子死活起不来,最后用示波器量了整整一天才发现问题。
U-Boot 的 DDR 初始化通常放在 board/ 目录下的 ddr.c 或 lowlevel_init.S 里。主要步骤:
- 配置 DDR 控制器:设置寄存器,包括时序、刷新率、驱动强度
- 配置 DDR 颗粒:通过 I2C 读取 SPD 信息,或者直接写死参数
- 校准:写读测试,调整 DQS 延迟
以 i.MX6ULL 为例,DDR 参数通常在 board/freescale/myboard/ddr.c 里:
static struct mx6_ddr3_cfg myboard_ddr3_cfg = {
.mem_speed = 800, /* 800MHz */
.density = 4, /* 4Gb per chip */
.width = 16, /* 16-bit per chip */
.banks = 8,
.rows = 15,
.cols = 10,
.tRCD = 14, /* 时序参数 */
.tRP = 14,
.tRAS = 35,
.tWR = 15,
.tRFC = 350,
};
避坑指南:我曾经因为 DDR 时序参数抄错了参考板的配置,导致板子在高温下随机死机。后来发现是 tRFC 值偏小。建议你拿到 DDR 颗粒的 datasheet,对照着填,不要完全相信参考板的值。
初始化完成后,U-Boot 会做一次简单的内存测试。如果测试通过,你会看到类似这样的输出:
DRAM: 512 MiB
如果没看到,或者显示的大小不对,说明 DDR 初始化有问题。这时候我会先检查时钟配置,再检查时序参数,最后用示波器量一下 DQS 和 CLK 的波形。
2.4 网络驱动移植:让板子连上网
网络驱动移植的目标是让 U-Boot 能通过网卡下载内核和设备树。常用的网卡芯片有:
- FEC:NXP 内部以太网控制器,i.MX 系列常用
- RTL8211:Realtek 千兆 PHY
- KSZ9031:Microchip 千兆 PHY
- DM9000:Davicom 百兆芯片,老项目里常见
移植步骤:
- 确认硬件连接:看原理图,确认 MAC 和 PHY 之间的接口(MII/RMII/RGMII),以及 PHY 地址
- 使能驱动:在
myboard_defconfig里加上CONFIG_FEC_MXC=y和CONFIG_PHYLIB=y - 配置 PHY 地址:在头文件里定义
CONFIG_FEC_MXC_PHYADDR - 配置时钟和 GPIO:在
board_early_init_f里设置网卡时钟和复位引脚
举个例子,配置 FEC 的时钟和复位:
static void setup_fec(void)
{
/* 使能网卡时钟 */
enable_fec_clk();
/* 复位 PHY */
gpio_direction_output(PHY_RST_GPIO, 0);
udelay(1000);
gpio_direction_output(PHY_RST_GPIO, 1);
udelay(10000);
}
调试技巧:如果网络不通,先用 mdio 命令读 PHY 的寄存器。比如 mdio list 看 PHY 地址,mdio read 0 1 读 PHY ID。如果读出来全是 0xffff,说明 MDIO 通信有问题,检查时钟和上拉电阻。
网络驱动移植完成后,你就可以用 tftp 命令下载内核了:
setenv serverip 192.168.1.100
setenv ipaddr 192.168.1.10
tftp 0x80800000 zImage
tftp 0x83000000 myboard.dtb
bootz 0x80800000 - 0x83000000
2.5 本章知识体系
下面这张图总结了 U-Boot 移植的核心流程和关键文件:
我的建议:移植 U-Boot 时,不要追求一步到位。先让串口能打印信息,再让 DDR 能跑起来,最后才是网络。每一步都验证通过,再进入下一步。这样出了问题,你能快速定位到是哪个环节的问题。