114 lines
7.7 KiB
Markdown
114 lines
7.7 KiB
Markdown
# 设备A/B分区固件升级与启动确认协议
|
|||
|
|
|
||
|
|
## 1. 目的与适用边界
|
||
|
|
|
||
|
|
本协议用于把云端已经完成的签名制品、灰度命令和阶段回执,与量产终端常见的A/B双分区升级机制对齐。它定义设备必须持久化的升级状态、云端可观察阶段、启动健康窗口和自动回退结果,但不替代具体芯片、Bootloader、分区表、看门狗或厂商烧写SDK。当前`cmd/sim-device`实现的是协议级模拟器,用于在真机到位前发现状态机和联调问题;是否能安全写入真实分区、断电后继续启动及由Bootloader自动回退,仍必须在量产候选硬件上验收。
|
||
|
|
|
||
|
|
旧设备不声明或不启用A/B能力时继续使用既有`downloading → installing → confirmed/failed`链路。A/B模拟器只有在`SIM_FIRMWARE_AB_UPDATE=true`且提供绝对路径`SIM_FIRMWARE_STATE_FILE`时启用,不改变设备v2既有必填字段和命令终态。
|
||
|
|
|
||
|
|
## 2. 设计原则
|
||
|
|
|
||
|
|
1. 设备只信任安全存储或受控镜像预置的厂商公钥,不信任升级命令携带的公钥。
|
||
|
|
2. 制品大小、SHA-256和Ed25519签名全部通过后,才允许写入非活动分区。
|
||
|
|
3. 任何时刻都不得直接覆盖当前活动且已确认可启动的分区。
|
||
|
|
4. 设置下次启动分区和健康截止时间前,必须先持久化升级日志;云端回执不得先于本地耐久状态。
|
||
|
|
5. 新分区只有在核心进程、配置读取、设备身份和必要外设健康检查通过后才能确认。
|
||
|
|
6. 健康检查失败或超过截止时间时,由设备/Bootloader回到旧分区,并向云端提交带回退事实的稳定失败终态。
|
||
|
|
7. 设备重启后可重新拉取仍为`ACKNOWLEDGED`的同一命令;云端增加投递次数但不把状态回退到`DELIVERED`。
|
||
|
|
|
||
|
|
## 3. 设备侧状态机
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
stateDiagram-v2
|
||
|
|
[*] --> StableA: A分区已确认
|
||
|
|
StableA --> Downloading: 收到firmware_update
|
||
|
|
Downloading --> ArtifactVerified: 摘要和签名通过
|
||
|
|
Downloading --> Failed: 下载/摘要/签名失败
|
||
|
|
ArtifactVerified --> StagedB: 写入并校验非活动B分区
|
||
|
|
StagedB --> RebootPending: 持久化启动尝试和健康截止时间
|
||
|
|
RebootPending --> BootVerifyingB: 重启进入B分区
|
||
|
|
BootVerifyingB --> StableB: 健康检查通过并确认B
|
||
|
|
BootVerifyingB --> RolledBackA: 健康失败或超时
|
||
|
|
RolledBackA --> Failed: 上报已自动回退
|
||
|
|
StableB --> [*]
|
||
|
|
Failed --> [*]
|
||
|
|
```
|
||
|
|
|
||
|
|
当活动分区为B时,下一次升级使用完全对称的B→A流程。`power_loss_after_stage`保留`STAGED`日志,重启后从设置启动分区继续;`power_loss_during_boot`保留`REBOOT_PENDING`日志,后续启动继续执行健康检查。重复执行必须针对同一命令编号、版本、发布编号和摘要,任一不一致都以`LOCAL_UPDATE_STATE_CONFLICT`停止,不能覆盖未知的在途升级。
|
||
|
|
|
||
|
|
## 4. 本地耐久日志
|
||
|
|
|
||
|
|
模拟器状态文件只保存非敏感升级元数据,权限为当前进程可读写。字段如下:
|
||
|
|
|
||
|
|
| 字段 | 含义 |
|
||
|
|
|---|---|
|
||
|
|
| `phase` | `STAGED`、`REBOOT_PENDING`、`CONFIRMED`或`ROLLED_BACK` |
|
||
|
|
| `command_id`、`release_id` | 绑定当前可靠命令和云端发布记录 |
|
||
|
|
| `target_version`、`previous_version` | 目标版本和回退基线版本 |
|
||
|
|
| `sha256` | 已验证制品摘要,用于重启后核对同一升级意图 |
|
||
|
|
| `active_slot`、`previous_slot`、`target_slot` | 当前稳定分区、回退分区和待启动分区 |
|
||
|
|
| `boot_attempt_id` | 每次设置下次启动分区时生成的随机启动尝试编号 |
|
||
|
|
| `health_deadline` | 新分区必须完成健康确认的UTC截止时间 |
|
||
|
|
| `updated_at` | 最近一次耐久更新时刻 |
|
||
|
|
|
||
|
|
`CONFIRMED`和`ROLLED_BACK`记录作为下一次升级的本地稳定基线保留。量产实现应把等价数据写入具备掉电保护的Bootloader环境区或专用元数据分区,并使用冗余、校验和或事务写策略处理半写入,不能直接照搬普通文件实现。
|
||
|
|
|
||
|
|
## 5. 云端回执与主状态映射
|
||
|
|
|
||
|
|
命令仍只使用`ACKNOWLEDGED`、`SUCCEEDED`和`FAILED`三类状态。A/B细节放在可选`result_summary`中,云端将所有设备侧写入、重启和健康确认阶段保持为`firmware_assignment.status=INSTALLING`,避免扩展既有主状态枚举破坏客户端兼容。
|
||
|
|
|
||
|
|
| 回执阶段 | 命令状态 | 分配主状态 | 建议进度 | 关键字段 |
|
||
|
|
|---|---|---|---:|---|
|
||
|
|
| `downloading` | `ACKNOWLEDGED` | `DOWNLOADING` | 5—100 | `result_code`、`verified_sha256` |
|
||
|
|
| `installing` | `ACKNOWLEDGED` | `INSTALLING` | 80 | `INSTALL_STARTED` |
|
||
|
|
| `staging` | `ACKNOWLEDGED` | `INSTALLING` | 85 | `active_slot`、`target_slot` |
|
||
|
|
| `reboot_pending` | `ACKNOWLEDGED` | `INSTALLING` | 90 | `boot_attempt_id`、`boot_health_deadline` |
|
||
|
|
| `boot_verifying` | `ACKNOWLEDGED` | `INSTALLING` | 95 | `active_slot`、`previous_slot` |
|
||
|
|
| `confirmed` | `SUCCEEDED` | `CONFIRMED` | 100 | `installed_version`、`active_slot` |
|
||
|
|
| `rolled_back` | `FAILED` | `FAILED` | 95 | `rollback_applied=true`、`rollback_version`、`failed_slot` |
|
||
|
|
|
||
|
|
启动失败回退示例:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"status": "FAILED",
|
||
|
|
"result_summary": {
|
||
|
|
"phase": "rolled_back",
|
||
|
|
"progress_percent": 95,
|
||
|
|
"result_code": "BOOT_HEALTH_FAILED_ROLLED_BACK",
|
||
|
|
"rollback_applied": true,
|
||
|
|
"active_slot": "A",
|
||
|
|
"failed_slot": "B",
|
||
|
|
"rollback_version": "0.2.0-sim",
|
||
|
|
"boot_attempt_id": "<随机编号>"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
回退已经保护设备可启动性,但目标版本未成功,因此云端仍把命令和分配标记为失败、把批次暂停并停止扩量。运营页面从`last_result`展示非活动分区写入、等待重启、健康确认或已自动回退,不把“回到了可用旧版本”误报成升级成功。
|
||
|
|
|
||
|
|
## 6. 模拟器参数和故障注入
|
||
|
|
|
||
|
|
| 参数 | 默认值 | 作用 |
|
||
|
|
|---|---|---|
|
||
|
|
| `SIM_FIRMWARE_AB_UPDATE` | `false` | 显式启用A/B升级协议 |
|
||
|
|
| `SIM_FIRMWARE_STATE_FILE` | 无 | A/B耐久日志绝对路径;启用时必填 |
|
||
|
|
| `SIM_FIRMWARE_ACTIVE_SLOT` | `A` | 首次运行且没有稳定日志时的活动分区,只允许A/B |
|
||
|
|
| `SIM_FIRMWARE_CURRENT_VERSION` | `0.2.0-sim` | 首次运行的回退基线版本 |
|
||
|
|
| `SIM_FIRMWARE_BOOT_HEALTH_WINDOW` | `2m` | 启动健康窗口,范围1秒至30分钟 |
|
||
|
|
| `SIM_FIRMWARE_FAULT=power_loss_after_stage` | 无 | 写完非活动分区后模拟掉电,保留`STAGED` |
|
||
|
|
| `SIM_FIRMWARE_FAULT=power_loss_during_boot` | 无 | 新分区健康确认时模拟掉电,保留`REBOOT_PENDING` |
|
||
|
|
| `SIM_FIRMWARE_FAULT=boot_health_failed` | 无 | 模拟新分区健康失败并自动回退 |
|
||
|
|
|
||
|
|
## 7. 轻量纵向验证
|
||
|
|
|
||
|
|
```powershell
|
||
|
|
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\smoke-sim-firmware.ps1 -Mode ABRollback
|
||
|
|
```
|
||
|
|
|
||
|
|
脚本创建一个临时签名版本和单设备批次。第一次运行下载、验签、写入B分区并持久化`REBOOT_PENDING`,断言云端为`ACKNOWLEDGED/INSTALLING/90/REBOOT_ARMED`;第二次运行模拟B分区健康失败,断言本地回到A分区、原版本恢复,云端收敛为`FAILED/FAILED/95/BOOT_HEALTH_FAILED_ROLLED_BACK`,批次暂停且观察设备为0。结束后精确清理数据库、对象存储、固件文件和本地状态文件。
|
||
|
|
|
||
|
|
同一脚本还生成独立的`firmware-device-evidence-v1`设备侧证据,读取包含7条去重阶段事件的云端批次响应,并调用`cmd/firmware-conformance`核对命令、发布、启动尝试、制品摘要、签名密钥、阶段顺序、时间相关性、分区和回退版本。证据字段及报告归档规则见[固件真机联调一致性证据采集与校验说明](firmware-conformance-evidence.md)。
|
||
|
|
|
||
|
|
该结果证明协议和云端状态闭环可联调,不证明真实Bootloader、闪存掉电一致性或真实外设健康检查已经通过。真机验收仍须覆盖至少3台量产候选终端、A→B和B→A、写入中断电、首次启动断电、健康超时、看门狗复位、连续升级、云端暂停和已验证版本回滚。
|