1、Cortex-A7内核有8个异常中断:
向量地址 中断类型 中断模式
(1)0x00 复位中断(Rest) 特权模式(SVC)
(2)0x04 未定义指令中断(Undefined Instruction) 未定义指令中止模式(Undef)
(3)0x08 软中断(Software Interrupt,SWI) 特权模式(SVC)
(4)0xOC 指令预取中止中断(Prefetch Abort) 中止模式
(5)0x10 数据访问中止中断(Data Abort) 中止模式
(6)0x14 未使用(Not Used) 未使用
(7)0x18 IRQ中断(IRQ Interrupt) 外部中断模式(IRQ)
(8)0x1C FIQ中断(FIQ Interrupt) 快速中断模式(FIQ)
2、中断简介
(1)复位中断(Rest), CPU 复位以后就会进入复位中断,我们可以在复位中断服务函数里面做一些初始化工作,比如初始化 SP 指针、 DDR 等等。
(2)未定义指令中断(Undefined Instruction),如果指令不能识别的话就会产生此中断。
(3)软中断(Software Interrupt,SWI),由 SWI 指令引起的中断, Linux 的系统调用会用 SWI指令来引起软中断,通过软中断来陷入到内核空间。
(4)指令预取中止中断(Prefetch Abort),预取指令的出错的时候会产生此中断。
(5)数据访问中止中断(Data Abort),访问数据出错的时候会产生此中断。
(6)IRQ中断(IRQ Interrupt),外部中断,芯片内部的外设中断都会引起此中断的发送。
(7)FIQ中断(FIQ Interrupt),快速中断,如果需要快速处理中断的话就可以使用此中断。
3、GIC控制器简介
(1)GIC控制器向内核汇报4种信号:
①、VFIQ:虚拟快速FIQ
②、VIRQ:虚拟快速IRQ
③、FIQ:快速中断IRQ
④、IRQ:外部中断IRQ
(2)GIC将中断源分为三类:
①、SPI(Shared Peripheral Interrupt),共享中断,顾名思义,所有Core共享的中断,这个是最常见的,那些外部中断都属于SPI中断(注意!不是SPI总线那个中断)。
比如按键中断、串口中断等等,这些中断所有的Core都可以处理,不限定特定Core。
②、PPI(Private Peripheral Interrupt),私有中断,我们说了GIC是支持多核的,每个核肯定有自己独有的中断。这些独有的中断肯定要指定的核心处理,因此这些中断
就叫做私有中断。
③、SGI(Software-generated Interrupt),软件中断,由软件出发引起的中断,通过向寄存器GICD_SGIR写入数据来触发,系统会使用SGI中断来完成多核之间的通信。
(3)中断ID:
为了区分不同的中断源,给每个中断源分配了唯一的ID。每个CPU最多支持1020个中断ID,中断ID号为ID0~ID1019。
ID0~ID15:这16个ID分配给SGI。
ID16~ID31:这16个ID分配给PPI。
ID32~ID1019:这988个ID分配给SPI,像GPIO中断、串口中断等这些外部中断。
I.MX6U总共使用了128个中断ID,加上前面属于PPI和SGI的32个ID,I.MX6U的中断源共有128+32=160个。
(4)GIC逻辑分块:Distributor(分发器端)和CPU Interface(CPU接口端)
1)Distributor(分发器端):此逻辑块负责处理各个中断事件的分发问题,也就是中断事件应该发送到哪个CPU Interface上去。分发器收集所有的中断源,可以控制每个
中断的优先级,它总是将优先级最高的中断事件发送到CPU接口端。分发器要做的主要工作如下:
①、全局中断使能控制。
②、控制每个中断的使能或者关闭。
③、设置每个中断的优先级。
④、设置每个中断的目标处理器列表。
⑤、设置每个外部中断的触发模式:电平触发或边沿触发。
⑥、设置每个中断属于组0还是组1。
2)CPU Interface(CPU接口端):CPU接口端听名字就知道是和CPU Core相连接的,因此每个CPU Core都可以在GIC中找到一个与之对应的CPU Interface。CPU接口端就是分
发器和CPU Core之间的桥梁,CPU接口端主要工作如下:
①、使能或者关闭发送到CPU Core的中断请求信号。
②、应答中断。
③、通知中断处理完成。
④、设置优先级掩码,通过掩码来设置哪些中断不需要上报给CPU Core。
⑤、定义抢占策略。
⑥、当多个中断到来的时候,选择优先级最高的中断通知给CPU Core。
4、CP15协处理器
CP15协处理器一般用于存储系统管理,但是在中断中也会使用到,CP15协处理器一共有16个32位寄存器。CP15协处理器的访问通过如下另个指令完成:
MRC:将CP15协处理器中的寄存器数据读到ARM寄存器中,即读CP15寄存器。
MCR:将ARM寄存器的数据写入到CP15协处理器寄存器中,即写CP15寄存器。
命令:MCR {cond} p15,
CP15协处理器的用途:
(1)通过c0寄存器可以获取到处理器内核信息;
(2)通过c1寄存器可以使能或禁止MMU、I/D Cache等;
(3)通过c12寄存器可以设置中断向量偏移;
(4)通过c15寄存器可以获取GIC基地址;
5、中断使能
(1)IRQ和FIQ总中断使能
寄存器CPSR的I=1禁止IRQ,当I=0使能IRQ;F=1禁止FIQ,F=0使能FIQ。也可以使用下列指令:
①、cpsid i 禁止IRQ中断
②、cpsie i 使能IRQ中断
③、cpsid f 禁止FIQ中断
④、cpsie f 使能FIQ中断
(2)ID0~ID1019中断使能和禁止
GIC寄存器GICD_ISENABLERn和GICD_ICENABLERn用来完成外部中断的使能和禁止,对于Cortex-A7内核来说中断ID只使用了512个。一个bit控制一个中断ID的使能,那么就
需要512/32=16个GICD_ISENABLER寄存器来完成中断的使能。同理,也需要16个GICD_ICENABLER寄存器来完成中断的禁止。其中GICD_ISENABLER0的bit[15:0]对应ID15~0
的SGI中断,GICD_ISENABLER0的bit[31:16]对应ID31~16的PPI中断。剩下的GICD_ISENABLER1~GICD_ISENABLER15就是控制SPI中断的。
6、中断优先级设置
(1)优先级数配置
Cortex-A7最多可以支持256个优先级,数值越小,优先级越高。I.MX6U选择32个优先级。
在使用中断的时候需要初始化GICC_PMR寄存器,此寄存器用来决定使用几级优先级。I.MX6U支持32个优先级,所有GICC_PMR设置为0b11111000。
(2)抢占优先级和子优先级位数设置
抢占优先级和子优先级各占多少位是由寄存器GICC_BPR来决定的。一般将所有的中断优先级配置为抢占优先级,比如I.MX6U的优先级位数为5(32个优先级),所有可以设
置Binary point为2,表示5个优先级全部为抢占优先级。
(3)优先级设置
Cortex-A7使用了512个中断ID,每个中断ID配有一个优先级寄存器,所有一共有512个D_IPRIORITYR寄存器。如果优先级个数为32的话,使用寄存器D_IPRIORITYR的bit7:4
来设置优先级,也就是说实际的优先级要左移3位。
如果要设置ID40中断的优先级为5:GICD_IPRIORITYR[40]=5<<3;
(4)优先级设置总结
①、设置寄存器GICC_PMR,配置优先级个数,比如I.MX6U支持32级优先级。
②、设置抢占优先级和子优先级位数,一般为了简单起见,会将所有的位数都设置为抢占优先级。
③、设置指定中断ID的优先级,也就是设置外设优先级。
7、中断SDK包
(1)GIC_Init:初始化GIC
(2)GIC_EnableIRQ:使能指定的外设中断
(3)GIC_DisableIRQ:关闭指定的外设中断
(4)GIC_AcknowledgeIRQ:返回中断号
(5)GIC_DeactivateIRQ:无效化指定中断
(6)GIC_GetRunningPriority:获取当前正在运行的中断优先级
(7)GIC_SetPriorityGrouping:设置抢占式优先级位数
(8)GIC_GetPriorityGrouping:获取抢占式优先级位数
(9)GIC_SetPriority:设置指定中断的优先级
(10)GIC_GetPriority:获取指定中断的优先级
8、中断向量表
.global _start /* 全局标号 */
/*
* 描述: _start函数,首先是中断向量表的创建
* 参考文档:ARM Cortex-A(armV7)编程手册V4.0.pdf P42,3 ARM Processor Modes and Registers(ARM处理器模型和寄存器)
* ARM Cortex-A(armV7)编程手册V4.0.pdf P165 11.1.1 Exception priorities(异常)
*/
_start:
ldr pc, =Reset_Handler /* 复位中断 */
ldr pc, =Undefined_Handler /* 未定义中断 */
ldr pc, =SVC_Handler /* SVC(Supervisor)中断 */
ldr pc, =PrefAbort_Handler /* 预取终止中断 */
ldr pc, =DataAbort_Handler /* 数据终止中断 */
ldr pc, =NotUsed_Handler /* 未使用中断 */
ldr pc, =IRQ_Handler /* IRQ中断 */
ldr pc, =FIQ_Handler /* FIQ(快速中断)未定义中断 */
/* 复位中断 */
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函数 */
/* 未定义中断 */
Undefined_Handler:
ldr r0, =Undefined_Handler
bx r0
/* SVC中断 */
SVC_Handler:
ldr r0, =SVC_Handler
bx r0
/* 预取终止中断 */
PrefAbort_Handler:
ldr r0, =PrefAbort_Handler
bx r0
/* 数据终止中断 */
DataAbort_Handler:
ldr r0, =DataAbort_Handler
bx r0
/* 未使用的中断 */
NotUsed_Handler:
ldr r0, =NotUsed_Handler
bx r0
/* 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 */
/* FIQ中断 */
FIQ_Handler:
ldr r0, =FIQ_Handler
bx r0