从 eMMC 读取 U-Boot FIT
SPL 已经运行,并不代表后面的 U-Boot 一定能被读出来。下面这段实机日志停在 FIT 读取阶段:DRAM 初始化成功,SPL 也识别出启动设备,但读取 FIT 时立即失败。
U-Boot SPL 2026.07-dirty
DRAM: 4096 MiB
Trying to boot from MMC2
mmc_load_image_raw_sector: mmc block read error
Error: -38
SPL: Unsupported Boot Device!
这段日志先排除了 BootROM、eGON 头和 DRAM,问题范围只剩下两件事:SPL 正在读 eMMC 的哪一个硬件区域,以及 MMC2 是否采用了适合 T527 的控制器时序。
为什么同一个扇区会读到不同内容
eMMC 不只有通常用于分区和文件系统的 User Area,还带有 Boot0、Boot1 两个硬件启动区。它们各自从扇区 0 开始编号。
| 区域 | 本工程中的内容 | 谁在读取 |
|---|---|---|
| Boot0 | 一份带 eGON 头的 SPL 与 U-Boot 组合镜像 | T527 BootROM |
| Boot1 | 不作为系统数据区;这里只用来强制产生一次真实分区切换 | SPL |
| User Area | MBR、8 KiB 处的组合 U-Boot、FAT 启动分区、ext4 根分区 | SPL、U-Boot、Linux |
BootROM 从 Boot0 把 SPL 装进 SRAM 后,卡仍可能保持 Boot 分区访问状态。此时 SPL 即使计算出正确扇区,也可能在 Boot0 而不是 User Area 中读取它。
更隐蔽的是,U-Boot 的块设备描述符可能已经把 hwpart 缓存为 0。直接请求“切换到分区 0”会被判断为无需操作,真正的 eMMC 分区状态却没有改变。因此当前工程先切到 Boot1,再切回 User Area:
if (IS_ENABLED(CONFIG_MACH_SUN55I_A523) && IS_MMC(mmc)) {
ret = mmc_switch_part(mmc, EMMC_HWPART_BOOT1);
if (ret)
return ret;
ret = mmc_switch_part(mmc, EMMC_HWPART_DEFAULT);
if (ret)
return ret;
part = EMMC_HWPART_DEFAULT;
}
成功启动时,串口把这次状态变化完整打印了出来:
LYNX_MMC_FORCE_USER: mode=1 before_part_config=0x00 cached_hwpart=0
LYNX_MMC_FORCE_USER: boot1_ret=0 cached_hwpart=1
LYNX_MMC_FORCE_USER: user_ret=0 after_part_config=0x00 cached_hwpart=0
boot1_ret=0 与 user_ret=0 说明两次 CMD6 均成功;最后的 cached_hwpart=0 才表示后续裸读面向 User Area。LYNX_ 是本工程为了移植调试加入的日志前缀,并非 U-Boot 上游定义的接口。
FIT 为什么位于扇区 0x70
当前组合文件写在 User Area 的 8 KiB 位置。eGON 头记录的 SPL 对齐长度为 0xc000,eMMC 逻辑块大小为 512 字节,因此 FIT 的绝对位置为:
组合 U-Boot 起点 0x2000
SPL 对齐长度 + 0xc000
--------
FIT 字节偏移 0xe000
FIT 扇区号 0xe000 / 0x200 = 0x70
工程中的配置也参与了这个计算:
CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR=0x40
CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_DATA_PART_OFFSET=0x10
sunxi 平台代码根据 eGON 长度把基础值调整为 0x60,再叠加 User Area 的 0x10 扇区偏移,最终得到 0x70。实机日志与离线计算一致:
LYNX_MMC_DIAG: mode=1 part=0 base=0x60 offset=0x10 final=0x70
LYNX_MMC_DIAG: read sector=0x70 blksz=512
这两行很重要:如果以后 SPL 体积增长,base 会随 eGON 长度改变;教程不应把 0x70 当成所有构建都不变的常量。
配置 T527 MMC2 的传输参数
分区切换正确后,读数据仍可能出现 CRC 或超时。T527/A523 的 MMC2 是 timing-mode-4 控制器,而 U-Boot 原有的通用 sunxi 路径会启用 2x 时钟模式,没有设置这代控制器需要的驱动延时、采样延时和阈值寄存器。
当前 drivers/mmc/sunxi_mmc.c 为 MMC2 增加了三层处理:
- 关闭通用的 2x 模式,按 Tina5 的 TM4 驱动设置
drv_dl、samp_dl、timeout、thldc、csdc和dbgc; - 在 SPL 与 U-Boot proper 两条初始化路径中都把最高时钟限制为 25 MHz;
- 使用当前工程已经实测稳定的 4-bit 总线参数。
这里参考 Tina5 的是 MMC 控制器寄存器配置方法,最终运行的仍然是主线 U-Boot。厂商源码在这一阶段的价值,是补充数据手册没有公开完整描述的控制器时序,而不是把厂商 Boot0 混进主线启动链。
U-Boot 自己使用的设备树也要保持一致:
&mmc2 {
bus-width = <4>;
cap-mmc-hw-reset;
max-frequency = <25000000>;
non-removable;
vmmc-supply = <®_cldo3>;
vqmmc-supply = <®_cldo1>;
};
只改 DTS 不够,因为 SPL 使用的是精简的早期 MMC 初始化路径;只改 SPL 也不够,因为 U-Boot proper 随后还要重新初始化控制器并从 FAT 读取约 49 MiB 的 Linux Image。两条路径必须采用同一组保守参数。
确认 SPL 已经读取并解析 FIT
本次实机启动出现了下面的连续证据:
LYNX_MMC_SAFE_IO: mmc=2 f_max=25000000 b_max=128
LYNX_MMC_FORCE_USER: boot1_ret=0 cached_hwpart=1
LYNX_MMC_FORCE_USER: user_ret=0 after_part_config=0x00 cached_hwpart=0
LYNX_MMC_DIAG: mode=1 part=0 base=0x60 offset=0x10 final=0x70
LYNX_MMC_DIAG: read sector=0x70 blksz=512
NOTICE: BL31: v2.12.0(debug):b5de74a68
最后一行已经进入 TF-A,说明 SPL 不只是“发起了读取”,而是成功解析 FIT、装载 BL31 并完成跳转。这就是本节需要确认的结果。
完整 U-Boot 修改可下载为 Avaota A1 eMMC 启动补丁。补丁基于 U-Boot v2026.07,应用前应先确认基点提交,不能直接套到其他版本。
下一节继续处理 U-Boot proper 的运行位置,以及加载大体积 Linux Image 时为什么还要限制单次 MMC 请求。