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。我会这样做:

  1. 复制板级目录:cp -r board/freescale/mx6ull_14x14_evk board/freescale/myboard
  2. 修改 Kconfig 和 Makefile:把里面的名字改成 myboard
  3. 创建默认配置:cp configs/mx6ull_14x14_evk_defconfig configs/myboard_defconfig
  4. 修改板级头文件: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 里。主要步骤:

  1. 配置 DDR 控制器:设置寄存器,包括时序、刷新率、驱动强度
  2. 配置 DDR 颗粒:通过 I2C 读取 SPD 信息,或者直接写死参数
  3. 校准:写读测试,调整 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 百兆芯片,老项目里常见

移植步骤:

  1. 确认硬件连接:看原理图,确认 MAC 和 PHY 之间的接口(MII/RMII/RGMII),以及 PHY 地址
  2. 使能驱动:在 myboard_defconfig 里加上 CONFIG_FEC_MXC=y 和 CONFIG_PHYLIB=y
  3. 配置 PHY 地址:在头文件里定义 CONFIG_FEC_MXC_PHYADDR
  4. 配置时钟和 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 移植核心流程 U-Boot 移植核心流程 1. 源码结构分析 2. 板级配置修改 3. DDR 初始化 4. 网络驱动移植 5. 验证与调试 关键文件 • board/myboard/ • include/configs/myboard.h • configs/myboard_defconfig • dts/myboard.dts 调试工具 • mdio 命令 • 示波器 • 串口日志 • 内存测试命令

我的建议:移植 U-Boot 时,不要追求一步到位。先让串口能打印信息,再让 DDR 能跑起来,最后才是网络。每一步都验证通过,再进入下一步。这样出了问题,你能快速定位到是哪个环节的问题。