4. 中断号获取:设备树中中断描述与 platform_get_irq() 函数使用

在 Subdev 驱动开发中,中断号的正确获取是中断处理流程的起点。设备树(Device Tree)提供了描述硬件中断连接的标准方式,而 platform_get_irq() 是驱动开发者最常用的中断号获取接口。本章将深入解析设备树中的中断描述规范,并详细讲解 platform_get_irq() 的使用方法、参数含义及常见陷阱。

核心要点:设备树描述中断拓扑 → 内核解析并建立映射 → 驱动通过标准 API 获取中断号。理解这一链路是正确配置中断的前提。

4.1 设备树中的中断描述规范

设备树通过 interrupts 属性和 interrupt-parent 属性来描述设备的中断连接关系。中断控制器(Interrupt Controller)是中断路由的核心节点,负责接收来自多个设备的中断信号并转发给 CPU。

4.1.1 中断描述的基本属性

属性名 类型 说明
interrupt-parent phandle 指向设备所连接的中断控制器节点
interrupts u32 数组 描述中断号、触发类型等信息的元组
interrupt-names string 数组 为每个中断指定名称,便于驱动按名获取
interrupts-extended phandle + u32 数组 当设备连接多个不同中断控制器时使用

4.1.2 中断元组格式

interrupts 属性的元组格式由中断控制器的 #interrupt-cells 属性决定。常见的中断控制器类型及其元组格式如下:

中断控制器类型 #interrupt-cells 元组格式 示例
GIC (Generic Interrupt Controller) 3 <中断类型 中断号 触发标志> <0 42 4>
简单中断控制器 1 <中断号> <31>
带触发类型的中断控制器 2 <中断号 触发标志> <31 2>
GIC 中断类型说明:0 表示 SPI(共享外设中断),1 表示 PPI(私有外设中断),2 表示 LPI(本地特定中断)。触发标志中,1 表示上升沿触发,2 表示下降沿触发,4 表示高电平触发,8 表示低电平触发。

4.1.3 设备树中断描述示例

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

// 使用 GIC 的子设备节点
uart0: serial@f0002000 {
    compatible = "vendor,uart";
    reg = <0x0 0xf0002000 0x0 0x1000>;
    interrupt-parent = <&gic>;
    interrupts = <0 42 4>;  // SPI 中断号 42,高电平触发
    interrupt-names = "rx_irq";
};

// 使用 interrupts-extended 连接多个中断控制器
dma: dma-controller@f0003000 {
    compatible = "vendor,dma";
    reg = <0x0 0xf0003000 0x0 0x1000>;
    interrupts-extended = <&gic 0 55 4>, <&intc 8>;
    interrupt-names = "dma_done", "dma_error";
};
设备树中断描述结构图 设备树根节点 gic: interrupt-controller #interrupt-cells = <3> uart0: serial interrupts = <0 42 4> dma: dma-controller interrupts-extended 中断描述关键点 1. interrupt-parent 指向中断控制器节点(phandle 引用) 2. interrupts 元组格式由 #interrupt-cells 决定 3. interrupt-names 为每个中断提供语义化名称 4. interrupts-extended 用于连接多个不同中断控制器 5. 中断号是硬件相关编号,需参考 SoC 数据手册

4.2 platform_get_irq() 函数详解

platform_get_irq() 是 Linux 内核为 platform 设备驱动提供的中断号获取标准 API。该函数封装了设备树解析、中断域映射等底层操作,驱动开发者只需传入设备指针和中断索引即可获得中断号。

4.2.1 函数原型与参数

#include <linux/platform_device.h>

/**
 * platform_get_irq - 获取 platform 设备的中断号
 * @pdev: platform 设备指针
 * @num: 中断资源索引(从 0 开始)
 *
 * 返回值:成功返回中断号(正数),失败返回负错误码
 */
int platform_get_irq(struct platform_device *pdev, unsigned int num);
参数 说明 注意事项
pdev 由 probe 函数传入的 platform_device 指针 必须非空,否则返回 -EINVAL
num 中断资源的索引号,从 0 开始 与设备树中 interrupts 属性的顺序对应
返回值 成功返回中断号(≥0),失败返回负值 常见错误:-EPROBE_DEFER, -EINVAL, -ENXIO

4.2.2 使用示例

static int my_subdev_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    int irq;
    int ret;

    // 获取第一个中断(索引 0)
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        dev_err(dev, "failed to get irq: %d\n", irq);
        return irq;
    }

    dev_info(dev, "got irq number: %d\n", irq);

    // 注册中断处理函数
    ret = request_irq(irq, my_interrupt_handler,
                      IRQF_TRIGGER_HIGH | IRQF_SHARED,
                      "my_subdev", dev);
    if (ret) {
        dev_err(dev, "failed to request irq %d: %d\n", irq, ret);
        return ret;
    }

    return 0;
}

static int my_subdev_remove(struct platform_device *pdev)
{
    int irq = platform_get_irq(pdev, 0);
    if (irq >= 0)
        free_irq(irq, &pdev->dev);
    return 0;
}

4.2.3 多中断场景处理

当设备具有多个中断时,可以使用 platform_get_irq_byname() 按名称获取,或通过循环遍历所有中断:

// 方法一:按名称获取(推荐)
int rx_irq = platform_get_irq_byname(pdev, "rx_irq");
int tx_irq = platform_get_irq_byname(pdev, "tx_irq");

// 方法二:按索引循环获取
int irq;
int i = 0;

while ((irq = platform_get_irq(pdev, i)) >= 0) {
    dev_info(dev, "irq[%d] = %d\n", i, irq);
    // 为每个中断注册处理函数
    request_irq(irq, handler_array[i], 0, "my_dev", &dev_data[i]);
    i++;
}
重要提醒:platform_get_irq() 返回的中断号是 Linux 内核虚拟中断号,并非硬件原始中断号。内核通过 irq_domain 机制将硬件中断号映射为虚拟中断号。驱动开发者应始终使用此虚拟中断号进行 request_irq()、free_irq() 等操作。

4.3 中断号获取的完整流程

从设备树描述到驱动获取中断号,中间经历了多个内核子系统的协同处理。理解这一流程有助于排查中断相关的问题。

中断号获取完整流程图 ① 设备树解析 (DTB → device_node) 内核解析 DTB,建立 device_node 树 ② 中断资源构建 (of_irq_to_resource) 解析 interrupts 属性,构建 struct resource ③ irq_domain 映射 (硬件→虚拟中断号) 通过 irq_domain 将硬件中断号转为虚拟中断号 ④ platform_get_irq() 调用 驱动调用 API,从 resource 中提取中断号 ⑤ request_irq() 注册中断处理函数 驱动使用虚拟中断号注册 handler 关键路径:设备树 → of_irq_to_resource → irq_create_of_mapping → platform_get_irq 任何一步出错都会导致中断号获取失败,返回负错误码

4.4 常见错误与调试方法

4.4.1 典型错误码分析

错误码 含义 常见原因 排查方向
-EPROBE_DEFER 延迟探测 中断控制器驱动尚未加载 检查中断控制器节点 compatible 匹配
-EINVAL 无效参数 pdev 为空或 num 超出范围 检查设备树中 interrupts 属性数量
-ENXIO 无此设备 设备树中未定义中断资源 检查设备节点是否包含 interrupts 属性
-ENOENT 无此条目 interrupt-names 名称不匹配 检查名称拼写与设备树定义一致

4.4.2 调试技巧

  1. 查看设备树内容:使用 dtc -I fs -O dts /sys/firmware/devicetree/base 导出当前设备树,检查中断属性是否正确。
  2. 检查中断映射:查看 /proc/interrupts 确认中断号是否已注册,以及中断触发次数。
  3. 启用内核调试:在 cmdline 中添加 irq_debug 或 dyndbg="file irqdomain.c +p" 获取详细的中断映射日志。
  4. 使用 dev_dbg:在 probe 函数中添加 dev_dbg(dev, "irq=%d\n", irq); 确认获取到的中断号。
最佳实践:始终检查 platform_get_irq() 的返回值。如果返回 -EPROBE_DEFER,直接返回该错误码,内核会稍后重新尝试 probe。不要忽略或转换 -EPROBE_DEFER,否则可能导致中断控制器未就绪时错误地注册中断。

4.5 中断号获取的进阶话题

4.5.1 中断号与 irq_domain 的关系

每个中断控制器在 Linux 内核中对应一个 irq_domain 实例。设备树中的 interrupt-parent 属性指向中断控制器节点,内核通过该引用找到对应的 irq_domain,然后调用 irq_create_of_mapping() 将硬件中断号转换为全局唯一的虚拟中断号。

4.5.2 共享中断的处理

当多个设备共享同一个中断号时,需要在 request_irq() 中设置 IRQF_SHARED 标志,并且中断处理函数必须能够区分中断来源:

irq = platform_get_irq(pdev, 0);
if (irq < 0)
    return irq;

// 共享中断必须设置 IRQF_SHARED
ret = request_irq(irq, shared_handler,
                  IRQF_SHARED | IRQF_TRIGGER_HIGH,
                  "my_subdev", dev);
if (ret) {
    dev_err(dev, "request_irq failed: %d\n", ret);
    return ret;
}

// 中断处理函数中必须检查硬件状态
irqreturn_t shared_handler(int irq, void *dev_id)
{
    struct my_dev *dev = dev_id;

    if (!(readl(dev->regs + STATUS_REG) & IRQ_PENDING))
        return IRQ_NONE;  // 不是本设备的中断

    // 处理中断
    writel(IRQ_CLEAR, dev->regs + CLEAR_REG);
    return IRQ_HANDLED;
}

4.5.3 中断号的动态分配

在某些场景下(如 PCIe 设备、动态创建的 platform 设备),中断号不是在设备树中静态定义的,而是通过 irq_alloc_descs() 动态分配。此时驱动应使用 platform_get_resource(pdev, IORESOURCE_IRQ, num) 获取中断资源,而非直接调用 platform_get_irq()。

总结:设备树中断描述是驱动获取中断号的“硬件说明书”,platform_get_irq() 是驱动开发者最便捷的“翻译官”。正确理解设备树属性、合理使用 API、严谨处理错误码,是 Subdev 驱动中断处理设计的基础保障。