GIC起源
上一节中,粗略讲了hylicos上用的armv7上的一个通用中断控制器,其只支持60个中断源。但现代SoC上,中断系统正变得越来越复杂,旧的中断控制器已经无法胜任这些系统,主要体现在以下几点上:
- 中断源越来越多,有的系统中断源有几百个,甚至上千个。
- 中断类型越来越多,比如普通外设中断,软件触发中断,CPU Core之间的中断,还有类似于PCIe上的基于消息传递的中断等
- 虚拟化技术的引入,主要开始支持虚拟中断
针对这些需求,arm开发了GIC,专门管理中断。这里以GIC-V2作例子讲解,GIC-V2已经支持虚拟化,典型的IP核心为GIC-400。
中断状态,触发方式,硬件中断号
每一个中断支持的状态有:
- 不活跃状态(inactive): 中断处于无效状态
- 等待状态(pending):中断处于有效状态,但是等待CPU响应该中断
- 活跃状态(active):CPU已经响应该中断
- 活跃并等待(active and pending):CPU正在处理该中断,但是该中断源又发了中断过来。
针对外设中断,有两种触发方式:
- 边沿触发
- 电平触发
GIC会为每一个硬件外设,也就是每一个硬件中断源,都分配一个中断号,即硬件中断号。
中断类型 | 中断号范围 |
---|---|
SGI | 0 ~ 15 |
PPI | 16 ~ 31 |
SPI | 32 ~ 1019 |
GIC-V2
以ARM Vexpress V2P-CA15_CA7为例子,GIC和core cluster之间的关系
GIC-400同时支持两个cpu cluster
GIC-400支持多种中断类型:
- SGI - 软件触发中断,通常用于多核之间的通信。 GIC-V2最多支持16个SGI中断,中断号范围为0~15。SGI通常在linux内核中被用作处理器之间的中断(Inter-Processor Interrupt IPI),被送达指定CPU上。
- PPI - 私有外设中断,每个处理器内核私有的中断。 最多吃吃16个PPI,16 ~ 31。PPI会被送达到指定的CPU上处理,典型的,CPU的local timer interrupt就是PPI的。
- SPI - 共享外设中断,所有处理器共享的中断。可以支持988个外设中断。32 ~ 1019.
GIC 组成
主要由分发器(distributor)和CPU接口组成。
分发器:主要用于仲裁和分发中断请求给CPU
- 全局中断使能
- 每个中断的使能
- 中断的优先级
- 中断的分组
- 中断的目的core
- 中断触发方式
- 对于SGI中断,传输中断到指定的core
- 每个中断的状态管理 提供软件,可以修改中断的pending状态
CPU接口:负责与CPU内核连接,通过nIRQ和nFIQ与CPU Core交流
- 将中断请求发送给cpu
- 对中断进行认可(acknowledging an interrupt)
- 中断完成识别(indicating completion of an interrupt)
- 设置中断优先级屏蔽
- 定义中断抢占策略
- 决定当前处于pending状态最高优先级中断
中断流程
GIC 检测当前的中断流程如下:
- 当GIC检测到一个中断发生时,会将该中断标记为pending
- 对于每一个pending状态的中断,分发器负责为其确定目标CPU,将请求发送到该CPU
- 对于每个CPU,分发器会从众多pending状态的中断中,选择一个优先级最高的中断请求发送到与目标CPU对接的CPU接口。
- CPU接口会决定这个中断是否可以发送给目标CPU。如果优先级满足,GIC会发送一个中断请求给目标CPU。
- GIC进入中断异常,读取GICC_IAR来响应这个中断(由软件的异常处理程序负责,这个过程也被叫做中断认可)。GICC_IAR里保存的是硬件中断号。对于SGI来说,返回源CPI的ID。
- GIC在感知到软件读取该寄存器后会做如下处理:
a. 如果中断处于pending,则pending --> active
b. 如果中断重新产生,则pending --> active and pending
c. 如果中断处于active,则active --> active and pending - 处理器执行完中断的处理,发送一个EOI信号给GIC。
GIC-V2寄存器
寄存器也分为两部分:
- 分发器的寄存器:包括中断的设置和配置
- CPU接口的寄存器:CPU相关的特殊设置
GIC-V2的寄存器,名称以n结尾的,有n个。比如GICD_ISENABLERn寄存器有n个。这些寄存器每个寄存器上的每一个bit都用于描述一个中断源是否使能,因为中断源非常多,因此需要很多个这样的寄存器。GIC-V2支持1020个中断号,所以需要1020/32,即32个这样的寄存器,每个寄存器占用4字节偏移,所以其地址空间是0x100 ~ 0x100 + 0x4*31(0x7c)。
对于中断号m来说,要判断其是否使能:
n = m / 32;
off_bit = m mod 32;
这里n就是它所在寄存器GICD_ISENABLERn名称上的n。以此可以算出GICD_ISENABLERn所在的地址偏移为:
0x100 + n * 0x4
不是每个寄存器,都是用1bit来描述一个中断源,1bit描述的信息太少,有的寄存器需要以多bit来描述一个中断源的信息:
GICD_ITARGETSRn,用于描述一个中断源所能路由的目标CPU。
类似的,n的计算公式
n = m / 4;
寄存器偏移计算公式:
0x800 + n * 4
distributor部分
CPU Interface部分
中断路由
由于SGI和PPI都是cpu core私有的,中断路由只为SPI的中断号工作。分发器来负责中断路由,将SPI中断路由到不同的CPU Core上。如何配置路由?
GICD_ITARGETSRn就是做路由这件事的,通过配置GICD_ITARGETSRn可以指定中断号m配分发到指定的CPU Core上。GICD_ITARGETSRn是个32位的寄存器,每8位用于描述可为一个中断源处理的CP有哪些U,最多支持描述8个CPU ID,每一bit都描述一个CPU ID。某一bit被置位,说明对应的中断源可以被分发到以该索引值为CPU ID的cpu core上。
注:由于SGI和PPI的目标CPU Core是固定的,所以前32个中断源的路由配置是硬件配置死的。33 ~ 1019号中断是可以自由配置的。
GIC-400的配置
GIC-400的初始化流程
- 设置分发器和CPU接口寄存器组的基地址
- 读取GICD_TYPER,计算当前支持最多多少个中断源
- 初始化分发器
a. 关闭分发器
b. 设置SPI(比如串口中断)的路由
c. 设置SPI的触发类型,例如,边沿触发
d. 关闭所有中断源
e. 重新打开分发器 - 初始化CPU接口
a. 设置GIC_CPU_PRIMASK寄存器,配置优先级
b. 配置GICC_CTLR寄存器,打开CPU接口
响应中断的流程
- 中断发生,CPU跳转到异常向量表
- 跳转到GIC中断函数,例如gic_handle_irq()函数
- 读取GICC_IAR,获取中断号
- 根绝中断号,进行相应中断处理,例如,读取的中断号是30 ,在树莓派4B上,就是PNS定时器触发了中断,跳转到定时器中断处理函数,处理中断。
- 中断返回。