2. Linux 中断子系统架构:中断控制器(GIC)、中断描述符(irq_desc)、中断域(irq_domain)与中断号映射

在 Android 多设备驱动共享中断的场景下,理解 Linux 中断子系统的核心架构是高效调试与设计的基础。本章深入剖析中断控制器(GIC)、中断描述符(irq_desc)、中断域(irq_domain)以及中断号映射机制,揭示从硬件中断信号到驱动处理函数的完整链路。

2.1 中断子系统总体架构

Linux 中断子系统采用分层设计,将硬件相关逻辑与通用中断管理解耦。其核心组件包括:

  • 中断控制器(Interrupt Controller):硬件单元,负责收集、优先级排序、屏蔽和分发中断信号。在 ARM/ARM64 平台上,通用中断控制器(GIC)是事实标准。
  • 中断描述符(irq_desc):内核为每个中断源分配的数据结构,记录中断状态、处理函数链、触发类型、亲和性等元数据。
  • 中断域(irq_domain):软件抽象层,实现硬件中断号(hwirq)到 Linux 中断号(irq)的映射,支持设备树(DT)和 ACPI 等描述方式。
关键概念: 中断号映射是中断共享的基础。多个设备共享同一硬件中断线时,irq_domain 确保每个设备都能正确注册并响应中断。
Linux 中断子系统分层架构 Linux 中断子系统分层架构 硬件层:中断控制器 (GIC) SPI / PPI / LPI 中断线 · 优先级 · 亲和性 · 屏蔽 核心层:中断描述符 (irq_desc) irq_data · action 链表 · 触发类型 · 状态标志 · 线程化标志 映射层:中断域 (irq_domain) hwirq → irq 映射 · 线性映射 / 基数树 / 不映射 接口层:驱动中断处理函数 (request_irq / devm_request_threaded_irq)

2.2 中断控制器:GIC 详解

通用中断控制器(Generic Interrupt Controller, GIC)是 ARM 架构的标准中断控制器,从 GICv2 到 GICv4 不断演进。在 Android 设备中,GICv2 和 GICv3 最为常见。

GIC 版本 中断类型 最大中断数 关键特性
GICv2 PPI, SPI, SGI 1020 经典架构,支持 CPU 接口,最多 8 个 PE
GICv3 PPI, SPI, SGI, LPI 无上限(LPI 可达 16M) 支持 >8 个 PE,独立 Redistributor,MSI 支持增强
GICv4 PPI, SPI, SGI, LPI, vLPI 无上限 虚拟化中断直通,Direct Injection,减少 VM exit

中断类型说明:

  • SGI (Software Generated Interrupt):软件触发,用于核间通信(IPI),中断号 0-15。
  • PPI (Private Peripheral Interrupt):私有外设中断,每个 CPU 核独享,中断号 16-31。
  • SPI (Shared Peripheral Interrupt):共享外设中断,可路由到任意 CPU 核,中断号 32-1019。
  • LPI (Locality-specific Peripheral Interrupt):基于消息的中断,通过 ITS(Interrupt Translation Service)路由,支持大量中断源。
调试提示: 在 /proc/interrupts 中查看中断分布时,SPI 显示为中断号减去 32 后的值。例如,中断号 42 对应 SPI 10。

2.3 中断描述符:irq_desc 结构

每个 Linux 中断号对应一个 irq_desc 结构体,定义在 kernel/irq/internals.h 中。该结构体是中断子系统的核心数据载体。

struct irq_desc {
    struct irq_common_data  irq_common_data;
    struct irq_data         irq_data;
    unsigned int __percpu   *kstat_irqs;
    irq_flow_handler_t      handle_irq;
    struct irqaction        *action;    /* 中断处理函数链表 */
    unsigned int            status_use_accessors;
    unsigned int            core_internal_state__do_not_mess_with_it;
    unsigned int            depth;      /* 嵌套深度 */
    unsigned int            wake_depth; /* 唤醒深度 */
    unsigned int            tot_count;
    unsigned int            irq_count;  /* 中断计数 */
    unsigned long           last_unhandled;
    unsigned int            irqs_unhandled;
    raw_spinlock_t          lock;
    struct cpumask          *percpu_enabled;
    const char              *name;
} ____cacheline_internodealigned_in_smp;

关键字段解析:

字段 作用 中断共享相关
action 指向 irqaction 链表的头指针 共享中断时,链表包含多个设备的处理函数
handle_irq 高层中断流处理函数(如 handle_edge_irq) 决定中断是边缘触发还是电平触发
depth 中断屏蔽嵌套深度 共享中断中,一个设备屏蔽不影响其他设备
irq_count 中断触发次数统计 用于检测中断风暴(unhandled 计数)
注意: 共享中断时,action 链表中的每个 irqaction 必须设置 IRQF_SHARED 标志,否则 request_irq 会返回 -EBUSY。

2.4 中断域:irq_domain 与中断号映射

中断域(irq_domain)是连接硬件中断号(hwirq)与 Linux 中断号(irq)的桥梁。它解决了设备树或 ACPI 描述的中断资源如何转换为内核可识别的 IRQ 号的问题。

2.4.1 映射方式

映射类型 数据结构 适用场景 查找复杂度
线性映射 (Linear) 预分配数组 中断源数量固定且较少(如 GPIO 控制器) O(1)
基数树映射 (Radix Tree) 基数树 中断源数量大且稀疏(如 MSI 中断) O(log n)
不映射 (No Map) 直接使用 hwirq 作为 irq 硬件中断号与 Linux 中断号一致(罕见) O(1)

2.4.2 映射流程

  1. 设备树解析:内核解析 interrupts 属性,获取中断控制器 phandle 和 hwirq。
  2. irq_domain 查找:通过中断控制器节点找到对应的 irq_domain。
  3. 映射创建:调用 irq_domain_alloc_irqs() 或 irq_create_mapping(),分配 Linux irq 并建立映射。
  4. irq_desc 初始化:新分配的 irq 对应的 irq_desc 被初始化,设置 irq_data 中的 hwirq 和 chip。
  5. 驱动注册:驱动通过 platform_get_irq() 或 irq_of_parse_and_map() 获取 Linux irq,然后调用 request_irq()。
irq_domain 中断号映射流程 irq_domain 中断号映射流程 设备树 / ACPI irq_domain 查找 映射创建 irq_desc 初始化 驱动注册中断 映射关系存储于 irq_data.hwirq 与 irq_desc.irq_data 中 驱动通过 platform_get_irq() 获取 Linux irq,无需关心底层 hwirq

2.5 中断共享与 irq_domain 的交互

在中断共享场景中,多个设备可能连接到同一个 GIC SPI 中断线。此时,irq_domain 映射的同一个 Linux irq 会被多个驱动共享。关键交互点包括:

  • 共享中断注册:每个驱动调用 request_irq(irq, handler, IRQF_SHARED, name, dev),内核将 irqaction 添加到 irq_desc.action 链表。
  • 中断触发处理:GIC 触发中断后,handle_irq 遍历 action 链表,依次调用每个驱动的处理函数。
  • 映射一致性:所有共享设备必须使用相同的 irq 号,该号由 irq_domain 统一分配。
调试技巧: 使用 cat /sys/kernel/debug/irq/irqs/<irq_num> 查看指定中断号的 irq_desc 详细信息,包括 action 链表中的每个设备名称、触发次数、CPU 亲和性等。

2.6 中断号映射实战:设备树示例

以下是一个典型的中断共享设备树配置,两个设备共享同一个 GIC SPI 中断线(hwirq 42)。

// 中断控制器节点
gic: interrupt-controller@f0001000 {
    compatible = "arm,gic-400";
    reg = <0xf0001000 0x1000>,
          <0xf0002000 0x2000>;
    interrupt-controller;
    #interrupt-cells = <3>;
};

// 设备 A:共享中断
device_a: device-a@a000000 {
    compatible = "vendor,device-a";
    reg = <0xa000000 0x1000>;
    interrupt-parent = <&gic>;
    interrupts = <0 42 4>;  // GIC_SPI 42, 高电平触发
};

// 设备 B:共享中断
device_b: device-b@b000000 {
    compatible = "vendor,device-b";
    reg = <0xb000000 0x1000>;
    interrupt-parent = <&gic>;
    interrupts = <0 42 4>;  // 与设备 A 共享同一中断线
};

内核解析流程:

  1. 解析 device-a 的 interrupts,得到 hwirq=42,触发类型=4(高电平有效)。
  2. 通过 interrupt-parent 找到 gic 的 irq_domain。
  3. 调用 irq_domain_alloc_irqs() 分配 Linux irq(假设为 168),建立 hwirq 42 → irq 168 的映射。
  4. 解析 device-b 时,发现 hwirq 42 已映射,直接返回 irq 168。
  5. 两个驱动分别调用 platform_get_irq() 得到 irq 168,注册共享中断处理函数。
验证命令: 使用 ls -l /sys/bus/platform/devices/*/irq 查看设备的中断号,确认共享设备指向同一 irq。

2.7 本章小结

Linux 中断子系统通过 GIC、irq_desc、irq_domain 三层架构,实现了从硬件中断信号到驱动处理函数的清晰解耦。理解中断号映射机制是掌握中断共享的前提:

  • GIC 负责硬件中断的收集与分发,SPI 是共享中断的主要载体。
  • irq_desc 是每个中断号的核心数据结构,action 链表支持多设备共享。
  • irq_domain 提供 hwirq 到 irq 的灵活映射,支持线性、基数树等多种方式。
  • 设备树中的 interrupts 属性通过 irq_domain 转换为 Linux irq,驱动无需关心底层硬件细节。