ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

NXP S32K3xx HSE安全启动实战:SMR配置与多核协同

NXP S32K3xx HSE安全启动实战:SMR配置与多核协同 1. 为什么S32K3xx的HSE安全启动值得花时间啃第一次接触S32K3xx的HSEHardware Security Engine安全启动很多人会被那一堆缩写砸晕SMR、HSE FW、IVT、SBAF、MU、LC、UCID……文档翻了几百页代码还是跑不起来。我自己从S32K344的裸机工程开始到把HSE固件刷进去、配好SMR、让多核各自完成校验并跳转到应用前后折腾了将近三周中间踩过的坑足够写一本小册子。这篇内容就是把这套流程完整拆开讲一遍。核心关键词是NXP S32K3xx、HSE、安全启动、SMR。我会从HSE的基本角色讲起把SMRSecure Memory Region配置、HSE固件安装、多核启动协同、常见报错排查这几块串起来给出可以直接参考的配置思路和代码片段。适合正在做S32K3系列功能安全或信息安全项目的嵌入式工程师也适合刚拿到S32K3开发板、想搞清楚“为什么我的程序一上电就卡在启动阶段”的朋友。先说清楚HSE在S32K3里到底扮演什么角色。S32K3是NXP面向汽车和工业的高可靠MCU内部集成了一个独立的HSE子系统它有自己的内核、RAM、ROM和加密加速单元独立于应用核Cortex-M7运行。HSE负责密钥管理、加解密、安全启动校验、生命周期管理这些安全相关的事。应用核想用安全功能必须通过MUMessaging Unit跟HSE通信发命令、收响应。安全启动的本质就是让HSE在应用核真正跑起来之前先验证要执行的代码是不是被篡改过验证通过才放行。SMR就是这套验证机制里的“规则表”。你告诉HSE从哪个地址开始、多长的一段内存、用什么密钥、做哪种校验。HSE按你配的规则去验验过了才允许对应核启动。听起来简单但SMR的配置项、密钥槽位、校验类型、多核之间的先后顺序每一项都有讲究。下面我按实际操作的顺序一层层展开。2. HSE安全启动的整体设计与方案选型2.1 安全启动到底在防什么先想清楚威胁模型才知道配置该怎么选。安全启动主要防两类问题一是Flash里的应用代码被恶意替换或篡改二是启动流程被劫持跳到未授权的代码上执行。S32K3的HSE通过校验镜像的完整性和真实性来防这两类问题。完整性靠哈希比如SHA-256真实性靠签名比如ECDSA、RSA或者对称密钥的CMAC。这里有个关键选择用签名校验还是CMAC校验。签名校验用非对称密钥公钥存在HSE里私钥在开发端保管安全性高适合量产阶段CMAC用对称密钥速度快、资源占用小适合开发调试阶段或者对性能敏感的场景。我自己的做法是开发阶段先用CMAC快速迭代量产前切到ECDSA签名。这个切换不是改一行代码那么简单密钥槽位、SMR配置、镜像生成工具都要跟着变后面会细说。2.2 为什么SMR要按核和区域分开配S32K3是多核架构以S32K344为例有锁步的Cortex-M7主核还有独立的Cortex-M7核CM7_1。每个核有自己的启动入口和代码区。HSE的SMR是按“内存区域”来定义的一个SMR描述一段连续的地址空间和它的校验规则。多核场景下你不能用一个SMR覆盖所有核的代码因为不同核的代码可能用不同的密钥、不同的校验强度而且启动时机也不一样。我的方案是主核的启动代码包括启动头、向量表、HSE通信初始化用一个SMR从核的应用代码用另一个SMR共享的库或数据区如果需要保护再单独配。这样做的理由是主核代码是启动链的根必须最先校验、最严格从核代码可以在主核完成HSE初始化之后再校验灵活性更高。如果所有代码塞进一个SMR一旦某段代码需要更新整个SMR都要重算量产维护会很痛苦。2.3 HSE固件版本与SMR格式的匹配问题这一点特别容易被忽略。HSE固件HSE FW本身是有版本的不同版本的FW支持的SMR字段、命令集、密钥槽数量可能不一样。我遇到过用旧版FW的SMR配置模板去配新版FW结果HSE返回“invalid SMR”错误。所以动手之前先确认你手上的HSE FW版本然后找对应版本的HSE Firmware Reference Manual。S32K3的HSE FW通常随RTDReal-Time Drivers包一起发布在HSE_FW目录下能看到版本号和对应的文档。选型上我建议直接用RTD包里配套的HSE FW和配置工具不要自己去拼。NXP提供的HSE配置工具比如基于S32 Design Studio的插件或者独立的配置工具能生成SMR配置的二进制和C数组省去手写寄存器的麻烦。但工具生成的配置你得看得懂否则出了问题无从排查。3. SMR配置的核心细节与实操要点3.1 SMR的数据结构长什么样SMR在HSE里是一个结构体数组每个元素描述一个受保护区域。核心字段包括起始地址、长度、校验类型哈希/签名/CMAC、密钥索引、以及一些标志位比如是否允许该区域被调试器读取。具体字段名各版本略有差异但逻辑一致。下面是一个简化后的SMR条目示意基于常见实践实际字段以你手上的HSE FW文档为准typedef struct { uint32_t start_addr; // 受保护区域起始地址通常要求对齐 uint32_t length; // 区域长度必须是块大小的整数倍 uint8_t check_type; // 0哈希, 1CMAC, 2ECDSA签名 uint8_t key_index; // 使用的密钥槽位 uint8_t flags; // 访问权限、调试控制等 uint8_t reserved; } smr_entry_t;地址对齐是个硬性要求。S32K3的HSE通常要求SMR起始地址按4字节或更大边界对齐长度按块大小比如64字节对齐。我一开始没注意配了个非对齐的地址HSE直接返回参数错误查了半天才发现是地址没对齐。所以配SMR之前先把你的代码段地址和长度算清楚用链接脚本里的符号比如__start_text、__end_text来取别手写死地址。3.2 密钥槽位怎么规划HSE内部有一组密钥槽Key Slot每个槽可以存对称密钥或非对称密钥的公钥。SMR引用的是槽位号。规划槽位时要注意有些槽位是HSE保留的比如用于FW自身校验不能随便用有些槽位在生命周期状态变化后会被锁定或擦除。我的做法是画一张表把每个槽的用途、密钥类型、对应哪个SMR、在哪个生命周期状态可用全部列清楚。槽位号用途密钥类型对应SMR可用生命周期0HSE FW保留--全部1主核启动代码ECDSA公钥SMR0OEM生产及以后2从核应用代码CMAC密钥SMR1开发及以后3共享数据区CMAC密钥SMR2开发及以后这张表在项目初期就要定下来因为密钥一旦写入槽位在高级生命周期状态下是改不了的。开发阶段用“开发模式”可以反复擦写但量产模式比如OEM生产状态下密钥写入就是一次性的。我见过有人开发时随便用槽位量产时发现槽位不够或者被占用只能重新流片代价极大。3.3 校验类型的选择与性能权衡前面提到签名和CMAC的选择。这里补充一个实际数据在S32K344上用CMAC校验1MB代码大约几十毫秒用ECDSA验签同样的代码可能要几百毫秒甚至更长因为非对称运算慢。安全启动是在上电时做的太慢会影响启动时间。汽车项目对启动时间有要求比如CAN通信要在100ms内就绪所以校验策略要平衡。我的策略是主核的关键启动代码几十KB用ECDSA保证根信任大块的应用代码用CMAC速度快。如果项目对安全性要求极高可以全用签名但要接受启动变慢或者用HSE的并行校验能力如果FW支持来分摊时间。另外哈希校验本身很快如果只防意外损坏不防恶意篡改纯哈希也能用但安全性最低不建议在安全启动里用。注意校验类型一旦在SMR里定下对应的密钥必须提前注入HSE。密钥注入本身是个独立流程通常通过HSE的密钥导入命令或者NXP的密钥配置工具完成不要在应用代码里硬编码密钥。3.4 SMR配置的生成与烧录配置SMR有两条路一是用NXP的配置工具图形化生成二是手写配置数组然后通过HSE命令下发。工具生成的好处是不容易出错坏处是黑盒出问题不好查。我建议两条路都走一遍先用工具生成一份对照文档理解每个字段然后自己手写一份做对比确认理解无误。生成的SMR配置最终要变成HSE能识别的格式通常是一个二进制块通过MU发给HSE的“Install SMR”命令。这个命令一般在启动早期、HSE FW安装之后、应用核跳转之前执行。烧录时要注意SMR配置本身也要存在Flash的某个固定位置HSE启动时会去读。这个位置在S32K3的启动流程里有约定查参考手册的“Boot Flow”章节能找到。4. 多核协同启动的实操过程4.1 启动流程的完整时序S32K3上电后的启动顺序大致是ROM code先跑初始化基本时钟和HSE然后HSE FW从Flash加载并自检接着HSE读取SMR配置开始校验主核代码。主核代码校验通过后主核开始执行初始化MU、跟HSE建立通信、触发从核的校验和启动。从核代码校验通过后从核才被释放执行。这个时序里主核和从核的同步是关键。从核不能自己先跑起来必须等主核通知。S32K3提供了核间中断和启动控制寄存器来实现这个同步。我的做法是从核上电后先进入一个等待循环检查一个共享的启动标志放在共享RAM里主核完成HSE初始化和从核SMR校验后置位这个标志并触发从核中断从核才跳转到自己的应用入口。4.2 主核侧的关键代码主核的职责是初始化MU、安装SMR、触发校验、等待结果、释放从核。下面是一段示意代码展示主核如何通过MU跟HSE交互具体API以RTD的HSE驱动为准/* 初始化MU建立与HSE的通信通道 */ HSE_MU_Init(); /* 安装SMR配置config是生成的SMR数组 */ hseStatus HSE_InstallSmr(SMR_CONFIG_BASE, SMR_CONFIG_SIZE); if (hseStatus ! HSE_STATUS_SUCCESS) { /* 安装失败进入安全错误处理 */ HSE_ErrorHandler(); } /* 触发主核代码校验 */ hseStatus HSE_VerifySmr(SMR_INDEX_MAIN_CORE); if (hseStatus ! HSE_STATUS_SUCCESS) { HSE_ErrorHandler(); } /* 主核校验通过继续初始化 */ SystemInit(); /* 触发从核代码校验 */ hseStatus HSE_VerifySmr(SMR_INDEX_SECOND_CORE); if (hseStatus ! HSE_STATUS_SUCCESS) { HSE_ErrorHandler(); } /* 置位共享标志释放从核 */ SHARED_FLAG CORE1_START_MAGIC; __DSB(); /* 触发从核中断或直接写启动控制寄存器 */ CORE1_START_CTRL 1;这里有个细节HSE_VerifySmr是阻塞还是非阻塞取决于驱动实现。如果是非阻塞主核要轮询HSE的响应或者等MU中断。我用的RTD版本里校验命令是异步的需要等MU的接收中断然后在中断里读结果。如果直接轮询要注意超时处理别死等。4.3 从核侧的等待与跳转从核的代码相对简单核心是等待主核的信号然后跳转到应用。示意如下void Core1_Entry(void) { /* 等待主核置位启动标志 */ while (SHARED_FLAG ! CORE1_START_MAGIC) { __WFE(); /* 低功耗等待省电 */ } /* 内存屏障确保看到主核的所有写入 */ __DSB(); __ISB(); /* 跳转到从核应用入口 */ Core1_AppMain(); }__WFE和内存屏障这两句别省。我一开始没加屏障从核偶尔会读到旧的标志值导致启动随机失败。加了__DSB和__ISB之后稳定了。这种多核同步的坑单核思维很容易忽略。4.4 共享RAM的分配与保护主核和从核通信用的共享标志、状态变量要放在两个核都能访问的RAM区域。S32K3的RAM分区域有些区域默认只给某个核访问需要在启动时配置访问权限通过AXBS或类似的交叉开关。如果共享区没配对从核读到的可能是0或者触发总线错误。我的做法是在链接脚本里单独划一段共享RAM两个核的链接脚本都引用同一段地址然后在启动代码里配置这段RAM的访问权限为两个核都可读写。共享区里的变量用volatile修饰防止编译器优化掉读写。另外共享区如果也要被SMR保护记得把它纳入某个SMR的范围否则它就成了安全链上的缺口。5. 常见问题与排查技巧实录5.1 HSE返回“SMR校验失败”怎么查这是最常见的报错。排查顺序我总结成一张表排查项可能原因处理方法地址/长度未对齐或超出实际代码范围用链接脚本符号取地址确认对齐密钥密钥未注入或槽位错误检查密钥注入流程和槽位号校验类型SMR配的类型与密钥不匹配确认CMAC/签名与密钥类型一致镜像代码被修改但SMR未更新重新生成镜像和SMR配置FW版本SMR格式与FW版本不兼容核对FW文档用配套工具生成我遇到最多的是“代码改了但SMR没重算”。开发阶段频繁改代码每次改完都要重新生成SMR配置并烧录否则HSE校验的是旧哈希必然失败。后来我把SMR生成集成到构建脚本里每次编译自动生成省了很多事。5.2 启动卡死无响应的定位思路如果上电后没有任何输出先确认是不是卡在HSE校验阶段。方法是用调试器连上主核看PC停在哪里。如果停在HSE驱动的等待循环里说明HSE没响应。可能的原因HSE FW没正确加载、MU没初始化好、SMR配置地址错误导致HSE读不到。我遇到过一次是HSE FW的加载地址和链接脚本冲突FW被覆盖了HSE起不来。后来把FW区域在链接脚本里保留出来问题解决。另一个常见原因是生命周期状态不对。HSE在不同生命周期状态下行为不同比如在“开发”状态下某些校验可以跳过在“生产”状态下必须严格校验。如果设备被误切到生产状态而密钥没准备好启动就会失败。用HSE的“Get Life Cycle”命令可以查当前状态。5.3 多核启动不同步的典型表现从核偶尔不启动或者启动后跑飞。除了前面说的内存屏障问题还可能是从核的SMR校验没通过但主核没检查返回值。主核触发从核校验后一定要检查HSE的响应确认校验成功再释放从核。我有一次图省事没检查结果从核代码有问题校验失败但从核还是被释放了跑飞后触发了硬件错误现象很难定位。还有一种情况是从核的向量表没配对。S32K3的每个核有自己的向量表从核的向量表地址要在启动时设置到对应的寄存器比如VTOR。如果从核跳转后用的还是主核的向量表中断一来就乱套。这个在从核初始化代码里要显式设置。5.4 调试器连接对安全启动的影响安全启动和调试器是有冲突的。如果SMR配置里禁止了调试访问连上调试器可能导致HSE认为安全被破坏拒绝启动或者触发安全响应。我在调试阶段会把SMR的调试权限打开量产前再关掉。另外有些调试操作比如读受保护区域会触发HSE的访问控制返回错误。调试时如果发现读某个地址失败先查这个地址是不是在SMR保护范围内。提示开发阶段建议保留一个“调试友好”的SMR配置量产时切换到“安全严格”配置。两套配置分开管理别混用。5.5 密钥注入失败的排查密钥注入是独立于SMR配置的流程但两者强相关。注入失败常见原因生命周期状态不允许注入、密钥格式不对、MU通信超时。我建议密钥注入单独做一个测试工程先确认能成功注入和读回读回通常只返回密钥的哈希或状态不返回明文再集成到主流程。注入时注意密钥的字节序和对齐HSE对密钥格式有严格要求差一个字节就失败。6. 几个容易被忽略的实操心得6.1 SMR配置的版本管理SMR配置和代码是强绑定的代码一变SMR就得变。所以SMR配置文件必须跟代码一起做版本管理最好在构建时自动生成并记录哈希。我现在的做法是构建脚本编译完代码后自动调用SMR生成工具把生成的配置和当前代码的git commit号一起存档。这样任何时候都能追溯某个固件对应哪份SMR配置出了问题能快速定位。6.2 启动时间的实测与优化安全启动会增加启动时间具体增加多少要实测。我用GPIO翻转加示波器测过CMAC校验1MB代码约40msECDSA验签256KB代码约300ms。如果项目要求启动快可以把校验分散到多个核并行做或者只对关键代码做签名、其余做CMAC。实测数据比文档里的理论值靠谱建议自己测一遍。6.3 安全错误处理的设计HSE校验失败后怎么办这个要在设计阶段就想好。直接死循环是最简单的但不利于诊断。我的做法是校验失败时把错误码写到一个保留的Flash区域或者通过CAN发出去然后进入一个可控的安全状态比如关闭输出、进入低功耗。这样现场出了问题能拿到错误信息而不是一块“砖”。6.4 从核代码的独立性从核代码尽量独立编译、独立链接生成独立的镜像这样它的SMR可以单独配置和更新。如果从核代码和主核代码混在一个镜像里更新从核代码就要重算主核的SMR牵一发动全身。独立镜像的代价是链接脚本复杂一点但维护性好很多。6.5 HSE命令的超时与重试HSE通信不是百分百可靠偶尔会因为MU忙或者HSE内部状态返回忙。我的驱动里给每个HSE命令加了超时和有限次重试。超时时间根据命令类型定校验类命令给长一点比如500ms配置类命令短一点。重试次数不要太多两三次够了多了可能是真有问题重试也白搭。这套流程走下来S32K3xx的HSE安全启动从配置到多核协同基本就通了。最难的不是某个单点技术而是把SMR、密钥、多核同步、错误处理这些环节串成一条完整的链每个环节都不能掉。我自己的经验是先在开发板上把最小系统跑通单核、CMAC、一个SMR再逐步加签名、加从核、加错误处理每加一步都实测验证别想着一次配好。踩过的坑多了自然就有感觉了。
返回列表