一. IRQ中断函数
本文介绍 IMX6ULL的IRQ中断函数实现,具体是汇编实现。对汇编实现过程进行一下初步的了解。
二. IRQ中断函数代码实现
IRQ 中断函数涉及 CP15协处理器 与 GIC中断控制器。
关于
CP15
协处理 器和其 相关寄存 器的详细 内容 请参考下 面两份文 档:
《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》
第
1469
页“
B3.17 Oranization
of the CP15 registers in a VMSA implementation
”文档 与
文档
《Cortex-A7 Technical ReferenceManua.pdf》
第
55
页“
Capter 4 System Control
”。
GIC中断控制器可参考
ARM Generic Interrupt Controller(ARM GIC控制器)V2.0.pdf 文档。
GIC
架构分为了两个逻辑块:Distributor
和
CPU Interface
,也就是分发器端和
CPU
接口端。
start.S 汇编文件中 IMX6ULL的 IRQ中断函数,代码实现如下:
/* IRQ中断!重点!!!!! */
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 */
以上代码分析如下:
mrc p15, 4, r1, c15, c0, 0 //读取CP15的CBAR寄存器。
CBAR寄存器保存了GIC控制器的寄存器组首地址。GIC寄存器组偏移0x1000~0x1fff为GIC的分发器。0x2000~0x3fff为CPU接口端。意味我们可以访问GIC控制器了!
代码中,R1寄存器将保存着GIC控制器的CPU接口端基地址。
读取CPU接口端的 GICC_IAR寄存器的值保存到 R0寄存器里面。
可以从GICC_IAR的bit9~0读取中断ID,我们读取中断ID的目的就是为了得到对应的中断处理函数。
system_irqhandler 就是具体的中断处理函数,此函数有一个参数,为 GICC_IAR寄存器的值。
system_irqhandler 处理完具体的中断以后,需要将对应的中断ID 值写入到 GICC_EOIR寄存器里面。
关于如下代码:
subs pc, lr, #4 /* 将lr-4赋给pc */
分析如下:
LR寄存器(lr): 即链接寄存器
。
例如,当触发了中断或者调用了其他函数后执行完以后,就要返回到以前的中断点接着执行,LR寄存器保存的是返回的指令的地址。
PC寄存器(pc):保存当前正在取指的地址。
这里为什么要将 lr-4 然后赋给 pc 呢?而不是直接将 lr 赋值给 pc?
ARM 的指令是三级流水线:取指、译指、执
行,
pc
指向的是正在取值的地址,这就是很多书上说的
pc=
当前执行指令地址
+8
。
例如下面代码示例:
0X2000 MOV R1, R0 ;执行
0X2004 MOV R2, R3 ;译指
0X2008 MOV R4, R5 ;取值 PC
左侧一列是地址,中间是指令,最右边是流水线。
当前正在执行 0X2000 地址处的指令“MOV R1, R0”,但是 PC 里面已经保存了 0X2008 地址处的指令“MOV R4, R5”。
假设此时发生了中断,中断发生的时候保存在 lr 中的是 pc 的值,也就是地址 0X2008。
当中断处理完成以后肯定需要回到被中断点接着执行,如果直接跳转到 lr 里面保存的地址处(0X2008) 开始运行,那么就有一个指令没有执行,那就是地址 0X2004 处的指令“MOV R2, R3”,显然这 是一个很严重的错误!
所以,就需要将 lr-4 赋值给 pc,也就是 pc=0X2004,从指令“MOV R2,R3”开始执行。