ARM Cortex-R52 内核详解(三)——异常处理机制
本文主要介绍ARM Cortex-R52内核异常处理机制,对应TriCore的Trap系统,包含相关实例演示,对比Cortex-M7异常机制说明核心差异。
目录
1 异常处理机制——Fault系统介绍
1.1 计算机异常处理机制简介
1.2 Cortex-R52的异常处理机制简介
2 Cortex-R52异常系统
2.1 异常向量表
2.2 异常响应流程
2.3 异常处理程序
2.4 异常退出流程
2.5 同步异常和异步异常
2.6 异常优先级
2.7 异常相关寄存器
2.7.1 向量表偏移寄存器(VTOR)
2.7.2 可配置故障状态寄存器(CFSR,地址0xE000ED28)
2.7.3 硬错状态寄存器(HFSR,地址0xE000ED2C)
2.7.4 内存管理错误地址寄存器(MMFAR,地址0xE000ED34)、总线错误地址寄存器(BFAR,地址0xE000ED38)、安全错误地址寄存器(SFAR,地址0xE000ED3C)
2.8 异常系统小结
3 使用示例
4 小结
参考资料
1 异常处理机制——Fault系统介绍
1.1 计算机异常处理机制简介
异常是CPU硬件自带的基础机制,当系统运行过程中,出现了需要CPU立即处理的特殊事件(不管是程序运行错误,还是外设的异步请求),CPU会暂停当前正在执行的普通程序,保存当前运行状态,跳转到专门的处理程序中处理事件,处理完成后再回到原来的程序继续执行,这套机制就是异常处理。
- 广义上来说:中断本质属于异常的一个子类,二者都是打断当前程序执行处理特殊事件的机制;
- 狭义细分的区别在于触发时机:
- 异常(同步异常):由CPU当前执行的指令触发,和CPU运行同步,比如执行了非法指令、除零错误、非法内存访问,这类错误一定是当前指令执行出错才会触发,是同步的;
- 中断(异步异常):由CPU外部的外设触发,和当前CPU执行的指令无关,随时可能发生,比如UART收到数据、ADC转换完成触发的请求,是异步的。
在ARM Cortex-R架构的官方定义中,同样把所有需要CPU处理的特殊事件统一归为「异常」,我们常说的外部外设中断就是R52异常体系中的一类异步异常,错误类异常通常称为Fault,对应TriCore的Trap系统,后续我们统一遵循这个定义规范。
1.2 Cortex-R52的异常处理机制简介
Cortex-R52基于ARMv8-R AArch32架构,将硬件检测到的运行错误、紧急事件、系统调用统一归类为内核系统异常,错误类Fault对应TriCore的Trap,由硬件自动检测、自动触发处理流程。Cortex-R52原生支持TrustZone安全隔离,新增安全错误异常分类,整体对比Cortex-M7更安全、延迟更低,具体对应TriCore Trap类的关系如下:
| 异常类型 | 对应TriCore Trap类 | 功能说明 |
|---|---|---|
| 复位(Reset) | 无 | 芯片上电/复位触发,初始化系统 |
| 不可屏蔽中断(NMI) | Class 7 不可屏蔽Trap | 优先级仅低于复位,用于处理紧急硬件错误,无法被普通中断屏蔽 |
| 硬错(HardFault) | 所有未注册/不可恢复Trap | 所有未开启细分处理的错误都会汇聚到这里,是默认的错误处理入口 |
| 内存管理错(MemManage) | Class 0 MMU Trap/Class 1 保护Trap | MPU内存保护触发的访问违规,对应TriCore内存保护Trap |
| 总线错(BusFault) | Class 4 总线错误Trap | 总线访问错误(如访问不存在的地址、对齐错误)触发,对应TriCore数据总线错误Trap |
| 使用错(UsageFault) | Class 2 指令错误Trap | 非法指令、未对齐访问、协处理器错误触发,对应TriCore指令错误Trap |
| 安全错(SecureFault) | 安全访问违规Trap | TrustZone安全访问违规触发(非安全访问安全资源、安全状态切换错误),Cortex-M7无该类型异常 |
| 系统服务调用(SVC) | Class 6 系统调用Trap | 用户程序触发内核系统调用,用于RTOS内核,对应用户态到内核态的切换 |
| 可挂起系统调用(PendSV) | 无 | 专门用于RTOS任务延迟切换,无对应TriCore Trap |
| 普通IRQ中断 | 普通外设中断 | 普通优先级外设异步中断 |
| FIQ快速中断 | 最高优先级硬实时中断 | 最低延迟最高优先级外设中断,Cortex-M7无该独立分类 |
⚠️ 特别注意:和Cortex-M7一致,默认情况下Cortex-R52仅开启HardFault,MemManage、BusFault、UsageFault、SecureFault都被禁用,所有对应错误都会升级触发HardFault,开发调试阶段必须手动开启这几类Fault,才能定位具体错误类型,这是R52开发最常见的踩坑点。
和TriCore的Trap系统、Cortex-M7的Fault系统类似,R52使用独立向量表管理异常入口,每个异常有固定的向量偏移,硬件自动跳转,用户可以自定义每个异常的处理逻辑,同时依靠分组寄存器设计实现更低的异常响应延迟。
2 Cortex-R52异常系统
Cortex-R52的Fault异常机制核心目标和TriCore的Trap一致:硬件自动检测异常,自动完成上下文保护,通过向量表跳转进入自定义处理程序,处理完成后自动恢复上下文,对比Cortex-M7更低延迟、原生支持安全隔离,下面分模块介绍:
2.1 异常向量表
Cortex-R52的异常向量表每个异常对应1个4字节的入口地址,安全世界和非安全世界拥有完全独立的两个向量表,非安全异常向量存放在非安全向量表,安全异常向量存放在安全向量表,非安全代码无法访问修改安全向量表,硬件自动隔离,这是对比Cortex-M7的核心差异。
具体规则:
- 向量表基地址要求最小128字节对齐,支持向量表重定位,基地址通过各自的VTOR寄存器配置,对应TriCore的BTV基址寄存器;
- 系统异常按固定顺序排列在向量表开头,外部中断跟在系统异常之后,和Cortex-M7规则一致;
以下是TI AM62x(Cortex-R52核心)官方启动代码中的向量表示例,逻辑和TriCore的MCAL向量表一致:
/* 异常向量表定义,128字节对齐 */ __align(128) const uint32_t g_pfnVectors[] = { (uint32_t)Reset_Handler, /* 复位异常,注:Cortex-R不需要开头放初始SP,和Cortex-M不同! */ (uint32_t)NMI_Handler, /* 不可屏蔽中断NMI,对应TriCore Class7 NMI Trap */ (uint32_t)HardFault_Handler, /* 硬错,对应未处理的所有Trap */ (uint32_t)MemManage_Handler, /* 内存管理错,对应TriCore Class0/1保护Trap */ (uint32_t)BusFault_Handler, /* 总线错,对应TriCore Class4总线错误Trap */ (uint32_t)UsageFault_Handler, /* 使用错,对应TriCore Class2指令错误Trap */ (uint32_t)SecureFault_Handler, /* 安全错,Cortex-R52 TrustZone特有,Cortex-M无 */ /* 保留位省略 */ (uint32_t)SVC_Handler, /* 系统调用,对应TriCore Class6系统调用Trap */ /* 其他异常、外部中断向量省略 */ };系统启动时需要分别初始化安全和非安全的VTOR寄存器,以下是非安全世界的初始化示例:
/* 配置非安全向量表基地址,从链接脚本获取向量表起始地址 */ SCB->VTOR = (uint32_t)g_pfnVectors;开启细分Fault的代码和Cortex-M7类似,额外增加SecureFault的使能:
// 开启所有细分Fault,Cortex-R52额外需要开启SecureFault(TrustZone使能时) SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk | SCB_SHCSR_BUSFAULTENA_Msk | SCB_SHCSR_USGFAULTENA_Msk | SCB_SHCSR_SECUREFAULTENA_Msk;当异常发生时,硬件自动计算入口地址:入口地址 = 当前世界VTOR + 异常固定偏移 × 4,直接跳转到对应处理程序,错误类型保存在专用状态寄存器中。
2.2 异常响应流程
和TriCore类似,Cortex-R52的异常也分为可恢复异常(修复错误后可继续运行)和不可恢复异常(错误发生后系统无法继续运行),可恢复异常的标准响应流程如下,核心差异和Cortex-M7对比如下:
步骤1:触发请求与优先级仲裁
- 若为外部中断:外设产生请求后发送给GICv3(Cortex-R52标配通用中断控制器,替代Cortex-M7的NVIC),GIC置对应中断的Pending挂起位;
- 若为内核异常(Fault/NMI等):内核硬件检测到错误,直接生成请求给GIC;
- GIC做优先级仲裁:只有当新事件的优先级高于当前CPU的有效优先级时,才会响应;否则保持挂起状态,等待当前高优先级事件处理完成后再响应;
- 确认响应后,GIC自动清除该事件的Pending挂起位,置位对应事件的Active活跃位;
- ⚠️ TrustZone特殊规则:安全异常优先级默认高于所有非安全异常,无论优先级数值,保证安全错误能优先响应,这是Cortex-M7不具备的规则。
步骤2:硬件自动切换异常模式,保存核心状态到分组寄存器
这是R52对比Cortex-M7最大的设计差异:Cortex-M7需要把8个核心寄存器压栈保存,R52每个异常模式拥有独立的硬件分组寄存器,核心状态自动保存在分组中,不需要压栈:
- 硬件自动切换到对应异常模式(IRQ/FIQ/ABT等),如果是安全异常,自动切换到安全世界的分组上下文;
- 自动将原运行模式的CPSR(程序状态寄存器)保存到当前异常模式的分组SPSR(保存程序状态寄存器);
- 自动将异常返回地址保存到当前异常模式的分组LR(链接寄存器);
- 自动切换到当前异常模式的分组SP(栈指针),使用异常独立栈,不占用用户任务栈空间,原模式的SP自动保留在原分组;
- 全程不需要软件干预,也不需要占用栈空间保存核心状态,响应延迟比Cortex-M7低50%以上。
步骤3:软件保存共用通用寄存器
只有和用户模式共用的通用寄存器需要软件压栈保存到异常独立栈:
- 普通IRQ异常:仅需要保存R0~R3、R12共4个通用寄存器,总大小16字节;
- FIQ快速异常:FIQ模式拥有独立的R8~R12分组,仅需要保存R0~R3共4个通用寄存器,总大小仅16字节,不需要保存其他寄存器;
- 如果用到FPU/NEON,额外保存浮点/向量寄存器,未用到不需要保存; 对应TriCore软件保存低上下文的操作,R52需要保存的内容远少于Cortex-M7。
步骤4:更新中断屏蔽,配置嵌套规则
⚠️ 核心差异注意:和Cortex-M7天生支持中断嵌套不同,R52进入异常后会自动修改CPSR的中断屏蔽位:
- 进入IRQ异常:自动置I位(关闭普通IRQ中断),FIQ保持原状态;
- 进入FIQ异常:自动置I位+F位(关闭所有中断);
- 如果需要支持中断嵌套,必须在异常处理程序入口手动清除对应的屏蔽位,重新打开中断,否则默认不支持嵌套,这和TriCore进入Trap默认关中断的规则一致,和Cortex-M7完全不同,是新手最容易踩的坑。
步骤5:向量寻址跳转,硬件流程结束
- 硬件计算异常入口地址:
入口地址 = 当前世界VTOR + 异常固定偏移 × 4; - 把入口地址写入PC寄存器,CPU跳转到对应的异常处理程序执行,至此所有硬件自动操作完成。
不可恢复异常的典型代表是Double Fault:当处理第一个Fault的过程中又发生了新的Fault,就会触发Double Fault,直接进入HardFault,一般出现后直接执行芯片复位,和TriCore的FATAL Trap逻辑一致。
⚠️ 返回地址特殊注意:不同异常的LR中存储的返回地址需要偏移调整才能得到触发异常的指令地址:
- 未定义指令/预取中止:触发地址 = LR - 4;
- 数据中止:触发地址 = LR - 8;
- 中断/系统调用:不需要调整,Cortex-M7没有这个规则,开发汇编异常处理时必须注意,否则返回地址错误会导致程序跑飞。
2.3 异常处理程序
异常处理程序由用户自定义,需要为每类开启的Fault编写独立处理逻辑,绝大多数嵌入式功能安全开发中,Fault发生后意味着系统已经出现不可靠的错误,即使硬件支持恢复,一般也会先保存错误信息,然后执行芯片复位,避免出现更严重的问题,这一点和TriCore Trap、Cortex-M7 Fault的处理习惯完全一致。
以下是Cortex-R52通用的BusFault处理程序示例,用于调试阶段定位错误:
void BusFault_Handler(void) { uint32_t cfsr = SCB->CFSR; uint32_t fault_addr = SCB->BFAR; uint32_t trigger_pc = 0; /* 同步BusFault直接从LR计算触发地址,不需要从栈中取,和Cortex-M不同 */ trigger_pc = LR - 8; // 数据总线错误偏移8字节,预取错误偏移4字节 printf("BusFault occurred!\r\n"); printf("CFSR = 0x%08X\r\n", cfsr); if((cfsr & (1<<15)) != 0) { // BFARVALID位为1说明地址有效 printf("Fault Address = 0x%08X\r\n", fault_addr); } printf("Trigger PC = 0x%08X\r\n", trigger_pc); /* 清除错误标志,写1清除 */ SCB->CFSR = cfsr; /* 实际产品中一般保存错误信息到非易失存储器后复位 */ NVIC_SystemReset(); }2.4 异常退出流程
Cortex-R52的异常退出由异常返回指令触发,硬件自动恢复所有核心状态,对应TriCore的RFE指令,具体步骤如下:
步骤1:软件恢复共用寄存器异常处理完成后,软件从异常栈中弹出之前保存的R0~R3、R12(FIQ仅弹出R0~R3)、FPU/NEON寄存器(若有),SP恢复到异常进入后的位置。
步骤2:执行异常返回,硬件恢复所有状态执行BX LR或MOV PC, SPSR风格的异常返回指令,硬件自动完成:
- 从异常模式的SPSR中恢复原运行模式的CPSR,恢复原来的中断屏蔽位;
- 自动切换回原运行模式的SP、LR,恢复原来的栈指针和特权级;
- 把LR中的返回地址写入PC,回到触发异常前的原程序继续执行;
- 如果是TrustZone切换返回,硬件自动切回原来的安全世界/非安全世界,全程不需要软件干预。
和Cortex-M7对比:R52核心状态都保存在硬件分组寄存器中,不需要弹栈恢复核心寄存器,退出延迟远低于Cortex-M7。
如果是不可恢复Fault,处理程序不会返回,直接执行芯片复位。
2.5 同步异常和异步异常
Cortex-R52的Fault也分为同步和异步两类,逻辑和TriCore、Cortex-M7完全一致:
- 同步Fault:执行触发指令时立即检测到异常,比如非法指令、未对齐访问、精确的地址访问错误,返回地址就是触发指令的地址,很容易定位;
- 异步Fault(不精确Fault):一般发生在开启Cache之后,或者总线流水线操作中,错误在指令执行完成后才被检测到,此时PC已经执行到后续指令,返回地址没有参考性,很难定位;定位方法和Cortex-M7一致:暂时关闭Cache,添加内存屏障,让错误变为同步Fault,方便定位根因。
2.6 异常优先级
Cortex-R52异常优先级遵循「数值越小,优先级越高」的规则,和Cortex-M7一致,和很多开发者的直觉相反,需要特别注意,整体优先级排序符合功能安全场景的通用逻辑:
- 复位 > 不可屏蔽中断NMI > Double Fault > 所有普通Fault异常 > 安全错 > FIQ快速中断 > 普通IRQ中断 > 系统服务调用SVC > PendSV,和TriCore的优先级排序逻辑一致;
- Fault异常优先级整体高于所有普通外部中断,保证错误能及时响应;
- TrustZone规则:所有安全异常优先级默认高于非安全异常,无论优先级数值,保证安全错误优先处理;
- 同一条指令触发多个Fault时,按错误类型固定优先级排序,内存访问错误优先级高于指令错误。
2.7 异常相关寄存器
Cortex-R52所有Fault相关寄存器都映射在SCB(系统控制块)地址空间,安全世界和非安全世界各有一套独立的寄存器,用来存储错误类型、错误地址,辅助定位问题,对应TriCore的PSTR/DSTR/DEADD等寄存器,核心寄存器如下:
2.7.1 向量表偏移寄存器(VTOR)
对应TriCore的BTV寄存器,存储异常向量表的基地址,支持向量表重定位,BootLoader跳转应用程序必须修改该寄存器。Cortex-R52安全世界和非安全世界各有一个独立的VTOR寄存器,非安全VTOR不影响安全向量表,这是对比Cortex-M7的核心差异,要求基地址128字节对齐。
2.7.2 可配置故障状态寄存器(CFSR,地址0xE000ED28)
对应TriCore的PSTR/DSTR/DATR,分为四个独立的状态域,每一位对应一种错误类型:
MMFSR(高8位):存储内存管理错误的类型,比如指令访问违规、数据访问违规;BFSR(中8位):存储总线错误的类型,比如取指总线错、精确总线错、异步不精确总线错;UFSR(低16位):存储使用错误的类型,比如非法指令、未对齐访问、协处理器错误;SFSR(高8位,TrustZone使能时有效):存储安全错误的类型,比如非安全访问安全寄存器、安全状态切换错误,Cortex-M7无该域; 所有位都是写1清除,读取错误信息后需要手动清除,否则会影响下一次异常的状态判断。
2.7.3 硬错状态寄存器(HFSR,地址0xE000ED2C)
存储HardFault的触发原因,功能和Cortex-M7一致:
FORCED位:如果该位置1,说明这个HardFault是其他未开启的Fault升级而来,绝大多数默认HardFault都是这个原因,只要开启细分Fault就能定位具体错误;VECTBL位:向量表读取错误触发,说明向量表基地址配置错误。
2.7.4 内存管理错误地址寄存器(MMFAR,地址0xE000ED34)、总线错误地址寄存器(BFAR,地址0xE000ED38)、安全错误地址寄存器(SFAR,地址0xE000ED3C)
对应TriCore的DEADD寄存器,MMFAR存储触发MemManage的非法访问地址,BFAR存储触发BusFault的非法访问地址,SFAR是Cortex-R52特有,存储触发SecureFault的非法访问地址,Cortex-M7无该寄存器。只有对应状态寄存器中的xxxVALID位为1时,地址才有效,异步错误一般地址无效。
2.8 异常系统小结
Cortex-R52的Fault异常机制和TriCore的Trap系统设计目标一致,对比Cortex-M7有以下核心特点:
- 依靠分组寄存器自动保存核心上下文,异常响应和退出延迟比Cortex-M7低50%以上,接近TriCore CSA硬件机制的性能,满足最高等级硬实时需求;
- 原生支持TrustZone安全隔离,安全异常和非安全异常拥有独立的向量表、寄存器和栈,非安全代码无法干扰安全异常处理,原生满足ISO 26262 ASIL-D功能安全要求,安全性远高于Cortex-M7;
- 不需要预留独立的上下文存储区,内存分配灵活,调试比TriCore CSA更简单,和Cortex-M7一样方便;
- 核心注意点需要牢记:
- 默认只开启HardFault,必须手动开启MemManage、BusFault、UsageFault、SecureFault才能定位具体错误;
- 进入异常默认关闭中断,要支持中断嵌套必须软件手动重新开中断,和Cortex-M7天生支持嵌套不同;
- 安全世界和非安全世界拥有独立的向量表,非安全代码无法访问安全向量表,开发TrustZone固件时需要分别初始化。
3 使用示例
我们构造一个非安全世界非法访问安全内存的场景,触发SecureFault,演示完整定位流程:
// 安全内存地址0x70000000,非安全世界配置MPU禁止访问 if(TestFlag) uint32_t value = *(uint32_t*)0x70000000; // 非法访问触发安全错误- 提前开启了细分Fault,在非法访问指令前打断点,单步执行,硬件自动检测到安全访问违规,进入SecureFault异常入口;
- 在SecureFault_Handler入口打断点,进入处理程序后读取寄存器:CFSR的SFSR域中
INVEP位(非法访问安全地址)置位,SFARVALID位为1; - 读取SFAR寄存器,值为
0x70000000,正好是我们构造的非法地址; - 从LR计算得到触发异常的PC,对应代码位置就是非法访问指令,完成定位,整个流程和TriCore Trap定位完全一致。
4 小结
本文对应TriCore的Trap系统、Cortex-M7的Fault异常机制,完整介绍了Cortex-R52的异常处理机制,从向量表配置、响应流程、错误定位、寄存器说明都做了对应讲解,Cortex-R52的Fault设计兼顾了TriCore的低延迟优势和Cortex-M7的简洁性,原生支持安全隔离,完全满足车规、工业等高可靠硬实时场景的需求,掌握异常机制可以帮助开发者快速定位系统级错误,缩短调试周期。
参考资料
- ARM® Cortex®-R52 Generic User GuideARM官方
- ARMv8-R Architecture Reference ManualARM官方
- ARM TrustZone for Cortex-R Programming GuideARM官方
- Texas Instruments AM62x Sitara Processors Programming Reference ManualTI官方
- NXP i.MX 8QuadMax Cortex-R52 Programming ManualNXP官方
