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 虚拟化场景、数据中心
演进核心: GICv3相比v2最大的变化是将中断路由逻辑从Distributor分散到每个CPU核心的Redistributor中,解决了多核扩展性问题。GICv4则在v3基础上增加了对虚拟中断直接注入的支持,显著提升了虚拟化场景下的中断性能。

2.2 GICv3/v4 核心组件:Distributor、Redistributor、CPU Interface

GICv3/v4架构由三个主要逻辑组件构成,它们协同工作完成中断的接收、分发和交付。下图展示了这些组件之间的连接关系:

GICv3/v4 核心组件架构图 GICv3/v4 核心组件架构 Distributor 中断配置、优先级、使能 SPI路由 Redistributor 集合 (每个CPU核心一个) Redistributor 0 PPI, SGI, LPI管理 Redistributor 1 PPI, SGI, LPI管理 Redistributor N PPI, SGI, LPI管理 CPU Interface 集合 (每个CPU核心一个) CPU Interface 0 中断屏蔽、优先级过滤 CPU Interface 1 中断屏蔽、优先级过滤 CPU Interface N 中断屏蔽、优先级过滤 CPU Core 0 CPU Core 1 CPU Core N

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方式更快,且避免了多核竞争。
数据流总结: 外设中断 → Distributor (SPI配置) → Redistributor (本地化处理) → CPU Interface (优先级过滤) → CPU Core (执行处理程序)。

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、高性能设备
注意: LPI是GICv3/v4引入的新类型,它使用中断翻译服务(ITS)和内存中的配置表进行管理,不通过传统的Distributor寄存器。这使得LPI可以支持海量中断(最多10240个),非常适合PCIe设备。

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亲和性 (路由到哪个核心)
};

映射流程通常分为以下步骤:

  1. 设备树解析: 内核解析设备树(Device Tree)或ACPI表,获取外设的中断信息(中断控制器、硬件中断ID、触发方式)。
  2. 中断域查找: 根据中断控制器节点,找到对应的irq_domain。
  3. 映射创建: 调用irq_domain_alloc_irqs()或irq_create_of_mapping(),分配一个Linux IRQ号,并调用GIC驱动的alloc回调函数建立映射。
  4. 触发方式配置: 在映射过程中,驱动会根据设备树指定的触发方式(如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;
}
配置陷阱: 在设备树中错误地指定触发方式(例如将电平触发的设备配置为边沿触发),可能导致中断丢失(电平信号没有边沿变化)或中断风暴(边沿触发设备在电平信号上反复触发)。务必参考设备数据手册确认正确的触发方式。