中断过程

STM32 的中断系统主要有以下几个关键点:
①、中断向量表。
②、NVIC(内嵌向量中断控制器)。
③、中断使能。
④、中断服务函数

而在imx6ul中的NVIC中断管理机构叫做GIC。

中断服务程序的入口地址或存放中断服务程序的首地址成为中断向量,因此中断向量表是一系列中断服务程序入口地址组成的表。

Cortex-A7中断系统简介

跟 STM32 一样,Cortex-A7 也有中断向量表,中断向量表也是在代码的最前面。CortexA7 内核有 8 个异常中断,这 8 个异常中断的中断向量表如表 :

①、复位中断(Rest),CPU 复位以后就会进入复位中断,我们可以在复位中断服务函数里面做一些初始化工作,比如初始化 SP 指针、DDR 等等。
②、未定义指令中断(Undefined Instruction),如果指令不能识别的话就会产生此中断。
③、软中断(Software Interrupt,SWI),由 SWI 指令引起的中断,Linux 的系统调用会用 SWI指令来引起软中断,通过软中断来陷入到内核空间。
④、指令预取中止中断(Prefetch Abort),预取指令的出错的时候会产生此中断。
⑤、数据访问中止中断(Data Abort),访问数据出错的时候会产生此中断。
⑥、IRQ 中断(IRQ Interrupt),外部中断,前面已经说了,芯片内部的外设中断都会引起此中断的发生。
⑦、FIQ 中断(FIQ Interrupt),快速中断,如果需要快速处理中断的话就可以使用此中断。

中断向量表一般写在start.S的前面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
示例代码 17.1.1.1 Cortex-A 向量表模板
1 .global _start /* 全局标号 */
2
3 _start:
4 ldr pc, =Reset_Handler /* 复位中断 */
5 ldr pc, =Undefined_Handler /* 未定义指令中断 */
6 ldr pc, =SVC_Handler /* SVC(Supervisor)中断 */
7 ldr pc, =PrefAbort_Handler /* 预取终止中断 */
8 ldr pc, =DataAbort_Handler /* 数据终止中断 */
9 ldr pc, =NotUsed_Handler /* 未使用中断 */
10 ldr pc, =IRQ_Handler /* IRQ 中断 */
11 ldr pc, =FIQ_Handler /* FIQ(快速中断)未定义中断 */
12
13 /* 复位中断 */
14 Reset_Handler:
15 /* 复位中断具体处理过程 */
16
17 /* 未定义中断 */
18 Undefined_Handler:
19 ldr r0, =Undefined_Handler
20 bx r0
21
22 /* SVC 中断 */
23 SVC_Handler:
24 ldr r0, =SVC_Handler
25 bx r0
26
27 /* 预取终止中断 */
28 PrefAbort_Handler:
29 ldr r0, =PrefAbort_Handler
30 bx r0
31
32 /* 数据终止中断 */
33 DataAbort_Handler:
34 ldr r0, =DataAbort_Handler
35 bx r0
36
37 /* 未使用的中断 */
38 NotUsed_Handler:
39
40 ldr r0, =NotUsed_Handler
41 bx r0
42
43 /* IRQ 中断!重点!!!!! */
44 IRQ_Handler:
45 /* 复位中断具体处理过程 */
46
47 /* FIQ 中断 */
48 FIQ_Handler:
49 ldr r0, =FIQ_Handler
50 bx r0

GIC控制器

ARM 针对 GIC V2 就开发出了 GIC400 这个中断控制器 IP 核。当 GIC 接收到外部中断信号以后就会报给 ARM 内核,但是ARM 内核只提供了四个信号给 GIC 来汇报中断情况:VFIQ、VIRQ、FIQ 和 IRQ。

VFIQ:虚拟快速 FIQ。
VIRQ:虚拟外部 IRQ。
FIQ:快速中断 IRQ。
IRQ:外部中断 IRQ。(GPIO中断属于IRQ外部中断。)

GIC 将众多的中断源分为分为三类:
①、SPI(Shared Peripheral Interrupt),共享中断,顾名思义,所有 Core 共享的中断,这个是最常见的,那些外部中断都属于 SPI 中断(注意!不是 SPI 总线那个中断) 。比如按键中断、串口中断等等,这些中断所有的 Core 都可以处理,不限定特定 Core。
②、PPI(Private Peripheral Interrupt),私有中断,我们说了 GIC 是支持多核的,每个核肯定有自己独有的中断。这些独有的中断肯定是要指定的核心处理,因此这些中断就叫做私有中断。
③、SGI(Software-generated Interrupt),软件中断,由软件触发引起的中断,通过向寄存器GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信。

中断源有很多,为了区分这些不同的中断源肯定要给他们分配一个唯一 ID,这些 ID 就是中断 ID。每一个 CPU 最多支持 1020 个中断 ID,中断 ID 号为 ID0ID1019。这 1020 个 ID 包含了 PPI、SPI 和 SGI。
ID0
ID15:这 16 个 ID 分配给 SGI。
ID16ID31:这 16 个 ID 分配给 PPI。
ID32
ID1019:这 988 个 ID 分配给 SPI。具体看手册。

GIC 架构分为了两个逻辑块:Distributor 和 CPU Interface,也就是分发器端和 CPU 接口端。

Distributor(分发器端):此逻辑块负责处理各个中断事件的分发问题,也就是中断事件应该发送到哪个 CPU Interface 上去。分发器收集所有的中断源,可以控制每个中断的优先级,它总是将优先级最高的中断事件发送到 CPU 接口端。分发器端要做的主要工作如下:
①、全局中断使能控制。
②、控制每一个中断的使能或者关闭。
③、设置每个中断的优先级。
④、设置每个中断的目标处理器列表。
⑤、设置每个外部中断的触发模式:电平触发或边沿触发。
⑥、设置每个中断属于组 0 还是组 1。

CPU Interface(CPU 接口端):CPU 接口端听名字就知道是和 CPU Core 相连接的,因此在每个 CPU Core 都可以在 GIC 中找到一个与之对应的 CPU Interface。CPU 接口端就是分发器和 CPU Core 之间的桥梁,CPU 接口端主要工作如下:
①、使能或者关闭发送到 CPU Core 的中断请求信号。
②、应答中断。
③、通知中断处理完成。
④、设置优先级掩码,通过掩码来设置哪些中断不需要上报给 CPU Core。
⑤、定义抢占策略。
⑥、当多个中断到来的时候,选择优先级最高的中断通知给 CPU Core。

CP15 协处理器

CP15 协处理器一般用于存储系统管理,但是在中断中也会使用到,CP15 协处理器一共有16 个 32 位寄存器。

CP15 协处理器的访问通过如下另个指令完成:

MRC: 将 CP15 协处理器中的寄存器数据读到 ARM 寄存器中。读CP5寄存器
MCR: 将 ARM 寄存器的数据写入到 CP15 协处理器寄存器中。写CP15寄存器

1
2
MCR{cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2>
MRC p15, 0, r0, c0, c0, 0 //CP15 中 C0 寄存器的值读取到 R0 寄存器

cond:指令执行的条件码,如果忽略的话就表示无条件执行。
opc1:协处理器要执行的操作码。
Rt:ARM 源寄存器,要写入到 CP15 寄存器的数据就保存在此寄存器中。
CRn:CP15 协处理器的目标寄存器。
CRm:协处理器中附加的目标寄存器或者源操作数寄存器,如果不需要附加信息就将
CRm 设置为 C0,否则结果不可预测。
opc2:可选的协处理器特定操作码,当不需要的时候要设置为 0。

CP15 协处理器有 16 个 32 位寄存器,c0~c15,在使用 MRC 或者 MCR 指令访问这 16 个
寄存器的时候,指令中的 CRn、opc1、CRm 和 opc2 通过不同的搭配,其得到的寄存器含义是
不同的。例如C1寄存器的搭配。

中断使能

中断使能包括两部分,一个是 IRQ 或者 FIQ 总中断使能,另一个就是 ID0~ID1019 这 1020
个中断源的使能。

GIC 寄存器 GICD_ISENABLERn 和 GICD_ ICENABLERn 用来完成外部中断的使能和禁止,对于 Cortex-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 中断的。

中断优先级设置

优先级数配置

GIC 控制器最多可以支持 256 个优先级,数字越小,优先级越高!Cortex-A7 选择了 32 个优先级。在使用中断的时候需要初始化 GICC_PMR 寄存器,此寄存器用来决定使用几级优先级。I.MX6U 是 Cortex-A7内核,所以支持 32 个优先级,因此 GICC_PMR 要设置为 0b11111000。

抢占优先级和子优先级位数设置

优先级寄存器分为两部分:抢占优先级和子优先级。分配给每个部分的位数是可配置的。抢占优先级定义了一个中断是否可以抢占一个已经在执行的中断。当两个抢占优先级相同的中断同时发生时,子优先级决定哪个中断将首先执行。

优先级设置

前面已经设置好了 I.MX6U 一共有 32 个抢占优先级,数字越小优先级越高。具体要使用某个中断的时候就可以设置其优先级为 0~31。某个中断 ID 的中断优先级设置由寄存器D_IPRIORITYR 来完成,前面说了 Cortex-A7 使用了 512 个中断 ID,每个中断 ID 配有一个优先级寄存器,所以一共有 512 个 D_IPRIORITYR 寄存器。如果优先级个数为 32 的话,使用寄存器 D_IPRIORITYR 的 bit7:4 来设置优先级,也就是说实际的优先级要左移 3 位。比如要设置ID40 中断的优先级为 5,示例代码如下:

1
GICD_IPRIORITYR[40] = 5 << 3;

有关优先级设置的内容就讲解到这里,优先级设置主要有三部分:
①、设置寄存器 GICC_PMR,配置优先级个数,比如 I.MX6U 支持 32 级优先级。
②、设置抢占优先级和子优先级位数。
③、设置指定中断 ID 的优先级,也就是设置外设优先级。