ARM Interrupt Controller
时间轴
2025-10-22
init
参考文档:
ARM64异常处理之中断处理
- ARM核心两个和中断相关的管脚:nIRQ和nFIQ
- 每个CPU核心有一对这样的中断相关的管脚

- PSTATE状态中有两个比特位和中断相关
- I 用来屏蔽IRQ中断
- F 用来屏蔽FIQ中断
ARM64中的GIC控制器
- ARM提供了标准的GIC控制器,例如树莓派4b上支持GIC-400
- 树莓派3b上支持传统的中断方式(legacy interrupt)
Legacy Interrupt

中断的处理过程


当处理器接受一个异常到 AArch64 执行状态时,所有的 PSTATE 中断掩码都会自动设置。这意味着后续的
异常将被禁用。如果软件要支持异常嵌套,例如,允许高优先级的中断去打断对低优先级源的处理,那么软
件需要显式地重新启用中断。
对于下面的指令:
1 | MSR DAIFClr, #imm |

嵌套处理程序需要一些额外的代码。它必须在堆栈上保存 SPSR_EL1 和 ELR_EL1 的内容。在确定(并清除)
中断源之后,我们还必须重新启用 IRQ。
树莓派4B上的中断源

ARM Core n (ARM核心的中断源)
- PNS timer IRQ对应 A Non-secure EL1 physical timer
- PS timer IRQ对应A secure EL1 physical timer
- HP timer IRQ(Hypervisor)对应A Non-secure EL2 physical timer
- V timer IRQ 对应 A virtual timer
- PMU 是Performance Monitor Unit(性能监控单元)
- repeated 4 times 表示每个核心都有一组这样的中断源,树莓派有4个核心因此repeated 4 times

ARM_LOCAL (只有CPU才能访问的中断源)
ARMC (CPU和GPU都能访问的中断源)
VideoCore (GPU核心的中断 源)
包括的中断如下表:
VC(VideoCore) peripheral IRQs


ETH_PCIe (PCIe的中断)
树莓派4B的Legacy Interrupt Routing

ARM Core IRQs直接路由到pre-core routing
ARMC和VC通过ARMC routing 硬件单元路由
Legacy IRQ status registers

FIQn/IRQn_PENDING2
FIQn/IRQn_PENDING0
FIQn/IRQn_PENDING1
FIQ/IRQ_SOURCEn
当source 寄存器的bit8被设置了,那么你需要读PENDING2状态寄存器
如果PENDING2的bit24置位了,那么你需要读PENDING0的状态寄存器
如果PENDING2的bit25置位了,那么你需要读PENDING1的寄存器
软件需要一步一步读取中断状态寄存器
1 | ┌──────────────┐ |
Example

以ARM Core的generic timer为例
- Cortex-A72支持4个ARM Core的generic timer
- CNT_PS_IRQ Secure EL1 Physical Timer Event Interrupt
- CNT_PNS_IRQ Nonsecure EL1 Physical Timer Event interrupt
- CNT_HP_IRQ Hypervisor Physical Timer Event interrupt, EL2
- CNT_V_IRQ Virtual Timer Event interrupt EL3

Timer支持两种触发方式


XXX_ELn → 表示这个系统寄存器 可在 ELn 及更高特权级访问。
- 比如
CNTP_CTL_EL0就是 EL0 和 EL1 都能访问。

EL1的Nonsecure generic timer的中断处理流程
- 初始化timer,设置cntp_ctl_el0寄存器的enable域为1
- 给timer的TimeValue一个初值,设置cntp_tval_el0寄存器
- 打开树莓派中断控制器中和timer相关的中断,设置TIMER_CNTRL0寄存器中的CNT_PNS_IRQ为1


打开PSTATE寄存器中的IRQ中断总开关
Timer中断发生
- CPU跳转到el1_irq汇编函数
- 保存中断上下文(使用kernel_entry宏)
- 跳转到中断处理函数
- 读取ARM_LOCAL中中断状态寄存器IRQ_SOURCE0
- 判断是否CNT_PNS_IRQ中断发生
- 如果是,重新设置TimeValue
- 返回到el1_irq汇编函数
- 恢复中断上下文
- 返回中断现场
中断现场
- 中断发生瞬间,CPU的状态包括:
- PSTATE寄存器
- PC值
- SP值
- x0~x30寄存器
- 使用一个栈框数据结构来描述需要保存的中断现场(struct pt_regs)

保存中断现场

恢复中断现场

中断实验1:在树莓派上实现generic timer

树莓派firmware启动时默认加载GIC控制器而不是使用Legacy Interrupt因此可以在QEMU上跑
初始化timer,设置cntp_ctl_el0寄存器的enable域为1

给timer的TimeValue一个初值,设置cntp_tval_el0寄存器

打开树莓派中断控制器中和timer相关的中断,设置TIMER_CNTRL0寄存器中的CNT_PNS_IRQ为1



打开PSTATE寄存器中的IRQ中断总开关

Timer中断发生
CPU跳转到el1_irq汇编函数


保存中断上下文(使用kernel_entry宏)

跳转到中断处理函数
读取ARM_LOCAL中中断状态寄存器IRQ_SOURCE0


判断是否CNT_PNS_IRQ中断发生
如果是,重新设置TimeValue

- 返回到el1_irq汇编函数
- 恢复中断上下文
- 返回中断现场


另一种实现:
1 |
|
顺序(推荐的顺序)
cntp_tval_el0← 写定时器值TIMER_CNTRL0.CNT_PNS_IRQ← 打开外设中断路径PSTATE.I← 关闭 CPU IRQ 总开关cntp_ctl_el0.enable← 启动定时器PSTATE.I← 打开 CPU IRQ 总开关这种顺序保证 在 CPU 开 IRQ 前,外设和定时器都准备好了,所以一旦中断触发,CPU 能立刻收到并处理。
在 裸机/内核初始化阶段,定时器还没准备好,如果这时候 CPU 允许 IRQ,就可能被 别的外设中断打断,导致:
- 初始化流程被中断,配置寄存器可能只做了一半。
- 定时器或者外设中断配置还没完成,就有中断 pending → 结果是中断丢失或乱序执行。
中断实验2:使用汇编函数的方式来保存和恢复中断现场

关键在于使用函数方式lr寄存器被破坏



GIC中断控制器
- 传统的中断控制器,例如树莓派4b上的legacy interrupt controller
- 中断enable寄存器
- 中断disable寄存器
- 中断状态状态寄存器
- 传统的使用简单状态寄存器的方式来管理中断,变得越来越难管理
- 中断源变得越来越多
- 不同类型的中断,比如多核间的中断,中断优先级,软件定义的中断等、

GIC 主要有两个主要的功能块:
- Distributor:系统中的所有中断源都连接到 distributor。Distributor 提供寄存器来控制各个中断的属性,例
如优先级、状态、安全性、路由信息和启用状态。distributor 通过附加的 CPU 接口确定将哪个中断转发到内
核。 - CPU Interface:cpu 通过它接收中断。CPU 接口提供寄存器来屏蔽、识别和控制转发到该内核的中断状态。
系统中的每个内核都有一个单独的 CPU 接口。
GIC支持的中断类型
SGI (Software Generated Interrupt),软件产生的中断,软中断,用于给其他CPU核心发送中断信号
PPI (Private Peripheral Interrupt),私有的外设中断,该中断时某个指定的CPU独有的
SPI(Shared Peripheral Interrupt),共享的外设中断,所有CPU都可以访问这个中断
LPI (Locality-specific Peripheral Interrupt),本地特殊外设中断,GICv3新增的中断类型。基于消息传递的中断类型

中断的触发类型
每个中断类型要么是Edge-triggered的要么是Level-sensitive的:

由GICD_ICFGRn寄存器控制
- Edge-triggered: 当 GIC 检测到相关输入的上升沿时被认为是有效的,并且在清除之前保持有效
- Level-sensitive: 仅当 GIC 的相关输入为高电平时才有效
中断号

banked interrupt 意思是对于 SGI 和 PPI,虽然它们的 ID 在整个系统里一样(例如 Timer PPI 在所有核都是 ID30),但是 它们是私有的,每个 CPU 都有自己的一份,互不干扰。
- SGI (0–15):某核触发 SGI0,不会影响别的核的 SGI0。
PPI (16–31):核 0 的 Timer PPI(比如 ID30)和核 1 的 Timer PPI(ID30)是独立的,它们不会共享。
中断号 1020~1023是保留的

中断优先级
每个中断优先级设置在GICD_IPRIORITYRn寄存器中
优先级字段的宽度
- 每个中断优先级字段是8bit
- 实际实现支持的优先级级数 = 2ⁿ,n ∈ [4, 8](即 16 ~ 256 级)。
- 如果硬件实现的比 8 小,比如只实现 5 bit,则写入低 3 bit 是 RAZ/WI(读为 0、写无效)。
数值大小与优先级
- 数值越小,优先级越高
- 最大优先级值依赖实现
- 初始化默认应当给所有中断一个中等优先级,避免打断系统调度,常见是0xa0或0x80
中断状态
inactive (不活跃状态):中断处于无效状态
pending (等待状态):中断处于有效状态,但是等待CPU响应该中断
active (活跃状态):CPU已经响应该中断
active and pending (活跃并等待状态):CPU正在响应该中断,但是该中断源又发送中断过来

中断路由

GICD_ITARGETSRn (Interrupt Processor Targets Registers)

GICD_ITARGETSRn寄存器用来配置Distributor可以把中断路由到哪个CPU上,是一个 32 位寄存器,通常每个中断有一个对应的寄存器。
- 8bit来表示一个中断源,共四个中断源,每个bit代表能路由的CPU
- byte-accessible
- 某个bit设置了,说明该中断源可以路由到这个CPU上
- 前32个中断源的路由配置是硬件设置好的,RO
- 第33~1019号中断,可以由软件来配置其路由,RW
对应计算方法:

假设m是中断ID, n是对应寄存器号
- GICD_ITARGETSRn寄存器与中断源的关系
- 每个GICD_ITARGETSRn控制四个中断源
- n = m DIV 4
- 计算n后要加上GICD_ITARGETSRn寄存器的起始地址偏移0x800
- Priority字段的字节偏移
- m MOD 4用来确定目标中断源对应的字节偏移
- 偏移 0 对应寄存器的 [7:0] 位(即最低字节),对应中断 ID m % 4 == 0。
- 偏移 1 对应寄存器的 [15:8] 位,适用于 m % 4 == 1。
- 偏移 2 对应寄存器的 [23:16] 位,适用于 m % 4 == 2。
- 偏移 3 对应寄存器的 [31:24] 位,适用于 m % 4 == 3。
- m MOD 4用来确定目标中断源对应的字节偏移
GICD_ITARGETSRn 是一个数组寄存器

GICV2中断控制器

- The Distributor registers (GICD_) 包含了中断设置和配置
- The CPU Interface registers (GICC_) 包含CPU相关的特殊设置

中断传递的优先级和目标 core 都在 distributor 中配置。
外围设备 distributor 发送的中断都处于待处理状态。distributor 确定最高优先级的待处理的中断,可以传递
到 core 并将其转发到 CPU 接口,在 CPU 接口处,中断依次发送给 core,此时 core 接受 FIQ 或 IRQ 异常。
core 执行异常处理程序作为响应。处理程序从 CPU 接口寄存器中查询中断 ID 并开始执行中断服务程序。完
成后,处理程序必须写入 CPU 接口寄存器以报告处理结束。
Distributor 提供了报告不同中断 ID 的当前状态的寄存器。在多核/多处理器系统中,单个 GIC 可以由多个内
核共享(在 GICv2 中最多 8 个)。GIC 提供寄存器来控制 SPI 所对应的内核。这个机制使操作系统能够跨内
核共享和分发中断,并且协调工作。
配置
GIC 的寄存器实现都是外部 memory_map 形式。所有的核都可以访问公共的 Distributor,但是 CPU 接口是
banked 的,即每个核使用相同的地址访问自己的私有 CPU 接口。一个内核不可能访问另一个内核的 CPU 接
口。
Distributor 包含许多寄存器,您可以使用它们来配置各个中断的属性。这些可配置的属性有:
- 中断优先级(GICD_IPRIORITY),distributor 使用它来确定哪个中断接下来被转发到 CPU 接口。
- 中断配置(GICD_ICFGR)。这决定了中断是电平还是边缘敏感。不适用于 SGI。
- 一个中断目标 core(GICD_ITARGETSR)。这确定了中断可以路由到哪些 core。仅适用于 SPI。
- 中断启用或禁用状态(GICD_ISENABLERGICD_ICENABLER)。只有那些在 distributor 中启用的中断。
当它们处于待处理状态时有资格被路由到 cpu 接口。 - 中断安全 (GICD_IGROUPR) 确定中断是否分配给安全或非安全。
- 中断状态。
Distributor 还提供了优先级屏蔽,通过它可以防止低于某个优先级的中断到达内核。distributor 在确定是否可
以将挂起的中断转发到特定内核时使用它每个内核上的 CPU 接口有助于微调中断控制和处理核
初始化:
Distributor 和 CPU interface 在复位时都被禁用。GIC 必须在复位后初始化,然后才能向内核提供中断。
在 Distributor 中,软件必须配置优先级、目标 core、安全性并启用各个中断。随后必须通过其控制寄存器
(GICD_CTLR) 启用 distributor。
对于每个 CPU interface,软件必须对优先级掩码和优先级抢占进行设置。每个 CPU interface 本身必须通过控制寄存器 (GICD_CTLR) 启用。
在 cpu 处理中断之前,软件通过设置使 cpu准备好接受中断向量表中的有效中断向量,并清除 PSTATE 中的中断屏蔽位,并设置路由控制。
可以通过禁用 Distributor 来禁用系统中的整个中断机制。也可以通过禁用其 CPU 接口来禁用对单个内核的中断传递。也可以在分配器中禁用(或启用)各个中断。
对于一个中断到达核心,单个中断、分配器和 CPU 接口必须全部启用。中断也需要有足够的优先级,即高于内核的优先掩码。
中断处理
当 core 接受了中断,就跳转到顶层中断向量表并且开始执行。顶层的中断处理程序从 CPU interface 读取寄存
器以获得中断 ID。
读取寄存器除了返回中断 ID 外,还会导致中断在 distributor 中被标记为活 active。一旦知道中断 ID(识别中
断源),顶层处理程序就可以调度特定于设备的中断处理程序来服务中断。
当中断处理程序完成执行时,顶层处理程序将相同的中断 ID 写入 CPU 接口块中的中断结束 (EoI) 寄存器,指
示中断处理结束。
除了移除活动状态,使最终的中断状态为 Inactive 或 pending(如果状态为 active and pending),这使 CPU 接口能够将更多挂起的中断转发到 core。这结束了单个中断的处理。
在同一个内核上可能有多个中断等待服务,但是 CPU 接口一次只能发出一个中断信号。顶层中断处理程序
可以重复上述顺序,直到读取到特殊中断 ID 值 1023,表示有在这个核心上没有更多的中断挂起。这个特殊
的中断 ID 被称为虚假的中断标识。虚假中断 ID 是一个保留值,不能分配给系统。当顶层处理程序读取了虚
假中断 ID 后,它可以完成它的执行,并准备内核在中断之前恢复它正在执行的任务。
通用中断控制器 (GIC)通常管理来自多个中断源的输入。并将它们分发给 IRQ 或 FIQ 。
中断状态时序图

M和N分别代表两个中断,Input表示中断信号,State表示中断状态机,nFIQCPU[n]表示连接到CPU核心的FIQ信号
假设M和N都是SPI中断,且N的优先级高于M
GICv2寄存器

有些寄存器是按中断号来描述的,比如使用某几个比特位来描述一个中断号的相关属性,同一个寄存器可以n个,例如GICD_ISENABLERn寄存器,它是用来使能某个中断号的。“n”表示它有n个这样的寄存器


计算方法:根据中断号计算对应寄存器

对于有些寄存器,会提示:
A register bit corresponding to an unimplemented interrupt is RAZ/WI.
RAZ = Read-As-Zero
如果你读这个寄存器位,返回值永远是 0,即使你写过别的值。
WI = Write-Ignored
如果你往这个位写入数据,硬件会忽略,不会改变,也不会报错。
GICD_CTLR
Distributor Control Register


GICD_ITARGETSRn
Interrupt Processor Targets Registers,见中断路由章节
GICD_TYPER (Interrupt Controller Type Register)
描述这个 GIC 的能力和配置参数,比如有多少中断、多少个 CPU 接口、是否支持某些特性等等。


GICD_ICFGRn
Interrupt Configuration Registers
GIC用来配置中断触发类型的寄存器
每个 GICD_ICFGRn 是 32 位寄存器。

分布方式
每个中断需要 2 bit (Int_config)来描述,所以一个寄存器可以配置 16 个中断。
第
n个寄存器(ICFGRn)对应的中断范围是:1
中断 ID = n*16 ~ (n*16 + 15)
Int_config字段编码
根据 ARM GICv2 TRM(Table 4-18):

对于PPI(Private Peripheral Interrupt)和SPI(Private Peripheral Interrupt)来说
| Bit[1] | Bit[0] | 含义 |
|---|---|---|
| 0 | reversed | Level-sensitive |
| 1 | reserved | Edge-triggered |
GICD_ISENABLERn
Interrupt Set-Enable Registers
负责打开中断转发
GICD_ISENABLERn是32位寄存器
每个
GICD_ISENABLERn控制 32 个中断源。
写 1 → 使能对应的中断(让 Distributor 可以把它转发给 CPU interface)
写 0 → 没有作用
读 → 可以看到某个中断当前是否被使能(1=已使能,0=未使能)
根据中断号计算GICD_ISENABLERn的n

GICD_ICENABLERn
Interrupt Clear-Enable Registers
- W1C (写 1 清除),禁用某个中断(禁止分发到 CPU)



GICD_ISACTIVERn
Interrupt Set-Active Registers
- W1S(写1设置),软件可以把一个中断状态人为标记为 active。



GICD_ICACTIVERn
Interrupt Clear-Active Registers
W1C(写1清除),清除中断的 active 状态。
和 EOI(End of Interrupt)有点类似,但 EOI 是 CPU interface 寄存器,只能由当前 CPU 对自己接收的中断做“中断服务完成”的动作;
ICACTIVERn在 Distributor,软件可以全局地强制清除某个中断的 active 位。



GICD_IPRIORITYRn
Interrupt Priority Registers
用于设置每个中断的优先级
每个中断占 8 bit
一个 32 位寄存器管理 4 个中断


地址计算:

GICC_CTLR
CPU Interface Control Register


GICC_PMR
Iterrupt Priority Mask Register
CPU 接口的优先级屏蔽寄存器,它决定了 CPU 能够接收哪些优先级的中断。
- 只有 优先级数值 ≤ PMR 的中断 才能被 CPU 接收。
- 注意:GIC 的优先级是 数值越小,优先级越高。


使用举例:假设你的 GIC 实现有 8 bit 优先级:
- 如果
GICC_PMR = 0xff- CPU 接收所有优先级的中断(最常见的初始化设置)。
- 如果
GICC_PMR = 0xa0- 只接收 优先级值 ≤ 0xa0 的中断,优先级更“低”的中断会被屏蔽。
- 如果
GICC_PMR = 0x0- 只接收最高优先级(0)的中断,其他都屏蔽
GICC_IAR
Interrupt Acknowledge Register
当 CPU 收到 IRQ 信号时,软件需要从这个寄存器读取当前 pending 中断的 ID,并确认它是哪一个 IRQ。

| Bits | 名称 | 含义 |
|---|---|---|
| [9:0] | INTID | 中断 ID(0~1019 表示有效中断号) |
| [12:10] | CPUID | 标识触发该中断的 CPU(多核系统有用) |
| [31:13] | Reserved | 保留,读出为 0 |
GICC_EOIR
End of Interrupt Register
软件在完成中断处理后,必须写入这个寄存器,告诉 GIC:“这个 IRQ 已经处理完了,可以清除 active 状态,并允许后续相同中断再次触发”。

| Bits | 名称 | 含义 |
|---|---|---|
| [9:0] | INTID | 中断 ID(必须与 IAR 读出的中断号一致) |
| [12:10] | CPUID | 发出该中断的 CPU ID(多核系统使用) |
| [31:13] | Reserved | 保留,写 0 |
树莓派4B的GIC400

- ARM Core的中断:
- Core n HP tiemr IRQ
- Core n V timer IRQ
- Legacy FIQn
- Core n PS timer IRQ
- Core n PNS timer IRQ (PPI ID 30)
- Legacy IRQn
- ARM local的中断
- ARM Mailbox IRQs
- Core 0 PMU IRQ
- Core 1 PMU IRQ
- Core 2 PMU IRQ
- Core 3 PMU IRQ
- AXIERR IRQ
- Local timer IRQ
- 16个ARMC外设中断
- 64个VC外设中断
- 51个PCI相关的外设中断
访问GIC-400寄存器
- 树莓派4b上的GIC-400的基地址




访问:树莓派GIC400的基地址 + GIC-400 memory map偏移 + 寄存器偏移
GIC400的初始化流程
- 设置distributor和CPU interface寄存器组的基地址
- 读取GICD_TYPER寄存器,计算当前GIC最大支持多少个中断源
- 初始化distributor
- Disable distributor,设置GICD_CTLR(这一步可以不做,因为复位时本来就是关闭的)
- 设置SPI中断的路由
- 前32个中断怎么路由是GIC芯片固定的,因此先读GICD_ITARGETSRn前面的值,获取能路由的所有CPU
- 将 SPI(Shared Peripheral Interrupt)中断 的路由设置为将中断分发到所有能路由的 CPU,因为SPI中断是Shared的中断,设置SPI的GICD_ITARGETSRn
- 设置SPI中断的触发类型,例如Level触发,设置GICD_ICFGRn
- Disactive和disable所有的SPI中断源
- 打开SGI中断(0-15),SMP会用到
- Enable distributor,设置GICD_CTLR
- 初始化CPU interface
- 为前32个中断源设置默认中断优先级,设置GICD_IPRIORITYRn
- 设置GICC_PRM,设置中断优先级mask level
- Enable CPU interface,设置GICC_PRM
注册中断
- 初始化外设
- 查找该外设的中断在GIC-400的中断号,例如PNS timer的中断号为30
- 设置GICD_ISENABLERn寄存器来enable这个中断号
- 打开设备相关的中断,例如树莓派上的generic timer,需要打开ARM_LOCAL寄存器中的TIMER_CNTRL0寄存器中相关的enable位
- 打开CPU的PSTATE中的I位
中断响应
- 中断发生
- 异常向量表
- 跳转到GIC中断函数里,gic_handle_irq()
- 读取GICC_IAR寄存器,获取中断号
- 根据中断号来进行相应的中断处理,例如读取的中断号为30,说明的是PNS的generic timer,然后跳转到generic timer的处理函数里。
GIC中断实验1:实现generic timer

GIC-400初始化
- 设置distributor和CPU interface寄存器组的基地址
1 |
|
- 读取GICD_TYPER寄存器,计算当前GIC最大支持多少个中断源

初始化distributor


初始化CPU interface

打开PNS_TIMER_IRQ的路由

- 测试


GIC中断实验2:实现树莓派上的System Timer

GICv3中断控制器
GICv3比GICv2改进了哪些内容
- GICv3兼容GICv2
- 支持更多CPU数量,>8
- 支持message-base中断(Message Signaled Interrupt,MSI)
- 支持ITS(Interrupt Translation Service)服务
- 支持更多的硬件中断号,>1020
- 为了更好兼容ARMv8异常模型,支持中断组(Interrupt grouping)
- 为了优化访问延时,提供系统寄存器的方式来访问CPU Interface
GICv3支持的中断类型
- Private Peripheral Interrupt(PPI)
- PPI是指本地CPU特有的中断,比如CPU内部的定时器等。不同的CPU可以使用相同的PPI中断号
- PPI可以在group0和group1
- 可以边沿触发(edge-triggered)或者水平触发(level-sensitve)
- Shared Peripheral Interrupt(SPI)
- SPI通常用于外设中断,它可以路由到任意一个CPU
- SPI可以在group0和group1
- 可以边沿触发或者水平触发
- Software Generated Interrupt(SGI)
- SGI通常软件触发的中断,用于核间通信,例如IPI(Inter-Processor Interrupts)
- SGI只能边沿触发
- 新增:Locality-specific Peripheral Interrupt(LPI)
- 在非安全中断组1
- 边沿触发
- 使用ITS服务
- 没有active状态
- message-based中断

有线中断与基于消息的中断


- SPI和LPI都支持message-base中断
- SPI的message-base中断不需要经过ITS,往GICD_SETSPI_NSR寄存器写入中断号触发中断
- LPI的message-base需要ITS
中断号分配

中断状态机
- Inactive 不活跃状态
- Pending 中断触发了,但是还没有被CPU响应(acknowledge)
- Active 中断被CPU响应和处理
- Active & Pending 当前有一个中断正在被CPU响应和处理,这时有一个相同的中断触发了,这个新的中断被设置为active & pending
- LPI没有active和active & pending状态
问题:如果GIC在响应中断的过程中,又有一个相同的中断触发了,那怎么办?
这里要分两种情况:
- 如果GIC正在响应第一个中断时,即第一个中断的状态为active,此时第二个相同的中断触发,那么蝶儿个中断的状态会变成 active & pending,这样避免丢失了中断
- 如果第一个中断还处于pending状态,此时又来了一个相同的中断,那么它们会merge成一个

中断亲和性路由层级(Affinity routing)
- GICv3支持4级路由
- Level 0面对的是redistributor


GIC-500的中断亲和性路由

- GIC-500支持两级的亲和性路由
- Level 0 是 core
- Level 1 是 cluster
- GIC-500最大支持128个cores以及32个cluster
0.0.0.1表示第0个cluster中的第1个core
0.0.1.1表示第1个cluster中的第1个core
- 中断组与安全模式
- ARMv8支持安全模式和非安全模式
- GICv3支持EL0~EL3,所以每个中断源都需要设置对应的中断组和安全模式
- Group0用于EL3
- 安全模式的Group1:用于Trust OS on EL2
- 非安全模式的Group1:用于VMM或者OS

Group0使用FIQ,Group1根据情况使用IRQ or FIQ


例子

注意:中断是否会路由到EL3要看SCR_EL3寄存器FIQ和IRQ字段
- 在非安全模式下:
- 非安全group1的中断直接在RichOS响应
- 安全group1和Group0的FIQ中断路由到EL3
- 在安全模式下
- 安全group1的IRQ中断直接在Trusted OS响应
- 非安全group1和group0的FIQ中断路由到EL3
特殊中断号

1021号中断的使用1

- CPU运行在安全模式下的trusted os,此时来了一个非安全模式下的OS的中断,那么需要陷入到EL3的FIQ来处理
- CPU陷入到EL3的安全监视器,安全监视器会读取IAR寄存器,并且读到1021,说明这个中断是希望在非安全模式下处理的。切换到非安全模式的Rich OS
- CPU切换到非安全模式的Rich OS来处理这个中断
1021号中断的使用2

- CPU运行在安全模式下的trusted os,此时来了一个非安全模式下OS的中断。那么需要陷入到EL3的FIQ来处理。但是由于SCR_EL3.FIQ=0,它只能在EL2中处理。
- EL2的Trusted OS执行SMC系统调用陷入到EL3
- 安全监视器会读取IAR寄存器,并且读到1021,说明这个中断是希望在非安全模式下处理的。切换到非安全模式的Rich OS
- CPU切换到非安全模式的Rich OS来处理这个中断
中断优先级
- GICv3支持8位优先级,最多256级别
- 支持2个安全模式时,最少支持32个中断优先级,最多支持256个
- 支持1个安全模式时,最少支持16个中断优先级
- 中断优先级
- 数值越小,中断优先级越高,0表示最高优先级,255表示最低优先级,idle priority
- GICR_IPRIORITYR\
设置PPI和SGI中断优先级 - GICD_IPRIORITYR\
设置SPI中断优先级 - LPI配置表保存了LPI的中断优先级
中断优先级分组寄存器(ICC_BPR0_EL1/ICC_BPR1_EL1)
- Binary Point Register寄存器把中断优先级分成两个字段
- group priority 需要抢占时,优先比较group priority
- subpriority 当需要抢占时,只有当group priority相同时,才比较subpriority

Group0的中断优先级分组情况,见ICC_BPR0_EL1寄存器
注意:group1和group0的分组情况略有不同
中断优先级阈值与运行中断优先级
- 中断阈值
- 寄存器:ICC_PMR_EL1
- PMR决定目标CPU的优先级阈值。GIC只有当pending中断的优先级高于这个中断优先级阈值,才会发送中断到CPU
- PMR为0,表示屏蔽了所有中断发送给CPU
- Running priority
- 寄存器:ICC_PMR_EL1
- 返回正在响应中的group priority
中断优先级的抢占
- GICv3支持中断抢占,当一个中断优先级同时满足下面条件
- 优先级高于CPU接口的优先级阈值PMR
- Group priority高于正在处理的running prirority
GICv3内部架构

- Distributor:优先级排队,派发SPI和SGI到redistributor
- Redistributor:连接CPU interface。每个CPU一个redistributor
- CPU interface:发送中断给CPU,响应中断等
- ITS: 中断转换服务,把LPIs中断请求转换到中断号以及发送到redistributor
ITS服务(Interrupt translation service)
- ITS作用:把设备(device_id)的Event_ID转成:
- 硬件中断号(INTID)
- 目标redistributor
- ITS转换过程
- 使用Device_ID来查询device table
- 使用Event_ID来查询Interrupt translation table
- 物理中断号INTID
- interrupt collection number
- 由ICID来查询Collection table得到目标redistributor
- ITS五张表
- 中断配置表configure table
- 中断pending表
- device table
- Interrupt translation table
- Collection table


配置表和pending表
中断配置表用来存储每个LPI中断的优先级和enable位
- 每个表项占8bit
- 配置表基地址:GICR_PROPBASER.Physical_Address
- 表项个数:2^(GICR_PROPBASER.IDbits)


- 中断pending 用来表示每个LPI中断的pending状态
- 每个表项占1个bit
- 每个redistributor有一个中断pending表

Device Table的创建
- Device Table由OS软件创建
- 分配内存,创建Table
- 把表的基地址设置到GITS_BASER.Physical_Address
Device Table的重要参数在GITS_BASER\
寄存器中 - 表的类型:GITS_BASER.Type
- 表项的大小:GITS_BASER.Entry_Size(8个字节)
- Table中每个page的大小:GITS_BASER.Page_Size(eg, 64KB)
- 是否需要二级页表:GITS_BASER_Indirect
- 设置表基地址:GITS_BASER.Physical_Address
- 需要多少个表项:2^(device_id位宽),device_id位宽在GITS_TYPER.devbits
Device Table支持1级或者2级表
- Ddevice Table表项的内容由:
- 软件通过command queue来发送命令给硬件,硬件来填充表项
- 通过MAPD命令,把deviceID映射到ITT表中


- Device Table的表项叫做DTE,用来指向ITT表的

- ITT的表项叫做ITE,用来描述EventID和最终物理ID号的关系

Interrupt translation table的创建
- ITT表项用来映射 EventID -> 物理中断号INTID以及ICID
- ITT重要参数
- ITT表项大小:GITS_TYPER.ITT_entry_size(eg:16字节)
- ITT表项个数:申请中断时请求的vector个数
- ITT表的基地址:由OS来分配
- ITT表项的内容由:
- 软件通过command queue来发送命令给硬件,硬件来完成
- 通过MAPD命令,把deviceID映射到ITT表中
Collection Table的创建
- Collection Table表项用来映射:ICID -> redistributor
- 这个表不需要在内存中分配内存
Collection Table表项的内容由:
- 软件通过command queue来发送命令给硬件,硬件来完成
- 软件告知:redistributor的物理地址或者GICR_TYPER.Processor_Number
- 通过MAPC命令来映射ICID -> redistributor
- 在GIC初始化的时候就遍历所有的redistributor,并且调用MAPC命令初始化
gic_smp_init()->
遍历所有present CPU()->
gic_starting_cpu()->
its_cpu_init()->
its_cpu_init_collections()->
its_cpu_init_collection()->
its_send_mapc()发送MAPC命令初始化collection table
Collection table的表项CTE

ITS Command Queue
- 三个与command queue相关的寄存器
- GITS_CBASER:指定command queue的大小和基地,基地址必须64KB对齐,大小必须4KB整数倍
- GITS_CREADR:ITS下一条要处理的command
- GITS_CWRITER:下一条要写入的command
- 常用的Command
- MAPD:映射device ID到ITT table中
- MAPD \
, \ , \
- MAPD \
- MAPI:映射eventid和deviceid以及硬件中断号到ITT中
- MAPI \
, \ , \
- MAPI \
- MAPTI:映射eventid,deviceid以及硬件中断号到ITT中
- MAPTI \
, \ , \ , \
- MAPTI \
- MAPC:映射collection id到目标redistributor
- MAPC \
, \
- MAPC \
- MAPD:映射device ID到ITT table中

例子
假设某个设备的Device ID为5,想把Event ID=0映射到物理中断号8192中,ITT表的基地址为0x850000。对应的Collection ID为3,对应的目标redistributor的物理地址为0x78400000

linux内核中喜欢用vector表示eventid
ITS在Linux中的实现

irq domain中断控制域
- 系统存在多级中断控制器的可能性
- 传统中断控制器-GIC
- 可以抽象成中断控制器:GIC ITS,GPIO等
- 虚拟中断控制器,platform irqdomain
- IRQ Domain看作是IRQ Controller的软件抽象

ITS驱动框架



创建MSI中断的API


Platform device中使用MSI中断的例子-SMMUv3

platform请求分配MSI中断流程图

PCI设备分配MSI中断流程图


IO设备如何触发中断?
对于GICv3中断控制来说,IO设备需要往GITS_TRANSLATER寄存器写入event ID,即可触发MSI中断

Linux内核封装了一个struct msi_msg数据结构,包括了这个寄存器的地址,以及将要写入的数据
1 | struct msi_msg{ |
在irq_chip的ops有一个its_irq_compose_msi_msg回调函数,用来填充这个msi_msg,就是把GITS_TRANSLATER寄存器的物理地址写入到msi_msg->address字段里
设备驱动程序在使用platform_msi_domain_alloc_irqs()注册MSI的时候,就会从msi_msg数据结构中获取到GITS_TRANSLATER寄存器的物理地址和eventID,然后写入到IO设备自己的寄存器(例如SMMU中的SMMU_EVENTQ_IRQ_CFGO和CFG1)里。
设备要触发MSI,就会自动往自己寄存器里写入eventid,来触发中断。
以SMMU为例:

ITS Debug Tips
- 最新的qemu支持ITS。可以通过QEMU+linux kernel来单步调试ITS和MSI
- 在kernel command line中添加”irq_gic_v3_its.dyndbg=+pflmt irqdomain.dyndbg=+pflmt”来打开相关的动态打印:

- 可以在its_domain_ops回调中添加”dump_stack()”来打印调用函数关系calltrace




