4. 中断号映射原理:硬件中断号与软件中断号的映射关系
中断号映射是中断子系统中最核心的抽象层之一。它解决了硬件平台相关的中断标识与Linux内核通用中断处理框架之间的解耦问题。理解这一映射关系,是掌握中断控制器驱动编写和中断调试的基础。
4.1 为什么需要映射?
硬件中断号(IRQ number)由具体的中断控制器(如GIC、IOAPIC、NVIC)定义,通常是一个物理管脚或中断线的编号。而Linux内核使用一个全局的、线性的软件中断描述符表(irq_desc[])来管理所有中断。两者之间存在以下差异:
- 硬件中断号不连续:硬件设计可能跳过某些编号,或存在多个中断控制器地址空间重叠。
- 硬件中断号不唯一:不同中断控制器可能使用相同的硬件中断号,但属于不同的中断源。
- 软件中断号需要连续:内核的irq_desc数组使用连续的索引(0 ~ NR_IRQS-1)进行快速查找。
因此,需要一个映射层将硬件中断号转换为内核使用的软件中断号(即irq_desc数组的索引)。
4.2 映射数据结构:irq_domain
Linux内核使用struct irq_domain来描述和管理中断号映射关系。每个中断控制器实例对应一个irq_domain对象。
| 字段 | 类型 | 说明 |
|---|---|---|
ops |
struct irq_domain_ops * |
映射操作函数集(map、unmap、xlate等) |
host_data |
void * |
指向中断控制器私有数据的指针 |
linear_revmap[] |
unsigned int [] |
线性映射表,用于快速查找 |
mapcount |
unsigned int |
已映射的中断数量 |
name |
const char * |
域名,用于调试和识别 |
4.3 三种映射方式
irq_domain支持三种映射策略,以适应不同的硬件拓扑:
| 映射方式 | 适用场景 | 映射函数 | 查找速度 |
|---|---|---|---|
| 线性映射 | 硬件中断号连续且范围较小 | irq_domain_add_linear() |
O(1) 直接索引 |
| 树形映射 | 硬件中断号稀疏或范围很大 | irq_domain_add_tree() |
O(log n) 基数树查找 |
| 不映射 | 硬件中断号与软件中断号一一对应 | irq_domain_add_nomap() |
O(1) 直接使用硬件号 |
实践建议: 对于大多数嵌入式SoC(如ARM GIC),硬件中断号通常连续且范围在32~1024之间,推荐使用线性映射,兼顾性能和内存占用。对于PCIe MSI中断(中断号可能高达数百万),必须使用树形映射。
4.4 映射流程:从硬件中断号到软件中断号
当设备驱动调用request_irq(irq, handler, flags, name, dev)时,传入的irq参数已经是软件中断号。映射过程发生在更早的阶段——中断控制器驱动初始化时。
典型流程如下:
- 注册irq_domain:中断控制器驱动调用
irq_domain_add_linear()等函数创建domain。 - 解析设备树:驱动从设备树节点读取
interrupts属性,获取硬件中断号。 - 调用map回调:通过
irq_create_mapping()或irq_of_parse_and_map()触发映射。 - 分配软件中断号:domain的
ops->map()回调被调用,分配一个空闲的软件中断号。 - 建立映射条目:将硬件中断号与软件中断号的对应关系存入domain的映射表(linear_revmap或radix tree)。
- 返回软件中断号:驱动获得软件中断号,后续所有操作(如request_irq、enable_irq)都使用此编号。
关键API:
// 从硬件中断号创建映射,返回软件中断号
unsigned int irq_create_mapping(struct irq_domain *domain,
irq_hw_number_t hwirq);
// 从设备树节点解析并创建映射(常用)
unsigned int irq_of_parse_and_map(struct device_node *dev,
int index);
// 反向查找:从软件中断号获取硬件中断号
irq_hw_number_t irq_find_mapping(struct irq_domain *domain,
unsigned int virq);
4.5 映射关系示意图
下图展示了从硬件中断线到软件中断描述符的完整映射路径:
4.6 反向查找:中断响应时的映射使用
当中断实际发生时,硬件中断控制器上报的是硬件中断号。内核需要快速找到对应的软件中断描述符。这个过程称为反向查找:
// 中断控制器驱动中的典型处理路径
irqreturn_t gic_handle_irq(struct pt_regs *regs) {
u32 hwirq = readl_relaxed(gic_base + GIC_INT_ACK); // 读取硬件中断号
struct irq_domain *domain = gic_data.domain;
// 反向查找:hwirq → virq
unsigned int virq = irq_find_mapping(domain, hwirq);
// 调用通用中断处理
generic_handle_irq(virq);
}
性能关键点: 中断响应路径上的反向查找必须是O(1)或接近O(1)的。线性映射通过
linear_revmap[hwirq]直接索引,而树形映射需要遍历基数树。因此,对于高频中断,线性映射具有明显的性能优势。
4.7 设备树中的中断号映射
在设备树中,中断号的映射通过interrupts属性和interrupt-parent属性共同描述:
// 设备树示例:UART设备连接到GIC中断控制器
uart0: serial@10000000 {
compatible = "arm,pl011";
reg = <0x10000000 0x1000>;
interrupt-parent = <&gic>; // 指定中断控制器
interrupts = <0 37 4>; // GIC SPI #37, 高电平触发
};
gic: interrupt-controller@2c001000 {
compatible = "arm,cortex-a15-gic";
reg = <0x2c001000 0x1000>,
<0x2c002000 0x2000>;
interrupt-controller;
#interrupt-cells = <3>; // 每个中断用3个cell描述
};
设备树中的interrupts = <0 37 4>经过irq_of_parse_and_map()解析后,最终映射为软件中断号。其中:
- 第一个cell (0):中断类型,0=SPI(共享外设中断),1=PPI(私有外设中断)
- 第二个cell (37):硬件中断号(相对于中断控制器)
- 第三个cell (4):触发方式(4=高电平触发)
4.8 映射关系总结
| 概念 | 标识符 | 作用范围 | 生命周期 |
|---|---|---|---|
| 硬件中断号 | hwirq |
中断控制器内部 | 硬件设计时确定 |
| 软件中断号 | virq |
Linux内核全局 | 驱动加载时分配 |
| irq_domain | struct irq_domain |
中断控制器实例 | 驱动初始化时创建 |
| 映射表 | linear_revmap[] / radix tree |
domain内部 | 随domain存在 |