自制工业控制系统:开机自检与固件更新实战——POST报告、A/B双槽与版本回滚
Contents
起因
十五年生命周期的机器,出厂那天固件就是旧的——升级是必然,升级变砖是事故。这篇讲两件让机器活得久的事:开机自检(POST,排障第一屏)与 A/B 双槽固件更新(升级不停产)。升级入口两条:U 盘(现场,操作员插盘点确认)和网络 OTA(含 4G 远程推送,合规篇里 4G 通道复用这条路);外加恢复出厂固件的兜底。
两个机制都不是新发明。开机自检是 IBM PC 传下来的老传统——当年内存条松了靠蜂鸣码报障,如今蜂鸣器换成屏幕,“上电先验家底"的逻辑没变。A/B 双槽的资历在嵌入式这头:汽车 ECU 和路由器很早就用双镜像保升级不断电,把它做成大众范式的名气归 Google——Android 7.0(2016)的无缝更新在后台槽写好新版本、重启即切换,变砖的恐惧从此有了工程解。
一、开机自检:把前面所有篇的"情报"串成一屏
各篇已经拿到的信息,串成一条 3 秒内的自检链:
| 检查项 | 来源 | 失败分级 |
|---|---|---|
| CPU/频率/核数 | CPUID(多核篇) | INFO |
| 内存容量与位图统计 | SMBIOS + 页帧分配器 | FATAL(容量异常)/ WARN(高水位) |
| 温度/风扇 | CPU 温度 + NCT6775(平台管理篇) | WARN 超阈提示 |
| NVMe 容量/健康 | NVMe 篇 Identify | WARN |
| U 盘/键鼠枚举 | EHCI/HID 篇 | INFO(拔了就提示) |
| 网络链路 | 网络篇链路状态寄存器 | DEGRADE 可继续 |
| 参数/快照完整性 | 参数篇双副本校验 | FATAL(双坏→出厂默认+报警) |
| 日志裸分区归档 | 日志篇 | INFO |
| 合规状态 | GPS/授权判定(合规篇) | FATAL(锁定则停在这屏) |
分级语义:FATAL = 停在这屏等维修(屏 + 蜂鸣 + 错误码);DEGRADE = 黄字提示继续跑(没网也是机床);INFO = 绿字。每项带耗时(TSC 打点),自检总时长本身就是一项健康指标。耗时是有账可算的:CPUID、内存位图、SMBIOS 是纯内存操作,加起来不到 1ms;NVMe Identify 一条管理队列命令,毫秒级;大头全在 USB 枚举——每台设备复位加地址设置 100ms 量级,键鼠加 U 盘就是几百毫秒。所以顺序按先要命、后费时排:内存、参数、合规这些 FATAL 项先出结论,第一屏落下就有判断;USB 枚举放最后。3 秒总预算里 USB 占掉大半,这是设备物理特性,不是实现缺陷。这一屏同时写进日志——客户说"昨天还好的”,你调日志。
二、固件布局:三槽一旗
分区图(参数篇的布局上加盖):
| 分区 | 内容 |
|---|---|
| ESP(FAT32) | BOOTX64.EFI 选择器 + KERNELA.EFI + KERNELB.EFI + KERNELF.EFI(出厂镜像) |
| FLAG 裸区 | 激活槽、bootcount、升级待装、恢复出厂请求 |
| SYS 裸区 | 参数/快照/日志裸分区 |
| DATA exFAT | 程序/归档/升级包暂存 |
关键设计——FAT32 一个字节都不自己写:ESP 上只有固件自己会碰的东西;需要写 ESP 的唯一场景(升级、恢复出厂),借 UEFI 固件的 SimpleFileSystem 协议在引导阶段完成——写 FAT32 是固件的强项,不是我们的。
FLAG 区结构 = 参数篇同款双副本:{激活槽 A/B、bootcount、升级待装标志、恢复出厂请求、gen、crc}。选择器用它,内核也用它(拿到 BlockDevice 后直接裸写那两个 LBA——Handoff 里带 LBA 范围)。落到扇区里就一个结构体,单扇区原子,跟快照同款论证:
|
|
FLAG 为什么也要双副本:选择器每次开机第一件事就是读它——单副本写一半断电,下次开机就成了不知道该启哪个槽的悬案,防砖机制自己先把机器砖了。双副本 + gen 让它跟参数表一样满足任意时刻断电最多坏一份。
坑:bootcount 想存 RTC CMOS 的 0x30+ 字节是诱人的陷阱——那片区域按芯片组私有定义各不相同,跨主板迁移就是暗雷。裸区双副本是唯一可控的。
三、启动链
上电 → 固件 → BOOTX64.EFI(我们的选择器,纯 UEFI 阶段代码):
- BlockIo 读 FLAG 双副本,选激活槽
- bootcount ≥ 3?→ 切到另一个槽,计数清零,留一笔日志
- 有"升级待装"?→ 用 SimpleFileSystem 把 DATA 区暂存的 KERNEL_NEW.BIN 拷进非激活槽,验 SHA-256,清待装标志
- 有"恢复出厂请求"?→ 把 KERNELF.EFI 拷满 A、B 两个槽,清请求,参数区同步恢复出厂默认
- LoadImage/StartImage 激活槽的 KERNELX.EFI → 内核起来后走 UEFI 篇的 Handoff 流程
选择器支持一个极简开机菜单(UEFI 的 SimpleTextIn 有自己的 USB 键盘驱动,不用我们管):倒计时 3 秒自动启动,按键进菜单——选 A/B 槽启动、触发恢复出厂。这是现场维修人员不进操作系统就能用的最后一层入口。
内核侧配合:自检全绿 + 稳定运行 10 分钟(或操作员在界面按"确认本次升级")→ 把 bootcount 清零。新固件起不来?选择器看到计数涨过 3 自动回旧槽——机器永远有一份能启动的固件。
两个语义要钉死。一是计数发生在什么时候:选择器在 StartImage 之前就把 bootcount +1 写回——内核活着并走到确认点才有机会清零,一次启动没活到确认点就记一次。开机中途断电、被拍急停也算失败,意味着存在偶发的误回滚(连着三次没撑到确认点就断电),但误回滚的代价是回旧版本接着干活,新固件带病上岗的代价是撞机,这笔账不用犹豫。二是升级拷贝的断电窗口:拷贝目标是非激活槽,激活槽从头到尾没动过,中途断电机器照旧从旧槽起、待装标志还在,下次开机选择器重拷一遍——整条升级链路里不存在半新半旧的状态。
四、升级包与完整性
升级包 = 内核镜像 + 版本号 + SHA-256。来源三条:U 盘(U 盘驱动篇现场升级)、有线网络和 4G OTA(远程推送,需要界面二次确认)。内核收到包:验哈希 → 写到 DATA 区 KERNEL_NEW.BIN(exFAT 普通文件,我们自己的地盘)→ 置"升级待装"进 FLAG → 提示重启,重启后由选择器完成真正的写槽。写序纪律跟参数表一笔账:DATA 里的包文件写完先 flush,落稳了才去置 FLAG 的待装标志——标志是提交点。包还没落稳就置标志,重启后选择器拷到半个文件,哈希对不上,升级失败还留一笔糊涂日志。
SHA-256 是新增的自研模块:参考 RFC 6234 的参考实现或 RustCrypto sha2(MIT/Apache-2.0,按依赖策略vendor 进仓库),用官方测试向量(“abc” → ba7816bf…f81ffa)验完再用。
坑:起步阶段用 SHA-256 + 物理来源控制(U 盘手工/网络二次确认)是够的,但要把"哈希≠签名"写进设计文档——哈希防传输损坏,不防有心人做包。上非对称签名(自签证书链)是量产化任务,与授权体系(合规篇)的 Ed25519 一起做。
五、Secure Boot 的现实答案
自研系统的 SB 不是去求微软签名,而是自己当 CA:自签一张证书,在主板固件里 enroll 进 db,固件只认自己的签名。有个容易混的点:UEFI SB 认的是 X.509 证书链(RSA-2048 起步,ECDSA 也行),跟授权体系用的 Ed25519 是两套东西——一个是行业标准规定的锁,一个是自用的签名工具,不共用钥匙。enroll 单台进 BIOS setup 手动导,产线批量走厂商工具或克隆镜像。还要记住 SB 打开后 BOOTX64.EFI 和两个内核镜像都得过签,CI 出包多一道签名工序,这步省不掉。没上签名之前的过渡态:BIOS 关 SB——这在自持硬件的工业场景是可接受的现实,写文档说明白即可。
六、最后的防砖
两个槽都坏(概率极低但要设计掉):选择器自身极简(只依赖 BlockIo + SimpleFileSystem,几千行以内),它坏的前提基本是 ESP 卷坏——恢复路径 = U 盘现场恢复:选择器留一个"签名扫描兜底",ESP 里找不到就扫 U 盘根目录的恢复包直接装 A 槽。这一步 + 参数篇的出厂默认,机器的"砖"就只剩硬件级的了。
总结
实战清单:自检链 9 项 + 三级失败分级 + 逐项耗时(USB 枚举占大头,FATAL 项先出结论);三槽一旗布局 + FLAG 单扇区双副本结构;选择器五步启动链 + 开机菜单;10 分钟稳定/手动确认清 bootcount;计数 3 次未清零、第 4 次开机自动回滚;U 盘/有线/4G 三条升级入口 + SHA-256 vendor + 测试向量 + flush 后置标志的写序;SB 自签路线图(X.509 证书链,与 Ed25519 授权签名分开);U 盘兜底恢复。跑通标志:升级写槽中途拔电 → 重启进旧槽正常干活,待装标志还在、下次开机重拷完成升级;人为让新固件启动即 panic → 第 4 次开机自动回旧槽并留日志;恢复出厂请求 → A/B 槽回灌出厂镜像、参数回默认、GPS 选配配置不变;U 盘恢复路径实测走通一次。
下一篇:国际化——机器卖到国外,第一屏的母语从哪来。
参考链接
Author 软件开发大郭
LastMod 2026-09-11