笔者简单介绍一下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 ,R14irq,#0x4
进入IRQ 异常,CPSR的I bit 位置1 意味着普通中断无法打断普通中断,只能由FIQ打断,
当然你可以清除 I bit,保存相关的寄存器数据,来实现嵌套。
2.3.2 快速中断
快速中断有自己的私有的8个寄存器,减少了寄存器保存的时间,所以响应速度更快。
nFIQm 引脚输入造成了快速中断异常
FIQ的返回,SUBS PC ,R14fiq ,#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 ;0x1CReset_Addr DCD Reset_Handler Abort_Addr DCD Abort_Handler Abort_Handler FUNCTION push ..... pop ..... ENDFUNC Prefetch_Handler FUNCTION push ..... pop ..... ENDFUNC123456789101112131415161718192021222324252627282930
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 读取12
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 读取12
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 来做什么样的处理。