3. U-Boot 启动与调试:从源码到板级实战

U-Boot 在 Jacinto 平台上的角色,说白了就是硬件的「初始化管家」和内核的「搬运工」。很多刚接触 BSP 的工程师容易把 U-Boot 当成黑盒,出了问题只会反复烧写。其实把源码结构和启动流程理清楚,大部分启动故障都能自己定位。

我个人习惯把 U-Boot 调试分成三个层次:源码结构理解、板级配置适配、启动流程跟踪。下面我们一层层拆开讲。

3.1 U-Boot 源码结构:别被目录吓到

第一次解压 U-Boot 源码,看到几十个目录确实头大。但 Jacinto 平台相关的代码其实集中在几个关键位置:

目录/文件 作用 Jacinto 关注点
arch/arm/mach-omap2/ SoC 级初始化代码 包含 AM65x / J721E 的时钟、DDR、PLL 配置
board/ti/j721e/ 板级特定代码 板级 GPIO、电源时序、EEPROM 读取
include/configs/j721e_evm.h 板级配置头文件 内存映射、环境变量、外设使能开关
drivers/ 外设驱动 MMC、NAND、以太网、USB 等驱动
common/ 通用命令和框架 md/mw/bdinfo 等命令实现
我的习惯:拿到一个新板子,先看 board/ti/ 下对应目录的 board.c 和 Kconfig。这里藏着板级初始化的「第一手线索」。

3.2 Jacinto 平台板级配置:从 defconfig 到实际运行

配置 U-Boot 不是直接改源码里的宏定义那么简单。现代 U-Boot 使用 Kconfig 机制,配置流程是:

  1. 选择 defconfig:make j721e_evm_defconfig 生成 .config
  2. 调整配置:make menuconfig 可视化修改
  3. 编译:make CROSS_COMPILE=aarch64-linux-gnu-

这里有个容易踩的坑——环境变量默认值。我曾经在调试一块 J721E 板子时,网络启动总是超时,查了两天才发现是 CONFIG_BOOTARGS 里控制台参数写错了。嗯,这种问题用 bdinfo 命令一眼就能看出来。

避坑指南:修改板级配置后,一定要 make clean 再编译。我遇到过好几次因为增量编译导致配置没生效的「灵异事件」。

3.3 U-Boot 启动流程分析:三段式跳转

Jacinto 平台的 U-Boot 启动分为三个阶段,我画了张图帮你建立整体认知:

U-Boot 启动流程(Jacinto 平台) SPL (第一阶段) ROM -> SRAM 初始化时钟/DDR/存储 U-Boot (第二阶段) DDR 中运行 完整驱动/命令/环境变量 内核启动 booti/bootm 设备树传递 关键流程说明: 1. ROM 加载 SPL 到 SRAM(约 128KB 限制) 2. SPL 初始化 DDR 后,将 U-Boot 主体从存储加载到 DDR 3. U-Boot 解析环境变量,执行 bootcmd,加载内核和设备树 调试切入点: • SPL 阶段:串口输出是否正常?DDR 初始化是否通过? • U-Boot 阶段:bdinfo 检查内存布局,md 验证数据完整性 • 内核加载:bootargs 是否正确?设备树地址是否匹配?

三个阶段各有各的调试重点。SPL 阶段资源受限,调试手段有限,主要靠串口打印。U-Boot 阶段就灵活多了,各种命令都能用。

3.4 常用调试命令:三板斧解决 80% 问题

调试 U-Boot 不需要记住所有命令,把下面三个用熟就够了:

3.4.1 md(内存显示)

查看指定内存地址的内容,我最常用的场景是检查设备树是否加载正确:

=> md 0x82000000 20
82000000: 12345678 9abcdef0 11223344 55667788  ...4...."3DUfw.
82000010: aabbccdd eeff0011 22334455 66778899  ........"3DUfw.
...
小技巧:用 md.b 按字节显示,md.w 按半字显示,md.l 按字显示。检查设备树魔数(0xd00dfeed)时,我习惯用 md.l 0x82000000 1 快速确认。

3.4.2 mw(内存写入)

调试时经常需要手动修改内存,比如临时改个内核参数:

=> mw.l 0x82000000 0xdeadbeef 4
=> md.l 0x82000000 4
82000000: deadbeef deadbeef deadbeef deadbeef  ........
注意:mw 操作的是物理内存,写错地址可能导致系统崩溃。我曾在调试时把 U-Boot 自己的代码段覆盖了,结果板子直接死机——只能重新上电。

3.4.3 bdinfo(板级信息)

这个命令是我调试时的「第一站」。它能显示当前板级的所有关键参数:

=> bdinfo
arch_number = 0x00000000
boot_params = 0x80000100
DRAM bank   = 0x00000000
-> start    = 0x80000000
-> size     = 0x80000000
ethaddr     = 70:ff:76:1a:2b:3c
ip_addr     = 192.168.1.100
baudrate    = 115200 bps
TLB addr    = 0xffff0000
relocaddr   = 0x9ff70000
reloc off   = 0x1ff70000
...

为什么我强调这个命令?因为它能快速验证 DDR 初始化是否正确、网络配置是否生效。有一次我发现 DRAM bank size 只有 256MB,但板子明明有 2GB——后来查出是 SPL 阶段的 DDR 配置表选错了。

3.5 网络启动与 NFS 挂载:开发效率翻倍

做 BSP 开发,频繁烧写 SD 卡或 eMMC 太浪费时间。网络启动是必备技能。

3.5.1 网络启动配置

在 U-Boot 中设置网络参数:

=> setenv ipaddr 192.168.1.100
=> setenv serverip 192.168.1.10
=> setenv netmask 255.255.255.0
=> setenv gatewayip 192.168.1.1
=> saveenv

然后从 TFTP 服务器加载内核和设备树:

=> tftp 0x82000000 Image
=> tftp 0x83000000 k3-j721e-common-proc-board.dtb
=> booti 0x82000000 - 0x83000000

3.5.2 NFS 根文件系统挂载

调试驱动时,频繁更新文件系统很痛苦。NFS 挂载可以让你在主机上修改,板子上实时生效:

=> setenv bootargs console=ttyS2,115200n8 root=/dev/nfs 
   nfsroot=192.168.1.10:/srv/nfs/rootfs,v3,tcp 
   ip=192.168.1.100:192.168.1.10:192.168.1.1:255.255.255.0::eth0:off
=> booti 0x82000000 - 0x83000000

实战经验:NFS 挂载失败 90% 是网络不通或权限问题。我的排查步骤:

  1. 先 ping serverip 确认二层连通性
  2. 在主机上 exportfs -v 确认 NFS 导出目录
  3. 检查主机防火墙(尤其 Ubuntu 的 ufw)

3.6 常见启动故障定位

最后分享几个我实际遇到过的故障案例:

现象 可能原因 排查命令
SPL 输出后无反应 DDR 初始化失败 检查串口日志中的 DDR 校准信息
U-Boot 启动到一半重启 环境变量损坏 env default -a 恢复默认
网络启动超时 PHY 未初始化或 IP 冲突 md 0x46000000 10 检查 PHY 寄存器
内核崩溃 设备树与内核不匹配 fdt addr 0x83000000; fdt list

调试 U-Boot 其实没那么玄乎。把源码结构理清楚,把启动流程走一遍,再配合那三个命令,大部分问题都能自己搞定。下次遇到板子起不来,别急着重新烧写——先接上串口,看看 U-Boot 到底卡在哪一步。