3、中断控制器基础:中断控制器(Interrupt Controller)的概念、GIC(Generic Interrupt Controller)架构简介
3.1 中断控制器:CPU 的中断管家
在嵌入式系统中,外设(如网卡、定时器、GPIO)数量远多于 CPU 的中断引脚。中断控制器(Interrupt Controller)作为 CPU 与众多外设之间的桥梁,负责收集、仲裁、路由和分发中断信号,是系统响应实时事件的核心硬件单元。
核心职责:
- 中断收集:接收来自多个外设的中断请求信号(IRQ)。
- 优先级仲裁:当多个中断同时到达时,按预设优先级决定处理顺序。
- 中断路由:将中断信号定向到指定的 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。
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 中断生命周期与状态机
一个中断从产生到处理完成,经历以下关键状态:
- Inactive:无中断请求。
- Pending:外设发出中断请求,等待 CPU 响应。
- Active:CPU 已确认中断,正在执行 ISR(中断服务例程)。
- Active and Pending:CPU 正在处理当前中断,同一中断源又有新中断到达。
- Completed:CPU 写入 EOIR(End of Interrupt Register),中断处理结束。
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 服务进行优化。