armV7基础概念
本文主要介绍armV7系列core(如CR5,CA7等)的一些基础知识。
一. 寄存器
armV7 core包括16个32位通用寄存器R0~R15,程序状态寄存器CPSR/SPSR。
1. R13为SP(栈指针)寄存器,存的是栈顶地址(递减栈);
2. R14为LR(连接)寄存器,存放的是bl跳转指令的返回地址,或是异常/中断发生时PC值减4(arm模式,指令长度为32位)或2(thumb模式,指令长度为16位);
3. R15为PC(程序计数)寄存器,存放的是下一条正在取值的指令的地址,由于armV7采用三级流水线:取指--译码--执行,因此指向当前执行指令后的第三条指令。一般对于arm模式来说,PC值为当前执行指令的地址+8
4. CPSR/SPSR为程序状态寄存器,其中CPSR为程序状态寄存器,包括当前处理器模式(arm/thumb),IRQ/FIQ使能,条件码等。SPSR用于在模式切换时保存CPSR,可以理解为前一模式CPSR的值。CPSR中,通常关注低8bit,其含义如下:
bit7/bit6用于控制IRQ/FIQ是否使能;bit5用于控制处理器处于arm模式还是thumb模式;bit[4:0]表示了cpu所处的模式,主要包括下列几种:
- USER mode,处于USER mode时,CPSR[4:0]=0x10,该模式下可以访问R0~R15, CPSR寄存器。注意USER mode没有SPSR寄存器,因为它不是异常处理模式。USER mode是unprivilege mode,在USERmode下,无法访问全部系统资源,例如某些寄存器。
- FIQ mode,处于FIQ mode时,CPSR[4:0]=0x11,该模式下可以访问R0~R17,R8_fiq~R14_fiq,R15,CPSR,SPSR_fiq寄存器。与USER mode相比,FIQ有单独的R8~R14,SPSR寄存器,用R8_fiq~R14_fiq,SPSR_fiq表示,其他模式同。当FIQ发生时,会打断当前正在执行的程序,被打断前的CPSR的值会被存入SPSR_fiq。当CPSR[5]!=1时,如果有FIQ中断到来,则会进入到该mode。也可以使用msr指令直接操作CPSR的值来切换mode,这个方式可以用在所有mode切换中,实际使用中,通过msr指令操作CPSR切换到不同mode后,设置对应mode的R13(SP)寄存器,从而可以实现不同mode使用不同的栈。
- IRQ mode,处于IRQ mode时,CPSR[4:0]=0x12,该模式下可以访问R0~R12,R13_irq、R14_irq,R15,CPSR,SPSR_irq寄存器。与FIQ相比,IRQ mode只有单独的R13_irq、R14_irq寄存器,因此虽然FIQ/IRQ都是中断,但IRQ需要压栈保护更多的寄存器(R8~R12),因此速度上会比FIQ更慢。当CPSR[6]!=1时,如果有IRQ中断到来,则会进入到该mode。
- SVC mode,处于SVC mode时,CPSR[4:0]=0x13,该模式可以访问R0~R12,R13_svc、R14_svc,R15,CPSR,SPSR_svc寄存器。可以使用swi #0指令进入SVC mode,另外,core reset/power on时,arm操作CPSR切换至SVC mode,这也是正常boot时的mode。
- ABT mode,处于ABT mode时,CPSR[4:0]=0x17,该模式可以访问R0~R12,R13_abt、R14_abt,R15,CPSR,SPSR_abt寄存器。当cpu遇到prefetch abort或data abort时,会进入该模式。
- UND mode,处于UND mode时,CPSR[4:0]=0x1B,该模式可以访问R0~R12,R13_und、R14_und,R15,CPSR,SPSR_und寄存器。当cpu遇到未定义指令时会进入该模式。
- SYS mode,处于SYS mode时,CPSR[4:0]=0x1F,该模式可以访问R0~R15,CPSR寄存器,并且由于不是异常处理mode,也没有SPSR寄存器。SYS mode可以访问的寄存器与USER mode相同,区别是SYS mode是privilege mode,可以访问系统资源。
- 其余还有MON mode和HYP mode,不常用,这里不展开。
5. R0~R3可以用来存放函数的前4个参数,如果参数多于4个,则需要通过栈来传递参数。因此,这四个寄存器是caller saved的,而其他的则是callee save的。举个例子,假如存在下列调用逻辑
void funA(void) { int a=1, b=2, c=3, d=4; funB(a, b, c, d); } void funB(int a, int b, int c, int d) { return; }
其中,funB有四个参数,那么在调用时,会首先将{a, b, c, d}赋给{r0-r3},写成汇编:
mov r0, #1
mov r1, #2
mov r2, #3
mov r3, #4
而r0-r3的原始值会在调用funB之前由funA(caller)压栈保护;而其他的寄存器,如果被用到了,则由funB(callee)压栈保护。随后调用funB,写成汇编:
而r0-r3的原始值会在调用funB之前由funA(caller)压栈保护;对于其他的寄存器,如果被用到了,则由funB(callee)压栈保护。随后调用funB,写成汇编:
bl funB
这条指令后,会将该条指令的下一句指令的地址存入R14(LR)寄存器,在funB函数中,return会被编成汇编:
bx r14
随后会返回R14(LR)寄存器中存的地址继续执行,需要注意的是,当使用bx Rn指令跳转时,如Rn寄存器中存的地址最低位为1,则跳转后进入thumb模式,否则为arm模式。
二. 中断向量表
与CortexM系列(如STM32系列)相比,armV7系列的core,其中断向量表有两个主要的不同:
1. 向量表中存的不再是地址,而是一条指令,通常是一句跳转指令,主要有两种形式:
a. b <label>
其中label为真正的回调的地址。该方式最简单,但是由于b指令的寻址范围为以pc指针为基准的前后32MB空间,适用于跳转距离较近的情况。
b. ldr pc, [pc, #offset]
这种方式可以访问到完整的32位指示的内存空间,但是相较于前一种方式,可能需要额外的周期数。
2. 向量表中不再包含所有的中断,只包括下列几种:
其中IRQ/FIQ对应于一般的的中断,Software Interrupt是通过swi指令产生的软件中断,Reset是上电/复位时产生的中断,Undefined/Prefetch Abort/Data Abort均是异常产生的中断,会在下文介绍。
默认情况下,中断向量表位于0地址处。当然,也可以通过调整SCTLR寄存器的BIT_13来修改:
- SCTLR[13]==1,向量表位于0xFFFF0000处,这也是linux采用的方式;
- SCTLR[13]==0,向量表位于0x00000000处,这是默认情况。
三. 中断和异常
当CPU收到中断信号或在执行过程中遇到异常时,根据遇到的中断/异常的不同,CPU会中断当前正在执行的程序流,跳转到中断向量表的对应位置,执行该位置内存的指令。
对于异常,相对复杂一些。前文提到过,armV7包括三级流水线:取指—译码—执行,即单个时钟周期内会同时执行这三种操作,假如代码在内存中的分布如下(A,A+4等代表地址,instruction 1, instruction2等代表指令):
A: instruction 1
A+4: instruction 2
A+8: instruction 3
A+12: instruction 4
在单个时钟周期内,cpu可以同时执行instruction 1@A,译码instruction 2@A+4,取指instruction 3@A+8。由于PC寄存器总是指向取指指令的地址,因此pc值是正在执行指令的地址+8。在不同阶段出现问题,CPU会抛出不同的类型的异常,主要包括:
- Prefetch Abort
在流水线的取指阶段,如果指令所在地址不可访问,则该指令被标记为无效,流水线上之前的指令继续执行。当cpu执行到该条无效指令时,cpu会抛出prefetch abort异常,此时LR寄存器中存的是异常指令地址+4;
- Undefined Abort
在流水线的译码阶段,如果指令无法解码,cpu执行到该条指令时,会抛出undefined abort异常,此时LR寄存器中存的是异常指令地址+4;
- Data Abort
在流水线的译码阶段,如果访问非法地址,该条指令执行完毕后则会抛出data abort异常,此时PC指针已经更新,LR寄存器中存的是异常指令地址+8;
对于普通的中断来说,如前面所述,和CortexM系列相比,由于中断向量表中只有IRQ/FIQ两项用于普通的中断响应,因此armV7系列的Core通常会外接一个ICU(Interrupt Control Unit)。响应中断时,通过外接的ICU判断中断号,对于CPU而言,所有的中断信号或在一起,统一通过IRQ/FIQ响应。此外,FIQ/IRQ中断是异步产生的,CPU无法知道何时会来中断。如CPU执行指令时中断到来,cpu不会立刻响应,而是会在当前周期完成后响应。例如,当cpu在执行instruction 1@A时产生中断,此时PC=A+8,cpu不会立刻响应中断。当instruction 1执行完毕后,PC更新为A+12,cpu响应中断,LR_irq/LR_fiq被赋为A+8。即指令instruction 2@A+8并未执行,因此当从中断返回时,需要回到LR_irq/LR_fiq-4继续执行。
然后是软件中断。过指令swi #imm
主动触发,用于切换至SVC mode。该中断产生于指令的执行期间,LR_svc寄存器被更新为swi #imm指令地址+4(其中#imm
为立即数,如swi #5
)。#imm
被存在32位swi #imm
指令的低24位,如对于swi #5
,32位指令的低24位即为5(swi #5
的对应二进制代码是0xEF000005)。在swi的中断回调中可以通过读该值进行不同的处理。与IRQ/FIQ中断不同,软件中断从中断中返回时无需-4,只需要回到LR_svc继续执行。
最后一点,与CortexM不同,中断/异常产生时,不再有硬件自动压栈保存的寄存器,除去不同模式独有的寄存器以外,原则上其他寄存器都需要压栈保存(R13、R15除外)。如果在回调中进行了其他的调用,那么R14(LR)也需要压栈保存。以最基础的IRQ为例,汇编代码可以简化为:
IRQ_Handler: push {r0-r12, lr} bl irq_handler pop {r0-r12, lr} sub lr, #4 bx lr
这样,一个完整的IRQ响应流程可以描述为:
a. 外部产生中断
b. core响应中断,硬件通过写CPSR的bit5禁掉IRQ,因此默认情况下不存在IRQ中断抢占,但FIQ仍然可以抢占IRQ。cpu跳转至0x18执行,0x18处的指令可以是
b IRQ_Handler
ldr pc, =IRQ_Handler
c. cpu跳转至IRQ_Handler执行
d. 返回被打断时执行的程序流
armV7在芯片中的使用非常广泛(CR5/CA7等)。在实际工程中,往往在异常函数的回调中保存各个模式下的寄存器,这样当CPU出现异常时,首先根据保存下来的CPSR判断遇到了那个种类的异常,再根据上面所述,通过LR寄存器判断异常发生的指令的位置,从而做进一步的分析。例如发现CPU发生了undefined abort,通过LR_abt定位到问题的指令,将指令的上下文的内容与正常情况下做对比,如果不一致,那么有可能是其他的线程/core踩了代码段。值得一提的是,armV7中存在异常中又触发异常的情况,比如在undefined abort中,如果恰好对应的回调的代码段也被踩了,那么就有可能出现undefined abort中又发生undefined abort,这点与CortexM系列不同,后者出现这种问题时会上访到HardFault异常。
四. boot流程
结合上面所述,armV7的基本boot流程可以表示为:
- 上电后,cpu执行reset handler,CPSR被设置为SVC mode,FIQ/IRQ disable,arm模式;
- 通过msr指令修改CPSR至不同的mode,设置不同mode下R13(SP)寄存器的初始值,即设置不同模式下的栈;
- 做外设的初始化,例如ICU(中断管理模块),时钟使能,变频等操作;
- 通过msr指令修改CPSR至USER mode(非必须,也可以默认运行在SVC mode),跳转至main函数,进入C语言的世界。
#嵌入式工程师##校招##嵌入式#
21届985微电子硕士,校招SSP进入紫光展锐任嵌入式工程师,8个月后跳槽至ASR工作至今。本专栏记录上学/工作时学到的一些知识,debug的一些工具使用等,希望有所帮助。