3、中断控制器基础:中断控制器(Interrupt Controller)的概念、GIC(Generic Interrupt Controller)架构简介

3.1 中断控制器:CPU 的中断管家

在嵌入式系统中,外设(如网卡、定时器、GPIO)数量远多于 CPU 的中断引脚。中断控制器(Interrupt Controller)作为 CPU 与众多外设之间的桥梁,负责收集、仲裁、路由和分发中断信号,是系统响应实时事件的核心硬件单元。

核心职责:
  • 中断收集:接收来自多个外设的中断请求信号(IRQ)。
  • 优先级仲裁:当多个中断同时到达时,按预设优先级决定处理顺序。
  • 中断路由:将中断信号定向到指定的 CPU 核心(多核场景)。
  • 中断屏蔽:允许软件动态启用/禁用特定中断源。
  • 中断状态管理:提供中断挂起、激活、完成等状态寄存器。
中断控制器在系统中的位置 中断控制器系统位置图 CPU 核心 (Cortex-A) GIC 中断控制器 外设 1 (UART) 外设 2 (Timer) 外设 3 (GPIO) 中断信号由外设流向 GIC GIC 仲裁后转发给 CPU

3.2 GIC 架构演进与版本对比

GIC(Generic Interrupt Controller)是 ARM 架构的标准中断控制器规范,从 GICv1 发展到 GICv4,每一代都在虚拟化、多核亲和性、消息中断等方面持续增强。

特性 GICv1 GICv2 GICv3 GICv4
CPU 接口 内存映射寄存器 内存映射寄存器 系统寄存器 (ICC_*_ELn) 系统寄存器
最大中断数 1020 1020 无限制 (16K SPI) 无限制
多核支持 有限 支持 (GICD + GICC) 原生多核 (RD + RDist) 增强多核
虚拟化 无 基础虚拟化 (GICH/GICV) 硬件虚拟化 (LPI) 直接注入虚拟中断
消息中断 (MSI) 不支持 不支持 支持 (ITS) 增强 ITS
典型应用 ARM11, Cortex-A8 Cortex-A9, A15 Cortex-A53, A72 Cortex-A76, Neoverse
实践要点: 当前主流嵌入式 Linux 平台(如 i.MX8M、RK3588)多采用 GICv3。在设备树中,compatible = "arm,gic-v3" 是识别 GICv3 的关键属性。GICv2 与 GICv3 在设备树中断描述方式上有显著差异,驱动开发时需注意匹配。

3.3 GICv3 内部架构详解

GICv3 采用分布式架构,主要包含以下核心组件:

  • Distributor (GICD):全局中断分发器,负责中断优先级、使能、触发方式(边沿/电平)的全局配置。
  • Redistributor (GICR):每个 CPU 核心对应一个 Redistributor,管理 PPI(私有外设中断)和 SGI(软件生成中断),以及 LPI(本地外设中断)的配置。
  • CPU Interface (ICC_*_ELn):通过系统寄存器访问,每个 CPU 核心独立的中断接口,处理中断确认、完成、优先级掩码等。
  • ITS (Interrupt Translation Service):可选组件,负责将 MSI(消息信号中断)转换为 LPI,支持中断路由到指定 vCPU。
GICv3 内部架构 GICv3 内部架构图 CPU Core 0 CPU Core 1 CPU Core 2 CPU Core 3 Redistributor 0 Redistributor 1 Redistributor 2 Redistributor 3 Distributor (GICD) ITS (可选) 外设中断 (SPI/PPI/LPI) 每个 CPU 对应一个 Redistributor Distributor 管理全局中断配置

3.4 中断类型与触发方式

GIC 将中断分为三种类型,每种类型在设备树中的描述方式和触发行为不同:

中断类型 ID 范围 目标 CPU 典型用途 设备树标志
SGI (Software Generated Interrupt) 0-15 指定 CPU 或广播 核间通信 (IPI) 不直接描述
PPI (Private Peripheral Interrupt) 16-31 单个 CPU 本地定时器、PMU interrupts = <GIC_PPI ...>
SPI (Shared Peripheral Interrupt) 32-1019 任意 CPU 或指定亲和性 UART、I2C、DMA interrupts = <GIC_SPI ...>

中断触发方式在设备树中使用 #interrupt-cells 和 interrupts 属性描述,GICv3 通常使用 3 个 cell:

// GICv3 中断描述格式:<中断类型 中断号 触发方式>
// 中断类型: 0 = SPI, 1 = PPI
// 触发方式: 1 = 边沿触发, 4 = 电平触发

// SPI 中断示例 (UART, 中断号 37, 电平触发)
interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH>;

// PPI 中断示例 (定时器, 中断号 13, 边沿触发)
interrupts = <GIC_PPI 13 IRQ_TYPE_EDGE_RISING>;
常见陷阱: 在设备树中描述中断时,SPI 的中断号是硬件中断号减去 32。例如硬件 SPI ID 为 69,设备树中应填写 37(69-32)。PPI 的中断号则是硬件 ID 减去 16。GICv2 与 GICv3 的 cell 数量不同(GICv2 为 3 cell,GICv3 也为 3 cell 但含义有差异),移植时需要仔细核对。

3.5 中断生命周期与状态机

一个中断从产生到处理完成,经历以下关键状态:

  1. Inactive:无中断请求。
  2. Pending:外设发出中断请求,等待 CPU 响应。
  3. Active:CPU 已确认中断,正在执行 ISR(中断服务例程)。
  4. Active and Pending:CPU 正在处理当前中断,同一中断源又有新中断到达。
  5. Completed:CPU 写入 EOIR(End of Interrupt Register),中断处理结束。
中断状态机 中断状态机 Inactive Pending Active Active & Pending 中断到达 CPU 确认 新中断到达 EOI 写入 EOI 写入

3.6 设备树中 GIC 节点的标准写法

在设备树中,GIC 控制器本身作为一个节点存在,其他外设通过 interrupt-parent 属性引用该控制器。以下是 GICv3 的标准节点示例:

// GICv3 控制器节点
gic: interrupt-controller@38800000 {
    compatible = "arm,gic-v3";
    reg = <0x0 0x38800000 0x0 0x10000>,  // GICD 寄存器基址
          <0x0 0x38880000 0x0 0x200000>; // GICR 寄存器基址
    interrupt-controller;
    #interrupt-cells = <3>;
    #address-cells = <2>;
    #size-cells = <2>;
    interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>; // GIC 维护中断
};

// 外设节点引用 GIC
uart0: serial@38a00000 {
    compatible = "arm,pl011", "arm,primecell";
    reg = <0x0 0x38a00000 0x0 0x1000>;
    interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH>;
    interrupt-parent = <&gic>;  // 显式指定中断控制器
    clock-names = "uartclk", "apb_pclk";
};
关键属性说明:
  • interrupt-controller;:空属性,声明该节点是一个中断控制器。
  • #interrupt-cells = <3>:指定子节点描述中断时需要 3 个 cell(类型、中断号、触发方式)。
  • reg:GICv3 至少需要 2 个地址范围——Distributor 和 Redistributor 的寄存器基址。
  • interrupt-parent:外设节点通过此属性指向 GIC 节点,建立中断依赖关系。

3.7 中断亲和性与中断负载均衡

在多核系统中,GICv3 支持将 SPI 中断路由到指定的 CPU 核心,实现中断负载均衡。设备树中通过 interrupt-affinity 属性或 GIC 的 interrupt-routing 配置实现:

// 将 UART 中断路由到 CPU0 和 CPU1
uart0: serial@38a00000 {
    interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH>;
    interrupt-affinity = <&cpu0 &cpu1>;  // 中断亲和性
};

// 在 GIC 节点中配置中断路由表
gic: interrupt-controller@38800000 {
    interrupt-routing = <
        GIC_SPI 37 0x1  // SPI 37 路由到 CPU0 (bitmask 0x1)
        GIC_SPI 38 0x2  // SPI 38 路由到 CPU1 (bitmask 0x2)
    >;
};
调试技巧: 在 Linux 内核中,可以通过 /proc/interrupts 查看每个中断在各个 CPU 上的分布情况。如果发现某个中断全部集中在 CPU0,而其他 CPU 空闲,说明中断负载不均衡,可以通过修改设备树或使用 irqbalance 服务进行优化。

3.8 本章小结