ARM学习(3) 异常模式学习(CortexR5)
笔者简单介绍一下ARM CortexR5异常模式 学习
1、由来
笔者工作中用到了SSD的主控芯片是基于CortexR5系列的,所以研究一下CortexR5系列的一些异常模型。
什么时候会用到这些异常模型呢?比如发生异常,程序进入abort 等,需要分析原因,这时候需要异常模型下的一些数据来反映现场的一些信息,比如PC、CPSR以及FAR等。
最典型的应用就是发生异常的时候触发CoreDump,保存现场数据。
2、异常模式
- 异常会打断正常程序流的顺序执行,同时为了返回正常的程序流处继续执行,在进入异常服务函数之前需要保存返回地址等信息。
- 异常处理主要有:复位、中断、Abort、SVC、undefined、断点指令等。
2.1 异常模式的进入与退出
初看这张表时,可能不太清楚这张表表达的意思是什么? - 第一列代表的是进入哪种异常
- 第二列返回异常的指令
- 第三列进入异常时LR与PC的关系,IA笔者理解的是instruction address,就是指令地址
那么LR是返回的地址,肯定比PC值大,所以根据当时异常LR的地址,可以推断出PC在什么地方发生异常,且LR的值在发生异常时,自动被赋值。
例如触发Data Abort,那么PC = LR -8 可以知道具体位置,从而恢复当时的现场,查出具体原因。
进入异常时,主要有以下过程:
- 保存下一条指令到LR,此时指令地址的保存依赖于异常类型以及执行状态(ARM或者Thumb),可能+2、+4或者+8
- 复制CPSR到SPSR,依赖于异常类型,可能会修改CPSR中的IT 执行状态,以便于促进程序返回
- 依赖于异常类型,修改CPSR的模式位M[4:0],清除IT 指令状态位
- 设置系统寄存器的中EE bit为1(进入异常时 该标志位为1 )
- 设置系统寄存器中TE bit 为1 (thumb下使能异常产生)
- 强制PC去相应的异常向量中取下一条指令
处理器也会禁止中断标志位,阻止一些其他不受管理的中断嵌套。
这里我理解的都是在异常模式下的寄存器进行操作,比如LR,也是异常模型下的LR。
2.2 复位
nRESETm 信号被驱动为低电平时,触发复位,处理器就放弃指令执行。
当nRESETm and nCPUHALTm 再次被驱动为高电平时,处理器有如下行为:
- 强制CPSR M[4:0] 进入 SVC模式。CPSR中的A、I、F bit置1,其他位 未知,基于CFGEE 引脚的状态置位。
- 强制PC从复位向量地址拿下一条指令执行
- 依据TEINIT 引脚的状态,转换成相应的ARM或者Thumb状态,并且重新开始执行。
复位之后,除PC以及CPSR外,其他寄存器都是未知的
2.3 中断
处理器有两个中断输入,正常中断(nIRQm)和快速中断(nFIQm),每个中断引脚,都会引起处理器采取合适类型的中断异常,CPSR 中的 I bit与 F bit 会屏蔽中断。
有很多措施来提升中断延迟,换句话说 就是减少中断引脚输入到中断服务函数的执行的时间。自ARMv6以来,处理器就引入了低中断延迟的措施。
处理器有端口来链接向量中断控制器,并且支持不可屏蔽中断。
2.3.1 普通中断
- IRQ异常是一个通用型的中断,由nIRQm输入引脚造成的异常,比FIQ 优先级更低,在进入FIQ序列时,IRQ被屏蔽。
- nIRQm引脚的输入要一直触发处理器认为这是一个中断请求或者 其来自于VIC接口 或者是软件中断才行。
- IRQ的返回,SUBS PC ,R14~irq~,#0x4
- 进入IRQ 异常,CPSR的I bit 位置1 意味着普通中断无法打断普通中断,只能由FIQ打断,
- 当然你可以清除 I bit,保存相关的寄存器数据,来实现嵌套。
2.3.2 快速中断
- 快速中断有自己的私有的8个寄存器,减少了寄存器保存的时间,所以响应速度更快。
- nFIQm 引脚输入造成了快速中断异常
- FIQ的返回,SUBS PC ,R14~fiq~ ,#0x4
- 进入FIQ 异常,CPSR的I bit F bit 位置1 意味着相关中断都无法打断
- 当然你可以清除F I bit,保存相关的寄存器数据,来实现嵌套。
- NMFI bit 可以阻止屏蔽FIQ中断
- 这里笔者觉得编译器做的不好的地方是,FIQ没有发挥出其优势,其有独特的R8-R14寄存器的优势,但是就当没有,和IRQ一样了,同样保存了r11 以及 r12这种。
- 这里的压栈,是使用异常模式下的栈,而不是正常程序运行的栈。
2.3.3 低中断延迟
- 一些内存访问指令,在中断来临前后,可能会被访问两次,所以,PC会回到LR -4的位置,这是为了加快中断响应,降低延迟,中断来临,来放弃一部分内存访问指令的访问,等待中断执行完成之后再继续访问
- 一些对内存访问敏感,比如设备内存或者强排序的内存,应该避免使用多自己写入加载的指令,这种访问一定会完成或者不完成,
2.3.4 中断控制器
- 多个中断请求输入,每个中断源一个,以及一个或者多个合并到处理器的中断请求输出
- 可以屏蔽中断请求
- 支持中断源的优先级嵌套
即使带了中断控制器,软件仍然需要:
- 决定哪个中断源是中断请求服务
- 决定中断源的服务程序在什么地方被加载
- 屏蔽和清除中断源,以便于允许其他中断被采取。
- 与中断控制器握手,获取IRQ中断处理器地址。
2.3.5
- VE 为IRQ 控制器的中断地址选择,是VIC提供还是默认的中断向量表0x18
- nFIQ 或者nIRQ 代表 触发源电平 ,这个条件判断有点意思,多种情况覆盖,F 禁止,nIRQ没有电平触发,I 禁止,nFIQ没有电平触发,或者 F与 I 都禁止,或者nFIQ与nIRQ 都没有信号 直接返回,其他情况下,可以继续进行。
- FIQ 如果在没有禁止的时候,有电平触发,则进入FIQ的流程,(做完一列列操作后(见上文异常模式的进入与退出),执行0x1C)
- IRQ 如果在没有禁止的时候,有电平触发,则进入IRQ的流程,
- IRQ 比较特殊的是,VIC可以提供中断地址,由VE bit指示,否则的话是 执行0x18地址
- V bit 可以指示 中断的地址 是 0x18/0x1C 还是 0xFFFF0018/0xFFFF001C
由上图可以看出,IRQ FIQ中断向量的地址 是 0x18 0x1C,不过其都没有顺序执行,而是跳到另外的地址执行,(有一种情况是FIQ handler 或者 IRQ Handler 被链接到固定的段上,所以需要跳转过去执行)
2.4 Aborts
当处理器内存系统不能正常的获取内存数据,会产生Abort,Abort发生的时候是有多种原因的:
- MPU保护的地址会触发
- 内存总线传输数据的错误响应
- ECC检测到的数据错误
作用:了解Abort之后,在发生ABort之后,在Abort处理函数里面做相应的处理,记录现场数据,就可以推测出问题的地址,进行排查问题。
指令预取的错误会触发prefetch abort,数据获取错误会触发 data abort,同步abort 是准确的,异步abort 是不准确的。
当abort发生时,处理器会记录abort的类型。
Abort的处理函数,会在中断向量表里面指定地址。
start_vector.s
Import Reset_Handler
Import Abort_Handler
Import Prefetch_Handler
Vectors
LDR PC,Reset_Addr ;0x0
LDR PC,Undefined_Addr ;0x4
LDR PC,SVC_Addr ;0x8
LDR PC,Prefetch_Addr ;0xC
LDR PC, Abort_Addr ;0x10
LDR PC,IRQ_Addr ;0x18
LDR PC,FIQ_Addr ;0x1C
Reset_Addr DCD Reset_Handler
Abort_Addr DCD Abort_Handler
Abort_Handler FUNCTION
push .....
pop .....
ENDFUNC
Prefetch_Handler FUNCTION
push .....
pop .....
ENDFUNC
2.4.1 Data Abort
获取内存数据发生错误会造成Data Abort,如果获取内存指令并没有执行或者被打断了,那么不会造成Data Abort。
- Data abort可能是同步或者异步的,根据造成的错误类型来定
- 异步Abort 可以被屏蔽,在CPSR中A bit可以控制。
- DFAR 以及DFSR 记录出错的地址与类型
DFSR 寄存器保存这出错的类型,有同步异常和异步异常
DFAR 记录着出错的地址
MRC p15,0,r0,c5,c0,0 ;dfsr 读取
MRC p15,0,r0,c6,c0,0 ;dfar 读取
2.4.2 Prefetch Abort
其abort 必须是同步的,当其发生时,处理器会将预取的指令标记为无效的,但是直到执行到这个指令时,才会触发Prefetch Abort,如果指令没有执行,则异常不会被触发。
- IFSR 记录着指令异常的类型,可以看到其下面没有异步的异常,而DATA Abort则有
MRC p15,0,r0,c5,c0,1 ;ifsr 读取
MRC p15,0,r0,c6,c0,2 ;ifar 读取
2.5 SVC指令
SVC 通常也称为软中断,通过指令可以进入SVC异常,在异常里面可以进行做事。
最常见的就是OS的调度处理,比如等信号量时 直接触发SVC指令,进入异常,然后在异常中进行栈切换,然后进入其他task去执行,如果等到信号量,则 直接触发SVC异常,再进行栈切换,进入到等信号量的task执行。
两级task ,task1 优先级1(高) ,task2 优先级2
抢占式调度(高优先级的任务 如果不释放,会一直执行)
- task1 任务执行
- task1 等待信号量,然后在等待信号的函数里面触发SVC异常,保存现场,切换到task2执行
- task2 任务执行
- task2 释放信号量,在释放信号量的函数里面触发SVC异常,保存现场,切换到task1执行
- task1 等待到信号量继续执行,
协程调度(靠主动释放 (yield)去进行栈切换)
- task1任务执行
- task1 等待信号量,然后在等待信号的函数里面触发SVC异常,保存现场,切换到task2执行
- task2 任务执行
- task2 释放信号量,在释放信号量的函数将task1加入就绪队列,任务继续执行
- 直到执行到yeiled 或者sleep 等等,然后触发SVC异常,保存现场,切换到task1执行。
SVC指令如下,在thumb 和 arm模式下有区别。
SVC 有相应的opcode,即imm8 可以区分在required sevvice是什么服务函数。
在SVC handler 里面读取请求服务opcode,来决定是什么请求service 来做什么样的处理。
共有 0 条评论