CP15协处理器
CP15协处理器一般用于存储系统管理,但是在中断中也会使用到,CP15协处理器一共有16个32位寄存器。CP15协处理器的访问通过以下指令完成:
- MRC:将CP15协处理器中的寄存器数据读到ARM寄存器中。
- MCR:将ARM寄存器的数据写入到CP15协处理器寄存器中。
- MCR{cond} p15, , , , ,
- cond:指令执行的条件码,如果忽略的话就表示无条件执行。
- opcl:协处理器要执行的操作码
- Rt:ARM源寄存器,要写到CP15寄存器的数据就保存到此寄存器中。
- CRn:CP15协处理器的目标寄存器。
- CRm:协处理器中附加的目标寄存器或者源操作数寄存器,如果不需要附加信息就将CRm设置为C0,否则结果不可预测。
- opc2:可选的协处理器特定操作码,当不需要的时候要设置为 0。
IRQ和FIQ总中断使能
IRQ 和 FIQ 分别是外部中断和快速中断的总开关,就类似家里买的进户总电闸,然后ID0~ID1019 这 1020 个中断源就类似家里面的各个电器开关。要想开电视,那肯定要保证进户总电闸是打开的,因此要想使用 I.MX6U 上的外设中断就必须先打开 IRQ 中断。
寄存器 CPSR 的 I=1 禁止 IRQ,当 I=0 使能 IRQ;F=1 禁止 FIQ,F=0 使能 FIQ。
我们还有更简单的指令来完成 IRQ 或者 FIQ 的使能和禁止:
ID0~ID1019中断使能和禁止
GIC寄存器GICD_ISENABLERn和GICD_ICENABLERn用来完成外部中断的使能和禁止,对于Coretex-A7内核来说中断ID只使用了512个。一个bit控制一个中断ID的使能,那么就需要512/32=16个GICD_ISENABLER 寄存器来完成中断的使能。同理,也需要 16 个
GICD_ICENABLER 寄存器来完成中断的禁止。
其中GICD_ISENABLER0的bit[15:0]对于ID150的SGI中断,GICD_ISENABLER0的bit[31:16]对应ID3116的PPI中断。剩下的
GICD_ISENABLER1~GICD_ISENABLER15 就是控制 SPI 中断的。
中断优先级设置
Cortex-M的中断优先级分为抢占优先级和子优先级,两者是可以配置的。同样的 Cortex-A7 的中断优先级也可以分为抢占优先级和子优先级,两者同样是可以配置的。
数字越小,优先级越高!
Cortex-A7选择了32个优先级,在使用中断的时候需要初始化GICC_PMR寄存器,此寄存器用来决定使用几级优先级。
GICC_PMR 寄存器只有低 8 位有效,这 8 位最多可以设置 256 个优先级。
I.MX6U 是 Cortex-A7内核,所以支持 32 个优先级,因此 GICC_PMR 要设置为 0b11111000。
抢占优先级和子优先级各占多少位是由寄存器 GICC_BPR 来决定的。
某个中断ID的中断优先级设置由寄存器D_IPRIORITY来完成,前面说了Cortex-A7使用了512个中断ID,每个中断ID配有一个优先级寄存器,所以一共有512 个 D_IPRIORITYR 寄存器。
当优先级个数为3时,使用寄存器 D_IPRIORITYR 的 bit7:4 来设置优先级,也就是说实际的优先级要左移 3 位。
因此,优先级设置主要有三部分:
- 设置寄存器GICC_PMR,配置优先级个数,比如I.MX6U支持32级优先级。
- 设置抢占优先级和子优先级位数,一般为了简单起见,会将所有的位数都设置为抢占优先级。
- 设置指定中断 ID 的优先级,也就是设置外设优先级。
重新编写start.S文件
/* 复位中断 */
Reset_Handler:
cpsid i /* 关闭全局中断 */
/* 关闭I,DCache和MMU
* 采取读-改-写的方式。
*/
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寄存器中 */
#if 0
/* 汇编版本设置中断向量表偏移 */
ldr r0, =0X87800000
dsb
isb
mcr p15, 0, r0, c12, c0, 0
dsb
isb
#endif
/* 设置各个模式下的栈指针,
* 注意:IMX6UL的堆栈是向下增长的!
* 堆栈指针地址一定要是4字节地址对齐的!!!
* DDR范围:0X80000000~0X9FFFFFFF
*/
/* 进入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 /* 打开全局中断 */
#if 0
/* 使能IRQ中断 */
mrs r0, cpsr /* 读取cpsr寄存器值到r0中 */
bic r0, r0, #0x80 /* 将r0寄存器中bit7清零,也就是CPSR中的I位清零,表示允许IRQ中断 */
msr cpsr, r0 /* 将r0重新写入到cpsr中 */
#endif
b main /* 跳转到main函数 */
- 先调用指令cpsid i关闭IRQ
- 关闭I/D Cache、MMU、对齐检测和分支预测
- 汇编版本的中断向量表重映射
- 设置不同模式下的sp指针,分别设置IRQ模式、SYS模式和SVC模式的栈指针,每种模式的栈大小都是2MB
- cpsie i重新打开IRQ中断
- 操作CPSR寄存器打开IRQ中断
- 当初始化工作都完成以后就可以进入main函数了。
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 */
- 保存现场
- 获取当前中断号,中断号被保存到了r0寄存器
- 调用函数system_irqhandler,此函数是一个C语言函数,此函数有一个参数为中断号,形参可以由r0~r3这四个寄存器来传递,所以给r0寄存器写入中断号就可以解决函数system_irqhandler参数传递问题。
- 向GICC_EOIR寄存器写入刚刚处理完成的中断号,当一个中断处理完成以后必须向GICC_EOIR寄存器写入其中断号表示中断处理完成。
- 恢复现场。
- 中断处理完成后就要重新返回曾经被中断打断的地方运行。