4. 固件镜像格式设计
在TRAVEO的固件升级体系中,镜像格式是升级流程能否可靠执行的基础。Bootloader需要在不依赖外部工具的情况下,仅凭镜像自身携带的信息完成合法性判断、版本比较、完整性校验和写入决策。因此,镜像格式设计的目标不是“把固件塞进一个文件”,而是让镜像成为一个自描述、可验证、可追溯的数据结构。
镜像的整体布局
一个典型的TRAVEO升级镜像由头部、元数据区、固件负载、签名区和尾部校验区组成。头部固定长度,位于镜像起始位置,Bootloader上电后首先读取该区域。头部之后是元数据区,用于承载版本描述、构建信息、目标硬件标识等可变长内容。固件负载是实际写入Flash的应用程序二进制。签名区存放对头部、元数据和负载的签名值。尾部校验区则提供对整个镜像的快速完整性校验。
头部字段的设计要点
头部是Bootloader最先解析的区域,字段设计必须兼顾固定偏移和可扩展性。以下是一个可供参考的头部结构定义:
typedef struct {
uint32_t magic; // 0x5452564F "TRVO"
uint16_t header_ver; // 头部格式版本,当前为 0x0100
uint16_t image_type; // 0x0001=应用, 0x0002=Bootloader, 0x0003=标定数据
uint32_t fw_version; // 主.次.修订.构建,如 0x01020304
uint32_t load_addr; // 负载写入 Flash 的目标起始地址
uint32_t image_len; // 固件负载字节数
uint32_t entry_point; // 跳转入口地址
uint32_t crc32; // 头部+元数据+负载的 CRC32
uint32_t sig_offset; // 签名区相对镜像起始的偏移
uint32_t meta_offset; // 元数据区相对镜像起始的偏移
uint32_t meta_len; // 元数据区长度
uint8_t reserved[212]; // 保留至 256 字节
} image_header_t;
magic字段用于快速识别合法镜像,避免Bootloader对随机数据做无意义解析。header_ver允许后续在不破坏旧Bootloader的前提下扩展头部。fw_version采用32位编码,高8位为主版本、次8位为次版本、再8位为修订号、低8位为构建号,便于Bootloader用一次整数比较完成版本判断。load_addr和image_len共同界定写入范围,Bootloader在擦写前必须校验该范围是否落在应用分区内,防止越界写入。
版本号与回滚策略
版本号不仅用于显示,更用于升级决策。Bootloader通常维护一个当前运行版本寄存器或备份区。当收到新镜像时,比较fw_version与当前版本:若新版本更高,允许升级;若相同,可根据策略拒绝或允许重刷;若更低,则进入回滚判断。回滚策略需要额外字段支持,例如在元数据中增加min_allowed_version,表示允许降级的最低版本。若新镜像版本低于该值,Bootloader直接拒绝,防止恶意降级到存在已知漏洞的旧固件。
校验字段与签名区
CRC32用于快速完整性校验,计算范围覆盖头部、元数据和固件负载,但不包括签名区本身。Bootloader在接收完镜像后先做CRC校验,若失败则丢弃,不进入签名验证,以节省非对称运算时间。签名区采用ECDSA P-256,签名对象是头部、元数据和负载拼接后的SHA-256哈希值。公钥预先烧录在Bootloader的只读区域或OTP中,Bootloader使用该公钥验证签名。只有CRC和签名都通过,镜像才被允许写入。
元数据区的TLV组织
元数据区采用TLV(Type-Length-Value)格式,便于按需扩展而不影响头部固定布局。每个条目由1字节类型、2字节长度和变长值组成。例如:
Type 0x01: build_time "2025-03-21T14:30:00"
Type 0x02: git_hash "a3f8c21e"
Type 0x03: target_hw_id 0x0001 (CYT4BF)
Type 0x04: toolchain "GCC 10.3.1"
Type 0x05: min_ver 0x01020000
Bootloader不需要解析全部元数据,只提取target_hw_id和min_ver等关键字段做匹配判断。其余字段供上位机工具和售后追溯使用。这种设计让镜像格式在保持Bootloader简洁的同时,具备工程可追溯性。
镜像打包工具链
镜像打包通常由上位机工具完成,输入是编译链接生成的ELF或BIN文件,输出是带头部、元数据和签名的完整升级镜像。工具链的核心步骤包括:从ELF提取负载和入口地址;计算负载长度和CRC;生成头部和元数据;对头部、元数据和负载做SHA-256并调用私钥签名;将各段按布局拼接输出。以下是一个打包命令的示例:
trav_pack --input app.bin \
--elf app.elf \
--version 1.2.3.4 \
--hw-id 0x0001 \
--load-addr 0x08010000 \
--key private_p256.pem \
--output app_v1.2.3.4.img
打包完成后,建议用配套的解析工具对镜像做一次回读校验,确认头部字段、CRC和签名均可正确解析。这一步能在产线烧录前发现打包参数错误,避免批量返工。
设计检查清单
- 头部长度固定,字段偏移在文档中明确,保留区足够后续扩展。
- magic值唯一且不易与Flash空白或随机数据混淆。
- load_addr与image_len的组合经过分区边界检查。
- CRC32覆盖范围明确排除签名区,避免循环依赖。
- 签名算法和曲线在Bootloader与打包工具中保持一致。
- 元数据TLV解析具备未知类型跳过能力,不因新增字段导致旧Bootloader失败。
- 版本比较逻辑包含相同版本和降级场景的明确策略。
镜像格式一旦发布并被Bootloader固化,后续修改成本很高。因此在设计阶段就应把版本兼容、回滚控制、签名验证和元数据扩展纳入考虑,使镜像格式能够支撑TRAVEO产品整个生命周期的固件治理需求。