ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

STM32H533 OEMiROT中AXI Non-Secure访问使能问题解析

STM32H533 OEMiROT中AXI Non-Secure访问使能问题解析 如果你最近在搞STM32H533这颗料而且已经用STM32CubeMX生成过一个OEMiROT的启动工程那“non secure project”这个词你一定不陌生。我前段时间正好用H533做了一套基于OEMiROT的启动方案应用部分完全跑在Non-Secure世界结果第一版代码在跳转之后各种外设一访问就卡死要么寄存器写不进去要么直接进HardFault。折腾了几天最后才发现问题出在一个很多人容易忽略的环节AXI总线上的Non-Secure访问使能。这篇文章就把我这次从建工程到排坑的全过程整理出来重点讲清楚OEMiROT和Non-Secure应用之间那条看不见的边界以及“axi non secure enablement”到底在使能什么、怎么使能、踩坑时怎么定位。适合正在用STM32H5系列做安全启动、或者刚接触TrustZone想搞明白Secure/Non-Secure外设权限的工程师。1. 这个工程到底在解决什么问题OEMiROT和Non-Secure世界的关系1.1 为什么量产设备需要一个“不可变信任根”做量产嵌入式设备的人应该都有体会固件安全不只是“加密一下”这么简单。你要防的是别人把Flash读出来、反汇编、改掉关键逻辑再刷回去。STM32H533这颗芯片定位就是中高端工业/物联网控制硬件上支持TrustZone、加密引擎、安全存储这些东西但如果你不上信任根前面的安全特性都白搭。信任根Root of Trust, RoT是整个安全链路的起点。它必须在芯片复位后第一个执行且不被任何外界代码修改。ST在STM32H5系列上提供了两种iRoT方案STiROT和OEMiROT。区别很简单STiROT的根密钥由ST托管OEMiROT的根密钥握在你自己手里。对很多做产品的公司来说密钥自持是刚需所以OEMiROT在这颗料上用得比较多。OEMiROT会被预先烧录在系统FlashSystem Flash里复位后CPU固定从那里开始执行。它负责验证外部Flash或者内部Flash里的用户应用镜像验签通过之后才把控制权交给应用。这个过程中OEMiROT也会完成TrustZone环境的最初配置包括SAU区域划分、RIF外设安全属性初始化为后续Secure/Non-Secure世界的切换做准备。换句话说它是那个“第一个被信任、且从此不再被修改”的角色。1.2 为什么用户应用可以只跑Non-Secure很多第一次接触TrustZone的人会有一个困惑既然做了安全启动为什么应用还要跑在Non-Secure世界不能整个系统都放Secure世界吗理论上当然可以全部放Secure但实际产品里没人这么干。原因很现实Secure世界的代码权限太高一旦应用层有漏洞被外部输入利用攻击者直接就在最高权限下执行了把应用放到Non-Secure世界即便被攻破Secure世界里的密钥和关键配置依然是隔离的攻击面被压缩到一个相对有限的范围内。这也正是Arm TrustZone的安全模型Secure世界用最小的代码量提供安全服务Non-Secure世界跑业务逻辑。OEMiROT的non-secure project就是这个思路的典型落地。你不用在应用层写任何安全逻辑只要在Secure世界放一个最小的初始化器配合OEMiROT完成安全配置然后把跳转指针交给Non-Secure的main函数就行。工程上这种结构会让你的应用代码跟普通单片机项目几乎一样开发不用处处留意安全限制只在启动阶段多了一层Secure引导代码。我在实际选型时看重的是H533的硬件加密引擎和安全存储但业务代码又不想被安全逻辑拖慢所以最终方案就是OEMiROT做不可变根Non-Secure App跑全部业务逻辑Secure世界只保留启动配置和少量安全服务回调。这个结构简单、安全边界清晰量产维护起来也方便。2. 理解前面那个坑的源头AXI外设的Non-Secure权限由谁决定2.1 TrustZone、SAU、RIF这三个家伙是怎么分工的要搞清楚“AXI non secure enablement”是什么得先把TrustZone体系里的几个组件分开来看否则后面排查会绕很多弯路。TrustZone是ARMv8-M架构层面的安全隔离机制它把CPU执行状态分成Secure和Non-Secure两种。CPU发出的每一次访存请求都会携带一个安全属性位这个位决定了这次访问是Secure事务还是Non-Secure事务。但要真正落地光有CPU状态还不够内存和外设都得能识别这个属性于是就需要SAU和RIF。SAUSecurity Attribution Unit是CPU内部的安全属性标记单元。它其实是一个地址匹配表通过配置一系列区域告诉CPU哪段地址是Safe、哪段是Non-Secure。但SAU只管CPU视角的地址属性管不到SoC内部的外设。真正在STM32H533上控制外设访问权限的是RIFResource Isolation Framework。H5系列是ST第一款全面引入RIF的MCU系列它比传统Cortex-M33的PPC外设保护更细。RIF给每个外设、每块SRAM、甚至Flash接口都配了一组访问控制寄存器里面最关键的几个字段就是SEC安全属性、PRIV特权属性和CID隔离分区号。你可以把RIF理解成一张“门口的权限表”。外设在哪里、谁有权访问它说了算。CPU就算在Non-Secure世界发出访问请求如果RIF里对应外设的SEC位配置成了1Secure only那这次访问就会被硬件直接拒绝表现就是寄存器读出来全是0xFF、写不进去、外设中断不响应。很多老工程师第一次碰H5都栽在这上面。2.2 所谓“axi non secure enablement”改的是哪几个寄存器现在可以说回“axi non secure enablement”这个词了。它说的并不是某个单独的寄存器而是一类操作让AXI总线互联上挂载的存储器和外设能够接收并响应Non-Secure事务。在STM32H533里AXI总线上挂的东西主要是Flash控制器和SRAM控制器。CPU取指、读常量、搬数据都要走AXI总线。如果你只把地址配置成Non-Secure但Flash控制器或SRAM控制器的RIF寄存器还是Secure only那Non-Secure世界的代码一样跑不起来甚至取指就会锁死。我那次遇到的具体情况是这样CubeMX生成的OEMiROT工程默认把所有存储器分区都配置成了SecureNon-Secure App的向量表放在外部Flash地址区但RIF里对应那个存储区域的安全属性没有放开。结果就是OEMiROT验签通过跳转指令也执行了可Non-Secure App第一条指令就取不到直接掉进HardFault。这类问题要改的核心寄存器主要有几个方向SAU区域寄存器SAU-RBAR、SAU-RLAR设置哪段地址属于Non-Secure这个是CPU视角。RIF里对应存储控制器的访问配置寄存器比如Flash接口的RIF配置、SRAM各分区的RIF配置把SEC位清掉。如果外部存储器走的是OSPI/FMC这类总线外设还需要在对应外设的RIF配置里把安全属性放开否则CPU访问不到外部Flexible Memory Controller映射的地址。这些配置在CubeMX里大多有图形化界面但你需要知道它生成的代码改的是什么。后面我在实测环节会详细讲排查过程这里先记住一个判断原则Non-Secure代码能跑起来的前提是它要访问的每一段地址空间、每一个外设在RIF和SAU两层都被标记为Non-Secure可访问缺一不可。3. 手把手创建H533的OEMiROT non-secure工程3.1 CubeMX里的配置顺序和关键开关我用的环境是STM32CubeMX 6.10以上的版本配合STM32CubeIDE 1.15芯片包版本对应最新H5系列支持包。生成OEMiROT工程的关键点就在配置顺序上顺序不对后面选项都不出现。先在CubeMX里选STM32H533这颗芯片新建工程后第一步打开System Core菜单找到SYS配置把Debug选成Serial Wire否则后面烧录和调试会异常。接下来是核心步骤在Security菜单下打开TrustZone使能。这一步会触发一次工程配置变更CubeMX会提示你重新配置时钟和内存映射直接确认即可。TrustZone打开之后Security菜单下面会多出几个子项其中就有你要的OEMiROT。选择OEMiROT之后CubeMX会弹出RoT配置页面让你选择是生成完整的三段式工程BootSecure AppNonSecure App还是只生成Boot和NonSecure App。既然目标是non-secure project直接选不带Secure App的简化模式。如果后续你还需要安全服务再手动加一个Secure工程也不迟。这里有个容易踩的坑OEMiROT配置页里有几个启动镜像相关的参数比如镜像在外部Flash的偏移地址、签名算法选择。务必把镜像偏移地址和你后续烧录外部Flash的地址对齐否则OEMiROT验签时找不到镜像就会一直停在boot阶段看起来像是程序没烧进去。时钟树就按H533最大主频250MHz配HSE外部晶振频率按你的板子填这个不展开每家板卡不一样。3.2 生成后的两个工程怎么协作代码生成之后你会得到两个独立的工程文件夹一个带Boot后缀一个带AppNonSecure后缀。这两个工程要分别编译Boot工程烧录到内部Flash的系统区域或者是用户指定的保留区AppNonSecure工程编译出来的镜像烧录到外部Flash。先看Boot工程。它的main函数里除了系统时钟初始化还会执行一系列安全配置函数比如TZ_SAU_Setup、RIF_Config和跳转函数。这些函数名不同CubeMX版本略有差异但逻辑是一样的先配置SAU区域把Non-Secure App要用的内存地址标成Non-Secure再配置RIF外设权限把Non-Secure App要用到的UART、GPIO、外部Flash控制器等全部放开最后跳转到Non-Secure App的Reset_Handler。NonSecure工程的启动逻辑就简单多了。它的链接脚本已经把向量表定位到了你配置的镜像偏移地址启动文件里直接就是正常的Reset_Handler。应用代码不需要知道Secure世界做了什么只需要按照普通单片机的方式初始化外设和跑业务逻辑。这两个工程的协作本质就是Boot负责铺路App负责跑。因为安全属性只能在Secure特权模式下修改所以NonSecure工程里没有任何修改SAU/RIF的代码。如果你在NonSecure工程里想改这些寄存器硬件会直接忽略写入这是很多人不理解“为什么写了没用”的原因。3.3 编译、烧录和首次启动验证烧录顺序有讲究。先用STM32CubeProgrammer把OEMiROT Boot镜像烧进内部Flash的boot分区。如果你用的是带板载ST-Link的开发板可以直接通过SWD端口烧录但注意烧录Boot时地址一定不能错。接下来烧录NonSecure App镜像到外部Flash。H533开发板一般有OSPI Flash或者FMC接口挂的外部NOR Flash。CubeMX生成的App工程里通常会带一个烧录脚本脚本里已经定义好了外部Flash的算法和地址直接在STM32CubeIDE里跑烧录就行。第一次启动验证我建议带上调试器看Boot里的串口打印。OEMiROT工程在跳转前会在默认调试串口打印一些状态信息比如验签是否通过、跳转地址是多少。如果串口完全没有输出大概率是Boot没跑起来或者UART的RIF配置还是Secure状态如果看到验签通过但后面无打印那问题就出在跳转后的Non-Secure环境上。我第一次把工程烧进去时串口打印到验签通过就断了程序卡在跳转后的位置调试器可以看到PC指针停在Non-Secure Flash地址区但执行不下去。这个现象基本就能锁定是Non-Secure访问权限问题也就是前面说的AXI non secure enablement没做完整。4. 实测记录Non-Secure应用启动后外设无响应的完整排查4.1 问题现象和初步定位那次现象很典型Boot串口打印“Image validated”然后跳转到App地址紧接着调试器里看到HardFault或者不进入HardFault但UART1初始化之后没有任何输出GPIO电平也不变化程序卡在一个死循环等待某个外设状态寄存器置位。我一开始以为是NonSecure工程的链接脚本有问题向量表偏移不对反复查了地址定义发现Boot跳转地址和App工程的Linker设置是一致的排除了这个问题。接着怀疑外部Flash时序不对用STM32CubeProgrammer的Memory Browser直接读外部Flash发现里面数据完整App镜像确实在又排除了烧录问题。这两步排查用掉不少时间最后静下来想H533上要同时满足两个条件才能让Non-Secure代码正常运行一是SAU把这些地址标成Non-Secure二是RIF里对应控制器的访问属性也放开。我在Boot工程里查TZ_SAU_Setup函数发现SAU区域确实把App地址范围标成了Non-Secure但RIF配置函数里只放了GPIO和部分外设外部Flash控制器和AXI SRAM相关区域的RIF属性没有动。这就是问题根源SAU把地址标成Non-Secure只是让CPU认为这段地址属于Non-Secure但AXI总线上实际的从设备控制器如果依然是Secure onlyNon-Secure事务同样会被拒绝。App在外部Flash取指时第一个访问就打在Secure-only的控制器上自然跑不动。4.2 通过RIF寄存器确认权限归属为了确认不是我的猜测我在调试器里直接查看了RIF相关寄存器的值。STM32CubeIDE的外设寄存器视图里可以找到RIF这一大组寄存器每个外设一个RIFAP寄存器里面SEC位就是安全属性。找到外部Flash控制器对应的RIFAP寄存器看到SEC位确实是1。再找到AXI SRAM对应的几个分区RIFAP寄存器同样是1。这就从硬件层面确认了Non-Secure世界访问这些资源时会被RIF拦下来。这里有个排查技巧可以分享。当Non-Secure代码访问外设卡住时先别急着看用户代码逻辑直接把调试器停在HardFault现场查看Cortex-M33的Fault状态寄存器CFSR、HFSR、MMFAR。如果是总线错误BUSFaultMMFAR里记的地址往往能直接告诉你是哪个外设或哪个内存区域访问被拒。我那次看到MMFAR指向外部Flash映射地址几乎就能断定是存储控制器权限问题。另外H533的RIF还有一个特点如果Non-Secure访问被拒这个拒绝事件有时不会产生总线错误而是表现为外设寄存器写不进。比如UART配置好了但TX标志一直不置位可能是UART的RIFAP.SEC1Non-Secure写操作被静默忽略。这种情况比HardFault更隐蔽建议在调试时用Memory Browser直接读外设寄存器发现值永远是复位值那基本就是RIF权限问题。4.3 修复过程和验证结果定位到是RIF配置缺失之后修复就很快了。在Boot工程的RIF配置函数里手动把外部Flash控制器对应的RIFAP寄存器SEC位清零同时把AXI SRAM需要用到的几个分区也放开然后重新编译Boot烧录后再次验证。如果你用的是CubeMX图形化配置更规范的做法是在RIF配置界面里找到对应的外设把Security选项改成Non-Secure然后重新生成代码。两种方式效果一样但推荐用CubeMX改这样重新生成工程不会丢配置。我那次因为工程已经手动改了不少链接脚本内容就直接在代码里动了寄存器改完后重新编译烧录串口立刻就有输出了GPIO也能正常翻转Non-Secure App整个跑了起来。从现象看问题确实解决了。这个案例给我最大的教训是STM32H5的TrustZone安全属性配置不是一个“开关”而是一层层叠加的权限判断。CPU地址属性、总线从设备属性、外设模块属性必须同时允许Non-Secure访问才能让Non-Secure代码正常操作资源。5. 这个方案在实际项目中的几个延伸建议5.1 设计内存布局时就要考虑安全边界很多人在CubeMX里生成OEMiROT工程后很少会回头看实际的内存布局。但如果你打算在产品里做OTA升级这个布局必须提前规划好。外部Flash上一般会划分出至少三个区域BootLoader区OEMiROT自己的代码或升级服务、App区当前固件、Download区下载的新固件。OEMiROT验签的地址必须指向当前App区如果下载区和App区重叠就可能出现边下载边执行边出错的奇葩问题。H533的内部SRAM也建议按功能分区尤其放安全相关的数据时尽量放在Secure专用的SRAM区域不要和Non-Secure世界共享。这样即使Non-Secure应用被攻破攻击者也读不到Secure区里的敏感数据。这个“内存边界即安全边界”的思路在H533这种带RIF的芯片上特别值得贯彻。5.2 把外设安全属性表当作项目文档的一部分这次排坑之后我做了一个外设安全属性清单用表格形式记录每个外设在Secure/Non-Secure世界里的归属。表格里有外设名、地址、RIFAP寄存器地址、SEC位默认值、当前配置值、工程里在哪里被修改。这张表在后期调驱动、做代码评审时特别有用相当于把“谁能碰哪个外设”这件事固化成了团队共识。建议你也这样维护原因很简单H533的外设数量不少每个外设都有一个RIFAP寄存器光靠记忆根本记不住哪个是Secure哪个是Non-Secure。而且CubeMX重新生成代码后某些外设的RIF配置可能会恢复默认值如果没有表格对照很难发现哪个外设被悄悄改回去了。这个习惯看着麻烦但真到量产维护阶段能帮你省下大量排查时间。我见过不止一个团队在开发后期因为一个外设RIF配置被覆盖排查了两三天才发现是代码重新生成时引入的问题。5.3 调试器连接和授权模型的影响最后提醒一个容易忽略的点当OEMiROT的安全策略开启后调试器的访问权限也会受到Secure/Non-Secure状态影响。用SWD连接H533时如果芯片停留在Non-Secure世界某些Secure区域的内存和外设寄存器在调试器里读出来会是乱码或者全零这并不代表芯片坏了只是调试器被安全策略挡住了。我习惯的做法是调试Non-Secure App时把Boot工程的符号表也加载进调试会话这样可以在Secure和Non-Secure两条世界线之间自由切换断点。ST-Link和J-Link都支持这个操作关键是调试配置里同时选择两个工程的elf文件。另外如果OEMiROT使能了调试锁定功能DBGMCU里的相关配置调试器在复位后可能无法立刻连接需要让Boot先跑一小段再连接或者在Boot代码里加一个延时方便调试器在跳转前挂上。这些细节在开发阶段影响不大但量产固件调试时能救你一次是一次。
返回列表