在嵌入式安全领域,ARM Total Compute平台的RSS(Root of Trust Secure Subsystem)架构提供了一套完整的硬件级信任根解决方案。作为安全启动的核心组件,RSS固件的启动流程设计直接影响整个系统的可信执行环境(TEE)建立。与传统的单阶段启动加载器不同,RSS采用分层验证的启动链设计,通过BL1和BL2的协同工作实现逐级安全验证。
RSS启动流程根据设备生命周期状态分为两个主要阶段:
这种分离设计实现了安全密钥与配置信息的分阶段注入,确保芯片制造商和设备制造商各自的安全资产相互隔离。在实际工程中,我曾遇到过因混淆这两个阶段导致的启动失败案例——某次在产线测试时误将DM阶段的供应包用于CM阶段,导致OTP编程失败。
RSS启动过程涉及多个关键组件的协同工作:
code复制┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ BL1_1 │───▶│ BL1_2 │───▶│ BL2 │
└─────────────┘ └─────────────┘ └─────────────┘
▲ ▲ ▲
│ │ │
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ ROM固件 │ │ OTP │ │ FIP │
└─────────────┘ └─────────────┘ └─────────────┘
BL1采用独特的双阶段设计,这种架构在安全性和灵活性之间取得了平衡:
BL1_1(ROM固件)特点:
BL1_2(OTP可编程)特点:
在调试某款车规级MCU时,我们发现BL1_2的OTP编程时序非常关键。过早触发编程会导致校验失败,建议在芯片上电稳定后延迟至少100ms再开始OTP操作。
关键调试技巧:当BL1_1卡在验证阶段时,可通过测量电源纹波和时钟稳定性来排除硬件问题。我们曾遇到因LDO输出不稳导致哈希校验随机失败的情况。
BL1_2作为承上启下的关键组件,主要完成以下任务:
FIP解析过程:
c复制// 典型FIP头部结构
typedef struct {
uint8_t uuid[16]; // 镜像UUID
uint64_t offset; // 在FIP中的偏移量
uint64_t size; // 镜像大小
uint32_t flags; // 属性标志
uint32_t reserved;
} fip_toc_entry_t;
BL2加载示例代码:
c复制// 从FIP中提取BL2镜像
int load_bl2_from_fip(void *fip_base, void **bl2_out, size_t *size_out) {
fip_toc_entry_t *toc = (fip_toc_entry_t *)(fip_base + FIP_TOC_OFFSET);
for (int i = 0; i < MAX_TOC_ENTRIES; i++) {
if (memcmp(toc[i].uuid, BL2_UUID, 16) == 0) {
*bl2_out = fip_base + toc[i].offset;
*size_out = toc[i].size;
return 0;
}
}
return -1; // BL2 not found
}
安全验证关键步骤:
实践提示:BL1_2的调试符号加载地址必须与链接脚本中的定义完全一致。常见错误是忽略BL1_2_CODE_START的偏移量配置,导致调试器无法正确解析符号。
CM供应包(encrypted_cm_provisioning_bundle_0.bin)包含芯片制造商的核心安全资产:
c复制struct cm_provisioning_data {
uint8_t bl1_2_image_hash[32]; // BL1_2镜像哈希
uint8_t bl1_2_image[BL1_2_CODE_SIZE]; // BL1_2完整镜像
uint32_t rss_id; // 芯片唯一标识
uint8_t guk[32]; // 全局唯一密钥
uint32_t cca_system_properties; // 系统属性位图
};
供应包通过以下命令加载到FVP:
bash复制--data css.rss.sram0=output/deploy/tc2/rss_encrypted_cm_provisioning_bundle_0.bin@0x0
DM供应包(encrypted_dm_provisioning_bundle.bin)包含设备制造商的安全配置:
c复制struct dm_provisioning_data {
uint8_t bl1_rotpk_0[56]; // BL1根公钥
uint8_t bl2_encryption_key[32]; // BL2加密密钥
uint8_t bl2_rotpk_0[32]; // BL2根公钥哈希
// ...其他安全资产
};
加载参数示例:
bash复制--data css.rss.sram1=output/deploy/tc2/rss_encrypted_dm_provisioning_bundle.bin@0x80000
供应阶段调试经验:
RSS选择MCUboot作为BL2的实现基础,主要考虑到:
关键目录结构:
code复制mcuboot-src/
├── boot/ # 核心启动逻辑
├── docs/ # 设计文档
├── scripts/ # 镜像处理工具
└── sim/ # 模拟器支持
使用imgtool进行签名的典型命令:
bash复制python3 imgtool.py sign \
--key ${MCUBOOT_KEY_S} \
--align 8 \
--header-size 1024 \
--version 1.2.0 \
--security-counter 5 \
bl2.bin bl2_signed.bin
签名布局文件示例(signing_layout_s.c):
c复制enum image_attributes {
RE_SECURE_IMAGE_OFFSET = 0x00010000,
RE_SECURE_IMAGE_MAX_SIZE = 0x000F0000,
RE_IMAGE_LOAD_ADDRESS = 0x71000000,
RE_SIGN_BIN_SIZE = 0x00100000
};
使用fiptool更新FIP的典型操作:
bash复制fiptool update \
--align 8192 \
--rss-bl2 bl2_signed.bin \
--rss-scp-bl1 scp_romfw_signed.bin \
fip.bin
经验分享:FIP中各个镜像的对齐参数直接影响加载性能。我们发现8KB对齐相比默认的4KB对齐能减少约15%的加载时间,特别是在eMMC存储介质上效果更明显。
c复制// 等待SCP启动完成的典型代码
while (channel_stat == 0) {
mhu_v2_x_channel_receive(&MHU_SCP_TO_RSS_DEV, 0, &channel_stat);
}
c复制mhu_v2_x_initiate_transfer(&MHU_RSS_TO_SCP_DEV);
mhu_v2_x_channel_send(&MHU_RSS_TO_SCP_DEV, 0, 1); // Slot 0用于启动命令
调试技巧: 当AP无法启动时,建议按以下顺序排查:
| 组件 | 符号文件路径 | 加载地址参数 |
|---|---|---|
| BL1_1 | output/build/bin/bl1_1.elf | ROM_BASE_S (0x11000000) |
| BL1_2 | output/build/bin/bl1_2.elf | BL1_2_CODE_START |
| CM Bundle | output/build/cm_provisioning_bundle.axf | PROVISIONING_BUNDLE_CODE_START |
bl1_1_entry()validate_image_at_addr()boot_load_image()mermaid复制graph TD
A[启动卡住] --> B{卡在哪个阶段?}
B -->|BL1_1| C[检查ROM固件哈希]
B -->|BL1_2| D[验证FIP完整性]
B -->|BL2| E[检查MCUboot日志]
C --> F[测量电源/时钟]
D --> G[确认OTP编程正确]
E --> H[验证签名密钥]
| 错误代码 | 可能原因 | 解决方案 |
|---|---|---|
| 0x8A01 | BL1_2哈希不匹配 | 重新生成CM供应包 |
| 0x9203 | FIP头部损坏 | 检查fiptool版本兼容性 |
| 0xC005 | 安全计数器回滚 | 更新NV计数器或使用新镜像 |
| 0xE102 | ATU映射失败 | 验证地址转换参数 |
在某个智能电表项目中,我们通过优化BL2的验证逻辑(将RSA验证改为预计算哈希比对),使启动时间从1.2秒缩短到780毫秒,同时保持了同等安全级别。