中断向量表存放的是中断向量,中断服务程序的入口地址或存放中断服务程序的首地址成为中断向量,因此中断向量表是一系列中断服务程序入口地址组成的表。当某个中断被触发以后就会自动跳转到中断向量表中对应的中断服务程序(函数)入口地址处。中断向量表在整个程序的最前面(是指中断向量表在链接到程序的首地址)
Cortex-A7 一共有 8 个中断,Cortex-A 内核 CPU 的所有外部中断都属于 IRQ 中断,当任意一个外部中断发生的时候都会触发 IRQ 中断。在 IRQ 中断服务函数里面可以读取指定的寄存器来判断发生的具体是什么中断(读取到中断号),进而根据具体的中断做出相应的处理
①复位中断(Rest): CPU 复位以后就会进入复位中断,可以在复位中断服务函数里面做一些初始化工作,比如初始化 SP 指针、 DDR
②未定义指令中断(Undefined Instruction):如果指令不能识别的话就会产生此中断
③软中断(Software Interrupt,SWI):由 SWI 指令引起的中断, Linux 的系统调用会用 SWI
指令来引起软中断,通过软中断来陷入到内核空间
④指令预取中止中断(Prefetch Abort):预取指令的出错的时候会产生此中断
⑤数据访问中止中断(Data Abort):访问数据出错的时候会产生此中断
⑥IRQ 中断(IRQ Interrupt):外部中断,芯片内部的外设中断都会引起此中断的发生
⑦FIQ 中断(FIQ Interrupt):快速中断,如果需要快速处理中断的话就可以使用此中断。
一般ARM 处理器都是从地址 0X00000000 开始运行的,但是阿尔法程序起始地址是0X87800000,所以,可以通过中断向量表偏移将中断向量表存放到任意地址处,中断向量表偏移配置在函数 int_init() (汇编也可以)中完成
常用是复位中断和 IRQ 中断,需要编写这两个中断的中断服务函数,根据上表 的内容来创建中断向量表,中断向量表处于程序最开始的地方
_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(快速中断)未定义中断 */
Cortex-M的中断控制器叫做 NVIC,Cortex-A的中断控制器叫做 GIC,类似于NVIC。当 GIC 接收到外部中断信号以后就会汇报给 ARM 内核,但是ARM 内核只提供了四个信号给 GIC 来汇报中断情况: VFIQ、 VIRQ、 FIQ 和 IRQ
GIC内部结构:
左侧部分是中断源,中间部分是 GIC 控制器,右侧是中断控制器向处理器内核发送中断信息
GIC 将中断源分为分为三类:
(1)SPI(Shared Peripheral Interrupt):共享中断,所有 Core 共享的中断,外部中断都属于 SPI 中断 。这些中断所有的 Core 都可以处理,不限定特定 Core
(2)PPI(Private Peripheral Interrupt):私有中断,GIC 是支持多核的,每个核有自己独有的中断。这些独有的中断是要指定的核心处理
(3)SGI(Software-generated Interrupt):软件中断,由软件触发引起的中断,通过向寄存器GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信
为了区分这些不同的中断源为它们分配一个唯一 ID,每一个 CPU 最多支持 1020 个中断 ID,中断 ID 号为 ID0~ID1019。020 个 ID 包含了 PPI、 SPI 和 SGI
SGI:D0~ID15
PPI:ID16~ID31
SPI:ID32~ID1019
3.GIC逻辑分块
GIC 架构分为了两个逻辑块: Distributor(分发器端) 和 CPU Interface(CPU 接口端)
Distributor(分发器端): 此逻辑块负责处理各个中断事件的分发问题,也就是中断事件应该发送到哪个 CPU Interface 上去。分发器收集所有的中断源,可以控制每个中断的优先级,它总是将优先级最高的中断事件发送到 CPU 接口端
CPU Interface(CPU 接口端):CPU Core 相连接的,每个 CPU Core 都可以在 GIC 中找到一个与之对应的 CPU Interface。 CPU 接口端就是分发器和 CPU Core 之间的桥梁
/*
* GIC 寄存器描述结构体,
* GIC 分为分发器端和 CPU 接口端
*/
typedef struct
{
/* 分发器端寄存器 */
uint32_t RESERVED0[1024];
__IOM uint32_t D_CTLR; /* Offset: 0x1000 (R/W) */
.....
/* CPU 接口端寄存器 */
__IOM uint32_t C_CTLR; /* Offset: 0x2000 (R/W) */
.....
}
GIC 的分发器端相关寄存器,相对于 GIC 基地址偏移为 0X1000,在获取到 GIC 基地址以后只需要加上 0X1000 即可访问 GIC 分发器端寄存器
CPU 接口端相关寄存器,相对于 GIC 基地址的偏移为 0X2000,获取到 GIC 基地址以后只需要加上 0X2000 即可访问 GIC 的 CPU 接口段寄存器
那么GIC 控制器的寄存器基地址在哪里呢?由Cortex-A 的 CP15 协处理器决定
CP15 协处理器一般用于存储系统管理, 但是在中断中也会使用到, CP15 协处理器一共有16 个 32 位寄存器
MRC: 将 CP15 协处理器中的寄存器数据读到 ARM 寄存器中
MCR: 将 ARM 寄存器的数据写入到 CP15 协处理器寄存器中
写:MCR{cond} p15,
,
cond:指令执行的条件码,如果忽略的话就表示无条件执行
opc1:协处理器要执行的操作码
Rt: ARM 源寄存器,要写入到 CP15 寄存器的数据就保存在此寄存器
CRn: CP15 协处理器的目标寄存器
CRm: 协处理器中附加的目标寄存器或者源操作数寄存器,如果不需要附加信息就将
CRm 设置为 C0,否则结果不可预测
opc2: 可选的协处理器特定操作码,当不需要的时候要设置为 0读:MRC{cond} p15,
, 此时,Rt 就是目标寄存器,也就是从CP15 指定寄存器读出来的数据会保存在 Rt 中
CRn 就是源寄存器,也就是要读取的写处理器寄存器
CP15 协处理器有 16 个 32 位寄存器, c0~c15,这里只介绍c0、 c1、 c12 和 c15 这四个寄
存器,在使用 MRC 或者 MCR 指令访问这 16 个寄存器的时候,指令中的 CRn、 opc1、 CRm 和 opc2 通过不同的搭配,其得到的寄存器含义是不同的
(1)c0寄存器:不同搭配的不同含义
当 MRC/MCR 指令中的 CRn=c0, opc1=0, CRm=c0, opc2=0 的时候就表示此时的 c0 就是 MIDR 寄存器(主 ID 寄存)
(2)c1寄存器
当 MRC/MCR 指令中的 CRn=c1, opc1=0, CRm=c0, opc2=0 的时候表示此时的 c1 就是 SCTLR 寄存器(系统控制寄存器)
SCTLR 寄存器主要是完成控制功能的,比如使能或者禁止 MMU、 I/D Cache
bit13(V): 中断向量表基地址选择位,为 0 中断向量表基地址为 0X00000000,软件可以使用 VBAR 来重映射此基地址(中断向量表重定位)。为 1 的话中断向量表基地址为0XFFFF0000,此基地址不能被重映射
bit12:I Cache 使能位,为 0 的话关闭 I Cache,为 1 的话使能 I Cache
bit11:分支预测使能位,如果开启 MMU 的话,此位也会使能
bit2:D Cache 和缓存一致性使能位,为 0 的时禁止 D Cache 和缓存一致性,为 1 时使能
bit1:内存对齐检查使能位,为 0 的时关闭内存对齐检查,为 1 的时使能内存对齐检查
bit0: MMU 使能位,为 0 的时禁止 MMU,为 1 的时使能 MMU。
/* 关闭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寄存器中 */
在复位中断复位函数中需要关闭I / D Cache和MMU
(3)c12寄存器
当 MRC/MCR 指令中的 CRn=c12, opc1=0, CRm=c0, opc2=0 的时表示此时 c12 为 VBAR 寄存器(向量表基地址寄存器)。设置中断向量表偏移的时需要将新的中断向量表基地址写入 VBAR 中
代码链接的起始地址为0X87800000,而中断向量表肯定要放到最前面
ldr r0, =0X87800000 ; r0=0X87800000
MCR p15, 0, r0, c12, c0, 0 ;将 r0 里面的数据写入到 c12 中,即 c12=0X87800000
(4)c15寄存器
当 MRC/MCR 指令中的 CRn=c15, opc1=4, CRm=c0, opc2=0 的时表示此时 c15 为 CBAR寄存器,GIC的基地址存放在该寄存器中
获取到 GIC 基地址:
MRC p15, 4, r1, c15, c0, 0 ; 获取 GIC 基础地址,基地址保存在 r1 中
获取到 GIC 基地址以后就可以设置 GIC 相关寄存器,比如读取当前中断 ID,当前中断 ID 保存在 GICC_IAR 中,寄存器 GICC_IAR 属于 CPU 接口端寄存器,寄存器地址相对于 CPU 接口端起始地址的偏移为 0XC,获取当前中断 ID :
MRC p15, 4, r1, c15, c0, 0 ;获取 GIC 基地址
ADD r1, r1, #0X2000 ;GIC 基地址加 0X2000 得到 CPU 接口端寄存器起始地址
LDR r0, [r1, #0XC] ;读取CPU 接口端起始地址+0XC 处的寄存器值,也是寄存器GIC_IAR 的值
小结:
通过 c0 寄存器可以获取到处理器内核信息
通过 c1 寄存器可以使能或禁止 MMU、 I/D Cache
通过 c12 寄存器可以设置中断向量偏移
通过 c15 寄存器可以获取 GIC 基地址
中断使能包括两部分: IRQ 或者 FIQ 总中断使能和ID0~ID1019 1020个中断源的使能
IRQ 和 FIQ 分别是外部中断和快速中断的总开关,寄存器 CPSR 的 I=1 禁止 IRQ,当 I=0 使
能 IRQ; F=1 禁止 FIQ, F=0 使能 FIQ
开关中断指令:
ID0~ID1019 中断使能和禁止:GIC 寄存器 GICD_ISENABLERn 和 GICD_ ICENABLERn 用来完成外部中断的使能和禁止;GICD_ISENABLER 寄存器来完成中断的使能,GICD_ICENABLER 寄存器来完成中断的禁止。一个 bit 控制一个中断 ID 的使能,对于 Cortex-A7 内核来说中断 ID 只使用了 512 个,需要 512/32=16 个 GICD_ISENABLER 寄存器来完成中断的使能
Cortex-A7 的中断优先级也可以分为抢占优先级和子优先级,GIC 控制器最多可以支持 256 个优先级,数字越小,优先级越高! Cortex-A7 选择了 32 个优先级。在使用中断的时候需要初始化GICC_PMR 寄存器,此寄存器用来决定使用几级优先级(低8位有效)
抢占优先级和子优先级各占多少位是由寄存器 GICC_BPR 来决定的(低3位有效)
一般将所有的中断优先级位都配置为抢占优先级,MX6U 的优先级位数为 5(32 个优先级),所以可以设置 Binary point 为 2,表示 5 个优先级位全部为抢占优先级
设置某个中断 ID 的中断优先级设置由寄存器D_IPRIORITYR 来完成,使用寄存器 D_IPRIORITYR 的 bit7:4 来设置优先级,也就是说实际的优先级要左移 3 位
设置ID40 中断的优先级为 5
GICD_IPRIORITYR[40] = 5 << 3;
小结:
(1)设置寄存器 GICC_PMR,配置优先级个数(32 级优先级)
(2)设置抢占优先级和子优先级位数, 将所有的位数都设置为抢占优先级
(3)设置指定中断 ID 的优先级,也就是设置外设优先级