一、ARM处理器的7种模式
问:处理异常,涉及ARM处理器的哪些工作模式?
除用户模式外,其余6种工作模式都属于特权模式;
特权模式中除了系统模式以外的其余5种模式称为异常模式;
大多数程序运行于用户模式;
进入特权模式是为了处理中断、异常、或者访问被保护的系统资源;
芯片从上电开始,就是SVC模式,在该模式下运行uboot,启动linux内核。在linux上运行用户应用程序时,内核会把ARM的工作模式切换到USR模式。
二、ARM处理器的通用寄存器
问:处理异常,需要读写哪些寄存器?
ARM寄存器及功能介绍
R0~R3通常用于函数调用,传递参数以及返回结果;
R4~R11通常用于子函数的局部变量,调用C函数时,这些寄存器会被ARM自动地保存与恢复;
R12~R15则可以有特别的用途。
R13(SP)——栈指针寄存器,用于保存堆栈指针;
R14(LR)——程序连接寄存器,当执行BL子程序调用指令时,R14中得到R15的备份,而当发生中断或异常时,R14保存R15的返回值;
R15(PC)——程序计数器;
三、异常向量表
问:异常发生时,程序指针PC是怎么找到异常处理函数的?
ARM架构的处理器有一个异常向量表,即发生某一个异常后,会根据异常向量表设置pc为相应的处理函数入口地址。
.text
.global _start
_start:
b reset /* vector 0 : reset */
ldr pc, und_addr /* vector 4 : und */
ldr pc, swi_addr /* vector 8 : swi */
b halt /* vector 0x0c : prefetch aboot */
b halt /* vector 0x10 : data abort */
b halt /* vector 0x14 : reserved */
ldr pc, irq_addr /* vector 0x18 : irq */
b halt /* vector 0x1c : fiq */
四、异常发生
问:异常发生时,程序应该做什么?ARM会帮我们做什么?
当一个异常导致模式的改变时,ARM核自动地:
(1)把cpsr保存到相应模式下的spsr
(2)把pc保存到相应模式下的lr
(3)设置cpsr为相应异常模式
(4)设置pc为相应异常处理程序的入口地址
对于IRQ或者FIQ而言,还多一项变化:禁用相关的中断IRQ或FIQ,禁止同类型的其他中断被触发。
(这也是自动实现的,因此正常情况下,ARM中断不可嵌套,会另加中断管理器来实现嵌套和管理优先级的功能)
在异常处理程序中,我们要做什么:
(1)保护现场,R0~R12这13个寄存器,在多个模式下是公用的,要避免寄存器被覆盖而导致程序出错。所以要先备份,再使用。
(2)处理异常,解决问题
(3)恢复现场,从异常中断处理程序退出时,需要我们在程序中用软件实现下面两个操作:
- 从spsr_mode中恢复数据到cpsr中
- 从lr_mode中恢复内容到pc中,返回到异常中断的指令的下一条指令处执行
五、异常处理后的PC值
BL、SWI、UDEF的返回值都是R14(LR)的值,而IRQ、FIQ的返回值还需要减4才可以。
六、und异常
当PC取到了一条未定义的指令,无法解析时,就会产生undefine异常,以下是汇编示例:
.text
.global _start
_start:
/* 在0地址出定义异常向量 */
b reset /* vector 0 : reset */
ldr pc, und_addr /* vector 4 : und */
und_addr:
.word do_und
/* .word的意思是整字,在32位ARM中则代表32位数据 */
do_und:
/* 执行到这里之前:
* 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_und保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为11011, 进入到und模式
* 4. 跳到0x4的地方执行程序
*/
/* 异常模式下的栈,sp_und未设置, 先设置它,指向一块空闲的内存 */
ldr sp, =0x34000000
/* 保存现场 */
/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* lr是异常处理完后的返回地址, 也要保存 */
stmdb sp!, {r0-r12, lr}
/* 处理und异常 */
mrs r0, cpsr
ldr r1, =und_string
/* R0,R1分别存放着传给C函数的参数,然后调用C函数printException */
bl printException
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
und_string:
.string "undefined instruction exception"
.align 4
//4字节对齐,因为前面字符串的大小不定
reset:
/* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0]
/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 设置CPU工作于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
* m = MDIV+8 = 92+8=100
* p = PDIV+2 = 1+2 = 3
* s = SDIV = 1
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12)|(1<<4)|(1<<0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* 设置内存: sp 栈 */
/* 分辨是nor/nand启动
* 写0到0地址, 再读出来
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
* 否则就是nor启动
*/
mov r1, #0
ldr r0, [r1] /* 读出原来的值备份 */
str r1, [r1] /* 0->[0] */
ldr r2, [r1] /* r2=[0] */
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
moveq sp, #4096 /* nand启动 */
streq r0, [r1] /* 恢复原来的值 */
bl sdram_init /* 初始化SDRAM */
/* 重定位text, rodata, data段整个程序 */
bl copy2sdram
/* 清除BSS段 */
bl clean_bss
/* 上面已完成代码重定位,使用绝对跳转指令,将PC定位到SDRAM中去 */
ldr pc, =sdram
sdram:
bl uart0_init
/* 调试信息,检查程序是否执行到这一步 */
bl print1
/* 故意加入一条未定义指令 */
und_code:
.word 0xdeadc0de /* 未定义指令 */
/* 调试信息,检查程序是否执行到这一步 */
bl print2
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
halt:
b halt
七、swi异常
软件中断异常,由程序调用指令而主动触发。对于处理器来说,swi只有一种异常,因此,用户需要自己定义编号(标志),在异常处理程序中,使用编号来区分不同的软件中断,如按键中断、定时器中断…
指令格式:SWI number
number 24位立即数,值为从0――16777215之间的整数。
若还想给中断处理函数传递参数,照样使用R0~R3来参数传递和结果返回。
八、irq异常
8.1、中断相关的寄存器
中断挂起寄存器:Pending
-
二级Pending
中断信号从中断源发出,(经过屏蔽寄存器SUBMASK)进入SECPND,将对应标志位置1,表明中断源信号出现! -
一级Pending
再经过中断控制器一些列的仲裁,从SRCPND众多位中挑选出一位最优先的,写入INTPND,等待传递给CPU。同一时刻仅有一位被置1。
当CPU的CPSR寄存器的 I 位被置0,表明IQR总中断使能,则这时的INTPND中的中断信号才能被觉察到,进而被CPU处理。
CPU处理完中断后,需要主动地将这些标志位清除。
中断屏蔽寄存器:Mask
- 二级屏蔽
当SUBMASK某位被置1,则对应的中断源信号被屏蔽 - 一级屏蔽
当MASK某位被置1,则对应的中断源信号被屏蔽
中断优先级寄存器:PRIORITY
中断源被中断管理器按优先级分为6个组,通过PRIORITY中的ARB_SELx与ARB_MODEx调配组间优先级
中断偏移寄存器:INTOFFSET
中断偏移寄存器中的值直接表明了是哪个 IRQ 模式的中断请求在 INTPND 寄存器中。此值随着 SRCPND 和 INTPND 被清除而自动清除。
疑问:
中断源有60个,中断挂起与屏蔽只有32位,意味着有些位被复用了:
外部中断8-23共用1位,外部中断4-7共用1位,那CPU怎么区分这些共用的中断呢?
答:在这些外部中断中,还有一些寄存器,位于GPIO模块
触发方式:EXTINTx
中断屏蔽:EINTMASK
滤波控制:EINTFLTx
中断标志:EINTPEND
8.2、IRQ编程实例
IRQ与FIQ与其他异常相比,增加了2个工作:
- 配置中断开关
SRCPND
INTPND
SUBMASK
MASK
PRIORITY
SCPR[I] - 消除中断标志位
SRCPND
INTPND
EINTPEND
配置中断开关
(1)中断源配置,将GPIO引脚(外设)配置成中断模式,触发方式,中断使能,可以发出中断信号
/* 初始化按键, 设为中断源 */
void key_eint_init(void)
{
/* 配置GPIO为中断引脚 */
GPFCON &= ~((3<<0) | (3<<4));
GPFCON |= ((2<<0) | (2<<4)); /* S2,S3被配置为中断引脚 */
GPGCON &= ~((3<<6) | (3<<22));
GPGCON |= ((2<<6) | (2<<22)); /* S4,S5被配置为中断引脚 */
/* 设置中断触发方式: 双边沿触发 */
EXTINT0 |= (7<<0) | (7<<8); /* S2,S3 */
EXTINT1 |= (7<<12); /* S4 */
EXTINT2 |= (7<<12); /* S5 */
/* 设置EINTMASK使能eint11,19 */
EINTMASK &= ~((1<<11) | (1<<19));
}
(2)中断控制器配置(夹在中断源与CPU之间),可以对多个中断源管理屏蔽等,汇总成最终的IRQ信号给CPU
/* 初始化中断控制器 */
void interrupt_init(void)
{
/* INTMSK 用来屏蔽中断 */
INTMSK &= ~((1<<0) | (1<<2) | (1<<5));
}
(3)CPU配置,reset CPSR中的 I 位(bit 7),使能中断,才能响应中断控制器传递来的IRQ信号。同理,若reset CPSR中的F位,则使能FIQ。
/* 复位之后, cpu处于svc模式
* 现在, 切换到usr模式
*/
mrs r0, cpsr /* 读出cpsr */
bic r0, r0, #0xf /* 修改M4-M0为0b10000, 进入usr模式 */
bic r0, r0, #(1<<7) /* 清除I位, 使能中断 */
msr cpsr, r0
/* 切换至USR模式不是必要的,在SVC模式也能使能中断,例如: */
mrs r0, cpsr
bic r0, r0, #(1<<7) /* 清除I位, 使能中断 */
msr cpsr, r0
消除中断标志位
void handle_irq_c(void)
{
/* 分辨中断源 */
unsigned int bit = INTOFFSET;
unsigned int val = EINTPEND;
/* 调用对应的处理函数 */
if (bit == 0 || bit == 2 || bit == 5) /* eint0,2,eint8_23 */
{
key_eint_irq(bit); /* 处理中断 */
}
/* 清中断 : 从源头开始清 */
EINTPEND = val;
SRCPND = (1<<bit);
INTPND = (1<<bit);
}