本章节涉及到的参考文档有三个:
- BCM2711 ARM Peripherals.pdf
- ARM Generic Interrupt Controller Architecture Specification.pdf (简称gic_v2)
- CoreLink GIC-400 Generic Interrupt Controller Technical Reference Manual.pdf
1. GIC 发展历史
在早期的 ARM 系统中,比如ARM7,ARM9 都是采用单核处理器设计,比如STM32,三星的2410。基本上几个简单的寄存器就可以描述中断源的使能,关闭以及状态。
树莓派4B上默认的 legacy 中断控制器就是采用了类似的实现。当然了树莓派4B上的中断源比较多,可以采用多级串联的方式来实现。三星的2410也是采用多级串联的方式实现。
但随着Soc 越来越复杂,中断源越来越多,中断类型也越来越多,以及现在比较流行的支持虚拟化等等因素。 ARM 公司开发了 GIC (Generic Interrupt Controller) 专门来管理中断。
2. GIC 通用常识
2.1. 中断类型
- SGI : 软件产生的中断 (Software Generated Interrupt), 软中断即软件产生的中断,用于给其他CPU核心发送中断信号。
- PPI: 私有外设中断(Private Peripheral Interrupt), 私有的外设中断,该中断时某个指定的CPU 独有的。
- SPI : 共享外设中断 (Shared Peripheral Interrupt), 共享的外设中断,也就是我们常说的外设中断,所有CPU都可以访问这个中断。
- LPI: 本地特殊外设中断(Locality-specific Peripheral Interrupt), GICv3 新增的中断类型,基于消息传递的中断类型。
2.2. 中断触发方式
- 边沿触发(edge-triggered): 当中断源产生一个上升沿或者下降沿时,触发一个中断。
- 电平触发(level-triggered): 当中断信号产生一个高电平或者低电平时,触发一个中断。
2.3. 中断状态
- 不活跃状态(inactive): 中断处于无效状态。
- 等待状态(pending): 中断处理有效状态,但是等待 CPU 响应该中断。
- 活跃(active) : CPU 相应该中断。
- 活跃并等待状态(active and pending): CPU 正在相应该中断的同时该中断源又发送新的中断过来。
2.4. GIC-V2中断控制器
GIC -V2 有两个硬件单元组成的。
- Distributor :分发器,对应的 Distributor Registers(GICD_) 包含了中断设置和配置
- CPU interface : CPU接口,每个CPU内核有一个CPU接口。对应的 CPU Interface registers(GICC_) 包含了CPU 相关的特殊设置。
2.5. 中断路由
GIC-V2 最多支持 8 个CPU.
GICD_ITARGETSRn 寄存器用来配置 Distributor 可以把中断路由到 哪个CPU 上,
- 每 8bit 表示一个中断源,每个 bit 代表能路由到的 CPU
- 每个 bit 设置了,说明该中断源可以路由到这个 CPU 上
- 前 32 个中断源的路由配置时硬件设置好的,权限是只读RO
- 第 33~1019 号中断,可以有软件来配置其路由,权限是读写RW
2.6. 中断流程
GIC检测中断
GIC检测中断的流程如下:
- 当GIC 检测到一个中断发生时,会将该中断状态从inactive状态标记为pending状态。
- 对于处在penging状态的中断,分发器会确定目标 CPU, 将中断请求发给这个CPU。
- 对于每个 CPU, 分发器会从众多处于等待状态的中断中选择一个优先级最高的中断,发送到目标CPU 的 CPU 接口。
- CPU 接口会决定这个中断是否可以发送给CPU, 如果这个中断的优先级满足要求,GIC 会发送一个中断请求信号给 CPU.
- CPU 进入中断异常, 读取 GICC_IAR 来响应该中断(一般是由 Linux 内核的中断处理程序来读寄存器)。寄存器会返回硬件中断号(hardware interrupt ID)。对于 SGI 来说,返回源CPU 的ID (source processor ID) 。当GIC感知到软件读取了该寄存器后,根据如下情况处理。
- 如果该中断源处于pending 状态,则将该中断状态切换到 active 状态
- 如果该中断又重新产生,那么该中断状态则变成 active and pending 状态
- 如果该中断正在忙,正在处理其他中断, 则该中断状态其切换为 active and pending 状态,等待CPU将当前当前的中断处理结束之后,再将该中断切换到 active 状态
- 处理器完成中断服务,发送一个完成信号结束中断(End of Interupt,EOI) 给 GIC。该中断状态再切换到 inactive 状态。
当然,实际情况可能更复杂。
3.2.4 Interrupt handling state machine, ARM Generic Interrupt Controller Architecture Specification.pdf
GIC中断时序图
GIC 支持中断优先级抢占功能。一个高优先级的中断可以抢占一个处于 active 状态的低优先级中断,即 GIC 的分发器会先找出并记录当前优先级最高并且处于 peding 的中断,然后抢占当前的中断服务,转而先处理高优先级中断,上述内容是从 GIC 角度分析的。总之,GIC 的分发器总会把 pending 状态中优先级最高的中断请求发送给 CPU。
从 Linxu 内核角度分析,如果在低优先级的中断处理程序中发生了 GIC 抢占,虽然 GIC 会发送高优先级中断请求给 CPU, 但是CPU处于关中断的状态,需要等到CPU开中断时才会响应高优先级中断。这一点需要注意。
B1. Interrupt signaling in the GIC-400 with physical interrupts only, CoreLink GIC-400 Generic Interrupt Controller Technical Reference Manual.pdf
假设中断N和中断M 都是 SPI 类型的外设中断且通过快速中断请求(Fast Interrupt Request, FIR) 来处理,高电平触发,N的优先级比M的高,它们的目标CPU 相同。
- T1时刻,GIC 的分发器检测到中断M的电平变化。
- T2时刻,分发器设置中断 M 的状态为 pending.
- T17 时刻,CPU接口会拉低 nFIQCPU[n] 信号来向 CPU 报告中断请求。分发器需要这些时间来计算哪个是 pending状态下的优先级最高的中断。
- T42时刻,分发器检测到另外一个更高优先级更高的中断 N.
- T43时刻,分发器用中断 N 来替换中断 M, 作为当前pengding 状态下最高的中断,并设置中断 N 处于 pending 状态。
- T58时刻,经过 tph个时钟周期之后,CPU 接口拉低 nFIQCPU[n] 信号来通知 CPU。nFIQCPU[n] 信号在T17时刻已经被拉低。 CPU接口会更新 GICC_IAR的 ID 字段,该字段的值变成中断 N 的硬件中断号。
- T61 时刻,CPU (Linux 内核的中断服务程序) 读取GICC_IAR, 即软件响应了中断 N, 这时分发器把中断 N 的状态从 pending 状态切换为 active and pending 状态。
- T61-T131 时刻,Linux 内核处理中断 N 的中断服务程序。
- T64时刻,在中断 N 被 Linux 内核响应后的 3个时钟周期内,CPU接口完成对 nFIQCPU[n] 信号的复位,即拉高 nFIQCPU[n]信号。
- T126时刻,外设也复位了中断 N。
- T128时刻,退出了中断 N 的 Pending 状态。
- T131时刻,处理器(Linux内核中断服务程序)把中断 N 的硬件ID 写入 GICC_EOIR来完成中断 N 的全部处理过程。
- T146 时刻,在向GICC_EOIR 写入中断N 硬件 ID 后的 tph 个时钟周期后,分发器会选择下一个优先级最高的中断,即中断M, 发送中断请求给 CPU 接口。CPU接口拉低 nFIQCPU[n] 信号来向 CPU 报告中断 M 的请求。 CPU接口会更新 GICC_IAR的 ID 字段,该字段的值变成中断 M 的硬件中断号。
- T211 时刻,CPU(Linux 内核中断服务程序)读取GICC_IAR 来响应该中断,分发器设置中断M的状态为 active and pending 状态。
- T214时刻,在 CPU 响应中断后的3个时钟周期内,CPU 接口拉高 nFIQCPU[n] 信号来完成复位动作。
2.7. GIC-v2 寄存器
不考虑虚拟化的话,GIC 的寄存器可以分为两组:
- D: 表示 Distributor 相关的寄存器, 以 ”GICD_“ 打头的寄存器
- C: 表示 CPU interface 相关的寄存器,以 ”GICC_“ 打头的寄存器
Chapter 4.1 Distributor register map, ARM Generic Interrupt Controller Architecture Specification.pdf
Chapter 4.1 CPU interface register map, ARM Generic Interrupt Controller Architecture Specification.pdf
GIC-V2寄存器有个特点:名称以n 结束的寄存器会有 n个。比如 GICD_ISENABLERn寄存器就有 n 个GICD_ISENABLER 寄存器。
第一种:一个bit表示一个中断号类型的寄存器
GICD_ISENABLERn寄存器是用来使能某个中断号的,并且是按照中断号来描述的。
Chapter 4.1 Distributor register map, ARM Generic Interrupt Controller Architecture Specification.pdf
此寄存器的偏移量是 0x100~0x17c。 总共有n = (0x17c-0x100)/4 = 31 个寄存器。分别是 GICD_ISENABLER0 到 GICD_ISENABLER31。
对于中断号 m 来说,我们需要计算中断m对应的是哪个寄存器,也就是说我们要计算偏移多少。
Chapter 4.3.5 Interrupt Set-Enable Registers, GICD_ISENABLERn, ARM Generic Interrupt Controller Architecture Specification.pdf
GICD_ISENABLERn寄存器中每bit表示一个中断源。所以 n=m/32。 那么对于中断号 m 的GICD_ISENABLERn寄存器的地址就是 (0x100+4n)。
假设 m = 50, 即50号中断。 n = 50/32=1。 即GICD_ISENABLER1,偏移地址是(0x100+4*1)
第二种:8bits表示一个中断源类型的寄存器
GICD_ITARGETsRn 寄存器 使用 8位表示一个中断源所能路由的目标CPU有哪些。
Chapter 4.3.5 Interrupt Set-Enable Registers, GICD_ISENABLERn, ARM Generic Interrupt Controller Architecture Specification.pdf
- 前32个中断的Target reg 是固定的,只读。
- 后面的32~1019个中断的 Target 是软件可以设置的。
Chapter 4.3.12 Interrupt Processor Targets Register, GICD_ITARGETSRn, ARM Generic Interrupt Controller Architecture Specification.pdf
Chapter 4.3.12 Interrupt Processor Targets Register, GICD_ITARGETSRn, ARM Generic Interrupt Controller Architecture Specification.pdf
对于中断 m来说,这个寄存器 n的计算公式变成了 n =m/4。这里是取整计算。 寄存器的偏移量等于(0x800+4n)。
假设 m =50, 即50号中断。 n=50/4=12。 即GICD_ITARGETSR12。寄存器的偏移量是 0x800 +4*12 = 0x830。
3. 树莓派4B 上的 GIC-400
树莓派4B上集成了 GIC-V2架构的GIC-400。 GIC-400 上的具体中断号的分配和Soc 芯片的实现相关。
6.3 GIC-400 interrupt controller, BCM2711 ARM Peripherals.pdf
树莓派上的GIC-400分配有5种情况:
- ARM Core 中断组:所有的PPI的中断组以及部分 SPI 寄存器组,包括ARM 核心上的通用寄存器,例如,PNS(non-secure physical)定时器的中断号就是30
- Core n PS(Secure Physical) 定时器中断,PPI
- Core n PNS(Non-Secure Physical) 定时器中断,PPI
- Core n HP(Hypervisor) 定时器中断,PPI
- Core n V(Virtual) 定时器中断,PPI
- Core n PMU(Performance Monitoring Unit) 中断,SPI
- ARM local 的中断,这部分都是SPI中断,例如, 16个 ARM 邮箱中断号为 32~47, CPU 内核0 上的 PMU 中断号是48
- ARMC 外设中断组,CPU和GPU都可以访问的中断。这部分都是SPI中断。他们对应的中断号为64~79,比如常见的软件中断
- VC(VideoCore)外设中断组,对应的中断号为96~159。这部分都是SPI中断。比如树莓派4B的 system timer 0 的中断号是96。相应的中断寄存器也是在 ARMC 寄存器组里。
- 与PCIe相关的中断,对应中断号为 160~216。这部分都是SPI中断,
这些中断是如何映射到GIC-400 中的呢?
ARMC 外设中断组中每个中断定义如下
Chapter 6.2.3 ARMC interrupt, BCM2711 ARM Peripherals.pdf
GIC-400 分配的 ARMC peripheral IRQs 的 中断号是从 64-79,直接映射过去即可,比如 Timer 在ARMC中断组的id是0 ,那么映射到GIC-400就是64。 Software Interrupt 7 在ARMC组的id是15, 那么映射到GIC-400就是79。
另外,ARMC 中断组的寄存器 IRQn_PENDING2 每个bit 表示一个中断。
Chapter 6.5.3 ARMC, BCM2711 ARM Peripherals.pdf
VC(VideoCore) 外设中断组详细中断号如下
Chapter 6.2.4. VideoCore interrupts, BCM2711 ARM Peripherals.pdf
GIC-400 分配的 VC(VideoCore) peripheral IRQs 的 中断号是从 96-159, 那么System Timer 1 映射到 GIC-400,中断号就是97。
另外,VC 中断组的寄存器IRQn_PENDING0, IRQn_PENDING1 每个bit 表示一个中断。总共64个。
Chapter 6.5.3 ARMC, BCM2711 ARM Peripherals.pdf
PCIe 的中断组详细的中断号如下
Chapter 6.2.5. ETH_PCIe interrupts, BCM2711 ARM Peripherals.pdf
4. 访问树莓派上 GIC-400 寄存器
树莓派4B有两种地址模式
- 高地址模式,此模式下GIC-400的基地址是0x4C0040000
- 低地址模式, 此模式下GIC-400的基地址是0xFF840000
一般我们使用低地址模式(enable low peripheral),对应的基地址是0x0 FF84 0000。
Chapter 6.5.1 GIC-400, BCM2711 ARM Peripherals.pdf
然后,我们需要继续查看 GIC-V2的手册来确定 Distributor 和 CPU interface 的偏移量。
3.2 GIC-400 register map, CoreLink GIC-400 Generic Interrupt Controller Technical Reference Manual.pdf
从上图可以知道, Distributor 和CPU interface 的基地址分别是 0x1000和0x2000。
最后,在查看对应的寄存器的偏移量。寄存器的偏移量可以参考[[014 - GIC-400#2.5. GIC-v2 寄存器]]
我们继续使用中断 m 来举例, 假设通过[[014 - GIC-400#3. 树莓派4B 上的 GIC-400]]c 参考到中断M的中断号是 50。
我们来计算下,中断 m 对应的 GICD_ISENABLER1 和 GICD_ITARGETSR12 在树莓派4B设置为低地址模式下的访问地址。
GICD_ISENABLER1 的访问地址 = GIC-400 的基地址+ GIC-400 的 Distributor的偏移量+ 指定寄存器的偏移量 = 0x FF84 0000 + 0x100 + (0x100+4*1) = 0x FF84 0204
GICD_ITARGETSR12 的访问地址 = GIC-400 的基地址+ GIC-400 的 Distributor 的偏移量+ 指定寄存器的偏移量 = 0xFF84 0000 + 0x100 +(0x800+4*\12) = 0xFF84 093C
这些地址的来源总结下:
- GIC-400 的基地址: 来自树莓派的手册,
Chapter 6.5.1 GIC-400, BCM2711 ARM Peripherals.pdf
- GIC-400 的 Distributor/CPU 的偏移量 : 来自 gic -v2,
3.2 GIC-400 register map, CoreLink GIC-400 Generic Interrupt Controller Technical Reference Manual.pdf
- GIC-400 的 指定寄存器的偏移量 : 来自 gic -v2,
4.3 Distributor register descriptions, ARM Generic Interrupt Controller Architecture Specification
以及4.4 CPU interface register descriptions, ARM Generic Interrupt Controller Architecture Specification.pdf
“GICC_” 打头的寄存器也可以这样计算访问地址。
5. GIC-400 中断处理流程
5.1. GIC-400 初始化
- 设置Distributor 和 CPU interface 寄存器组的基地址
- 读取 GICD_TYPER寄存器,计算当前GIC 最大支持多少个中断源
- 初始化 Distributor
- Disable Distributor
- 设置SPI 中断的路由
- 设置SPI 中断的触发类型,例如 level触发
- Disactive and disable 所有的中断源
- Enable distributor
- 初始化 CPU interface
- 设置 GIC_CPU_PRIMASK, 设置中断优先级mask level
- Enable CPU interface
5.2. 注册中断
- 初始化外设
- 查找该外设的中断在 GIC-400 的中断号,例如 PNS timer 的中断号为30
- 设置 GIC_DIST_ENABLE_SET 寄存器enable 这个中断号
- 打开设备相关的中断,例如树莓派上的 generic timer, 需要打开ARM_LOCAL 寄存器组的 TIMER_CNTRL0 寄存器种相关的 enable 位
- 打开 CPU 的 PSTAE 中 I 位(PSTATE.I)
5.3. 中断响应
- 中断发生
- 异常向量表
- 跳转到GIC中断函数里,gic_handle_irq()
- 读取GIC_IAR 寄存器,获取中断号
- 根据中断号来进行相应中断处理,例如读取的中断号为30,说明是 PNS 的 generic timer,然后跳转到generic timer 的处理函数里。