ARM学习(5) 异常模式学习(CortexM3/M4)

笔者来聊聊对CortexM3/M4的异常模式理解。

CortexM3/M4异常模型了解学习

    • 1、通用寄存器
    • 2、异常与中断
      • 2.1、系统异常
      • 2.2、中断
    • 3、参考

之前的了解,都是基于具体的芯片而言的,比如ST/GD/NXP公司的,STM32,很常用,基于Keil或者IAR集成开发环境,一直到工作之后,才更清楚的了解了架构这种概念,这种芯片本质上还是基于CortexM3/M4这种架构的,各种厂商都是基于这种架构进行开发的,然后添加很多外设,满足不同的功能需求。

1、通用寄存器

寄存器首先是架构的不可或缺的一部分,也是了解架构编程模型的重要地方。

  1. 寄存器 ,r0-r12,r13(sp),r14(lr),r15(pc);

  2. 状态寄存器PSR:APSR、EPSR、IPSR;
    ARM学习(5) 异常模式学习(CortexM3/M4)_第1张图片
    T:thumb指令,总是为1
    异常编号:正常处理的异常编号,与接下来介绍的中断向量表编号相关,
    NZCV:标志位,
    ARM学习(5) 异常模式学习(CortexM3/M4)_第2张图片
    与经典的arm架构相比(比如arm7 ),M系列做了很多改变。
    模式:模式减少,只有特权模式和非特权模式(均是在线程下,异常模式自动是特权等级),
    堆栈:只有主栈+线程栈,MSP+PSP,不像经典架构,每个模式都有各自独立的栈。
    寄存器:减少,模式下除了SP,其他寄存器都是共享,经典架构下,寄存器较多,各个模式下都有几个独立的寄存器。
    异常:异常也有变化,新增hard fault等,将svc模式转为svc的函数处理,等等

  3. 中断相关寄存器 :PRIMASK、FAULTMASK和BASEPRI
    a. PRIMASK:中断屏蔽位,1位宽,屏蔽除NMI以及hard_fault之外的中断
    b. FAULTMASK:中断屏蔽位,1位宽,屏蔽除NMI之外的所有中断
    c . BASEPRI:当前优先级设置,位数取决于厂商有多少个外设中断,
    d. CMSIS-Core 提供了多个C函数,可以访问中断相关寄存器,也提供了汇编函数

x = __get_BASEPRI(); // Read BASEPRI register
x = __get_PRIMARK(); // Read PRIMASK register
x = __get_FAULTMASK(); // Read FAULTMASK register
__set_BASEPRI(x); // Set new value for BASEPRI
__set_PRIMASK(x); // Set new value for PRIMASK
__set_FAULTMASK(x); // Set new value for FAULTMASK
__disable_irq(); // Set PRIMASK, disable IRQ
enable_irq(); // Clear PRIMASK, enable IRQ

; 汇编代码
MRS r0, BASEPRI ; Read BASEPRI register into R0
MRS r0, PRIMASK ; Read PRIMASK register into R0
MRS r0, FAULTMASK ; Read FAULTMASK register into R0
MSR BASEPRI, r0 ; Write R0 into BASEPRI register
MSR PRIMASK, r0 ; Write R0 into PRIMASK register
MSR FAULTMASK, r0 ; Write R0 into FAULTMASK register

;primask 以及 faultmask 特殊处理
CPSIE i ; Enable interrupt (clear PRIMASK)
CPSID i ; Disable interrupt (set PRIMASK)
CPSIE f ; Enable interrupt (clear FAULTMASK)
CPSID f ; Disable interrupt (set FAULTMASK)
  • Control寄存器
    nPRIV:设置特权等级还是非特权等级
    SPSEL:选择主栈还是线程栈
    之前介绍的,异常永远是特权模式,且使用主栈。
    ARM学习(5) 异常模式学习(CortexM3/M4)_第3张图片
    这里有两种情况:(通常RTOS中栈切换版本会使用进程栈/线程栈 PSP,其他裸机情况下都使用的时候主栈MSP)
    a .进入异常模式之前是主栈:压栈使用主栈,然后切到异常函数下执行(下文图1),
    b. 进入异常模式之前是进程栈(线程栈): 压栈使用进程栈,然后切到异常函数下执行,这时使用主栈,特权模式(下文图2)
    ARM学习(5) 异常模式学习(CortexM3/M4)_第4张图片
    步奏:
  • 线程模式下:使用主栈(MSP)
  • 中断#1来临,首先使用主栈(MSP)压栈,然后切到中断服务程序#1,这时处于特权模式下,使用主栈(MSP)
  • 中断#2来临:使用主栈进行压栈,然后切到中断服务程序#2执行,这时处于特权模式下,使用主栈(MSP)
  • 中断#2结束:根据LR 选择 MSP主栈,进行出栈,同时还是处于特权模式下,
  • 中断#1结束,根据LR选择MSP主栈,进行出栈,同时还是特权模式下执行
  • 线程模式下:使用主栈继续执行
    ARM学习(5) 异常模式学习(CortexM3/M4)_第5张图片
    步奏:
  • 线程模式下:使用线程栈
  • 中断#1来临,首先使用进程栈(PSP)压栈,然后切到中断服务程序#1,这时处于特权模式下,使用主栈(MSP)
  • 中断#2来临:使用主栈进行压栈,然后切到中断服务程序#2执行,这时处于特权模式下,使用主栈(MSP)
  • 中断#2结束:根据LR 选择 MSP主栈,进行出栈,同时还是处于特权模式下,
  • 中断#1结束,根据LR选择PSP进程栈,进行出栈,同时切到线程非特权模式下执行
  • 线程模式下:使用线程栈继续执行

上文说的:特权模式与图中的处理模式一致含义。

2、异常与中断

2.1、系统异常

  • 复位:reset_handler,用来做系统初始化以及分散加载,最终跳到main函数,
  • 异常函数处理:hard_fault,mem_mange_fault,bus_fault,usg_fault,处理各种异常
  • SVC:常用来做系统调用,必须立即响应,否则造成硬件错误
  • PendSV:RTOS常用该异常做上下文切换,因为该异常可以软件触发且挂起
  • SysTick:定时中断,可用作滴答定时器

ARM学习(5) 异常模式学习(CortexM3/M4)_第6张图片
CortexM3/M4响应异常时,会有一些列的操作,

  1. 入栈:硬件自动压入xPSR, PC, LR, R12以及R3‐R0
  2. 取向量:取出正确的异常向量,这样能找到异常处理函数,
  3. 选择堆栈指针:更新SP指针,更新PSR,更新PC,更新LR(EXC_RETURN),
    ARM学习(5) 异常模式学习(CortexM3/M4)_第7张图片
    中断返回:
  4. 出栈:与入栈的顺序想对应,
  5. 更新NVIC寄存器:清除相关的中断或者异常标志位,

异常返回值LR (EXC_RETURN),异常处理LR在M3/M4架构下有新的意义,
ARM学习(5) 异常模式学习(CortexM3/M4)_第8张图片
ARM学习(5) 异常模式学习(CortexM3/M4)_第9张图片
异常的寄存器如下:主要是4个中断(hardfault、mem/bus/usr fault )关联的寄存器信息。
ARM学习(5) 异常模式学习(CortexM3/M4)_第10张图片

  • hardfault:硬件错误中断,很多场景被叫做硬件上访中断。
    • 中断优先级是-1,primask无法屏蔽,但是faultmask可以屏蔽。
    • 其他异常中断没有使能,则会进入该中断
    • 汇编屏蔽代码如上面介绍
    • HFSR状态寄存器是指示出现hardfault的原因的寄存器信息,30、31 bit分别指示是:上访错误和调试事件 1bit是指:获取向量错误,比如中断向量表获取?待测试。
      ARM学习(5) 异常模式学习(CortexM3/M4)_第11张图片
__set_PRIMASK(1);  //屏蔽除NMI 和 HardFault以外其他的中断 ;__set_PRIMASK(0); 
__set_FAULTMASK(1); //屏蔽除NMI 中断;__set_FAULTMASK(0);
//可嵌套的开关中断函数primask
u32 arm_v7_disable_irq() 
{ 
    u32 mask;
	asm volatile("mrs %0, primask" : "=r"(mask));
	asm volatile("cpsid i");
	return mask
	
} 

void arm_v7_enable_irq(u32 mask)
{
	asm volatile("msr primask,%0" : : "r"(mask));
}

//可嵌套的开关中断函数faultmask
u32 arm_v7_disable_fiq() 
{ 
    u32 mask;
	asm volatile("mrs %0, faultmask" : "=r"(mask));
	asm volatile("cpsid i");
	return mask
	
} 
void arm_v7_enable_fiq(u32 mask)
{
	asm volatile("msr faultmask,%0" : : "r"(mask));
}

void save_generic_regs();
save_generic_regs:
    stmfd   sp!,{r9-r12}
    stmfd   sp!,{r5-r8}
    stmfd   sp!,{r1-r4}
    stmfd   sp!,{r0}
    bx  lr
 
 /* void restore_generic_regs(); */

restore_generic_regs_from_stack:
    ldmfd   sp!,{r0}
    ldmfd   sp!,{r1-r4}
    ldmfd   sp!,{r5-r8}
    ldmfd   sp!,{r9-r12}
    bx  lr
  
  u32 regs_data[15];
 void save_regs_to_global_var(u32 save_reg_addr)
 {
 	memcpy((u8*)regs_data,(u8*)save_reg_addr,15*4);
 }
 
 /*1:hard 2:mem 3:bus 4:usr*/
void enter_exception_mode(u8 exception_reason)
{
	/* get the exception reg info*/
	/* print backtrace */
	/* save the stack and global reg */
}

void  HardFault_Handler();
HardFault_Handler:

	/* save the lr reg to the stack */
	push lr
	
	/* reserve stack space for sp  */
    sub   sp,sp,#4
   
    /*save r0-r12 to stack (13*4=52) */
    bl    save_generic_regs

    /*save sp to stack ,the sp is the reg before it entered the hardfault,4+4+52=60*/
    add   r0,sp,#60
    str   r0,[sp,#52]

    /*save regs to the global reg */
    mov   r0,sp
    bl   save_regs_to_global_var

    /* restore the regs from the stack */
    bl    restore_generic_regs

    mov r0,#1
    /* enter the assert mode */
    blx   enter_exception_mode

    /* skip the stack space for sp */
    add   sp,sp,#4

    /* restore the lr from stack */
    ldmfd sp!,{lr}

    bx     lr

ARM学习(5) 异常模式学习(CortexM3/M4)_第12张图片

  • mem fault:存储器访问异常中断,
    • 配置来使能MemManage,可设置优先级
    • 由cfsr低字节指示错误,如MMA VALID 有效,mmfar指示错误地址
    • MPU的配置规则,导致指令或者数据访问冲突,比如MPU配置特权访问,而非特权模式却访问对应区域;或者对MPU只读区域进行写操作;压栈和出栈导致的错误。
SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk;
NVIC_SetPriority(MemoryManagement_IRQn,0);

ARM学习(5) 异常模式学习(CortexM3/M4)_第13张图片

  • bus fault:总线访问异常

    • 配置来使能BusFault,可设置优先级,由总线接口上的错误响应处罚
    • 由fsr第二个字节来表示,如BFAR VALID,则BFAR指示错误地址。
    • 1、读(取)指令或者读写数据终止,取指令必须是错误的阶段到了执行阶段才会触发异常,取向量异常会强制进入hardfault,即使使能总线错误。
    • 2、压栈或者出栈错误
    • 3、访问非法的存储器位置,设备未就绪就开始访问(Dram未初始化),收到从设备错误响应,存储器访问权限问题。
    • 4、与mem fault错误由很多类似的错误。
      ARM学习(5) 异常模式学习(CortexM3/M4)_第14张图片
  • usr fault:

    • 配置来使能UsgFault,可设置优先级,由总线接口上的错误响应处罚
    • 222
    • 333

来看一个实际的例子,引发了非对齐访问的血案。

typedef struct wifi_data_rec_struct
{
	u16 header;
	u16 addr;
	u32 arg1;
	u32 arg2;
	u16 arg3;
	u16 checksum;
}wifi_data_rec_t,*wifi_data_rec_ptr_t;

typedef strcut wifi_info_struct
{
	u32 wifi_addr;
	u8 recv_flag;
	u8 test_flag;
	u16 wifi_data;
	u8 tx_buf[128+2];
	u8 rx_buf[16];
}wifi_info_t, wifi_info_ptr_t;

wifi_info_t wifi_info_g;
u8* rx_buf=wifi_info_g.rx_buf;


/*......*/
u16 temp_value = (*(u16*)rx_buf);
PRINTF("temp_value=%x rx_buf_addr=0x%p \r\n",temp_value,rx_buf);
wifi_data_rec_t temp_checksum=(*(wifi_data_rec_ptr_t )rx_buf);
PRINTF("temp_value=%x temp_check_sum=%x\r\n",temp value, temp_checksum.checksum); 
u16 cal_check_sum=cal_wifi_data_cmd(temp_checksum);

详细介绍一下错误的原因:

  1. 由下图串口助手以及调试器数据,可以看到打印完成temp_value之后,就导致进入hardfault,其实是usg_fault,因为笔者并没有使能usg_handler函数。
  • 串口助手打印:
    在这里插入图片描述
  • Ozone调试界面:
    ARM学习(5) 异常模式学习(CortexM3/M4)_第15张图片
    ARM学习(5) 异常模式学习(CortexM3/M4)_第16张图片
  1. 那就是那句强转代码导致的异常,

  2. 因为后面要计算校验和,所以编译器就把每个数据加载到寄存器,然后去计算。

  • Trace32加载axf查看汇编:
    ARM学习(5) 异常模式学习(CortexM3/M4)_第17张图片
  1. 又因为数据恰好是16个字节,编译器直接使用多字节加载指令:ldm,寄存器寻址;而ldm要求地址必须4Byte对齐(WORD,后面图中的arm手册查询),但是笔者的代码中rx buf地址由于上面tx buf的原因,导致没有按照4Byte对齐,引发了对齐访问错误。
  • ARM手册查看:
    ARM学习(5) 异常模式学习(CortexM3/M4)_第18张图片
    ARM学习(5) 异常模式学习(CortexM3/M4)_第19张图片
  1. 笔者尝试把计算校验和的代码删除,则没有发生错误,从汇编来看,只加载了checksum,其他数据没有加载,而单字加载,arm指令是支持非对齐访问的,所以就没有出现异常。
    ARM学习(5) 异常模式学习(CortexM3/M4)_第20张图片
    在这里插入图片描述
    ARM学习(5) 异常模式学习(CortexM3/M4)_第21张图片
    ARM学习(5) 异常模式学习(CortexM3/M4)_第22张图片
    上图中介绍了CCR寄存器,系统控制器,如果使能了非对齐检查,那么LDR 以及LDRH也不能非对齐访问了,如果产生,能报异常。
SCB->CCR = SCB_CCR_UNALIGN_TRP_MSK;

使能了改bit之后,ldrh 正常执行,因为其满足 half word对齐,但是ldr造成了异常,因为不满足word 对齐。
ARM学习(5) 异常模式学习(CortexM3/M4)_第23张图片
ARM学习(5) 异常模式学习(CortexM3/M4)_第24张图片
ARM学习(5) 异常模式学习(CortexM3/M4)_第25张图片

  • addr2line 查看异常地址信息
    320行来看,其实并不是强转的代码,是因为编译器把打印和加载放到一行代码来对应,所以也不能全部按照源码的行数来看,因为源码和汇编指令可能不对应,在开了优化的情况下。
    在这里插入图片描述
    如果关闭优化,321行,就是强转的那行代码,对于调试来说,更加友好。
    ARM学习(5) 异常模式学习(CortexM3/M4)_第26张图片
    在这里插入图片描述

  • 寄存器信息与自动压栈匹配:
    ARM学习(5) 异常模式学习(CortexM3/M4)_第27张图片
    笔者打开SP指针对应得内存区域,可以看到已经自动将pc lr等数据压入堆栈,比如:

  • 0x200098E8:R0-R3

  • 0x20009FE8:R12,LR,PC,xPSR
    可以见到PC:0x080086A0,可以正确看到程序出错前的地址,帮助我们分析错误。

    2.2、中断

ARM学习(5) 异常模式学习(CortexM3/M4)_第28张图片
中断管理使用NVIC,嵌入式向量中断控制器进行管理,

3、参考

ARM Cortex-M3与Cortex-M4权威指南
Cortex-M3权威指南

你可能感兴趣的:(ARM,arm,CortexM3/4,异常模式,特权模式,中断向量)