三个阶段概述
IMX6ULL运行Linux系统的Bootloader过程通常分为三个部分:
1️⃣ 处理器内部 ROM 代码(BootROM)
- 这部分代码存储在 SoC 的只读存储器(ROM) 中,由芯片厂商在生产时烧录,无法修改。
- 主要作用:
- 读取 eMMC、SPI Flash、SD 卡、NAND Flash 等存储设备的 引导扇区。
- 加载并执行 SPL(或 TPL)。
- 如果没有 SPL,它可能直接加载完整的 Bootloader(如 U-Boot)。
📌 在 i.MX6ULL 中,BootROM 负责加载 SPL(通常从 eMMC 的 0x400(即1KB) 偏移处开始读取SPL)。
2️⃣ SPL(Secondary Program Loader)
- SPL 是一个精简版的 Bootloader,主要任务是初始化 DDR(RAM),然后加载完整的 U-Boot。
- SPL 代码精简,因为它通常运行在片上 SRAM(OCRAM)中,而 SRAM 只有几十 KB,无法容纳完整的 U-Boot。
- 在 i.MX6ULL 上,SPL 可能会:
- 初始化时钟、电源管理。
- 初始化 eMMC/SD/NAND 以便加载完整的 U-Boot。
- 把 U-Boot 从 eMMC 0x10000(64KB 处)加载到 DDR 并跳转执行。
📌 在 i.MX6ULL 上,SPL 由 u-boot-spl.bin
生成,并包含在 u-boot.imx
里。
3️⃣ U-Boot(完整 Bootloader)
- U-Boot 是真正的 Bootloader,它运行在 DDR 内存中,提供完整的功能:
- 初始化更多外设(串口、网卡、USB 等)。
- 解析设备树(FDT)。
- 读取内核镜像(zImage、Image)和 DTB 文件。
- 加载 Linux 内核,并跳转执行。
📌 在 i.MX6ULL 上,U-Boot 通常存储在 eMMC 0x10000(64KB 处)并由 SPL 加载执行。
完整的启动流程
1. BootROM(ROM 代码)
└── 读取 eMMC/SD/NAND,引导 SPL
2. SPL(运行在 OCRAM)
└── 初始化 RAM,加载 U-Boot
3. U-Boot(运行在 DDR)
└── 读取 zImage/DTB,加载 Linux
小结
✅ 可以把 IMX6ULL运行Linux 的 Bootloader 分为三个部分:
- BootROM(处理器内部 ROM)—— 负责加载 SPL。
- SPL(Secondary Program Loader)—— 负责初始化 RAM,并加载 U-Boot。
- U-Boot(完整 Bootloader)—— 负责加载 Linux 内核并启动系统。
🎯 在 i.MX6ULL 平台上,BootROM 先从 eMMC 0x400(1KB) 处加载 SPL,SPL 再加载 U-Boot(0x10000 处,即64KB 处),U-Boot 最终加载 Linux 内核。
SPL和U-Boot在镜像文件中是如何存储的?如何保证二者写到eMMC中后,是如何分别存储到指定的位置的?
从上面的叙述中,我们知道对于u-boot的源码而言,当执行下面的命令:
make -j4
后,得到了镜像文件u-boot-dtb.imx
,从Bootloader的三个阶段来讲,这个镜像文件中包含着后两个阶段——SPL和U-Boot的代码。
SPL对应的二进制文件通常为u-boot-spl.bin
,路径为spl/u-boot-spl.bin
;
U-Boot对应的二进制文件通常为u-boot.bin
。
也就是说镜像文件u-boot-dtb.imx
是由二进制文件u-boot-spl.bin
和二进制文件u-boot.bin
合并而成的。
那么现在问题来了,当我们用烧写工具把镜像文件u-boot-dtb.imx
烧写到eMMC时,按道理来说二者应该是连续存放的,因为它们拼接形成了镜像文件u-boot-dtb.imx
呀,但事实上我们却发现二者并没有连续存放,SPL在eMMC中的起始位置是0x400(即1KB) 处,而U-Boot在eMMC中的起始位置是在0x10000(即64KB)处,二者中间还隔了一段存储空间,那这是怎么回事呢?
答案很简单:
在连接它们形成镜像文件的时候,中间是填充了占位数据的,占位数据通常是 0x00 或 0xFF。
具体说明如下:
1. 为什么会有填充数据?
在编译 u-boot.imx
时,SPL 和 U-Boot 的存放地址是预先确定的,比如:
- SPL 从 0x400(1KB) 处开始存放。
- U-Boot 需要存放在 0x10000(64KB) 处。
但 SPL 的大小通常不会刚好填满 0x400 ~ 0xFFFF 这段区域,而 U-Boot 也不能直接紧挨着 SPL 放置,因为启动流程会按照 固定偏移地址 读取 U-Boot。因此,编译时会自动在 u-boot.imx
里插入填充数据,使 U-Boot 仍然对齐到 0x10000 处。
2. 填充数据是如何插入的?
填充数据是在镜像打包阶段加入的,U-Boot 的 imx-mkimage
工具或者 binman
工具会:
- 计算 SPL 的大小
- 计算 U-Boot 需要放置的偏移
- 在两者之间插入
0x00
或0xFF
作为填充
如果你用 hexdump
查看 u-boot.imx
,你会看到:
hexdump -C u-boot.imx | less
可能的输出:
00000400 xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx |XXXXXXXXXXXXXXXX|
...
0000ff00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00010000 yy yy yy yy yy yy yy yy yy yy yy yy yy yy yy yy |YYYYYYYYYYYYYYYY|
xx xx xx xx
→ SPL 代码00 00 00 00
→ 填充数据yy yy yy yy
→ U-Boot 代码
3. 烧录时,这些填充数据也会被写入 eMMC 吗?
是的,烧录会原封不动地写入 u-boot.imx
的所有内容,包括这些填充数据。例如下面这条在Linux下的烧录命令(dd命令):
dd if=u-boot.imx of=/dev/mmcblk0 bs=1K seek=1
运等,eMMC 里会按顺序存放:
eMMC 物理地址:
+------------+------------+---------------------+------------+
| 0x00400 | SPL | (填充区 0x00) | U-Boot |
| (1KB) | (~30KB) | (占位到 64KB) | (完整) |
+------------+------------+---------------------+------------+
这些填充数据对 eMMC 运行没有影响,因为 SPL 只会去 0x10000 处加载 U-Boot,而不会读取填充区。
4. 结论
✅ 如果 SPL 和 U-Boot 之间有空白区域,u-boot.imx
镜像文件会用 0x00 或 0xFF 进行填充,确保 U-Boot 位于正确偏移地址。
✅ 使用 dd
烧录时,填充数据也会被写入 eMMC,但不会影响运行。
✅ 如果手动烧录 SPL 和 U-Boot,可以省略填充数据,直接写入相应偏移地址。