4. 设备树中断属性详解:interrupt-parent、interrupts、interrupt-names、interrupt-controller

在嵌入式 Linux 驱动开发中,中断是外设与 CPU 进行异步通信的核心机制。设备树(Device Tree)通过一组标准属性来描述硬件中断拓扑,驱动开发者必须准确理解这些属性才能正确申请和使用中断。本节将逐一剖析 interrupt-parent、interrupts、interrupt-names 和 interrupt-controller 四个关键属性,并结合实际案例说明其配置方法。

核心概念:设备树中的中断描述遵循“中断控制器(Interrupt Controller)→ 中断源(Interrupt Source)”的层级模型。每个外设节点通过 interrupts 属性声明自己需要的中断线,通过 interrupt-parent 指向提供中断服务的中断控制器。

4.1 interrupt-controller:声明中断控制器节点

interrupt-controller 是一个空属性(无值),用于标记某个节点为中断控制器。只有被标记为中断控制器的节点才能被其他节点通过 interrupt-parent 引用。通常,SoC 内部的 GIC(Generic Interrupt Controller)或 GPIO 控制器都会声明此属性。

此外,中断控制器节点还需要通过 #interrupt-cells 属性指定其 interrupts 属性中需要多少个 cell(32 位整数)来描述一条中断。例如,ARM GICv2 通常使用 3 个 cell:中断类型(SPI / PPI)、中断号、触发方式。

// ARM GIC 中断控制器节点示例
gic: interrupt-controller@f0001000 {
    compatible = "arm,cortex-a9-gic";
    reg = <0xf0001000 0x1000>,
          <0xf0000100 0x0100>;
    interrupt-controller;        // 标记为中断控制器
    #interrupt-cells = <3>;      // 每个中断描述需要 3 个 cell
};
关键点:如果一个节点没有 interrupt-controller 属性,则它不能作为 interrupt-parent 的目标。驱动代码通过 irq_of_parse_and_map() 解析设备树中断时,内核会自动沿 interrupt-parent 链向上查找中断控制器。
中断控制器节点声明与中断映射关系 GIC 中断控制器 interrupt-controller; #interrupt-cells = <3>; GPIO 中断控制器 interrupt-controller; #interrupt-cells = <2>; 外设节点(UART) interrupt-parent = <&gic>; interrupt-parent 引用 说明:中断控制器节点必须声明 interrupt-controller 和 #interrupt-cells;外设节点通过 interrupt-parent 指向控制器。

4.2 interrupt-parent:指定中断控制器

interrupt-parent 属性用于指定当前节点使用哪个中断控制器提供的中断线。其值为一个 phandle(指向中断控制器节点的指针)。如果某个节点没有显式声明 interrupt-parent,则继承其父节点的 interrupt-parent,直到根节点。

在驱动代码中,内核通过 of_irq_parse_one() 函数沿着 interrupt-parent 链向上查找,最终找到中断控制器并解析中断描述符。

// 外设节点显式指定 interrupt-parent
uart0: serial@10000000 {
    compatible = "ns16550";
    reg = <0x10000000 0x1000>;
    interrupt-parent = <&gic>;      // 指向 GIC 中断控制器
    interrupts = <0 33 4>;          // SPI 类型,中断号 33,高电平触发
};

// 子节点未指定 interrupt-parent,继承父节点
i2c@20000000 {
    compatible = "snps,designware-i2c";
    reg = <0x20000000 0x1000>;
    interrupt-parent = <&gic>;      // 父节点指定
    interrupts = <0 45 4>;
    
    touch@1a {
        compatible = "edt,ft5x06";
        reg = <0x1a>;
        // 未指定 interrupt-parent,继承 i2c 节点的 &gic
        interrupts = <0 46 4>;
    };
};
常见错误:如果 interrupt-parent 指向的节点没有声明 interrupt-controller 属性,内核在解析中断时会报错并返回无效中断号。务必确保 interrupt-parent 的 phandle 指向正确的中断控制器节点。

4.3 interrupts:描述中断线

interrupts 属性是一个 u32 数组,用于描述设备需要的中断线。每个中断由若干个 cell 组成,cell 的数量由中断控制器的 #interrupt-cells 属性决定。常见的 cell 编码方式如下:

中断控制器类型 #interrupt-cells cell 含义 示例
ARM GICv2 3 cell[0]: 中断类型 (0=SPI, 1=PPI)
cell[1]: 中断号
cell[2]: 触发方式 (1=上升沿, 4=高电平)
<0 33 4>
ARM GICv3 3 与 GICv2 类似,但 SPI 中断号范围扩展 <0 100 4>
GPIO 控制器 2 cell[0]: GPIO 引脚号
cell[1]: 触发标志 (低电平/高电平/上升沿/下降沿)
<12 2>
TI OMAP 中断控制器 2 cell[0]: 中断线号
cell[1]: 触发类型
<56 1>
// 多个中断描述示例
ethernet@30000000 {
    compatible = "smsc,lan9115";
    reg = <0x30000000 0x10000>;
    interrupt-parent = <&gic>;
    interrupts = <0 67 4>;           // 一个中断:SPI 67,高电平触发
};

// 支持多个中断的外设
sound@40000000 {
    compatible = "fsl,imx6q-sai";
    reg = <0x40000000 0x4000>;
    interrupt-parent = <&gic>;
    interrupts = <0 97 4>, <0 98 4>;  // 两个中断:TX 和 RX
    interrupt-names = "tx", "rx";       // 为每个中断命名
};
interrupts 属性解析流程 设备树节点 interrupts = <0 33 4>; interrupt-parent = <&gic>; 步骤 1 of_irq_parse_one() 解析 interrupts cell 步骤 2 irq_create_of_mapping() 映射为 Linux IRQ 号 结果:驱动通过 platform_get_irq() 或 irq_of_parse_and_map() 获取 Linux IRQ 号,用于 request_irq() 注册中断处理函数。 cell 分解:<0 33 4> → 中断类型=0(SPI) | 中断号=33 | 触发方式=4(高电平触发)

4.4 interrupt-names:为中断线命名

interrupt-names 属性是一个字符串列表,为 interrupts 属性中的每个中断线指定一个名称。当外设拥有多个中断时,命名可以显著提高驱动代码的可读性和可维护性。驱动可以通过名称而不是索引来获取对应的中断号。

// 使用 interrupt-names 的多中断设备
dma@50000000 {
    compatible = "ti,edma3";
    reg = <0x50000000 0x10000>;
    interrupt-parent = <&gic>;
    interrupts = <0 12 4>, <0 13 4>, <0 14 4>;
    interrupt-names = "ch0", "ch1", "err";  // 分别为三个中断命名
};

// 驱动代码中通过名称获取中断
static int dma_probe(struct platform_device *pdev)
{
    int irq_ch0, irq_ch1, irq_err;
    
    // 通过名称获取中断号,更直观
    irq_ch0 = platform_get_irq_byname(pdev, "ch0");
    irq_ch1 = platform_get_irq_byname(pdev, "ch1");
    irq_err = platform_get_irq_byname(pdev, "err");
    
    if (irq_ch0 < 0 || irq_ch1 < 0 || irq_err < 0) {
        dev_err(&pdev->dev, "failed to get IRQs\n");
        return -EINVAL;
    }
    
    // 注册中断处理函数
    request_irq(irq_ch0, dma_ch0_handler, 0, "dma_ch0", dev);
    request_irq(irq_ch1, dma_ch1_handler, 0, "dma_ch1", dev);
    request_irq(irq_err, dma_err_handler, 0, "dma_err", dev);
    
    return 0;
}
最佳实践:当设备树中定义了 interrupt-names 时,驱动应优先使用 platform_get_irq_byname() 而非 platform_get_irq()。这样即使设备树中中断的顺序发生变化,驱动代码也无需修改,增强了兼容性。

4.5 综合示例:完整的中断配置

以下是一个完整的设备树中断配置示例,展示了中断控制器、外设节点和驱动代码之间的协作关系:

// 设备树文件(.dts)
/ {
    #address-cells = <1>;
    #size-cells = <1>;
    
    // 中断控制器
    gic: interrupt-controller@f0001000 {
        compatible = "arm,cortex-a9-gic";
        reg = <0xf0001000 0x1000>,
              <0xf0000100 0x0100>;
        interrupt-controller;
        #interrupt-cells = <3>;
    };
    
    // 外设节点
    uart1: serial@10001000 {
        compatible = "ns16550";
        reg = <0x10001000 0x1000>;
        interrupts = <0 35 4>;          // SPI 35,高电平触发
        interrupt-parent = <&gic>;      // 引用 GIC
        clock-frequency = <24000000>;
    };
    
    // 带多个中断的外设
    mmc0: mmc@20000000 {
        compatible = "fsl,imx6q-usdhc";
        reg = <0x20000000 0x4000>;
        interrupts = <0 56 4>, <0 57 4>;
        interrupt-names = "card", "dma";
        interrupt-parent = <&gic>;
        bus-width = <8>;
    };
};

// 驱动代码(简化)
static int uart_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    int irq;
    
    // 获取中断号
    irq = platform_get_irq(pdev, 0);
    if (irq < 0)
        return irq;
    
    // 申请中断
    ret = devm_request_irq(dev, irq, uart_isr, 0, "uart1", port);
    if (ret) {
        dev_err(dev, "failed to request IRQ %d\n", irq);
        return ret;
    }
    
    return 0;
}
完整中断配置架构图 硬件层(Hardware) CPU Core ARM Cortex-A9 GIC 中断控制器 UART 外设 MMC 外设 设备树层(Device Tree) interrupt-controller; #interrupt-cells = <3>; interrupt-parent = <&gic>; interrupts = <0 35 4>; interrupt-names = "card","dma"; 驱动层(Driver)→ platform_get_irq() / devm_request_irq()

4.6 属性对比总结

属性名 类型 作用 是否必需 使用场景
interrupt-controller 空属性 标记节点为中断控制器 中断控制器节点必需 GIC、GPIO 控制器、INTC 等
#interrupt-cells u32 指定 interrupts 属性中每个中断的 cell 数 中断控制器节点必需 与 interrupt-controller 配合使用
interrupt-parent phandle 指定当前节点使用的中断控制器 外设节点推荐显式指定 所有需要中断的外设节点
interrupts u32 数组 描述中断线(类型、号、触发方式) 外设节点必需 所有需要中断的外设节点
interrupt-names string 列表 为 interrupts 中的每个中断命名 可选(多中断时推荐) 多中断外设(DMA、MMC、音频等)
学习建议:在实际开发中,建议先阅读 SoC 参考手册了解中断控制器的 #interrupt-cells 编码规则,然后对照设备树绑定文档(Documentation/devicetree/bindings/interrupt-controller/)进行配置。驱动代码中优先使用 devm_request_irq() 和 platform_get_irq_byname() 以简化资源管理。