ARM-A7通用中断服务函数-1

中断流程

保存现场-执行中断服务函数-返回现场

.S文件的修改

先看代码:

IRQ_Handler:

	push {lr}					/* 保存lr地址 */

	push {r0-r3, r12}			/* 保存r0-r3,r12寄存器 */



	mrs r0, spsr				/* 读取spsr寄存器 */

	push {r0}					/* 保存spsr寄存器 */



	mrc p15, 4, r1, c15, c0, 0 /* 从CP15的C0寄存器内的值到R1寄存器中

								* 参考文档ARM Cortex-A(armV7)编程手册V4.0.pdf P49

								* Cortex-A7 Technical ReferenceManua.pdf P68 P138

								*/							

	add r1, r1, #0X2000			/* GIC基地址加0X2000,也就是GIC的CPU接口端基地址 */

	ldr r0, [r1, #0XC]			/* GIC的CPU接口端基地址加0X0C就是GICC_IAR寄存器,

								 * GICC_IAR寄存器保存这当前发生中断的中断号,我们要根据

								 * 这个中断号来绝对调用哪个中断服务函数

								 */

	push {r0, r1}				/* 保存r0,r1 */

	

	cps #0x13					/* 进入SVC模式,允许其他中断再次进去 */

	

	push {lr}					/* 保存SVC模式的lr寄存器 */

	ldr r2, =system_irqhandler	/* 加载C语言中断处理函数到r2寄存器中*/

	blx r2						/* 运行C语言中断处理函数,带有一个参数,保存在R0寄存器中 */



	pop {lr}					/* 执行完C语言中断服务函数,lr出栈 */

	cps #0x12					/* 进入IRQ模式 */

	pop {r0, r1}				

	str r0, [r1, #0X10]			/* 中断执行完成,写EOIR */



	pop {r0}						

	msr spsr_cxsf, r0			/* 恢复spsr */



	pop {r0-r3, r12}			/* r0-r3,r12出栈 */

	pop {lr}					/* lr出栈 */

	subs pc, lr, #4				/* 将lr-4赋给pc */

以下分析代码。
前面几行都是在保存寄存器,例如lr、r0这些。为什么不保存所有的寄存器是因为进入IRQ模式后,系统会自动保存一部分寄存器,而那些没有自动保存的就需要手动保存了。
然后是第一个需要仔细理解的:

mrc p15, 4, r1, c15, c0, 0

mrc是从CP15里读寄存器c15寄存器中的CBAR寄存器,存储到r1里。
CBAR寄存器是保存GIC(Generic Interrupt Controller)寄存器组的物理地址(首地址)。

GIC分发器

GIC寄存器组偏移0x1000~0x1FFF为GIC的分发器(Distributer)。分发器复杂处理各个中断事件的分发,也就是中断事件应该发送到哪个CPU接口上去。分发器收集所有的中断源,可以控制中断优先级,它总是将优先级最高的中断时间紧发送到CPU接口端。

GIC CPU接口

GIC寄存器组偏移0x2000~0x3FFF为GIC的CPU接口端。CPU接口端负责将分发器与CPU Core连接。

到这里,我们就可以访问GIC控制器了。类似STM32里的NVIC。
继续看代码:

add r1, r1, #0X2000	
ldr r0, [r1, #0XC]	

第一句是将r1的值偏移了0x2000,注意此时r1寄存器里面是GIC寄存器组的基地址,偏移之后就是CPU接口的基地址了。
第二句是将r1的值+0xC后写到r0寄存器里。注意此时r1寄存器里的值是CPU接口的基地址,+0xC之后得到的就是GICC_IAR寄存器的地址。那么r0寄存器里面保存的就是GICC_IAR寄存器的

GICC_IAR寄存器

CPU会读取这个寄存器的值,得到中断对应的ID。这次读取也意味着中断被感知到。
GICC_IAR[9:0]: 中断ID
GICC_IAR[12:10]: CPU ID
有了中断ID之后,才可以知道具体触发了哪个中断,进而执行这个ID对应的中断处理函数。

之后通过push将r0(中断ID) 和r1(CPU接口首地址)保存。

然后,通过cps #0x13 再次进入SVC模式。因为触发了IRQ后,CPU会工作在IRQ模式下。由于我们需要执行system_irqhandler函数,因此需要先进入SVC模式,然后保存SVC模式下的lr寄存器,然后将system_irqhandler放到r2寄存器里。之后执行blx r2 ,跳转到r2里面,执行system_irqhandler。注意,system_irqhandler是具体的中断处理函数,此函数有一个参数,为GICC_IAR寄存的值。
执行完毕后pop {lr} lr寄存器出栈。然后cps #0x12再次进入IRQ模式。

GICC_EOIR寄存器

之后的代码仔细分析一下:

	pop {r0, r1}				

	str r0, [r1, #0X10]	

首先将r0、r1寄存器出栈,此时r0保存的是GICC_IAR寄存器的值,r1保存的是GIC寄存器组中CPU接口的基地址。
然后将r0的值写入到GIC基地址+0X10的位置去。
GIC+0x10是GICC_EOIR寄存器。意思是中断结束寄存器。所以这两句代码的意思是将GIC_IAR寄存器的值写到GIC_EOIR寄存器里。
A7的架构决定了说任何一个CPU核在读取到有效的中断ID值(GICC_IAR)后,都必须将这个值写到GICC_EOIR中去。
GICC_EOIR寄存器的功能: 被写入时,标识一个中断处理的完成。

之后就是出栈了。不易理解的是:

	subs pc, lr, #4	

这里是将lr-4之后赋值给pc。
lr: link register 保存中断点
pc: program counter 当前运行的代码地址。
那么为什么要lr-4之后赋值给PC呢? 这里和CPU架构有关。ARM芯片是Fetch-Decode-Execute 这种三级流水线的架构。而PC的值是当前执行的指令地址+8.例如:

0X2000 MOV R1, R0 //EXECUTE
0X2004 MOV R2, R3 //DECODE
0X2008 MOV R4, R5 //FETCH

假设CPU已经在执行0X2000处的代码,那么此时PC里面已经保存了0X2008地址处的指令。假设此时发生中断,则lr寄存器里保存的地址就是pc的值,也就是0x2008。中断执行完成后,如果直接跳转到lr保存的地址处,就会出现位于0X2004处的指令没有执行。这样是不对的。因此,需要将lr-4的值赋予pc,此时pc = 0x2004,继续执行。

  • 12
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值