一、Cortext-A7中断系统简介
Cortex-A7 也有中断向量表,内核有 8 个异常中断,中断向量表也是在代码的最前面。
看起来A7的中断向量表比STM32F103少很多,这是因为STM32F103使用的Cortex-M系列芯片,中断向量表列举出了一款芯片所有的中断向量,包括芯片外设的所有中断。对于 Cotex-A 内核来说并没有这么做 Cortex-A 内核 CPU 的所有外部中断都属于这个 IRQ 中断,当任意一个外部中断发生的时候都会触发 IRQ 中断。在 IRQ 中断服务函数里面就可以读取指定的寄存器来判断发生的具体是什么中断,进而根据具体的中断做出相应的处理。
c
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
第 4 到 11 行是中断向量表,当指定的中断发生以后就会调用对应的中断复位函数。第 14 到 50 行就是对应的中断服务函数,中断服务函数都是用汇编编写的,我们实际需要编写的只有复位中断服务函数 Reset_Handler 和 IRQ 中断服务函数 IRQ_Handler。
二、GIC 控制器简介
GIC 是 Cortex-A/R 内核的一个中断控制器,类似 Cortex-M 内核中的NVIC。当 GIC 接收到外部中断信号以后就会报给 ARM 内核,但是ARM 内核只提供了四个信号给 GIC 来汇报中断情况
这四个信号分别是:
VFIQ:虚拟快速 FIQ。
VIRQ:虚拟外部 IRQ。
FIQ:快速中断 IRQ。
IRQ:外部中断 IRQ。
接下来我们快速预览下整个GIC的结构
GIC中的中断分类
我们前面说了,GIC一端连接着众多的中断源,另一端连接着处理器,而GIC将众多的中断源分为分为三类:
- SPI(Shared Peripheral Interrupt),共享中断,顾名思义,所有 Core 共享的中断,这个是最常见的,那些外部中断都属于 SPI 中断。比如按键中断、串口中断等等,这些中断所有的 Core 都可以处理,不限定特定 Core。
- PPI(Private Peripheral Interrupt),私有中断,我们说了 GIC 是支持多核的,每个核肯定有自己独有的中断。这些独有的中断肯定是要指定的核心处理,因此这些中断就叫做私有中断。
- SGI(Software-generated Interrupt),软件中断,由软件触发引起的中断,通过向寄存器GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信。
为了区分不同的中断,我们需要为各种中断分配一个独一无二的ID,这就是中断ID。在ARM v7架构中,每一个 CPU 最多支持 1020 个中断 ID,中断 ID 号为 ID0~ID1019。这 1020 个 ID 包含了 PPI、SPI 和 SGI。其中0-15分配给了SPI,16-31分配给了PPI,而32-1019分配给了SPI。具体的每个ID对应的是什么中断,则由相关的半导体厂商根据实际情况去定义,还有就是只是ARM v7架构最大支持1020个中断源,但是并不是所有芯片都有1020个中断源的,比如说Cortex A7就只支持了512个中断源。
GIC的具体结构
GIC 架构分为了两个逻辑块:Distributor 和 CPU Interface,也就是分发器端和 CPU 接口端。
Distributor(分发器端):从图 17.1.3.2 可以看出,此逻辑块负责处理各个中断事件的分发问题,也就是中断事件应该发送到哪个 CPU Interface 上去。分发器收集所有的中断源,可以控制每个中断的优先级,它总是将优先级最高的中断事件发送到 CPU 接口端。主要的工作如下:
- 全局中断使能控制。
- 控制每一个中断的使能或者关闭。
- 设置每个中断的优先级。
- 设置每个中断的目标处理器列表。
- 设置每个外部中断的触发模式:电平触发或边沿触发。
- 设置每个中断属于组 0 还是组 1。
CPU Interface(CPU 接口端):CPU 接口端听名字就知道是和 CPU Core 相连接的,因此在每个 CPU Core 都可以在 GIC 中找到一个与之对应的 CPU Interface。CPU 接口端就是分发器和 CPU Core 之间的桥梁
- 使能或者关闭发送到 CPU Core 的中断请求信号。
- 应答中断。
- 通知中断处理完成。
- 设置优先级掩码,通过掩码来设置哪些中断不需要上报给 CPU Core。
- 定义抢占策略。
- 当多个中断到来的时候,选择优先级最高的中断通知给 CPU Core。
三、CP15协处理器
CP15寄存器是Cortex-A7中断处理中的一个重要协处理器,它的主业其实是存储系统的管理。CP15 协处理器有 16 个 32 位寄存器,c0~c15,我们在中断中主要要使用的c0, c1, c12和c15这几个寄存器,其中通过 c0 寄存器可以获取到处理器内核信息;通过 c1 寄存器可以使能或禁止 MMU、I/D Cache 等;通过 c12 寄存器可以设置中断向量偏移;通过 c15 寄存器可以获取 GIC 基地址。
!!其中最重要的是c15!!这是我们要了解CP15的关键之一,通过c15获得GIC基地址之后,通过地址偏移量可以获得GICC_IAR寄存器,该寄存器存储着当前发生中断的中断号,通过中断号+IRQ中断信号,CPU得以处理不同类型的中断
CP15协处理器的常用指令包括:
MRC : Move to Register from CP15, 将 CP15 协处理器中的寄存器数据读到 ARM 寄存器中。
MCR: Move to CP15 from Register, 将 ARM 寄存器的数据写入到 CP15 协处理器寄存器中。
指令格式为:MCR{cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2>
- cond:指令执行的条件码,如果忽略的话就表示无条件执行。
- opc1:协处理器要执行的操作码。
- Rt:ARM 源寄存器,要写入到 CP15 寄存器的数据就保存在此寄存器中。
- CRn:CP15 协处理器的目标寄存器。
- CRm:协处理器中附加的目标寄存器或者源操作数寄存器,如果不需要附加信息就设置为 C0,否则结果不可预测。
- opc2:可选的协处理器特定操作码,当不需要的时候要设置为 0。
比如指令:
MRC p15, 0, r0, c0, c0, 0
表示将 CP15 中 C0 寄存器的值读取到 R0 寄存器中。
指令中的 CRn、opc1、CRm 和 opc2 通过不同的搭配,其得到的寄存器含义是
不同的,我们已c0为例子,其搭配如下:
例如,当 MRC/MCR 指令中的 CRn=c0,opc1=0,CRm=c0,opc2=0 的时候就表示此时的 c0 就是 MIDR 寄存器,也就是主 ID 寄存器。
四、中断使能
中断使能包括两部分,一个是 IRQ 或者 FIQ 总中断使能,另一个就是 ID0~ID1019 这 1020个中断源的使能。IRQ 和 FIQ 分别是外部中断和快速中断的总开关,回顾二、GIC 控制器简介
的图可知,1020个中断源产生中断后,将中断传给GIC,然后GIC再通过它的CPU接口端通过IRQ发送中断信号给CPU。寄存器 CPSR用于控制IRQ和FIQ的中断使能,而中断源使能则通过GIC 寄存器 GICD_ISENABLERn 和 GICD_ ICENABLERn 来完成,对于 Cortex-A7 内核来说中断 ID 只使用了 512 个。
中断优先级设置
-
优先级数量配置
GIC 控制器最多可以支持 256 个优先级,数字越小,优先级越高!Cortex-A7 选择了 32 个优先级。在使用中断的时候需要初始化 GICC_PMR 寄存器,此寄存器用来决定GIC有几个优先级,其优先级设置如下:
既然Cortex A7是32个优先级,我们只需要设置GIC保持和Cortex A7一致就好,设置为0b11111000。
-
设置抢占优先级和子优先级位数
和STM32的Cortex M系列一样,Cortex-A7 的中断优先级也可以分为抢占优先级和子优先级,两者同样是可以配置的。抢占优先级和子优先级各占多少位是由寄存器 GICC_BPR 来决定的,该寄存器只有低3位有用,其余位均为留存,具体含义如下:
比如如果GICC_BPR=0b0000 0010,则意味着5位为抢占优先级,3位为子优先级,但是Cortex A7只有5位共32个优先级,那这意味着32个优先级都是抢占优先级。
-
设置具体中断的优先级
具体要使用某个中断的时候就可以设置其优先级为 0~31。某个中断 ID 的中断优先级设置由寄存器D_IPRIORITYR 来完成,前面说了 Cortex-A7 使用了 512 个中断 ID,每个中断 ID 配有一个优先级寄存器,所以一共有 512 个 D_IPRIORITYR 寄存器。
概括
经过上面的一系列讲解,其实整个中断的大体思路就已经清晰了:
- GIC收到中断信号,通过内部处理后向CPU发送IRQ中断信号
- CPU核心收到GIC传来的IRQ中断,通过中断向量表进入start.S中的IRQ处理函数
- IRQ函数会屏蔽掉IRQ中断,然后去读取GIC的GICC_IAR寄存器,获取中断号后关闭掉IRQ中断屏蔽,回到SVC状态,并且将处理IRQ的C语言函数的地址读取到某个寄存器Rx中,并且执行对应函数,将中断号作为参数传入该函数中
- C语言的IRQ处理函数会根据中断号查询中断列表,中断列表中包含着每一个中断号对应的中断处理函数,然后执行对应中断号的中断处理函数
- 中断函数执行完毕后,再次进入IRQ模式, 向GICC_EOIR 寄存器写入刚刚处理完成的中断号,当一个中断处理完成以后必须向 GICC_EOIR 寄存器写入其中断号表示中断处理完成。
- 恢复中断现场,回到中断前执行的位置继续执行
现在我们可以初步概括ARM中断系统应该如何配置了。首先我需要在start.S中配置好中断向量表,包括各种中断其对应的处理函数。然后编写对应的中断处理函数,以IRQ函数为例,IRQ函数是中断时触发的,那就需要首先做好中断上下文保存,将LR、r0-r3、r12、spsr等寄存器压栈保存,然后通过CP15寄存器读取出GIC的基地址,GIC的基地址加上0x200就是GIC的CPU端口地址,然后在CPU端口地址的基础上再加0x0C就是GICC_IAR寄存器,里面存放着当前发生中断的中断号。(挖个坑,后面再写)