cpsid i /* 关闭全局中断 */
MRC: 将 CP15 协处理器中的寄存器数据读到 ARM 寄存器中。
MCR: 将 ARM 寄存器的数据写入到 CP15协处理器寄存器中。
mrc p15, 0, r0, c1, c0, 0 /* 读取CP15的C1寄存器到R0中 */
bic r0, r0, #(0x1 << 12) /* 清除C1寄存器的bit12位(I位),关闭I Cache */
bic r0, r0, #(0x1 << 2) /* 清除C1寄存器的bit2(C位),关闭D Cache */
bic r0, r0, #0x2 /* 清除C1寄存器的bit1(A位),关闭对齐 */
bic r0, r0, #(0x1 << 11) /* 清除C1寄存器的bit11(Z位),关闭分支预测 */
bic r0, r0, #0x1 /* 清除C1寄存器的bit0(M位),关闭MMU */
mcr p15, 0, r0, c1, c0, 0 /* 将r0寄存器中的值写入到CP15的C1寄存器中 */
ldr r0, =0X87800000
/*
DSB
数据同步隔离。比 DMB 严格: 仅当所有在它前面的存储器访问操作都执行完毕后,才执行在它后面的指令(亦即任何指令都要等待存储器访 问操作——译者注)
ISB
指令同步隔离。最严格:它会清洗流水线,以保证所有它前面的指令都执行完毕之后,才执行它后面的指令。
*/
dsb
isb
mcr p15, 0, r0, c12, c0, 0
dsb
isb
/* 进入IRQ模式 */
mrs r0, cpsr
bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */
orr r0, r0, #0x12 /* r0或上0x13,表示使用IRQ模式 */
msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */
ldr sp, =0x80600000 /* 设置IRQ模式下的栈首地址为0X80600000,大小为2MB */
/* 进入SYS模式 */
mrs r0, cpsr
bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */
orr r0, r0, #0x1f /* r0或上0x13,表示使用SYS模式 */
msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */
ldr sp, =0x80400000 /* 设置SYS模式下的栈首地址为0X80400000,大小为2MB */
/* 进入SVC模式 */
mrs r0, cpsr
bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */
orr r0, r0, #0x13 /* r0或上0x13,表示使用SVC模式 */
msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */
ldr sp, =0X80200000 /* 设置SVC模式下的栈首地址为0X80200000,大小为2MB */
cpsie i /* 打开全局中断 */
/* 使能IRQ中断 */
mrs r0, cpsr /* 读取cpsr寄存器值到r0中 */
bic r0, r0, #0x80 /* 将r0寄存器中bit7清零,也就是CPSR中的I位清零,表示允许IRQ中断 */
msr cpsr, r0 /* 将r0重新写入到cpsr中 */
b main /* 跳转到main函数 */
110 IRQ_Handler:
111 push {lr} /* 保存 lr 地址 */
112 push {r0-r3, r12} /* 保存 r0-r3, r12 寄存器 */
113
114 mrs r0, spsr /* 读取 spsr 寄存器 */
115 push {r0} /* 保存 spsr 寄存器 */
116
117 mrc p15, 4, r1, c15, c0, 0 /* 将 CP15 的 C0 内的值到 R1 寄存器中
118 * 参考文档 ARM Cortex-A(armV7)编程手册 V4.0.pdf P49
119 * Cortex-A7 Technical ReferenceManua.pdf P68 P138
120 */
121 add r1, r1, #0X2000 /* GIC 基地址加 0X2000, 得到 CPU 接口端基地址 */
122 ldr r0, [r1, #0XC] /* CPU 接口端基地址加 0X0C 就是 GICC_IAR 寄存器,
123 * GICC_IAR 保存着当前发生中断的中断号,我们要根据原子哥在线教学: www.yuanzige.com 论坛:www.openedv.com
406
I.MX6U 嵌入式 Linux 驱动开发指南
124 * 这个中断号来绝对调用哪个中断服务函数
125 */
126 push {r0, r1} /* 保存 r0,r1 */
127
128 cps #0x13 /* 进入 SVC 模式,允许其他中断再次进去 */
129
130 push {lr} /* 保存 SVC 模式的 lr 寄存器 */
131 ldr r2, =system_irqhandler /* 加载 C 语言中断处理函数到 r2 寄存器中*/
132 blx r2 /* 运行 C 语言中断处理函数,带有一个参数 */
133
134 pop {lr} /* 执行完 C 语言中断服务函数, lr 出栈 */
135 cps #0x12 /* 进入 IRQ 模式 */
136 pop {r0, r1}
137 str r0, [r1, #0X10] /* 中断执行完成,写 EOIR */
138
139 pop {r0}
140 msr spsr_cxsf, r0 /* 恢复 spsr */
141
142 pop {r0-r3, r12} /* r0-r3,r12 出栈 */
143 pop {lr} /* lr 出栈 */
144 subs pc, lr, #4 /* 将 lr-4 赋给 pc */
145
146 /* FIQ 中断 */
147 FIQ_Handler:
148
149 ldr r0, =FIQ_Handler
150 bx r0
第 110 到 144 行是中断服务函数 IRQ_Handler,这个是本章的重点,因为所有的外部中断最终都会触发 IRQ 中断, 所以 IRQ 中断服务函数主要的工作就是区分去当前发生的什么中断(中断 ID)?然后针对不同的外部中断做出不同的处理。
第 111 到 115 行是保存现场,
https://blog.csdn.net/monkea123/article/details/102961206
第 117 到122 行是获取当前中断号,中断号被保存到了 r0 寄存器中。
第 131 和 132 行才是中断处理的重点,这两行相当于调用了函数 system_irqhandler,函数 system_irqhandler 是一个 C 语言函数,此函数有一个参数,这个参数中断号,所以我们需要传递一个参数。汇编中调用 C 函数如何实现参数传递呢?根据 ATPCS(ARM-Thumb Procedure Call Standard)定义的函数参数传递规则,在汇编调用 C 函数的时候建议形参不要超过 4 个, 形参可以由 r0~r3 这四个寄存器来传递,如果形参大于 4 个, 那么大于 4 个的部分要使用堆栈进行传递。 以给 r0 寄存器写入中断号就可以了函数 system_irqhandler 的参数传递
在 136 行已经向 r0 寄存器写入了中断号了。中断的真正处理过程其实是在函数 system_irqhandler 中完成,稍后需要编写函数stem_irqhandler。
第 151 行向 GICC_EOIR 寄存器写入刚刚处理完成的中断号, 当一个中断处理完成以后必须向 GICC_EOIR 寄存器写入其中断号表示中断处理完成。
第 153 到 157 行就是恢复现场。
第 158 行中断处理完成以后就要重新返回到曾经被中断打断的地方运行,这里为什么要将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”开始执行。