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参数已经是软件中断号。映射过程发生在更早的阶段——中断控制器驱动初始化时。

典型流程如下:

  1. 注册irq_domain:中断控制器驱动调用irq_domain_add_linear()等函数创建domain。
  2. 解析设备树:驱动从设备树节点读取interrupts属性,获取硬件中断号。
  3. 调用map回调:通过irq_create_mapping()或irq_of_parse_and_map()触发映射。
  4. 分配软件中断号:domain的ops->map()回调被调用,分配一个空闲的软件中断号。
  5. 建立映射条目:将硬件中断号与软件中断号的对应关系存入domain的映射表(linear_revmap或radix tree)。
  6. 返回软件中断号:驱动获得软件中断号,后续所有操作(如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 映射关系示意图

下图展示了从硬件中断线到软件中断描述符的完整映射路径:

硬件中断号 → 软件中断号映射流程 硬件中断线 HW IRQ #37 (GIC SPI) irq_domain 映射层 线性映射表 linear_revmap[] map() / xlate() 回调 分配软件中断号 (virq) 软件中断描述符 irq_desc[42] (virq=42) hwirq=37 virq=42 映射关系示例: • 硬件中断线 GIC SPI #37 通过 irq_domain 映射为软件中断号 42 • 驱动调用 request_irq(42, handler, ...) 注册中断处理函数 • 中断发生时,GIC 上报 hwirq=37,内核通过 domain 反向查找得到 virq=42 • 最终执行 irq_desc[42]->action->handler()

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存在