嵌入式系统中中断关键点
中断向量表
中断向量表是一个表,这个表里面存放的是中断向量。中断服务程序的入口地址或存放中断服务程序的首地址成为中断向量,因此中断向量表是一系列中断服务程序入口地址组成的表。在中断向量表中的位置是由半导体厂商定好的,当某个中断被触发以后就会自动跳转到中断向量表中对应的中断服务程序 (函数 )入口地址处。中断向量表在整个程序的最前面。
GIC
全称:general interrupt controller
类比STM32中的NVIC
中断使能/配置
要使用某个外设的中断,肯定要先使能这个外设的中断。
中断服务函数
我们使用中断的目的就是为了使用中断服务函数,当中断发生以后中断服务函数就会被调
用,我们要处理的工作就可以放到中断服务函数中去完成。同样以
Cortex-A7中断系统简介
Cortex-A7内核有 8个异常中断,其中有一个未使用,实际只有7个
M系列与A系类中断向量区别:
对于Cortex-M 内核来说,中断向量表列举出了一款芯片所有的中断向量,包括芯片外设的所有中断。
对于Cotex-A 内核来说并没有这么做,在表中有个IRQ 中断, Cortex-A 内核CPU 的所有外部中断都属于这个IQR 中断,当任意一个外部中断发生的时候都会触发IRQ 中断。在IRQ 中断服务函数里面就可以读取指定的寄存器来判断发生的具体是什么中断,进而根据具体的中断做出相应的处理。
7个中断简介:
- 复位中断(Rest),CPU 复位以后就会进入复位中断,我们可以在复位中断服务函数里面做一些初始化工作,比如初始化SP 指针、DDR 等等。
- 未定义指令中断(Undefined Instruction),如果指令不能识别的话就会产生此中断。
- 软中断(Software Interrupt,SWI),由SWI 指令引起的中断,Linux 的系统调用会用SWI指令来引起软中断,通过软中断来陷入到内核空间。[[SWI指令]]
- 指令预取中止中断(Prefetch Abort),预取指令的出错的时候会产生此中断。
- 数据访问中止中断(Data Abort),访问数据出错的时候会产生此中断。
- IRQ 中断(IRQ Interrupt),外部中断,前面已经说了,芯片内部的外设中断都会引起此中断的发生。
- FIQ 中断(FIQ Interrupt),快速中断,如果需要快速处理中断的话就可以使用此中。
GIC 控制器
GIC 控制器总览
GIC 是ARM 公司给Cortex-A/R 内核提供的一个中断控制器,GIC V2 是给ARMv7-A 架构使用的,比如Cortex-A7、Cortex-A9、Cortex-A15 等,V3 和V4 是给ARMv8-A/R 架构使用的,也就是64 位芯片使用的。I.MX6U 是Cortex-A 内核的。
ARM 会根据GIC 版本的不同研发出不同的IP 核,那些半导体厂商直接购买对应的IP 核即可,比如ARM 针对GIC V2 就开发出了GIC400 这个中断控制器IP 核。当GIC 接收到外部中断信号以后就会报给ARM 内核,但是ARM 内核只提供了四个信号给GIC 来汇报中断情况:VFIQ、VIRQ、FIQ 和IRQ。
四个信号含义:
- VFIQ:虚拟快速FIQ。
- VIRQ:虚拟快速IRQ。
- FIQ:快速中断IRQ。
- IRQ:外部中断IRQ。
现阶段只研究IRQ。
左侧中断源,中间GIC控制器,最右侧GIC向ARM内核发送的中断信息。
GIC部分将众多的中断源分为三类:
- SPI(Shared Peripheral Interrupt),共享中断,顾名思义,所有 Core共享的中断,这个是最常见的,那些外部中断都属于 SPI中断 (注意!不是 SPI总线那个中断 ) 。比如按键中断、串口中断等等,这些中断所有的 Core都可以处理,不限定特定 Core。
- PPI(Private Peripheral Interrupt),私有中断,我们说了 GIC是支持多核的,每个核肯定有自己独有的中断。这些独有的中断肯定是要指定的核心处理,因此这些中断就叫做私有中断。
- SGI(Software-generated Interrupt),软件中断,由软件触发引起的中断,通过向寄存器GICD_SGIR写入数据来触发,系统会使用 SGI中断来完成多核之间的通信。
中断ID
不同中断源不同的ID。每一个 CPU最多支持 1020个中断 ID,中断 ID号为 ID0~ID1019。这 1020个 ID包含了 PPI、 SPI和 SGI。
- 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个,这 128个中断 ID对应的中断在《 I.MX6ULL参考手册》的“ 3.2 Cortex A7 interrupts”小节
GIC逻辑分块
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协处理器有 16个 32位寄存器, c0~c15,现在需要用到 c0、 c1、 c12和 c15这四个寄存器。
通过c0 寄存器可以获取到处理器内核信息;通过c1 寄存器可以使能或禁止MMU、I/D Cache 等;通过c12 寄存器可以设置中断向量偏移;通过c15 寄存器可以获取GIC 基地址。
中断使能
中断使能包括两部分,一个是IRQ 或者FIQ 总中断使能,另一个就是ID0~ID1019 这1020个中断源的使能。
IRQ 和FIQ 总中断使能
IRQ 和FIQ 分别是外部中断和快速中断的总开关,I.MX6U 上的外设中断就必须先打开IRQ 中断。
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 中断的。
中断优先级设置
优先级数配置
Cortex-A7 最多可以支持256 个优先级,数字越小,优先级越高!半导体厂商自行决定选择多少个优先级。I.MX6U 选择了32 个优先级。在使用中断的时候需要初始化GICC_PMR 寄存器,此寄存器用来决定使用几级优先级。GICC_PMR 寄存器只有低8 位有效,这8 位最多可以设置256 个优先级。
抢占优先级和子优先级位数设置
抢占优先级和子优先级各占多少位是由寄存器==GICC_BPR ==来决定的。寄存器GICC_BPR 只有低3 位有效,其值不同,抢占优先级和子优先级占用的位数也不同。
为了简单起见,一般将所有的中断优先级位都配置为抢占优先级,比如I.MX6U 的优先级位数为5(32 个优先级),所以可以设置Binary point 为2,表示5 个优先级位全部为抢占优先级。
优先级设置
I.MX6U 一共有32 个抢占优先级,数字越小优先级越高。具体要使用某个中断的时候就可以设置其优先级为0~31。某个中断ID 的中断优先级设置由寄存器D_IPRIORITYR 来完成,前面说了Cortex-A7 使用了512 个中断ID,每个中断ID 配有一个优先级寄存器,所以一共有512 个D_IPRIORITYR 寄存器。
如果优先级个数为32 的话,使用寄存器D_IPRIORITYR 的bit7:4 来设置优先级,也就是说实际的优先级要左移3 位。比如要设置ID40 中断的优先级为5,示例代码如下:
GICD_IPRIORITYR[40] = 5 << 3;
最后
本文为笔者笔记,仅用于梳理学习逻辑,内容源自正点原子《I.MX6U嵌入式Linux驱动开发指南V1.5》。