4. U-Boot 编译与配置:从源码到烧录的完整链路

U-Boot 是嵌入式系统的“第一道门”。说白了,它负责把硬件初始化好,然后把内核加载进内存。在 Rockchip 平台上,U-Boot 的配置和编译有一套固定的套路。我刚开始接触 RK 平台时,也被那堆 defconfig 文件和 dts 搞得晕头转向。后来做多了,发现其实就那么几步。

4.1 获取 U-Boot 源码

Rockchip 官方维护了一个 U-Boot 分支,里面集成了对自家芯片的支持。我个人习惯直接从 GitHub 拉取:

git clone https://github.com/rockchip-linux/u-boot.git
cd u-boot
git checkout -b my_rk3588_branch origin/next-dev

这里要注意,next-dev 分支通常是最新的开发分支,包含了最新的 SoC 支持。如果你做量产项目,我建议切到某个稳定 tag,比如 rk3588-2023.07 之类的。我在项目中吃过亏,用了一个未稳定分支,结果某个驱动在量产时出了诡异问题,查了两天才发现是 U-Boot 的 bug。

小提示: 拉完代码后,先看看 configs/ 目录下有没有你板子的 defconfig 文件。比如 rockchip_rk3588_evb_defconfig。如果没有,就得自己配了。

4.2 板级配置:找到你的 defconfig

每个 Rockchip 芯片都有对应的参考板(EVB)。厂商会提供一套默认配置。你拿到自己的板子后,通常是在参考板基础上改。

查看支持的板级配置:

ls configs/ | grep rk3588

输出大概长这样:

rockchip_rk3588_evb_defconfig
rockchip_rk3588s_evb_defconfig
rockchip_rk3588_evb7_defconfig

选一个最接近你硬件的配置。比如我手头是 RK3588 EVB,那就用 rockchip_rk3588_evb_defconfig。

配置命令很简单:

make rockchip_rk3588_evb_defconfig

这条命令会把默认配置写入 .config 文件。你可以用 cat .config | grep -i "CONFIG_BAUDRATE" 看看串口波特率是不是 1500000,Rockchip 默认喜欢用这个速率。

注意: 如果你的板子 DDR 型号、PMIC 或时钟方案和 EVB 不同,直接烧录默认配置大概率起不来。我曾经遇到过 DDR 初始化失败,板子直接变砖,最后用 MaskROM 模式才救回来。

4.3 menuconfig:按需裁剪

默认配置通常包含很多你用不到的功能。比如你不需要网络启动,就可以关掉网络协议栈。我个人习惯用 menuconfig 做可视化裁剪:

make menuconfig

界面里常用的配置项:

配置项 说明 建议
CONFIG_BAUDRATE 串口波特率 保持 1500000 或改为 115200
CONFIG_BOOTDELAY 启动倒计时(秒) 量产设为 0,开发设为 3
CONFIG_BOOTCOMMAND 默认启动命令 根据你的分区表填写
CONFIG_CMD_NET 网络命令支持 不需要就关掉,省空间
CONFIG_CMD_USB USB 命令支持 量产可关

举个例子,我想让板子从 eMMC 启动内核,可以这样设置 CONFIG_BOOTCOMMAND:

setenv bootargs 'console=ttyS2,1500000 root=/dev/mmcblk0p5 rootwait'
setenv bootcmd 'ext4load mmc 0:4 0x4000000 /boot/Image; ext4load mmc 0:4 0x6000000 /boot/rk3588-evb.dtb; booti 0x4000000 - 0x6000000'
saveenv

嗯,这里要注意,booti 是启动 ARM64 内核的命令,32 位用 bootz。别搞混了。

核心经验: 裁剪 U-Boot 时,不要盲目关掉你不认识的选项。我有一回关掉了 CONFIG_DM(驱动模型),结果所有外设都挂了。建议先备份 .config,改一步编译一次,确保没坏。

4.4 编译:一步到位

配置搞定后,编译就简单了。Rockchip 平台通常用 ARM64 交叉编译器:

export CROSS_COMPILE=aarch64-linux-gnu-
export ARCH=arm64
make -j$(nproc)

编译产物在 u-boot.bin 和 u-boot.dtb。但 Rockchip 的启动流程比较特殊,它需要把 U-Boot 打包成 idbloader.img 和 u-boot.itb。别急,后面会讲。

如果你改了 dts 文件,记得先编译设备树:

make dtbs

然后重新编译 U-Boot:

make -j$(nproc)

我习惯写个简单的脚本,一键完成:

#!/bin/bash
export CROSS_COMPILE=aarch64-linux-gnu-
export ARCH=arm64
make distclean
make rockchip_rk3588_evb_defconfig
make menuconfig
make -j$(nproc) 2>&1 | tee build.log
echo "Build done. Check build.log for errors."
警告: 编译时如果报 undefined reference to `__aeabi_*',说明你的交叉编译器是 32 位的。Rockchip 平台必须用 aarch64 编译器。我刚开始就犯过这个错,折腾了半天才发现工具链不对。

4.5 烧录:让板子跑起来

编译出来的 u-boot.bin 不能直接烧。Rockchip 的启动 ROM 会先加载 idbloader.img(包含 DDR 初始化代码和 TPL/SPL),然后加载 u-boot.itb(包含 U-Boot 本身和 ATF)。

生成 idbloader.img:

cd u-boot
./tools/mkimage -n rk3588 -T rksd -d tpl/u-boot-tpl.bin idbloader.img
cat spl/u-boot-spl.bin >> idbloader.img

生成 u-boot.itb:

make u-boot.itb

烧录到 SD 卡(开发阶段常用):

sudo dd if=idbloader.img of=/dev/sdX seek=64
sudo dd if=u-boot.itb of=/dev/sdX seek=16384
sync

烧录到 eMMC(量产):

# 在 U-Boot 命令行中
ums 0 mmc 0
# 在 PC 端
sudo dd if=idbloader.img of=/dev/sdX seek=64
sudo dd if=u-boot.itb of=/dev/sdX seek=16384

这里的 seek 值很关键。偏移 64 扇区(32KB)是给 idbloader 的,偏移 16384 扇区(8MB)是给 u-boot.itb 的。不同芯片可能不同,RK3588 就是这样。

调试技巧: 如果板子起不来,串口打印停在 DDR 初始化阶段,多半是 idbloader 有问题。如果停在 Loading U-Boot,那就是 u-boot.itb 的问题。我一般会在 u-boot.itb 里打开 CONFIG_DEBUG_UART,这样能看到更详细的输出。

4.6 知识体系总览

下面这张图总结了 U-Boot 从源码到烧录的完整流程。你可以把它当作一个检查清单:

U-Boot 编译与配置流程 1. 获取源码 2. 板级配置 (defconfig) 3. menuconfig 裁剪 4. 编译 (make) 5. 打包 (idbloader + itb) 6. 烧录 (dd / rkdeveloptool) 关键偏移:idbloader → 扇区 64 (32KB) | u-boot.itb → 扇区 16384 (8MB)

4.7 常见问题与避坑

  • 编译报错: 检查交叉编译器版本。我推荐用 gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu,太新或太旧都可能出问题。
  • 烧录后板子没反应: 先确认串口接线是否正确。Rockchip 的调试串口通常是 UART2,引脚是 TX/RX/GND,不要接 VCC。
  • DDR 初始化失败: 检查 idbloader.img 是否包含了正确的 TPL。不同 DDR 型号(比如 LPDDR4 vs LPDDR5)需要不同的初始化代码。
  • U-Boot 启动后找不到内核: 检查 bootcmd 里的分区号和文件路径。我习惯在 U-Boot 命令行里先用 mmc part 查看分区表,确认无误再设 bootcmd。
一句话总结: U-Boot 的配置和编译,本质上就是“选对 defconfig → 按需裁剪 → 编译 → 打包 → 烧录”。每一步都有坑,但只要你理解了 Rockchip 的启动流程,这些坑都能避开。