板子上装了 SWUpdate,第一个包丢上去就报 magic not found。
包是按文档打的,工具也是开源的,两边看着都没错。
前阵子拿一块 i.MX6ULL 板子,把这条路从体检走到装完:
一、先弄清楚它管的是哪一段
MCU 上的升级很简单:一个 bin,签名,写进 flash,收工。
到了嵌入式 Linux,要升级的东西一下变多:内核、设备树、根文件系统,还有上面的应用。
谁先写、写失败怎么退、升级到一半断电算谁的。这些在 MCU 上可以不管,在 Linux 上必须有人管。
SWUpdate 就是"管"这件事的角色:把一次升级做成一个事务,多个镜像一起装,装不完整就别启动新的。
一个 .swu 里装了什么
它的升级包是 .swu,本质是一段 cpio 归档:头部 + 文件 + 结束标记。
里面的灵魂是 sw-description,一份升级说明书,必须排在包里第一个位置:
software =
{
version = "1.0.0";
/* 启动时用 -e rootfs,rootfs-1 选镜像集时,SWUpdate 找的是
software.rootfs.rootfs-1.images 这一条路径 */
rootfs = {
rootfs-1 = {
images: (
{
filename = "demo.txt"; /* 升级包里的文件名 */
device = "/tmp/ota-demo.txt"; /* 写到哪;演示用普通文件代替分区 */
/* sha256 = "..."; 这版固件没编哈希校验,写上反而直接报错 */
}
);
};
};
}
三个地方是重点:filename 是包里的文件名,device 是写到哪。
images 上面那两层(这里是 rootfs 和 rootfs-1)不是随便起的,第三节就栽在这上面。
写哪、怎么写,最终由 handler 决定。这块板子上注册的 handler 只有四个:
$ swupdate --version 2>&1
Swupdate v2019.11.0
$ swupdate -c -e rootfs,rootfs-1 -i /tmp/demo.swu 2>&1
Registered handlers:
dummy
raw
rawfile
rawcopy
四个都是 raw 系,够用。但也说明签名校验那套东西根本没编进来,第四节回到这一点。
二、坑 1:包得是 070702 那种 cpio
先看这次是怎么翻车的。包打好了,按最规范的姿势丢上去校验:
magic not found
CPIO Format not recognized: magic not found,连包都没被当成包。
原因在源码里只有一行:v2019.11 的 get_cpiohdr()(core/cpio_utils.c)只认 070702 那个 cpio 头。
而 cpio -H newc 生成的是 070701,两者差的就是"带不带校验和"这一点点东西。
当时的第一反应是打包脚本写错了,一个字节都没往包里改:
$ od -c -N 6 pkg.swu
0000000 0 7 0 7 0 2
看到 070701,就别再盯着 sw-description 改了,问题不在这儿。
三、坑 2:层级写在哪,由 -e 决定
包格式对了,还会遇到第二个"明明写了却找不到"。当时也以为是说明书括号写错了,其实不是。
SWUpdate 解析时,按四条优先级依次找 images、scripts 这些节点:
| 顺序 | 找的路径 |
|---|---|
| 1 | software.<board>.<selection>.<mode>.images |
| 2 | software.<selection>.<mode>.images |
| 3 | software.<board>.images |
| 4 | software.images |
<selection> 和 <mode> 就是命令行上的 -e <software>,<mode>。
这块板的 init 脚本给的就是它,它是在判断完当前槽之后才拼出来的:
S98swupdate 里的槽判断
脚本先读 /proc/cmdline 看现在跑在哪个分区,再用 -e rootfs,${ROOTFS} 告诉 SWUpdate 该装哪一层。
于是同一个包,带不带 -e,结果差得远:
不带 -e 与写了 sha256 的两种报错
不带 -e 时,四条优先级只剩第 4 条(software.images),说明书里那两层就白写了。
报的是 Found nothing to install,后面还跟着 JSON File corrupted 和 no parser available,容易把人带偏。
那是它找不到节点之后,回落到 JSON 解析器又失败了,不是你的包坏在 JSON 上。
同一个坑还有第二种表现形式。web 上传或后台模式下,启动参数没带 -e,上传的包会被判成 Image invalid or corrupted。
包没问题,是选层的那把钥匙没给。
四、坑 3:能校验到什么程度,是编译期决定的
顺手把哈希校验加上:
{ filename = "demo.txt"; device = "/tmp/ota-demo.txt"; sha256 = "bc7993f5..."; }
结果是解析阶段就挂了:hash verification not enabled but hash supplied for demo.txt。
意思是这份二进制没有编哈希校验,写 sha256 它反而当成错误。
去二进制里查一遍,能看出更多东西:
$ strings /usr/bin/swupdate | grep -iE 'signature|verif|sha256|rsa|cms|x509' | sort -u
ERROR : Checksum verification failed for %s: %x != %x
VERIFIED
sha256
有 sha256 和 VERIFIED,但没有任何 RSA、CMS、X.509 的影子。
也就是说,这块板子上的 SWUpdate:哈希校验没开,签名校验更没编。
fw_printenv 和 /etc/fw_env.config 同样不存在,通过 U-Boot 环境变量做"事务标记"那条路,在这个镜像里也是断的。
这三件事合起来就是一句提醒:校验能力不是"装上就有的"。
它是编译期一个个开关,配错了只会在解析或写入的时候,以奇怪的方式报出来。
五、在一块真板子上走一遍
1)先体检
板子上的 swupdate 是 247KB 的一个二进制,版本 v2019.11.0,2020 年 11 月编的:
板子环境
根文件系统是 Buildroot 2020.02,内核 4.9.88。整块 eMMC 3.6GB 左右,分成三个分区:
分区与命令行
p1 和 p2 各 1GB,p3 是 10MB 的小分区。
但 blkid 只认到 mmcblk1p2 有文件系统,另一个 1GB 的槽是空的,这个事实后面还会用到。
2)打包
PC 侧一个小脚本就能出包,关键是 cpio 头用哪一位,以及 sw-description 排在第一个:
PC 侧打包与包内结构
整包 992 字节,1KB 都不到:说明书 0.6KB,载荷 29 字节,再加一个结束标记。
3)只读校验
不确定包对不对,先用 -c 过一遍,它只读包、不碰设备:
校验通过
4)真装一次
把 device 指向一个普通文件,装完再读出来看看:
安装成功
有两处细节值得记下来。目标文件必须事先存在,raw handler 不会替你创建它:
raw handler 打不开目标
还有 /etc/swupdate/swupdate.cfg 里配了 postupdatecmd。
测试时用 -f 换一份不含它的配置,就不会在中途触发"置标记 + 重启"整套动作。
同一个图里还有两种失败。
把包里的字节改掉但校验和保持原样,报 Checksum verification failed for demo.txt: 9c9 != d95。
说明书里写了 demo.txt 却没放进包里,报 Requested file not found in image。
这两种都是"故意让它坏"。只有坏得符合预期,才说明校验链路在工作。
5)从网页上传
板子上的 SWUpdate 编了 mongoose web server,起一个上传口就能用,PC 侧 POST 一个包过去:
web 上传升级
接口返回 Ok, demo.swu - 992 bytes.,板子那边紧接着开始装。
命令里等 2 秒再去看日志,这一遍早就装完了。
日志是 SWUPDATE started → Installation in progress → SWUPDATE successful。
6)两个小数字,一个提醒
顺手测了一下耗时:校验这个 992 字节的包,real 0m0.055s;真装一遍,real 0m0.070s,两个动作都在 0.1 秒以内。
别被 0.07 秒骗了,它写下去的只有 29 字节。
生产上换成一个 1GB 的 rootfs,考验的是 eMMC 写入速度和流式处理,是另一回事。
用一个小包把链路跑通,再用真实尺寸的镜像压一遍,这两步不能省。
六、这块板的 A/B 是怎么串起来的
把上面这些碎片拼起来,整条链路:
A/B 升级链路
U-Boot 传 root=/dev/mmcblk1p2 启动,init 脚本据此判断当前跑在哪个槽,选好镜像集后拉起 SWUpdate。
装完,postupdate.sh 挂上下一个槽、在它根目录里放一个 update-ok 标记,然后重启。
下次开机的第一件事,就是看这个标记:存在,就往服务器回 -c 2(确认成功),再把标记删掉。
这就是 A/B 升级的标准形状。
七、排查表
包和说明书相关
| 现象 | 大概率原因 | 怎么定位 |
|---|---|---|
CPIO Format not recognized: magic not found |
包不是 070702 那种 cpio |
od -c -N 6 pkg.swu 看前 6 字节 |
Found nothing to install |
-e 选的层级和 sw-description 对不上 |
看服务是怎么起的,或加 -e 再试 |
Image invalid or corrupted(web/后台模式) |
服务启动参数没带 -e |
把 -e rootfs,rootfs-x 加到启动参数里 |
hash verification not enabled but hash supplied |
编译时没开哈希校验,说明书却写了 sha256 |
去掉 sha256,或重新编译固件 |
安装和切槽相关
| 现象 | 大概率原因 | 怎么定位 |
|---|---|---|
Checksum verification failed for xxx: 9c9 != d95 |
包里的字节和头里的校验和对不上 | 重打一次包,再对比声明值 |
Requested file not found in image |
说明书里写了,包里没放 | 重新打包并核对文件清单 |
Device ... cannot be opened |
raw handler 不会创建目标文件/设备 | 先确认目标分区或文件存在 |
| 装完重启,版本没变 | 槽没切、标记位置不对、确认后仍指向老分区 | 查 /update-ok、/proc/cmdline、收尾脚本 |
八、总结
SWUpdate 真正值钱的地方不是"能升级",而是把一次多镜像安装做成了一个事务。
要么整包落地,要么别启动新的。自己手搓升级脚本,九成的坑都埋在这上面;真正的成本也不在打包脚本,那个两百行就能写完。
174