在 U-Boot 中,“文件已下载到 RAM”“内容校验通过”“已经写入存储”“下次可以启动”是四个不同结果。每个阶段都要使用对应对象的地址和长度,并检查返回状态。
本页按上游 U-Boot v2024.01 的命令行为整理。硬件分区、启动镜像格式和烧录偏移必须来自目标板的 BSP/ROM 文档;文中的 kernel_addr_r 等变量需先与实际内存布局核对。命令参数速查见U-Boot 命令参考。
1. 建立本次操作的对象清单
跳转到“1. 建立本次操作的对象清单”开始前记录 version、bdinfo、printenv bootcmd bootargs,再分别确认:
| 对象 | 必须知道的信息 |
|---|---|
| 内核 | zImage / Image / Legacy / FIT 等格式,加载和解压区,实际文件长度。 |
| DTB | 准确板型、加载地址、完整性,以及供修改使用的额外空间。 |
| initramfs | 有无镜像头,加载地址及长度;不能借用最后一次任意加载的 filesize。 |
| 存储目标 | 设备、eMMC硬件分区、磁盘/MTD分区、偏移、容量及擦除/块大小。 |
| 恢复入口 | 当前启动副本、配置备份、可用的 ROM 恢复或调试方法。 |
先将 autoload 和 autostart 配置为符合本次调试意图的值。试验性 setenv 不需要立刻 saveenv。不要把一块板的 0x80000000、MMC设备0、NAND分区名和SPI偏移组合成另一块板的更新脚本。
flowchart TD A[确认板型、镜像、目标范围] --> B[加载一个对象到RAM] B --> C{加载成功且长度合理?} C -- 否 --> X[停止该分支并保留原启动内容] C -- 是 --> D[立即保存该对象长度] D --> E[核对格式与可信来源] E --> F{临时启动或更新存储?} F -- 临时启动 --> G[加载并检查其余启动对象] G --> H[执行匹配镜像格式的启动命令] F -- 更新 --> I[检查目标容量、对齐和擦除边界] I --> J[写入、读回、比较] J --> K[启动验证后再切换持久配置]2. 先理解 filesize 的生命周期
跳转到“2. 先理解 filesize 的生命周期”成功的 load / fatload / ext4load、网络加载和部分导出命令会更新 filesize。它保存的是最近一次相关操作的长度,不是“内核长度”这一永久含义。
假设内核为 0x600000 字节,DTB为 0x10000 字节:
tftpboot ${kernel_addr_r} zImagesetenv kernel_size ${filesize}tftpboot ${fdt_addr_r} board.dtbsetenv fdt_size ${filesize}两个加载命令都成功时,最终 kernel_size=600000、fdt_size=10000,而 filesize=10000。此时若拿 ${filesize} 写内核,只会按 DTB 的长度处理内核。
上面只解释长度保存位置。实际脚本必须先判断加载成功,再保存长度;失败之后 RAM 和旧环境值可能仍在,不能继续使用。
3. 从文件系统临时启动 Linux
跳转到“3. 从文件系统临时启动 Linux”以下示例适用于已经确认支持 bootz 的 32 位 ARM zImage + DTB 流程。mmc 0:1 和 board.dtb 均须换成目标工程的准确值;不要用于 ARM64 Image。
if mmc dev 0; then if load mmc 0:1 ${kernel_addr_r} zImage; then setenv kernel_size ${filesize} if load mmc 0:1 ${fdt_addr_r} board.dtb; then setenv fdt_size ${filesize} if fdt addr ${fdt_addr_r}; then bootz ${kernel_addr_r} - ${fdt_addr_r} else echo Invalid-device-tree false fi else echo Device-tree-load-failed false fi else echo Kernel-load-failed false fielse echo MMC-selection-failed falsefi这个结构保证加载失败的分支不会继续到启动命令。它仍不负责证明板型正确、地址不重叠或镜像可信,这些属于对象清单中的前置条件。设备树能通过头部解析,也不等于它描述的外设和实际硬件一致。
内核命令行应来自对应板卡,例如根设备的 PARTUUID、控制台名称、文件系统类型和 rootwait。rootwait 让内核等待根设备出现,不会把未编入或未加载的驱动自动补齐。U-Boot 的网络设置也不会自动等同于 Linux 的网络配置。
带原始 initramfs
跳转到“带原始 initramfs”在内核和 DTB 之外,再成功加载 initramfs,立即保存 ramdisk_size。原始 initramfs通常以 地址:大小 传入:
bootz ${kernel_addr_r} ${ramdisk_addr_r}:${ramdisk_size} ${fdt_addr_r}Legacy/FIT封装的ramdisk有不同的解释路径,应按镜像格式处理,不能看到扩展名 .img 就确定是否需要显式长度。
4. 网络启动、NFS 与多设备回退
跳转到“4. 网络启动、NFS 与多设备回退”TFTP 的 serverip 是服务器,ipaddr 是板卡自身地址。直连网络先检查网卡、链路、IP和子网,再检查服务器导出目录及文件读取权限。ping 成功不代表 TFTP 成功;初始请求使用 UDP 69,后续数据传输还涉及协商端口,防火墙应按实际流量放行,不能仅凭“开了69”判断。
setenv autoload nosetenv autostart nosetenv ipaddr 192.168.1.10setenv serverip 192.168.1.100setenv netmask 255.255.255.0ping ${serverip}上述地址仅为一个隔离开发网的例子。随后把第3节的 load 换成相应 tftpboot,保留同样的成功判断和独立长度变量。不要在 TFTP 失败后擦除旧镜像。
NFS有两种用途:U-Boot 的 nfs 命令把文件装入RAM;Linux 的 root=/dev/nfs、nfsroot=…、ip=… 指示内核挂载网络根文件系统。后者需要内核的网络、网卡、NFS root支持和服务器导出配置,不能只增加一行启动参数就保证可用。
多设备回退应把“选择设备、加载内核、加载DTB、格式检查、启动”作为完整分支。某个介质只加载成功内核,不能拿上一个分支残留的DTB启动。支持 Standard Boot 的工程可使用 bootflow,已有 extlinux 配置可考虑 sysboot;先确认目标版本提供这些入口。
5. 从字节数换算块数
跳转到“5. 从字节数换算块数”mmc write 地址 起始块 块数 按块操作。设有效镜像长度为 字节,块长为 字节,需要写的块数为
其中 是最后一块需要补齐的字节数。在已经确认 的情况下:
setexpr blocks ${image_size} + 0x1ffsetexpr blocks ${blocks} / 0x200setexpr padded_size ${blocks} '*' 0x200setexpr padding ${padded_size} - ${image_size}例如 L=513=0x201:N=2,实际写入1024字节,尾部需补511字节。直接把 filesize=0x201 传给 mmc write 会请求513块,即262656字节,而不是513字节。
算出块数之后仍需验证:
- 镜像长度非零、加法和乘法没有超出当前命令字宽。
- 起始块与块数落在获准更新的完整范围内,且设备及eMMC硬件分区正确。
- 源RAM和读回RAM各能容纳
padded_size,彼此及其他运行区域不重叠。 - 最后一块的所有字节都属于目标镜像区域;不能为了补齐而覆盖其他文件或数据。
补齐尾部必须显式初始化,否则会把RAM中的不相关内容一起写入介质。对一个独占的原始镜像区域,可以在加载后的末尾补零;对文件系统文件,应使用 fatwrite / save 等文件接口,由文件系统处理分配,不自行算整卡块号。
原始写入步骤应逐步执行并检查状态:选择目标 → 填充尾部 → mmc write → mmc read 到独立缓冲区 → cmp.b 比较 padded_size。发现任何失败立即停止,不把最后打印一行“完成”当作结果。
6. SPI NOR、NAND 与 UBI 的更新边界
跳转到“6. SPI NOR、NAND 与 UBI 的更新边界”SPI NOR
跳转到“SPI NOR”先通过 sf probe 识别器件和擦除单元,再核对板级布局。sf erase 0 0x80000 不是通用的U-Boot预留区:有的设备需要SPL、ROM头、签名容器、冗余环境或多份引导镜像。
当偏移和长度都已经确定,sf update 可以比较已有内容并更新变更的擦除单元。它不是事务,也不自动保护最后一个擦除单元中镜像以外的数据。更新后应读回到另一块RAM,再比较准确长度;源镜像与目标Flash偏移不能凭常见数值推测。SPI Flash 命令
NAND
跳转到“NAND”NAND还需区分页、擦除块、OOB、ECC和坏块跳过规则。Boot ROM读取引导镜像的布局,可能与普通 nand write 数据路径不同。写入需要满足的是实际命令/控制器的页及ECC约束,不能笼统说所有数据长度必须按擦除块对齐。
保持出厂坏块信息;不要把 nand scrub 当作常规格式化。nand erase.part kernel 也只有在当前分区定义确实对应目标板布局时才有意义。备份和读回校验要采用与目标格式一致的方式。
UBI / UBIFS
跳转到“UBI / UBIFS”对已有的UBI卷,确认卷名和可用容量后可使用 ubi write 更新卷内容,UBI管理底层坏块。创建卷与分段更新是不同操作:
ubi part rootfsubi info layout检查这两个查询结果后,再决定是否需要创建或更新特定卷。ubi write.part 向已有卷分段写入,不会自动创建文件系统或卷。给UBIFS卷写 .ubi 容器,或把 .ubifs 当原始NAND整机镜像,都会混淆层次。
涉及断电恢复的系统应设计冗余镜像、受保护的恢复入口或A/B机制。单副本“先擦再写”没有自动回滚能力,命令执行成功也不等于断电过程安全。
7. 环境备份与脚本封装
跳转到“7. 环境备份与脚本封装”用真实导出长度备份
跳转到“用真实导出长度备份”先确认 loadaddr 对应专用缓冲区、环境导出能放入其中、目标文件系统支持写入,再执行:
if env export -t ${loadaddr}; then setenv env_backup_size ${filesize} fatwrite mmc 0:1 ${loadaddr} uboot-env.txt ${env_backup_size}else echo Environment-export-failed falsefi不能固定保存 0x1000 或 0x10000:前者可能截断,后者可能把无关RAM内容写进文件。恢复时也使用本次成功加载的准确长度,并先审阅其中的MAC、网络、启动和设备特有变量;导入后先临时验证,最后才 saveenv。
Legacy启动脚本
跳转到“Legacy启动脚本”在主机创建文本 boot.cmd,用对应工具封装,再在U-Boot中加载执行:
mkimage -A arm -O linux -T script -C none -n 'Board boot' -d boot.cmd boot.scrif load mmc 0:1 ${loadaddr} boot.scr; then source ${loadaddr}else echo Script-load-failed falsefi体系结构参数和内容应与工程一致。普通文本不能仅改名成 boot.scr;source 地址 arg1 arg2 也不是通用的参数传递语法。FIT脚本可携带签名,但是否真正强制验签仍取决于信任链配置。source 官方说明
启动次数与回退
跳转到“启动次数与回退”仅在 bootcmd 中对内存变量 bootcount 加一,不保证跨掉电保留计数。应核对 CONFIG_BOOTCOUNT_LIMIT、实际计数后端、bootlimit、altbootcmd,以及操作系统确认健康后如何复位计数。也不要在每次启动随意 saveenv,把计数功能变成无控制的Flash写入。
8. 校验、签名与启动验证分别证明什么
跳转到“8. 校验、签名与启动验证分别证明什么”| 检查 | 能回答的问题 | 不能单独证明 |
|---|---|---|
| 下载返回成功与长度 | 本次协议加载是否完成、收了多少字节 | 镜像适合该板或来自可信发布者 |
| CRC32 | 是否出现许多常见意外损坏 | 抵抗主动伪造 |
| SHA-256与可信预期摘要比较 | 字节是否与可信基准一致 | 预期摘要自身的来源可信性 |
| FIT强制签名验证 | 在正确配置和密钥保护下验证被签名对象 | 整条系统链路不可绕过或具备防回滚 |
| 读回比较 | 存储读回内容与预期字节一致 | ROM一定认得布局,内核一定能运行 |
| 实际重启及功能检查 | 本板当前条件下能启动并运行检查项 | 所有断电、温度或异常条件都已覆盖 |
不要编写仅含注释 # verify_signature ... 的脚本,却在其末尾打印“安全验证通过”。同样,crc32 打印摘要之后不会自然产生名为 crc32_result 的变量;校验值的获取、可信基准和比较都要真实实现。
9. 失败时沿着边界定位
跳转到“9. 失败时沿着边界定位”- 加载失败:设备选择、分区、路径、网络连通和权限;此时不擦除目标。
- 格式失败:确认所用启动命令与镜像封装匹配,检查是否截断、压缩格式不符或使用旧长度。
- 写入失败:设备容量、分区范围、块/页/擦除对齐、写保护、坏块/ECC、返回状态。
- 读回一致但无法启动:ROM头、引导偏移、入口、DTB板型、内核命令行、签名配置和地址覆盖。
- 一次成功但偶发失败:供电、介质、时序和断电恢复机制,需要硬件与系统层证据,不能只重复烧录。
本页的数值推导与软件检查不能代替目标板烧录验收。实际项目应保存版本、命令、返回结果和镜像摘要;从中区分“已核对的规则”“已在模拟环境运行的脚本”和“已在板上验证的行为”。
本次在本地编译的 U-Boot v2024.01 sandbox 中验证了块数取整、两次加载后的长度保存、环境导出/导入、Legacy 脚本封装及加载失败分支。失败分支测试用主机临时文件和状态标记代替 MMC 与 ARM 内核启动,因此只证明所用 Hush 控制结构,不证明实际板卡可启动。