2. 中断控制器(GIC)架构:GICv2/v3/v4演进、Distributor与Redistributor、CPU Interface
通用中断控制器(Generic Interrupt Controller, GIC)是ARM架构中管理中断的核心硬件单元。它负责接收来自外设、软件和处理器内部的中断请求,进行优先级仲裁、分发,并最终路由到指定的CPU核心进行处理。理解GIC的架构演进和内部组件,是掌握中断号映射与触发方式配置的基础。
2.1 GIC架构演进:v2 → v3 → v4
GIC规范随着ARM架构的发展不断演进,每一代都在中断数量、路由灵活性和虚拟化支持方面有显著提升。下表总结了三个主要版本的核心差异:
| 特性 | GICv2 | GICv3 | GICv4 |
|---|---|---|---|
| 引入架构 | Cortex-A9/A15 | Cortex-A53/A72 (ARMv8) | ARMv8.1+ 虚拟化增强 |
| 最大中断数 | 最多 1020 (SPI + PPI) | 最多 10240 (SPI + PPI + LPI) | 同GICv3,优化LPI处理 |
| 路由模型 | 基于CPU接口的物理路由 | 基于Redistributor的物理路由 | 支持直接注入虚拟中断 (vLPI) |
| CPU接口 | 内存映射寄存器 (MMIO) | 系统寄存器 (SysReg) + MMIO | 同GICv3 |
| 虚拟化支持 | 基础虚拟中断 (vIRQ/vFIQ) | 虚拟CPU接口 (GICv3虚拟化扩展) | 直接注入,减少VM Exit |
| 典型应用 | 嵌入式系统、旧款手机 | 服务器、现代移动SoC | 虚拟化场景、数据中心 |
2.2 GICv3/v4 核心组件:Distributor、Redistributor、CPU Interface
GICv3/v4架构由三个主要逻辑组件构成,它们协同工作完成中断的接收、分发和交付。下图展示了这些组件之间的连接关系:
2.2.1 Distributor(分发器)
Distributor是GIC中的全局控制单元,负责管理所有共享外设中断(SPI)。它不直接与CPU核心交互,而是将配置和中断状态分发给各个Redistributor。
- 主要功能:
- 全局中断使能/禁止控制。
- 配置每个SPI的触发方式(电平触发或边沿触发)。
- 设置每个SPI的优先级。
- 将SPI路由到指定的Redistributor(进而路由到CPU核心)。
- 寄存器访问: 通过MMIO(内存映射I/O)访问,基地址由系统设计决定。
2.2.2 Redistributor(再分发器)
每个CPU核心都拥有一个专用的Redistributor。它是GICv3/v4架构中实现中断本地化的关键组件。
- 主要功能:
- 管理私有外设中断(PPI)和软件生成中断(SGI)。
- 处理本地中断(LPI)的配置和挂起状态(GICv3/v4新增)。
- 从Distributor接收SPI路由信息,并转发给本地的CPU Interface。
- 实现中断的电源管理(如休眠唤醒)。
- 关键区别: 在GICv2中,PPI和SGI的管理分散在Distributor和CPU Interface中;GICv3/v4将它们统一归入Redistributor,简化了设计。
2.2.3 CPU Interface(CPU接口)
CPU Interface是GIC与CPU核心之间的最后一道关卡。它负责向CPU核心发送中断信号(IRQ/FIQ),并处理CPU核心的响应。
- 主要功能:
- 中断屏蔽:根据CPU核心的当前运行状态(如中断优先级),决定是否将中断信号传递给CPU。
- 优先级过滤:只向CPU核心传递优先级高于当前运行任务优先级的中断。
- 中断确认(ACK):CPU核心读取
ICC_IAR0_EL1(系统寄存器)来获取中断ID。 - 中断结束(EOI):CPU核心写入
ICC_EOIR0_EL1来通知GIC中断处理完成。
- 访问方式: 在GICv3/v4中,CPU Interface主要通过系统寄存器(SysReg)访问,这比GICv2的MMIO方式更快,且避免了多核竞争。
2.3 中断类型与路由机制
GICv3/v4定义了四种中断类型,每种类型在路由和管理方式上有所不同:
| 中断类型 | ID范围 | 目标 | 管理组件 | 典型用途 |
|---|---|---|---|---|
| SGI (Software Generated Interrupt) | 0 - 15 | 特定CPU核心或所有核心 | Redistributor | 核间通信(IPI),如调度、TLB刷新 |
| PPI (Private Peripheral Interrupt) | 16 - 31 | 单个CPU核心(私有) | Redistributor | 本地定时器、性能监视单元(PMU) |
| SPI (Shared Peripheral Interrupt) | 32 - 1019 | 任意CPU核心(可配置) | Distributor + Redistributor | 外部设备中断(UART、I2C、网络) |
| LPI (Locality-specific Peripheral Interrupt) | 8192 - 10239 | 特定CPU核心(基于配置表) | Redistributor (ITS) | PCIe MSI/MSI-X、高性能设备 |
2.4 中断号映射:硬件中断ID到Linux IRQ号
在Linux内核中,GIC驱动负责将硬件中断ID(HW IRQ ID)映射到内核使用的虚拟IRQ号(Linux IRQ Number)。这个映射过程是中断子系统初始化的核心步骤。
// 以GICv3驱动为例 (drivers/irqchip/irq-gic-v3.c)
// 中断域 (irq_domain) 是映射的核心数据结构
static int gic_irq_domain_alloc(struct irq_domain *d, unsigned int virq,
unsigned int nr_irqs, void *arg)
{
struct irq_fwspec *fwspec = arg;
unsigned int hwirq = fwspec->param[1]; // 获取硬件中断ID
unsigned int type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; // 触发方式
// 1. 分配Linux IRQ号 (virq 由上层传入或自动分配)
// 2. 将 virq 与 hwirq 建立映射关系
irq_domain_set_hwirq_and_chip(d, virq, hwirq, &gic_chip, NULL);
// 3. 配置触发方式 (通过Distributor或Redistributor寄存器)
gic_configure_irq(virq, type, hwirq);
return 0;
}
// 中断芯片 (irq_chip) 提供底层硬件操作
static struct irq_chip gic_chip = {
.name = "GICv3",
.irq_mask = gic_mask_irq, // 屏蔽中断
.irq_unmask = gic_unmask_irq, // 取消屏蔽
.irq_eoi = gic_eoi_irq, // 中断结束 (写ICC_EOIR0_EL1)
.irq_set_type = gic_set_type, // 设置触发方式 (电平/边沿)
.irq_set_affinity = gic_set_affinity, // 设置CPU亲和性 (路由到哪个核心)
};
映射流程通常分为以下步骤:
- 设备树解析: 内核解析设备树(Device Tree)或ACPI表,获取外设的中断信息(中断控制器、硬件中断ID、触发方式)。
- 中断域查找: 根据中断控制器节点,找到对应的
irq_domain。 - 映射创建: 调用
irq_domain_alloc_irqs()或irq_create_of_mapping(),分配一个Linux IRQ号,并调用GIC驱动的alloc回调函数建立映射。 - 触发方式配置: 在映射过程中,驱动会根据设备树指定的触发方式(如
IRQ_TYPE_EDGE_RISING),通过irq_set_type回调配置GIC的相应寄存器。
cat /proc/interrupts 查看当前系统中所有中断的Linux IRQ号、CPU亲和性和触发次数。使用 cat /proc/irq/<IRQ号>/hwirq 可以查看对应的硬件中断ID。
2.5 触发方式配置:电平触发 vs 边沿触发
触发方式决定了GIC如何检测中断信号。配置错误会导致中断丢失或重复触发。
| 触发方式 | 检测机制 | 优点 | 缺点 | 典型设备 |
|---|---|---|---|---|
| 电平触发 (Level-triggered) | 检测信号线的电平(高或低) | 信号稳定,不易丢失 | 需要硬件保持电平直到CPU响应;共享中断时难以区分 | UART、I2C、GPIO |
| 边沿触发 (Edge-triggered) | 检测信号线的上升沿或下降沿 | 适合脉冲信号;共享中断时容易区分 | 容易丢失短脉冲;需要锁存器 | 网络控制器、定时器、PCIe MSI |
在GICv3中,配置触发方式通过操作Distributor的GICD_ICFGRn寄存器(用于SPI)或Redistributor的GICR_ICFGRn寄存器(用于PPI)实现。每个中断使用2位进行配置:
- 00: 保留
- 01: 上升沿触发
- 10: 保留
- 11: 高电平触发
// 内核中设置触发方式的示例 (gic_set_type 回调)
static int gic_set_type(struct irq_data *d, unsigned int type)
{
unsigned int hwirq = d->hwirq;
void __iomem *base;
u32 offset, val;
// 根据中断类型选择基地址 (Distributor 或 Redistributor)
if (hwirq < 32) { // PPI
base = gic_redist_base(d); // 获取当前CPU的Redistributor基地址
offset = GICR_ICFGR0 + (hwirq / 16) * 4;
} else { // SPI
base = gic_dist_base(d); // 获取Distributor基地址
offset = GICD_ICFGR + (hwirq / 16) * 4;
}
val = readl_relaxed(base + offset);
// 清除该中断的2位配置
val &= ~(0x3 << ((hwirq % 16) * 2));
if (type == IRQ_TYPE_EDGE_RISING)
val |= (0x1 << ((hwirq % 16) * 2)); // 01: 上升沿
else if (type == IRQ_TYPE_LEVEL_HIGH)
val |= (0x3 << ((hwirq % 16) * 2)); // 11: 高电平
writel_relaxed(val, base + offset);
return 0;
}