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 确保每个设备都能正确注册并响应中断。
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 映射流程
- 设备树解析:内核解析
interrupts属性,获取中断控制器 phandle 和 hwirq。 - irq_domain 查找:通过中断控制器节点找到对应的
irq_domain。 - 映射创建:调用
irq_domain_alloc_irqs()或irq_create_mapping(),分配 Linux irq 并建立映射。 - irq_desc 初始化:新分配的 irq 对应的
irq_desc被初始化,设置irq_data中的hwirq和chip。 - 驱动注册:驱动通过
platform_get_irq()或irq_of_parse_and_map()获取 Linux irq,然后调用request_irq()。
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 共享同一中断线
};
内核解析流程:
- 解析
device-a的interrupts,得到 hwirq=42,触发类型=4(高电平有效)。 - 通过
interrupt-parent找到gic的irq_domain。 - 调用
irq_domain_alloc_irqs()分配 Linux irq(假设为 168),建立 hwirq 42 → irq 168 的映射。 - 解析
device-b时,发现 hwirq 42 已映射,直接返回 irq 168。 - 两个驱动分别调用
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,驱动无需关心底层硬件细节。