• 正文
  • 相关推荐
申请入驻 产业图谱

SWUpdate实战:包怎么打、槽怎么切、失败了怎么回头

10/13 10:11
174
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

板子上装了 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 真正值钱的地方不是"能升级",而是把一次多镜像安装做成了一个事务。

要么整包落地,要么别启动新的。自己手搓升级脚本,九成的坑都埋在这上面;真正的成本也不在打包脚本,那个两百行就能写完。

相关推荐

本公众号专注于嵌入式技术,包括但不限于C/C++、嵌入式、物联网、Linux等编程学习笔记,同时,公众号内包含大量的学习资源。欢迎关注,一同交流学习,共同进步!