面向汽车电子 MCU | 芯片:CCFC3008PCSN | MCAL | HSM 固件 本文档是安全启动的"一页通 + 实操手册":把操作流程、设置参数、时钟配置、其他核启动时间点、代码配置、常见坑全部串起来。 所有接口、结构体、枚举、地址、行号均来自 MCAL 源码 + HSM 固件源码原文,非臆造。
0. 一页看懂:安全启动到底在干什么
用一句话概括:芯片上电后,HSM 安全核先跑起来,在主核(Z4/Z7)真正执行应用代码之前,先用"验签密钥"检查这些代码有没有被篡改,验签通过才把主核放出去跑;不通过就按你配的策略处理(复位/降级/锁死)。
上电
│
▼
HSM 安全核先启动(固件 自己初始化时钟/看门狗/串口)
│
▼
读取安全启动表(存在 D-Flash,A/B 双份冗余) getBootEntryPtr()
│
├── bootType == NONE ──────────────► 不验签,直接放核
├── 用户调过 IgnoreSecureBoot ──────► 跳过验签,直接放核
├── bootType == FAST ──────────────► 先放核(边跑边验),验签失败再回卷
└── bootType == STRICT ────────────► 先验签,通过才放核(最安全)
│
▼
验签(genAuthTag_RSA / genAuthTag_AES)
│
├── 通过 ──► z4_z70_z71_app_boot() 释放主核
└── 失败 ──► operationOnFail:RESET 软复位 / LOOP 锁死 / DEGRADE 降级放行
三个"灵魂"概念,先背下来:
|------------------------|------------------------------------------------|------------------------------|
| 概念 | 含义 | 一句话 |
| 信任根(Root of Trust) | 验签用的公钥/密钥,是"信不信代码"的源头 | 密钥在 HSM 里、出不来,所以可信 |
| 验签(Verify) | HSM 用公钥对代码段算签名、比对你预置的签名 | 签名不对 = 代码被改过 |
| 放核(Release Core) | HSM 写 MC_ME.CCTLx/CADDRx + MCTL,把主核从复位态放出来 | 唯一函数 z4_z70_z71_app_boot() |
1. 背景与核心概念
1.1 为什么车规 MCU 一定要安全启动
- 车规要求(ISO 21434 网络安全工程 / EVITA):ECU 上跑的固件必须是"可信"的。
- 攻击场景:OTA 升级包被篡改、Flash 被物理/逻辑攻击改写、供应链植入后门。
- 安全启动的作用:保证从"上电第一条指令"到"应用代码"整条链每一步都经过完整性 + 真实性校验,任何一环被改,系统拒绝启动或进入安全状态。
1.2 3008PCSN 的实现方式
- HSM(HSN 安全核)独立于主核,先于主核启动,天然是"信任根"的宿主。
- 验签密钥(RSA 公钥 / AES 对称密钥)存在 HSM 的 NVM 密钥区,主核拿不到明文。
- 主核代码段在出厂/量产阶段由产线工具(或用 HSM 自身)算好签名,签名连同"代码段地址+长度"一起写进安全启动表。
- 每次复位,HSM 都重跑一遍验签,保证"篡改即被发现"。
1.3 术语速查(本文高频词)
|--------------------------|------------------------------------------------|
| 术语 | 说明 |
| Secure Boot Entry(安全启动表) | 存在 D-Flash 的一张表,记录验签参数(hsmSecurebootEntry_t) |
| Code Segment(代码段) | 要验签的一段主核代码,含 address + length,最多 8 段 |
| Auth Tag(签名) | 对代码段算出来的签名值(RSA 256 字节 / AES-CMAC 16 字节) |
| DevAuth(设备认证) | 打开 HSM 受保护功能的"授权门":质询-应答 + CMAC |
| bootCore | 位掩码,标记要放哪些核(Z4_2 / Z7_0 / Z7_1) |
| A/B 双 bank | 启动表/密钥区各存两份,ping-pong 写入,防写坏 |
2. 三种启动模式:NONE / FAST / STRICT
安全启动类型枚举(Crypto_LLDriver.h:382~385 = 固件 hsm_common_type.h:352~355):
typedef uint8_t hsmSecureBootType_t;
#define HSM_SECURE_BOOT_TYPE_NONE ((hsmSecureBootType_t)0xFFU) // 不启用
#define HSM_SECURE_BOOT_TYPE_FAST ((hsmSecureBootType_t)0x00U) // 先放核后验签
#define HSM_SECURE_BOOT_TYPE_STRICT ((hsmSecureBootType_t)0x01U) // 先验签后放核
2.1 三种模式时序对比(核心中的核心)
固件分流点在 HSM_SRV_SecureBootCheck()(hsm_secure_boot.c:1096~1248):
|-------------------|--------------------------------------|--------|-----|------------|-------------------|
| 模式 | 放核时机 | 验签时机 | 安全性 | 启动速度 | 适用 |
| NONE (0xFF) | 立即(boot(TRUE,FALSE),L1125) | 不验签 | 无 | 最快 | 开发调试、无安全需求 |
| FAST (0x00) | 验签前 先放核(boot(TRUE,TRUE),L1137) | 放核后并行验 | 中 | 快(用户感知零等待) | 快速启动场景,验签失败后回卷/复位 |
| STRICT (0x01) | 验签后 (boot(TRUE,TRUE),L1243) | 放核前先验 | 高 | 慢(等验签完成) | 高安全场景(网关/域控/VCU) |
关键理解点:
- FAST 的"快"是相对的------主核先跑起来,HSM 在后台验签,验签失败再处理(但此时主核可能已经跑了一段,要靠
operationOnFail兜底)。
- STRICT 的"慢"是绝对的------主核必须等 HSM 把每个代码段都验完、签名匹配,才被放出去。
- NONE 和 IgnoreSecureBoot 都是
boot(TRUE,FALSE),第二个参数addrcheck=FALSE表示"不做地址/校验检查",纯粹放核。
2.2 失败动作 operationOnFail(三个选项)
枚举(Crypto_LLDriver.h:387~390):
typedef uint8_t hsmSecureBootFailOp_t;
#define HSM_SECURE_BOOT_ONFAIL_DEGRADE ((hsmSecureBootFailOp_t)0x00U) // 降级:照常放行,但不能用认证功能
#define HSM_SECURE_BOOT_ONFAIL_RESET ((hsmSecureBootFailOp_t)0x01U) // 软复位
#define HSM_SECURE_BOOT_ONFAIL_LOOP ((hsmSecureBootFailOp_t)0x02U) // 停核死循环(最狠)
固件落点(hsm_secure_boot.c:1212~1226):
if (g_boot_status == HSM_SECURE_BOOT_FAIL){
if (operationOnFail == HSM_SECURE_BOOT_ONFAIL_LOOP){
Sysclk_Init_Reback(); stopCore(); while(1){ watchDog_service(); } // 锁死
} else if (operationOnFail == HSM_SECURE_BOOT_ONFAIL_RESET){
Sysclk_Init_Reback(); softreset(); // 复位
} else { // DEGRADE
srvResp = HSM_SRV_RESP_OK; // 降级放行
}
}
注意:LOOP 和 RESET 前都会先调 Sysclk_Init_Reback() 把时钟回退(因为验签阶段 HSM 可能临时升频/降频了,失败要还原)。
3. 安全启动表与 A/B 双 bank 冗余
3.1 安全启动表结构 hsmSecurebootEntry_t
固件侧(hsm_common_type.h:897~920,NEW_SECURITY_BOOT 分支):
typedef struct {
uint32_t validFlag; // 有效性标志 = 0x87654321
hsmSecureBootType_t bootType; // NONE/FAST/STRICT
hsmSecureBootFailOp_t operationOnFail; // DEGRADE/RESET/LOOP
uint8_t segCount; // 代码段数量 1~8
uint8_t bootCore; // 位掩码:bit2=Z4_2, bit0=Z7_0, bit1=Z7_1
xoscClkCofigData_t xoscClkConfig; // 时钟快照(复位后验签用)
struct _verify_code_segment codeSeg[8]; // 8 段 {address, length}
hsmSecureBootTag_t authTags[8]; // 8 段签名(RSA 256B / AES-CMAC 16B)
HOST_ADDR authKeyHandle; // 验签密钥句柄
hsmKeyType_t authKeyType; // AES / RSA_PUB / RSA_PAIR
hsmSmrInstall_t smrInstall; // SMR 安全内存区安装
uint64_t reversed;
} hsmSecurebootEntry_t;
两个 validFlag 别搞混(§19.7 结论):
|----------------------------------|---------------------------------------------|-----------------|--------------------------------|
| 标志 | 位置 | 含义 | 值 |
| BOOT_VALID_FLAG | bank 末尾的 HSM_DATA_x_VALID_ADDR(8 字节 + 取反) | 判"这张启动表写完整了没有" | 库头文件定义(开源未见,语义同 0x87654321 套路) |
| hsmSecurebootEntry_t.validFlag | 表结构体第一个字段 | 判"这张表内容是否是有效配置" | 0x87654321 |
3.2 参数区 A/B 双 bank + ping-pong
地址布局(V229 PCSN,hsm_reg_define.h:100~123):
|-----|------------|----------|----------|----------|
| 区域 | 起始 | entry | COUNTER | VALID |
| A 区 | 0x00610000 | 0x610100 | 0x61FFF0 | 0x61FFF8 |
| B 区 | 0x00620000 | 0x620100 | 0x62FFF0 | 0x62FFF8 |
- VALID_ADDR =
BOOT_VALID_FLAG+ 按位取反(8 字节),两份对得上才认为"这张表写完整、有效"。
- COUNTER_ADDR = 单调递增 counter + 取反(8 字节),当 A/B 两份都有效时,用 counter 判"谁更新"。
- ping-pong 写入 (
HSM_Util_FlsSetDataWithBackUp,hsm_util.c:614~795):先擦备份 bank → 搬数据 → counter+1 写备份 counter → 写备份 valid → 最后擦旧 bank。这样任何时刻至少有一份完整的有效数据,断电/写一半也不会丢。
3.3 getBootEntryPtr() 两级仲裁(hsm_secure_boot.c:60~118)
选 bank 逻辑:
- 第一级(VALID):读 A、B 的 VALID 标志。若 A 无效 → 取 B;若 B 无效 → 取 A。
- 第二级(COUNTER) :若 A、B 都有效 ,比较 counter,
flag_b[0]-1 == flag_a[0]说明 B 更新 → 取 B,否则取 A。
- 兜底 :若 A、B 都无效 ,先 A 后 B(最终返回 B 地址),此时 B 里可能是全 0xFF(出厂未配置)或半写坏残留,无正确性保证。
⚠️ 关键澄清(此前踩过的坑):
getBootEntryPtr的两个 if 判的是"无效 ",所以 A/B 同时有效时两个 if 都不进,落到 counter 仲裁,不会互相覆盖。
3.4 第二道闸 isBootEntryValid()(hsm_secure_boot.c:667~673)
getBootEntryPtr 只返回地址指针、不校验内容。内容是否可用由第二道闸判断:
- 用表结构体自身的
validFlag字段判断(= 0x87654321 才有效)。
- 特判 :
validFlag == 0xFFFFFFFF && bootType == 0xFF→ 视为"从未配置"(全擦除态),等效 NONE,直接放行。
- 调用点
HSM_SRV_PreSecureBootCheck(hsm_secure_boot.c:1038):isBootEntryValid()==0→ 返回VERIFY_FAILED。
结论:全 0xFF 放行(等效 NONE),半写坏/残留垃圾 → 验签失败。
4. 端到端操作流程(8 大步)
下面是一条"从零到安全启动跑起来"的完整链路。每一步都给出:做什么 + 调什么接口 + 关键参数。
第 1 步 导入 DevAuth 密钥(AES-128,设备认证用)
第 2 步 打开 DevAuth 权限(质询-应答,拿到 SECURE_BOOT 权限)
第 3 步 导入验签密钥(RSA 公钥 或 AES 对称密钥)
第 4 步 对主核代码段算签名(RSA PKCS#1 v1.5 + SHA-256,或 AES-CMAC)
第 5 步 配置安全启动表(ConfigSecureBoot,含时钟快照)
第 6 步 查询/自检(PreSecureBootCheck / GetSecureBootStatus)
第 7 步 复位(安全启动表复位后由 HSM 消费)
第 8 步 HSM 验签 → 放核(自动完成,主核此时才真正跑起来)
第 1 步:导入 DevAuth 密钥
DevAuth 密钥是一把 AES-128 密钥,HSM 用它做设备认证(质询-应答)。测试代码默认值(tester_crypto.c:11):
uint8 App_DevAuthKey[16] = {
0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,
0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88
};
密钥句柄(三级寻址,GET_KEY_HANDLE(catalog, group, slot) = (catalog<<16)|(group<<8)|slot):
#define APP_AES128_DEVAUTH_KEY_HANDLE GET_KEY_HANDLE(HSM_KEY_CATALOG_ID_NVM, HSM_KEY_GROUP_SYM, 0)
量产注意:DevAuth 密钥是"设备认证"的信任根,量产后一旦设为不可导出/不可更新就改不了,烧录流程要一次性做对。
第 2 步:打开 DevAuth 权限
App_OpenDevAuth_New()(tester_crypto.c:83~137)完整流程:
AppStatus_t App_OpenDevAuth_New(const uint8_t *pKey, uint16_t keyLen, uint8_t devAuthOption)
{
// 2.1 检查 DevAuth key 是否已存在,不存在则导入
status = HSM_LLD_GetKeyInfo(APP_AES128_DEVAUTH_KEY_HANDLE, &keyInfo);
if (STATUS_HSM_KEY_EMPTY == status) {
HSM_LLD_ImportPlainSymKey(APP_AES128_DEVAUTH_KEY_HANDLE,
HSM_KEY_TYPE_AES,
HSM_KEY_FLAG_USAGE_SIGN | HSM_KEY_FLAG_USAGE_VERIFY | HSM_KEY_FLAG_USAGE_AUTH,
keyLen, pKey);
}
// 2.2 发起质询(DevAuthReq):HSM 返回 32 字节 challenge
hsmAuthScheme.macScheme.macAlgo = HSM_MAC_ALGO_CMAC;
Crypto_AutoSar_Ext_DevAuthReq(APP_AES128_DEVAUTH_KEY_HANDLE,
devAuthOption, &hsmAuthScheme, challenge);
// 2.3 本地用同一把 key 对 challenge 算 CMAC(应答)
HSM_LLD_AesCmacGenerate(HSM_OPERATION_MODE_SINGLE_CALL,
APP_AES128_DEVAUTH_KEY_HANDLE,
challenge, sizeof(challenge), cmac, &cmacLen);
// 2.4 回送应答(DevAuthResp):HSM 比对 CMAC,一致则开放权限
Crypto_AutoSar_Ext_DevAuthResp(cmac, cmacLen, NULL, 0);
return 0;
}
DevAuth 权限位 (Crypto_LLDriver.h:326~332,这是"门禁"能开哪些能力):
#define HSM_DEV_AUTH_KEY_MGMT (1U << 0U) // 密钥管理
#define HSM_DEV_AUTH_USERDATA_MGMT (1U << 1U) // 用户数据安全读写
#define HSM_DEV_AUTH_FW_UPDATE (1U << 2U) // 固件升级
#define HSM_DEV_AUTH_FLASH_OPERATION (1U << 3U) // Flash 操作
#define HSM_DEV_AUTH_SECURE_BOOT (1U << 4U) // 安全启动(★本文核心)
#define HSM_DEV_AUTH_ALL (HSM_DEV_AUTH_SECURE_BOOT | HSM_DEV_AUTH_KEY_MGMT | ...)
⚠️ 配置安全启动(ConfigSecureBoot)前,必须先拿到 HSM_DEV_AUTH_SECURE_BOOT 权限 ,否则固件第一行就返回 HSM_SRV_RESP_DEV_NOT_AUTHED(hsm_secure_boot.c:696~699)。
第 3 步:导入验签密钥(RSA 公钥)
以 RSA-2048 为例。密钥句柄(tester_crypto_SecureBoot.c:30~36,非 EB 分支用 slot 3/4):
#define APP_RSA2048_DEVAUTH_PUKKEY_HANDLE GET_KEY_HANDLE(HSM_KEY_CATALOG_ID_NVM, HSM_KEY_GROUP_ASYM, 3) // 公钥
#define APP_RSA2048_DEVAUTH_PRIKEY_HANDLE GET_KEY_HANDLE(HSM_KEY_CATALOG_ID_NVM, HSM_KEY_GROUP_ASYM, 4) // 私钥
导入公钥(只需 modulus + publicExponent 两段,不需要私钥,私钥只在"算签名"时用,放在产线):
Crypto_HSM_KeyImportExportType App_Rsa2048Pubkey;
App_Rsa2048Pubkey.pKey[0] = (uint8 *)rsa2048key_modulus; // 模数 N(2048 位)
App_Rsa2048Pubkey.u16KeyLength[0] = 2048;
App_Rsa2048Pubkey.pKey[1] = (uint8 *)rsa2048key_publicExponent; // 公钥指数 e
App_Rsa2048Pubkey.u16KeyLength[1] = sizeof(rsa2048key_publicExponent);
Crypto_KeyElementSet(APP_RSA2048_PUB_KEY_ID, CRYPTO_KE_CIPHER_KEY,
(uint8_t *)&App_Rsa2048Pubkey, sizeof(App_Rsa2048Pubkey));
Crypto_KeySetValid(APP_RSA2048_PUB_KEY_ID); // 置有效
对称方案(AES)则导入一把 AES 密钥(APP_AES_DEVAUTH_KEY_HANDLE = GET_KEY_HANDLE(NVM, SYM, 12)),验签用 CMAC。RSA 方案验签用 RSASSA-PKCS1-v1.5 + SHA-256,签名 256 字节;AES 方案签名 16 字节。
第 4 步:对主核代码段算签名
代码段地址宏 (tester_crypto.h:137~216,这是理解"要验哪些代码"的关键):
#if (RESOURCE_CHIP_CORE_NUM > 1U) // 多核
#define CODE_SEG_0_ADDR_Z4 (0x1100000) // Z4 代码从 0x1100000 起
#else // 单核
#define CODE_SEG_0_ADDR_Z4 (0x1000000)
#endif
#define CODE_SEG_0_LEN_Z4 (0x00020000) // 每段 128KB
#define CODE_SEG_1_ADDR_Z4 (CODE_SEG_0_ADDR_Z4 + CODE_SEG_0_LEN_Z4)
// ... 依此类推,最多 8 段
#define CODE_SEG_0_ADDR_Z7_0 (0x1000000) // Z7_0 代码从 0x1000000 起
#define CODE_SEG_0_ADDR_Z7_1 (0x1080000) // Z7_1 代码从 0x1080000 起
填充段表(initVerifyCodeSegments_Z4_8Segs,tester_crypto_SecureBoot.c:276~303):
verifyCodeSegments appCodeSeg;
initVerifyCodeSegments_Z4_8Segs(&appCodeSeg); // 填 8 段 {address, length}
算签名(tester_crypto_SecureBoot.c:624~635):
for (index = 0; index < HSM_SECURE_BOOT_MAX_CODE_SEG; index++) {
if (appCodeSeg.seg[index].address > 0 && appCodeSeg.seg[index].length != 0) {
HSM_LLD_RsaPkcs1V15GenerateSignature(
APP_RSA2048_DEVAUTH_PRIKEY_HANDLE, // 用私钥签
HSM_HASH_ALGO_SHA2_256, // 先 SHA-256 再 RSA
(uint8 *)appCodeSeg.seg[index].address,
appCodeSeg.seg[index].length,
FALSE, g_CodeSignArray.sign[index].authTag0, &signatureLength);
segNum++;
}
}
签名落地:把每个段的签名写到 Flash 固定区(SECURE_BOOT_CODE_SIGN_ADDR,测试代码 tester_crypto_SecureBoot.c:637~665 用 Fls_Erase + Fls_Write 每段写 512 字节)。
第 5 步:配置安全启动表 ConfigSecureBoot
MCAL 侧接口 Crypto_AutoSar_Ext_ConfigSecureBoot()(10 个入参),对应固件 HSM_SRV_ConfigSecureBoot(hsm_secure_boot.c:677),下发命令 HSM_SRV_CMD_CONFIG_SECURE_BOOT。
retVal = Crypto_AutoSar_Ext_ConfigSecureBoot(
HSM_SECURE_BOOT_TYPE_STRICT, // ① bootType:NONE/FAST/STRICT
APP_RSA2048_DEVAUTH_PUKKEY_HANDLE, // ② authKeyHandle:验签公钥句柄
HSM_SECURE_BOOT_ONFAIL_RESET, // ③ operationOnFail:DEGRADE/RESET/LOOP
&xoscConfigData, // ④ xoscConfig:时钟快照(可为 NULL)
&appCodeSeg, // ⑤ verifySegs:代码段表 {address,length}[8]
(codeSigns0 *)signs, // ⑥ signs:各段签名指针数组
segNum, // ⑦ signlens/segcnt:段数量 1~8
TRUE, // ⑧ bootZ4:是否放 Z4_2 核
FALSE, // ⑨ bootZ7_0:是否放 Z7_0 核
FALSE); // ⑩ bootZ7_1:是否放 Z7_1 核
固件侧校验顺序 (hsm_secure_boot.c:696~900,务必记牢,排查参数错误按这个顺序):
- 检查
HSM_DEV_AUTH_SECURE_BOOT权限(没开 → DEV_NOT_AUTHED)
bootType三选一(NONE/FAST/STRICT,其他 → INVALID_PARAM)
bootZ4/bootZ7_0/bootZ7_1至少一个 TRUE(bootCore==0 → INVALID_PARAM)
operationOnFail三选一
verifySegs非空(NONE 模式可空)
segCount1~8(0 或 >8 → INVALID_PARAM)
- 读验签密钥(
HSM_Util_ReadKey,读不到 → 验签失败)
genAuthTag_RSA/genAuthTag_AES验签
- 比对预置签名 vs 重算签名(不一致 → VERIFY_FAILED)
updateBootEntryInFlash写启动表到 D-Flash(A/B ping-pong)
第 6 步:查询 / 自检
Crypto_AutoSar_Ext_GetSecureBootStatus(&secureBootStatus); // 读回启动状态
Crypto_AutoSar_Ext_PreSecureBootCheck(...); // 复位前预检
启动状态值 (Crypto_LLDriver.h:392~396):
|------------------|------------------|
| 值 | 含义 |
| 0x00 NOT_START | 尚未开始验签 |
| 0x11 OK | 验签通过 |
| 0x22 FAIL | 验签失败 |
| 0x33 NONE | 未配置安全启动(等效 NONE) |
第 7 步:复位
ConfigSecureBoot 只是把表写进 D-Flash,当下不生效。真正生效要等下一次复位,HSM 启动后读启动表、验签、放核。
第 8 步:HSM 自动验签 → 放核
复位后 HSM 固件自动走 HSM_SRV_SecureBootCheck()(§2.1 已详述),验签通过后调 z4_z70_z71_app_boot() 释放主核,主核从代码段首地址开始执行。
5. 时钟配置:两条路径,别混
安全启动涉及时钟,最容易搞混。分清两条链:
|-------------|-------------------------------------------------------------|-------------------------------------------|------|-----------------------|
| 路径 | 接口 | 作用 | 生效时机 | 存哪 |
| ① 时钟快照 | ConfigSecureBoot(xoscConfig) | 把主核完整时钟树镜像给 HSM,复位后 HSM 用它初始化自己的时钟(验签阶段用) | 复位后 | 安全启动表 xoscClkConfig |
| ② 立即配分频 | MCU_HSM_SetClkDiv(divider) / HSM_LLD_SetClkDiv(divider) | 立即写 HSM_CLKDIV,改 HSM 当前分频 | 立即 | 寄存器 HSM_CLKDIV |
5.1 时钟快照 xoscClkCofigData_t
convXOSCConfig()(tester_crypto_SecureBoot.c:371~507)从 Mcu_ClockConfigType 提取完整时钟树,逐个填寄存器值,末尾置 validFlag = 0x87654321。包含:MC_CGM 各 SC_DC/AC 分频、XOSC.CTL、IRCOSC.CTL、PLLDIG.PLL0DV/PLL1DV/PLL1FM/PLL1FD 等。
void convXOSCConfig(xoscClkCofigData_t* xoscClkConfig, Mcu_ClockConfigType Mcu_ClockConfig) {
xoscClkConfig->MC_CGM_SC_DC0_Value = (1U << 31U) | Mcu_ClockConfig.cgmConfig.sc_dc0;
xoscClkConfig->MC_CGM_SC_DC1_Value = (1U << 31U) | Mcu_ClockConfig.cgmConfig.sc_dc1;
// ...(几十个寄存器字段)
xoscClkConfig->validFlag = HSM_SECURE_BOOT_ENTRY_VALID_FLAG; // 0x87654321
}
关键结论(§17/§18 已核实):
xoscConfig非必须 :固件里if (srvReq->xoscConfig != 0)才 memcpy(hsm_secure_boot.c:764~766),传 NULL 则不更新时钟字段。
- 它不是当下生效 ,而是随启动表写进 D-Flash,复位后由 HSM 消费。
- HSM 时钟 ≠ 主核时钟 :
xoscClkConfig是"镜像给 HSM 验签用"的,主核自己的时钟仍由应用层Mcu_Init配置。
- ⚠️ 待闭环点:源码里只"保存"未见"复位后写回 MC_CGM/PLLDIG/XOSC 寄存器"的显式消费点,复位后动时钟靠
checkAndWaitLowDownClock(hsm_secure_boot.c:182)+Sysclk_Init_Reback(仅 extern 声明,定义在链接库)+SET_CLK_DIV写 HSM_CLKDIV。
5.2 立即配分频的两条接口链
主核想立即给 HSM 配时钟分频,有两条现成链(§18 已核实):
- 标准 MCAL 入口
MCU_HSM_SetClkDiv(divider)(Mcu_LLDriver.c:4195~4219)→ 发SET_ATTR(attrId=HSM_ATTR_ID_CORE_CLK_DIVIDER=5)→ 固件hsm_attr.c:274~284写HSM_CLKDIV = 0x80000000UL | (val<<16)。
- 底层 Crypto 专用
HSM_LLD_SetClkDiv(divider)(Crypto_LLDriver.c:466~488)→ 写HSM.ALG_PARAM[1]+HOST_FLAG=HSM_SRV_CMD_SET_CLK_DIV→ 固件HSM_IRQHandler写 HSM_CLKDIV。
主核已经自动调用 :
Mcu_LLD_ConfigureSystemClockDividers(Mcu_LLDriver.c:614)在 HSM 使能时调MCU_HSM_SetClkDiv(sc_dc1>>16)(Mcu_LLDriver.c:631),无需客户额外写代码。
两条路径边界 :ConfigSecureBoot 的 xoscConfig = 完整时钟快照(复位后验签用);SetClkDiv = 单个分频值(立即改 HSM_CLKDIV)。
5.3 HSM 时钟上限
⚠️ 代码注释明确 HSM 时钟 MAX 100MHz,配置时钟别超,否则验签/运算可能异常。
6. 其他核的启动时间点(FAST/STRICT/NONE 逐一定位)
结论先行:启动其他核的是 HSM ,唯一放核函数是 z4_z70_z71_app_boot(needVerify, addrcheck)(hsm_util.c:4202~4629)。
6.1 三种模式的放核调用时机(hsm_secure_boot.c:1119~1244)
|------------------|------------------------------------|-------|----------------------------------|------------|
| 模式 | 放核调用 | 行号 | 参数 | 说明 |
| NONE | z4_z70_z71_app_boot(TRUE, FALSE) | L1125 | needVerify=TRUE, addrcheck=FALSE | 不验签,直接放核 |
| IgnoreSecureBoot | z4_z70_z71_app_boot(TRUE, FALSE) | L1131 | 同上 | 用户主动跳过验签 |
| FAST | z4_z70_z71_app_boot(TRUE, TRUE) | L1137 | needVerify=TRUE, addrcheck=TRUE | 验签前先放核 |
| STRICT | z4_z70_z71_app_boot(TRUE, TRUE) | L1243 | needVerify=TRUE, addrcheck=TRUE | 验签后才放核 |
6.2 z4_z70_z71_app_boot() 内部做了什么(hsm_util.c:4202~4629)
// 伪代码还原(按源码行号注释)
z4_z70_z71_app_boot(needVerify, addrcheck)
{
if (z4z7_already_boot) return; // 防重入(L4216/4628)
// MBIST 检查:Z70_MBIST_RESULT == 0x5A 才继续(L4237~4239)
// 在 app_located_addr[8] 里搜 boot header 的 isValidVector(L44/4176)
// PCSN:写 MC_ME.CCTL1 / CADDR1(L4592~4594)
// 写 MC_ME.MCTL 触发 DRUN 模式切换(L4613~4614)
}
核释放机制的本质 :HSM 直接写 MC_ME.CCTLx/CADDRx(配好各核的启动地址)+ MC_ME.MCTL(触发 DRUN 转换),不是主核自跳。也就是说,主核在被放出来之前一直处于复位/停机态,由 HSM 决定何时、从哪个地址让它起跑。
6.3 多核 bootCore 位掩码
bootCore 是位掩码(hsm_secure_boot.c:728~738):
if (srvReq->bootZ4) secureBootEntry.bootCore |= 0x01 << CORE_ID_Z4_2; // Z4_2
if (srvReq->bootZ7_0) secureBootEntry.bootCore |= 0x01 << CORE_ID_Z7_0; // Z7_0
if (srvReq->bootZ7_1) secureBootEntry.bootCore |= 0x01 << CORE_ID_Z7_1; // Z7_1
核心 ID 枚举(hsm_core.h):Z7_0=0、Z7_1=1、Z4_2=2、Z7_3_Check=3、Z0_4_HSM=4。
对应关系:
bootZ4=TRUE→ bit2(Z4_2);bootZ7_0=TRUE→ bit0;bootZ7_1=TRUE→ bit1。至少一个为 TRUE,否则bootCore==0→ 参数错误。
6.4 三种模式的时间轴(可视化)
【NONE】 上电 → HSM 初始化 → 直接放核 → 主核跑(永不验签)
【FAST】 上电 → HSM 初始化 → 放核(主核先跑) → HSM 后台验签 → 失败则 operationOnFail 兜底
【STRICT】 上电 → HSM 初始化 → HSM 验签(主核等待) → 通过才放核 → 主核跑
^ ^
└── 主核处于复位/停机态 ──────────────┘
7. 完整代码配置示例(RSA-2048 STRICT 单核)
把 §4 的 8 步串成一段可直接参照的代码(精简自 tester_crypto_SecureBoot.c:510~689):
略
8. 参数速查总表(设置参数一页尽览)
8.1 ConfigSecureBoot 十参
|---|-----------------|-------------------------------------|----------|------------|
| # | 参数 | 类型/取值 | 必填 | 说明 |
| ① | bootType | NONE=0xFF / FAST=0x00 / STRICT=0x01 | 是 | 启动模式 |
| ② | authKeyHandle | RSA 公钥 / AES 密钥句柄 | 是(非NONE) | 验签密钥 |
| ③ | operationOnFail | DEGRADE=0 / RESET=1 / LOOP=2 | 是(非NONE) | 失败动作 |
| ④ | xoscConfig | 时钟快照指针,可 NULL | 否 | 复位后 HSM 时钟 |
| ⑤ | verifySegs | {address,length}8 指针 | 是(非NONE) | 代码段表 |
| ⑥ | signs | 各段签名指针数组 | 是(非NONE) | 签名 |
| ⑦ | segcnt | 1~8 | 是(非NONE) | 段数量 |
| ⑧ | bootZ4 | TRUE/FALSE | 至少一个TRUE | 放 Z4_2 |
| ⑨ | bootZ7_0 | TRUE/FALSE | 至少一个TRUE | 放 Z7_0 |
| ⑩ | bootZ7_1 | TRUE/FALSE | 至少一个TRUE | 放 Z7_1 |
8.2 服务侧结构体对照(hsm_srv_type.h:709~726)
typedef struct {
hsmSecureBootType_t bootType;
hsmKeyHandle_t authkeyHandle;
hsmSecureBootFailOp_t operationOnFail;
HOST_ADDR xoscConfig;
HOST_ADDR verifySegs;
HOST_ADDR signs;
uint32_t signlens;
boolean bootZ4;
boolean bootZ7_0;
boolean bootZ7_1;
} hsmSecureBootConfigSrv_t;
8.3 签名类型对照(hsm_common_type.h:832~849)
// 主核侧:uint8_t authTag0[512](RSA 最大 512 字节,AES-CMAC 16 字节)
typedef struct { uint8_t authTag0[512]; } hostSecureBootTag0_t;
// 认证算法(SMR/扩展用)
#define HSM_AUTH_ALGO_CMAC 0x00 // AES-CMAC
#define HSM_AUTH_ALGO_GMAC 0x01
#define HSM_AUTH_ALGO_HMAC 0x02
#define HSM_AUTH_ALGO_SM2 0x03
#define HSM_AUTH_ALGO_ECDSA 0x04
#define HSM_AUTH_ALGO_RSASSA_PKCS1V15 0x05 // ★RSA 安全启动默认
#define HSM_AUTH_ALGO_RSASSA_PSS 0x06
8.4 验签密钥类型判断(hsm_secure_boot.c:1180~1201)
|-------------------------------------|------------------|----------------|
| authKeyType | 验签函数 | 签名长度 |
| HSM_KEY_TYPE_AES | genAuthTag_AES | 16 字节(CMAC) |
| HSM_KEY_TYPE_RSA_PAIR / RSA_PUB | genAuthTag_RSA | 256 字节(2048 位) |
9. 常见坑与排查清单(FAE 实战)
9.1 配置阶段常见错误
|----------------------------------------------|----------------------------|-------------------------------------------------------------|
| 现象 | 根因 | 排查 |
| 返回 DEV_NOT_AUTHED | 没先开 DevAuth SECURE_BOOT 权限 | 确认 App_OpenDevAuth_New(..., HSM_DEV_AUTH_SECURE_BOOT) 已成功 |
| 返回 INVALID_PARAM + "bootType error" | bootType 不是 0/1/0xFF | 核对枚举值 |
| 返回 INVALID_PARAM + "no core is set boot" | 三个 bootZx 全 FALSE | 至少放一个核 |
| 返回 INVALID_PARAM + "no valid code segment" | segcnt 为 0 或 >8 | 段数量 1~8 |
| 返回 VERIFY_FAILED | 签名不匹配 | 检查私钥/公钥是否配对、段地址长度是否与签名时一致 |
| 返回 KEY_INVALID | 验签密钥类型不对 | 确认是 AES 或 RSA_PUB/RSA_PAIR |
9.2 复位后验签阶段常见错误
|-----------------|---------------------|-------------------------------------|
| 现象 | 根因 | 排查 |
| 主核起不来、HSM 复位循环 | 验签失败 + ONFAIL_RESET | 抓 HSM 串口日志,看 HSM_SECURE_BOOT_FAIL |
| 全 0xFF 却直接放行 | 启动表从未配置(等效 NONE) | 确认 ConfigSecureBoot 真的写成功了 |
| 半写坏/残留垃圾 → 验签失败 | A/B 都无效,兜底取了坏数据 | 检查 D-Flash 启动表区域是否被误擦 |
| 主核时钟不对 | xoscConfig 没传或没生效 | 分清"快照(复位后)" vs "立即分频"两条链 |
9.3 高频踩坑点(速记)
- 密钥一次性:DevAuth/验签密钥导入后设不可导出不可更新,就再也改不了,烧录流程一次性做对。
- 生命周期收窄:切生命周期后权限收窄、调试口可能被锁,量产前规划。
- HSM 时钟 ≤ 100MHz:别超。
- 失败动作提前定义:RESET/LOOP/DEGRADE 三者对客户体验差异巨大,别让主核带病运行。
- A/B 双份别只看一份:排查启动表问题要看 A、B 两个 bank + VALID/COUNTER。
- FAST 不是"不验签":FAST 是"先放后验",失败仍会触发 operationOnFail,只是主核已经跑了一段。
一句话总结:安全启动 = 上电 HSM 先跑 → 读 A/B 启动表 → 按 NONE/FAST/STRICT 决定"先放核还是先验签" → RSA/AES 验签 → 通过由 z4_z70_z71_app_boot 释放主核;时钟走"快照(复位后)"+"立即分频"两条链;密钥与权限是整条链的信任根。