3、Bootloader深度解析与移植(下):U-Boot移植实战、网络与存储驱动适配、环境变量与启动参数配置

上一讲我们把U-Boot的启动流程和源码结构梳理了一遍。这一讲咱们直接动手——把U-Boot移植到一块具体的板子上,搞定网络和存储驱动,再把环境变量和启动参数调明白。说白了,就是让Bootloader真正跑起来,能干活。

本章核心目标:完成U-Boot从源码到可启动镜像的完整移植,适配板级网络与存储外设,并掌握环境变量与启动参数的配置策略。

3.1 U-Boot移植实战:从零开始适配一块新板

移植U-Boot,说白了就是为你的硬件写一个“自我介绍”。U-Boot本身已经支持了大量参考板,我们要做的就是在它的框架里,告诉它你的板子长什么样。

第一步:选择参考板

我个人习惯,先找一颗和你用的SoC最接近的参考板。比如你用i.MX6ULL,那就找mx6ull_14x14_evm_defconfig。别自己从头写,那是给自己找麻烦。

第二步:创建板级目录和文件

在U-Boot源码中,你需要创建三个核心部分:

  • board/yourcompany/yourboard/ —— 板级初始化代码
  • include/configs/yourboard.h —— 板级配置头文件
  • configs/yourboard_defconfig —— 默认配置

举个例子,我曾在某款工控板上移植U-Boot,SoC是TI的AM335x。我直接复制了board/ti/am335x/,然后改里面的board.c文件。核心要改的就是board_init_f和board_init这两个函数。

// board/yourcompany/yourboard/yourboard.c
#include <common.h>
#include <asm/arch/cpu.h>
#include <asm/arch/sys_proto.h>

int board_init(void)
{
    /* 初始化GPIO、时钟、DDR等 */
    gd->bd->bi_arch_number = MACH_TYPE_YOURBOARD;
    gd->bd->bi_boot_params = PHYS_SDRAM_1 + 0x100;

    /* 我在这踩过坑:DDR参数没配好,板子死活起不来 */
    ddr_init();
    return 0;
}

我曾经在移植时忽略了DDR时序参数,结果U-Boot能编译通过,但一跑就死。后来用示波器量了波形才发现,CAS Latency差了1个周期。所以DDR参数一定要对照芯片手册逐项核对,别偷懒。

第三步:配置编译

创建好文件后,执行以下命令:

make yourboard_defconfig
make -j4

如果编译通过,你会得到u-boot.bin和u-boot.imx(不同SoC格式不同)。烧进去试试,串口能看到打印信息,就算成功了一半。

3.2 网络与存储驱动适配

板子能跑起来了,但还得能联网、能读写存储。不然你怎么烧写内核?

3.2.1 网络驱动适配

U-Boot的网络驱动模型是分层的。最底层是MAC/PHY驱动,中间是网络协议栈,上层是命令接口。我们主要适配底层。

以常见的FEC(Fast Ethernet Controller)为例:

// drivers/net/fec_mxc.c
static int fec_probe(struct udevice *dev)
{
    struct fec_priv *priv = dev_get_priv(dev);
    /* 获取设备树中的寄存器地址 */
    priv->base = dev_read_addr(dev);
    /* 复位PHY */
    fec_phy_reset(priv);
    /* 配置MAC地址 */
    fec_set_mac(priv, dev->enetaddr);
    return 0;
}

你想想看,网络驱动适配最头疼的是什么?是PHY芯片。不同PHY的寄存器布局不一样。我建议你在include/configs/yourboard.h里加上:

#define CONFIG_PHY_MICREL
#define CONFIG_FEC_MXC
#define CONFIG_MII
#define CONFIG_ETHPRIME "fec"

然后确保设备树里phy-handle指向正确的PHY节点。我曾经在一个项目里,PHY地址是0x01,但设备树里写成了0x00,结果网络死活不通。查了两天才发现。

3.2.2 存储驱动适配

存储驱动主要分两类:MMC/SD卡和NAND Flash。这里我重点讲MMC。

U-Boot的MMC驱动框架在drivers/mmc/下。以i.MX6的USDHC为例:

// drivers/mmc/fsl_esdhc_imx.c
static int fsl_esdhc_probe(struct udevice *dev)
{
    struct fsl_esdhc_priv *priv = dev_get_priv(dev);
    /* 获取时钟频率 */
    priv->sdhc_clk = mxc_get_clock(MXC_ESDHC_CLK);
    /* 初始化控制器 */
    fsl_esdhc_init(priv);
    return 0;
}

配置头文件里要打开:

#define CONFIG_MMC
#define CONFIG_FSL_ESDHC_IMX
#define CONFIG_SYS_FSL_ESDHC_ADDR 0x2190000

小技巧:如果你用的是eMMC,记得检查CONFIG_SYS_MMC_ENV_DEV和CONFIG_SYS_MMC_ENV_PART。我习惯把环境变量放在eMMC的boot分区,这样不容易被误删。

3.3 环境变量与启动参数配置

环境变量是U-Boot的灵魂。它决定了板子怎么启动、从哪里加载内核、传给内核什么参数。

3.3.1 环境变量存储布局

环境变量可以存在多个地方:SPI Flash、NAND、MMC、甚至网络。我一般用MMC,因为容量大、速度快。

存储介质 配置宏 偏移地址 大小
MMC/SD CONFIG_ENV_IS_IN_MMC CONFIG_ENV_OFFSET CONFIG_ENV_SIZE (通常8KB)
NAND Flash CONFIG_ENV_IS_IN_NAND CONFIG_ENV_OFFSET CONFIG_ENV_SIZE (通常16KB)
SPI Flash CONFIG_ENV_IS_IN_SPI_FLASH CONFIG_ENV_OFFSET CONFIG_ENV_SIZE (通常8KB)

3.3.2 关键环境变量详解

以下是我常用的环境变量模板:

#define CONFIG_EXTRA_ENV_SETTINGS \
    "bootargs=console=ttymxc0,115200 root=/dev/mmcblk1p2 rw rootwait\0" \
    "bootcmd=mmc dev 0; fatload mmc 0:1 0x12000000 zImage; " \
             "fatload mmc 0:1 0x18000000 imx6ull.dtb; " \
             "bootz 0x12000000 - 0x18000000\0" \
    "loadaddr=0x12000000\0" \
    "fdtaddr=0x18000000\0" \
    "mmcdev=0\0"

这里我解释几个关键点:

  • bootargs:传给内核的命令行参数。控制台、根文件系统位置都在这里。
  • bootcmd:自动启动时执行的命令序列。我习惯先加载设备树,再加载内核。
  • loadaddr/fdtaddr:内存加载地址。注意不要和U-Boot自身重叠。

我曾经在调试时把loadaddr设成了0x10000000,结果和U-Boot的代码段冲突,内核一启动就崩溃。后来我查了内存布局图,发现U-Boot在0x17800000之后才安全。所以一定要看你的板子内存映射。

3.3.3 启动参数配置策略

启动参数配置,说白了就是告诉内核:你的根文件系统在哪、用什么文件系统、控制台是哪个。我总结了一个检查清单:

  1. 控制台参数:console=ttymxc0,115200 —— 串口设备名和波特率
  2. 根文件系统:root=/dev/mmcblk1p2 —— 设备节点
  3. 文件系统类型:rootfstype=ext4 —— 可选,但建议指定
  4. 挂载选项:rw rootwait —— 读写挂载,等待设备就绪
  5. 调试选项:earlyprintk —— 早期调试用,量产时去掉

你想想看,如果根文件系统在NAND上,参数就变成root=/dev/mtdblock4。如果是网络根文件系统,那就是root=/dev/nfs nfsroot=...。不同场景参数差异很大。

3.4 本章知识体系

下面这张图总结了U-Boot移植的核心流程和依赖关系:

U-Boot移植实战知识体系 选择参考板 (defconfig) 创建板级文件 (board/ include/configs/) DDR/时钟初始化 网络驱动适配 存储驱动适配 编译 & 烧写 (make & dd/flash) 环境变量 & 启动参数配置 提示:每个环节都需对照芯片手册验证,DDR参数和PHY地址是常见坑点

3.5 常见问题与避坑指南

移植过程中,我遇到过不少问题。这里列几个典型的:

问题现象 可能原因 解决方案
U-Boot启动后无打印 串口引脚配置错误或波特率不匹配 检查设备树中uart引脚复用,确认波特率
网络ping不通 PHY地址错误或MDIO时序不对 用示波器量MDIO波形,核对PHY datasheet
MMC识别不到 时钟频率过高或电压不匹配 降低MMC时钟频率,检查电源域
环境变量保存失败 存储偏移地址与分区重叠 检查CONFIG_ENV_OFFSET是否在空闲区域

我的经验:移植时先别急着调网络和存储。先把串口打印调通,看到U-Boot的版本信息,心里就有底了。然后一个一个外设加,每次只改一个变量,出了问题好定位。

嗯,这一讲的内容就到这里。U-Boot移植是个细致活,但掌握了方法,其实套路很固定。多动手,多对照手册,你也能成为移植高手。