尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

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

STM32U5上TrustZone与LPBAM引发HardFault的根因解析与调试指南

STM32U5上TrustZone与LPBAM引发HardFault的根因解析与调试指南 如果你在STM32U5系列上同时碰到了TrustZone和LPBAM那你多半已经领略过什么叫“低功耗一时爽调试火葬场”。这个组合有个很经典的坑明明LPBAM配置看起来没问题外设事件也能正常触发可系统一进低功耗模式或者某个DMA描述符刚执行到一半直接就跳进HardFault_Handler。我最近在一款量产项目的低功耗数据采集模块上就踩了个正着。项目用STM32U585开启TrustZone隔离非安全世界跑RTOS和应用逻辑安全世界只负责密钥管理和安全存储。低功耗唤醒路径上用了LPBAM让BDMA在Stop模式下自动搬运ADC数据。功能验证阶段一切正常一到真实低功耗循环测试就偶发HardFault而且触发时机极其飘忽有时候几分钟有时候几个小时完全没有规律。这篇文章把整个定位过程和根因分析完整整理出来。涉及TrustZone安全属性配置、LPBAM描述符存放位置、DMA访问权限以及IAR环境下HardFault的通用调试手法。如果你也在TrustZone架构下碰LPBAM或者单纯被HardFault折磨得够呛这篇笔记值得收藏。1. TrustZone架构与LPBAM为什么会“打架”1.1 TrustZone的安全世界与非安全世界到底隔离了什么TrustZone并不只是“有个安全位”那么简单。在Cortex-M33内核上TrustZone把整个系统的资源分成安全Secure和非安全Non-Secure两个世界隔离粒度可以细到每一个外设、每一段SRAM、每一块Flash区域甚至同一个外设的不同中断。这个隔离是谁在管靠三套机制协同SAUSecurity Attribution Unit内核层面的安全属性单元由安全软件配置决定内存和外围设备的安全属性。GTZCGlobal TrustZone Controller系统层面的全局TrustZone控制器负责外设和存储器区域的访问授权包含TZSC和TZPC两部分。外设自身的安全位很多外设寄存器自带SECCFGR之类的安全配置位单独控制某个功能模块是否允许非安全访问。这三层是“与”的逻辑关系。也就是说一个非安全世界的设备要访问某段SRAM必须SAU允许、GTZC允许、这段SRAM本身也是非安全属性三者同时成立才能访问。任何一关没过硬件就直接产生总线错误或者内存管理错误最终上升为HardFault。理解这一点后面调试LPBAM的问题就顺了LPBAM虽然是“低功耗后台自主模式”但它做的事本质上还是DMA搬运数据。DMA要走SRAM、要访问外设寄存器那就必须遵守TrustZone的所有访问规则。1.2 LPBAM的工作机制与描述符链LPBAMLow-Power Background Autonomous Mode是ST在STM32U5系列上主推的一个低功耗特性核心思路是CPU进入Sleep甚至Stop模式由具备自主运行能力的DMA通道一般是LPDMA也就是低功耗DMA按照预先配置好的“描述符链”自动完成数据搬运。描述符链是LPBAM的灵魂。每一个描述符节点包含了传输方向、地址指针、数据长度、传输模式、触发条件、下一个描述符地址等信息。CPU只要把描述符链表的头指针配置给DMADMA就会按照链表的顺序依次执行传输全部执行完再产生中断唤醒CPU。这套机制的好处是CPU可以在数据采集期间全程睡觉功耗直接降一个数量级。但坏处也明显描述符链在内存里而内存是有安全属性的。如果你的描述符链表放在安全SRAM里DMA却工作在非安全状态或者相反访存权限不匹配硬件立马翻脸。1.3 冲突点DMA访问权限与SRAM属性不匹配这是最容易被忽视的地方。TrustZone下DMA的访问权限取决于通道的配置——每个DMA通道都可以设置成安全通道或非安全通道。而LPBAM使用的LPDMA通道默认工作状态受GTZC和外设安全配置共同影响。问题来了LPBAM描述符、数据缓冲区、甚至外设触发事件这三样东西的安全属性如果和DMA通道的安全属性不一致轻则传输数据不对重则直接异常。我这次遇到的情况就是典型的“非安全LPDMA访问安全SRAM”。描述符链被编译器默认放到了安全SRAM段LPDMA通道虽然已经配置成非安全访问模式但硬件可不管你的软件意图它只认物理属性——访问被拒触发MemManage Fault最终进HardFault。2. HardFault是如何触发的2.1 Cortex-M33的异常分级与Fault上升路径Cortex-M33的异常机制里HardFault既不是最底层也不是最顶层。比它优先级更低的是NMI更高的是Reset。在它下面还有三个可配置优先级的FaultMemManage Fault内存管理错误、BusFault总线错误、UsageFault用法错误。这三个Fault如果被Enable了就各走各的处理函数如果对应的异常没有被使能或者处理函数本身又出问题就会“升级”成HardFault。这是调试时必须想清楚的路径MemManage Fault主要是MPU或者TrustZone访问权限违规常见于非安全访问安全区域。BusFault处理器访问总线出错比如访问了不存在的地址、外设时钟没使能导致的bus error。UsageFault指令用法错误比如未对齐访问、除零、未定义指令、FPU未启用却执行浮点指令。LPBAM这种场景下最常出现的是MemManage Fault因为它本质上就是TrustZone访问权限被拒绝。但浮点运算触发HardFault则是另一条路径后面单独讲。2.2 现场还原HardFault发生时的关键寄存器我使用的调试环境是IAR Embedded Workbench芯片是STM32U585。HardFault触发后先把现场完整拍下来不要急着复位。当时记录到的关键寄存器状态是这样的SCB-CFSR 0x00008200CFSR是三个fault状态寄存器的组合包括MMFSR、BFSR、UFSRMMFSR 0x82其中BIT71表示MMARVALIDBIT11表示MSTKERRBIT01表示IACCVIOLMMAR 0x2003_0000 附近的一个地址0x82这个值非常关键。MMARVALID置位说明MMAR寄存器里的地址是有效的直接告诉我们出错地址在哪里。MSTKERR置位说明错误发生在异常入栈stacking阶段——也就是说Fault是在中断/异常处理的入栈过程中检测到的。实际报错的地址落在SRAM区域但那段SRAM在GTZC里被标记为Secure。从这个寄存器组合可以立刻判定这是一次内存管理错误出错的是一段带安全属性的SRAM而且时间点恰好发生在某个异常入栈阶段。大概率是LPDMA传输完成后试图访问描述符/内存权限不够触发了错误然后这个错误又发生在中断处理的入栈过程里。2.3 IAR下调试HardFault的具体操作手法IAR调试HardFault我总结了一套相对固定的流程能覆盖绝大多数情况。第一步在HardFault_Handler里打断点。注意如果代码里没有显式定义HardFault_HandlerIAR的启动文件里通常会有一个默认的weak版本直接在这个函数上下断点即可。第二步断点命中后打开View - Register把R0-R12、PC、LR、PSR全部记录下来。特别是LR它的值如果是0xFFFFFFEx系列说明这是异常返回时使用的EXC_RETURN能告诉我们异常是从线程模式还是处理模式进入的以及使用的是哪个栈指针。第三步打开View - Stack。IAR在这里会尝试做栈回溯。但要注意如果栈已经被破坏回溯可能失败。这时候可以用Disassembly窗口定位PC所在的函数再从反汇编里往前看几行找到真正触发Fault的指令。同时用Memory窗口查看MMAR和BFAR寄存器里的地址看看出错地址附近到底放了什么数据。第四步也是很多人忽略的是查看CFSR的完整拆分。IAR的Register窗口里可能不会直接展示这些状态寄存器需要手动在Watch窗口添加表达式比如SCB-CFSR、SCB-MMAR、SCB-BFAR。把这三个值看明白基本能区分是内存管理错误、总线错误还是用法错误。注意IAR下直接在Watch窗口输入SCB-CFSR是否能识别取决于你有没有包含CMSIS头文件。建议在包含stm32u5xx.h的C文件中打断点然后使用表达式否则IAR可能不认识这个符号。这套流程走完定位方向基本就锁定了。接下来才是最难的部分——搞清楚为什么LPBAM会去访问一个没有权限的地址。3. 根因分析两个HardFault的重要溯源方向3.1 浮点型运算触发HardFault的常见原因热搜词里有“浮点型运算触发hardfault的原因”这里专门展开说一下。Cortex-M33带FPU但浮点指令能不能用有一个前置开关CPACR寄存器的CP10和CP11位。如果CPACR里FPU没有使能CPU一旦执行浮点指令硬件会触发UsageFault中的NOCPNo Coprocessor错误。如果UsageFault没有单独使能这个错误会升级成HardFault。这种情况在使用IAR时特别容易踩IAR的工程选项里如果选择了FPU类型但启动代码里没有正确配置CPACR编译器生成了浮点指令运行到那儿就炸。另一个浮点相关的HighFault路径是FPU上下文保存。Cortex-M33支持Lazy Stacking机制也就是异常入栈时先不保存浮点寄存器等真正用到FPU了再保存。这个机制的开关在FPCCR寄存器里。在TrustZone架构下FPCCR还有一个TZ位决定是否允许非安全代码使用浮点单元。如果非安全代码被禁止使用FPU却在非安全中断里执行浮点运算异常状态也会演变成HardFault。以上两种情况在实际项目里都遇到过。第一种通常是初始化遗漏第二种多见于安全世界把FPU锁给了Secure世界非安全代码一用就挂。很多兄弟遇到HardFault第一反应是查数组越界其实FPU权限问题也很常见需要在定位时同步排查。3.2 本次LPBAM HardFault的真正根因回到本次问题。通过MMAR地址和CFSR的组合判断触发点锁定在地址0x2003_xxxx这一段正好落在我的LPBAM描述符链表所在区域。再结合GTZC的配置表一查问题就很清楚了。我的工程里LPBAM描述符链表通过链接脚本被分配到了安全SRAM段。而LPDMA的数据传输功能需要在非安全世界使用因为单片机在非安全世界配置LPBAM触发和缓冲区LPDMA通道因此被配置为Non-Secure。一个Non-Secure的DMA去访问Secure属性的SRAM硬件直接将访问拒绝生成MemManage Fault。为什么功能验证阶段没有暴露因为功能验证时我是在调试器全速运行下点对点测试描述符链刚好被某种方式预取到了缓存里或者测试时访问路径恰好碰上了允许的窗口。但实际低功耗运行中缓存失效、访问路径变化问题就浮出来了。这类偶发问题最坑——不是每次都能复现但只要安全属性配错了早晚要炸。3.3 GTZC配置与SRAM安全属性的关联GTZC在STM32U5里总共管三块东西外设安全配置TZPC、存储器安全配置TZSC、以及一个额外的MPUTZMPC。SRAM的分区安全属性就是靠TZSC和TZMPC配合实现的。SRAM在复位后的默认安全属性是全部Secure。如果你没有显式把某段SRAM配置为Non-Secure那么所有非安全世界的访问都不被允许。这是一个很阴险的默认值——因为它不会报错不会警告只是让你的工程“碰巧能跑”直到某条访问路径踩到它。正确做法是明确规划SRAM的分区把LPBAM描述符表、数据缓冲区、以及非安全代码需要访问的所有内存段统一配置为Non-Secure。如果确实需要安全世界持有某些数据又需要非安全世界参与搬运就得在安全世界写一个可信入口服务通过SCB-VTOR切换和SG指令做安全调用而不是直接把访问权开放出去。提示STM32CubeMX的TrustZone配置界面可以可视化地设置SRAM分区强烈建议先用CubeMX做规划并生成代码再手动微调别全部手写GTZC寄存器手写容易漏配而且出了问题很难查。4. 解决方案代码级修复与初始化顺序调整4.1 方案一把LPBAM描述符和缓冲区放到非安全SRAM最直接的改动把LPBAM描述符链表和ADC数据缓冲区都放到Non-Secure SRAM区域。LNKSCTL里有一个链接文件.icf默认模板有一段定义SRAM分区的代码。可以在icf文件里单独划出一块Non-Secure SRAM区域比如叫SRAM_NON_SECURE然后使用__attribute__((section(.lp_bam_desc)))把描述符数组放进去。在IAR的icf文件里可以这样定义define region SRAM_NON_SECURE mem:[from 0x20040000 to 0x2004FFFF]; place in SRAM_NON_SECURE { section .lp_bam_desc };然后在C代码里LPBAM描述符数组这样声明__attribute__((section(.lp_bam_desc), aligned(32))) LPBAM_DescriptorTypeDef lp_bam_desc[8];这样描述符就百分百落在非安全地址上不会因为链接器默认分配被丢到安全区域。4.2 方案二用GTZC把SRAM区域授权为非安全如果因为其他原因比如安全世界也有部分数据驻留在同一块SRAM不能简单地把整块SRAM都设成Non-Secure那就需要更精细地配置GTZC。用STM32CubeMX生成代码时在TrustZone配置页面里选中对应SRAM区域将其设置为Non-Secure。底层实现上CubeMX会调用GTZC的初始化和配置函数把TZSC相关的SRAM配置寄存器写好。如果手写代码大致的调用方式是这样GTZC_Config_SramSecurity(GTZC_SRAM_SEC_0, GTZC_CONFIG_SRAM_NONSEC);这个函数在stm32u5xx_hal_gtzc.c里具体参数要参考芯片的参考手册不同SRAM块对应的编号不同。配置完成后还需要确保MPU如果开了对这段区域的访问权限也允许非安全访问。4.3 正确的初始化顺序先配GTZC再使能DMA这里有个非常重要的初始化顺序问题。GTZC必须在任何外设发起DMA传输之前完成配置。如果LPBAM描述符已经写到了非安全SRAM但GTZC还没来得及把该区域授权为非安全DMA一旦启动就会撞上访问权限错误。推荐的初始化顺序是系统上电安全世界代码最先运行。配置GTZC包括SRAM安全属性和外设安全属性。配置SAU明确非安全代码可以访问的区域。配置LPDMA时钟和通道但先不使能传输。填充LPBAM描述符链。安全世界切换到非安全世界通过SG指令和FNCRO启动RTOS。RTOS里再执行完整的LPBAM初始化使能DMA通道。进入低功耗模式等待外设事件触发LPBAM。第2步和第3步绝不能颠倒。SAU决定了CPU视角的访问权限GTZC决定了系统视角的访问权限两者都要先于DMA访问之前就位。4.4 修复后的验证方法修复完成后不要急着上低功耗。先用普通模式跑一轮压力测试把LPBAM的触发周期设置为最小连续跑半小时观察是否有HardFault。然后再逐步加入低功耗模式。我自己的习惯是在HardFault_Handler里加一个反汇编断点每次触发都记录下来连续记录几天确保没有偶发。同时把LPBAM的错误中断打开——很多人会忽略LPBAM自己的错误中断其实它能提前捕捉到描述符执行过程中的问题比HardFault好定位得多。5. 常见问题排查与实用速查表5.1 HardFault诱因速查表现象特征可能原因定位手段CFSR中MMFSR置位MMAR有效TrustZone/MPU访问权限违规查看MMAR地址核对SAU/GTZC配置CFSR中BFSR置位BFAR有效访问了不存在的总线地址或外设未使能查看BFAR核对时钟使能和外设基地址CFSR中UFSR的NOCP置位浮点指令在FPU未使能时被执行检查CPACR、FPCCRCFSR中UFSR的DIVBYZERO置位整数除零检查除法运算使能UsageFault后可见HardFault发生在中断入栈阶段入栈地址非法常与栈指针错误有关检查MSP/PSP查看栈顶地址是否有效HardFault触发时机是低功耗唤醒后LPBAM/DMA描述符访问权限或缓存一致性问题检查DMA描述符所在区域安全属性5.2 IAR调试HardFault的快速操作清单在HardFault_Handler处打断点还不够以下是我在IAR里调试时必做的几步操作少一个都可能卡半天打开View - Register窗口逐项核对CPU寄存器是否异常。打开View - Stack尝试栈回溯。如果回溯失败用Disassembly窗口配合PC值手工反查。打开Watch窗口手动添加SCB-CFSR、SCB-MMAR、SCB-BFAR、SCB-DFSR、SCB-HFSR等寄存器表达式。打开Memory窗口查看出错地址附近的数据内容。如果怀疑栈问题同时查看MSP和PSP的值确认实际使用的栈指针。在IAR的Options - Debugger设置里有些工程默认开了“Allow stack usage analysis”之类的选项会影响栈回溯的效果建议全部打开。还有一个很实用的小技巧把HardFault_Handler的断点设置为“Log”模式而不是“Break”模式这样可以在不中断系统的情况下记录每次Fault的现场对偶发问题非常有帮助。5.3 TrustZone调试的独家避坑经验TrustZone调试比普通单片机调试多了一层复杂度调试器本身的访问权限。默认情况下调试器是通过Secure调试接口连接的可以访问所有世界。但如果工程启用了TrustZone的高级调试保护或者芯片处于锁定状态调试器可能只能看到非安全世界的内容。这种情况下你可能会碰到一个诡异的现象断点能停下来但Memory窗口看到的SRAM内容和程序实际运行的数据不一致。此时不要怀疑编译器优化先检查调试器是不是加载了正确的安全/非安全调试配置文件。ST-Link在IAR里可以配置为“Secure”或“Non-Secure”调试模式选错模式看到的物理内存完全不是同一份。另一个坑是在TrustZone工程里中断向量表有两个。安全世界的VTOR指向安全向量表非安全世界的VTOR指向非安全向量表。如果HardFault_Handler只存在安全世界里而非安全代码跳转到了它不该去的中断就会触发一个很奇怪的“假HardFault”——实际上不是内存错误而是地址跳转错误。排查时一定先确认当前断在哪个VTOR对应的异常处理函数里。6. 代码级修改实录一次完整的修复过程6.1 从错误现场到根因确认的三个关键步骤整个定位过程如果压缩成三个关键步骤应该是这样的第一步确认Fault类型。通过CFSR的拆分判断出是MemManage Fault不是BusFault也不是UsageFault。这一步把问题的范围从“所有可能”收窄到“访问权限类”。第二步确认出错的地址。MMAR给出的地址落在LPBAM描述符数组所在区域。我在icf文件里查到这个区域的安全属性是Secure而LPDMA通道是非安全通道权限不匹配的问题基本实锤。第三步确认访问时间点。从LR的EXC_RETURN置位情况和MSTKERR判断是LPBAM在中断上下文里发起了一次非法访问。结合GPIO翻转测试确认了触发时机就是LPBAM第一段描述符执行完成后的下一次取指或者数据访问。6.2 最小复现场景与修复验证代码为了反复验证修复效果我写了一个最小复现场景。这个场景独立于业务代码只做一件事在低功耗模式下用LPBAM把一段固定数据从一个外设搬到另一段SRAM循环执行每次传输完成后翻转一个测试GPIO。修复前这个最小场景能在约20秒内稳定触发HardFault修复后连续跑2个小时没有异常。最小复现代码的核心初始化逻辑大致是这样void LPBAM_NonSecure_Init(void) { /* 1. 确保GTZC已经把描述符区域配置为非安全 */ GTZC_Config_SramSecurity(GTZC_SRAM_SEC_LPBAM_REGION, GTZC_CONFIG_SRAM_NONSEC); /* 2. 将描述符数组放入非安全段 */ memset(lp_bam_desc, 0, sizeof(lp_bam_desc)); /* 3. 配置LPDMA通道确保通道属性为Non-Secure */ hdma.Init.Request DMA_REQUEST_LPBAM; hdma.Init.Direction DMA_PERIPH_TO_MEMORY; hdma.Init.PeriphInc DMA_PINC_DISABLE; hdma.Init.MemInc DMA_MINC_ENABLE; hdma.Init.PeriphDataAlignment DMA_PDATAALIGN_WORD; hdma.Init.MemDataAlignment DMA_MDATAALIGN_WORD; hdma.Init.Mode DMA_NORMAL; hdma.Init.Priority DMA_PRIORITY_HIGH; /* 关键必须将通道配置为Non-Secure否则无法访问Non-Secure SRAM */ hdma.Init.Secure DMA_CHANNEL_NONSECURE; HAL_DMA_Init(hdma); /* 4. 填充LPBAM描述符链具体字段按STM32U5参考手册填写 */ LPBAM_InitDescriptor(...); /* 5. 使能LPBAM并进入低功耗 */ LPBAM_Enable(hdma); HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); }代码里最关键的一行就是hdma.Init.Secure DMA_CHANNEL_NONSECURE。这个字段不是每个STM32型号都有U5系列有因为架构不一样。如果你用的芯片没有这个字段说明DMA安全属性是通过别的寄存器配置的需要查具体参考手册。本质上要做的事情是一致的让DMA的安全属性与它要访问的内存安全属性匹配。6.3 与FPU相关的HardFault排查清单既然热搜词里提到了浮点型运算触发HardFault这里给一份排查清单方便对照。确认工程选项里FPU类型与实际芯片一致。IAR中在Options - General Options - Target里选择Cortex-M33通常选“FPU5”或“Software”选错会导致指令集不匹配。检查启动代码里CPACR是否正确配置。默认启动文件会做但如果用了自写启动文件很容易漏。CPACR的CP10和CP11都应该设置为全访问。检查FPCCR的TZ位。TrustZone使能后如果安全世界没有开放FPU给非安全世界非安全代码执行浮点运算会触发UsageFault。如果用了FPU但在中断里频繁进出检查Lazy Stacking是否被意外关闭。FPCCR的LSPEN位如果被清零每次中断都会保存浮点寄存器一方面变慢另一方面如果在保存过程中内存访问违规也会升级成HardFault。检查链接脚本里是否分配了足够的栈空间给浮点上下文。浮点上下文入栈需要额外空间栈界定的太小浮点运算一多就直接把栈踩穿表现也是HardFault。清单里最后一条尤其容易被忽视。很多嵌入式工程师的栈都是估算出来的估算的时候根本没把浮点寄存器的保存算进去。等浮点运算一加入栈越界问题就来了。写在最后的一点经验这类TrustZone加LPBAM的HardFault本质上不是某一个寄存器或者某一行代码的错误而是整个系统的“安全属性一致性”出了问题。TrustZone架构下任何外设、内存、DMA的访问路径都要保证发起方的安全属性和目标资源的安全属性是匹配的任何一个环节脱节最终都会在某个偶然的时刻以HardFault的形式爆发出来。我个人的体会是排查这类问题不要一上来就看代码逻辑先花时间把CFSR拆明白、把MMAR/BFAR的地址对应到资源地图上再动手改代码效率最高。而预防的角度在工程初期用CubeMX把TrustZone的分区规划好远比后期出了问题再补配置要省心得多。希望这篇笔记能帮你少走点弯路。
返回列表