(五)ARM的异常处理

一、ARM处理器的7种模式

问:处理异常,涉及ARM处理器的哪些工作模式?

用户模式外,其余6种工作模式都属于特权模式;

特权模式中除了系统模式以外的其余5种模式称为异常模式

大多数程序运行于用户模式;

进入特权模式是为了处理中断、异常、或者访问被保护的系统资源;

32位ARM的七种工作模式与相关寄存器

ARM处理器架构-----异常/中断处理

芯片从上电开始,就是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异常

ARM 软中断指令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);	
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值