4. DTS中断属性详解:interrupt-parent、interrupts、interrupt-names、interrupt-cells、interrupt-map

中断是嵌入式系统响应外部或内部异步事件的核心机制。在设备树中,中断的描述遵循一套标准化的属性体系,使得操作系统能够在不修改内核代码的情况下,通过解析DTS自动完成中断控制器、中断引脚、触发方式等信息的绑定。本章将逐一剖析五个核心中断属性,并结合实际案例说明其配置方法与调试要点。

学习提示:中断属性的正确配置是驱动稳定性的基石。一个错误的interrupts属性可能导致中断无法触发、误触发甚至系统死锁。建议在阅读本节时,对照实际开发板的手册和DTS源文件进行验证。

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

interrupt-parent 属性用于显式指定当前设备节点所连接的中断控制器。它接受一个 phandle(指向中断控制器节点的指针)作为值。

  • 作用:将设备与中断控制器关联起来,明确中断请求信号的路由目标。
  • 继承规则:如果子节点未定义 interrupt-parent,则默认继承父节点的该属性。
  • 典型场景:SoC内部多个中断控制器(如GIC、GPIO控制器)共存时,每个外设需要明确指定挂载到哪个控制器上。
// 示例:设备节点显式指定中断控制器
gic: interrupt-controller@f0001000 {
    compatible = "arm,cortex-a15-gic";
    #interrupt-cells = <3>;
    interrupt-controller;
    reg = <0x0 0xf0001000 0x0 0x1000>;
};

uart0: serial@f0002000 {
    compatible = "ns16550a";
    reg = <0x0 0xf0002000 0x0 0x1000>;
    interrupt-parent = <&gic>;   // 指向GIC控制器
    interrupts = <0 33 4>;        // SPI 33, 上升沿触发
};
关键点:如果设备树中缺少 interrupt-parent,且父节点也未定义,内核将无法为该设备分配中断号,驱动中的 request_irq() 会返回 -EINVAL。
interrupt-parent 关联关系 中断控制器 (GIC) interrupt-controller; #interrupt-cells = <3>; UART0 设备节点 interrupt-parent = <&gic>; I2C0 设备节点 interrupt-parent = <&gic>; SPI1 设备节点 (继承父节点 interrupt-parent) 虚线表示继承关系 子节点未显式定义时自动继承父节点

4.2 interrupts:定义中断描述符

interrupts 属性是设备节点中最核心的中断描述字段,它由一个或多个中断描述符组成,每个描述符的格式由中断控制器的 #interrupt-cells 决定。

中断控制器类型 #interrupt-cells 中断描述符格式 示例
ARM GIC v2 3 <中断类型 SPI/PPI 中断号 触发方式> <0 33 4>
ARM GIC v3 3 <中断类型 中断号 触发方式> <0 33 4>
GPIO 控制器 2 <GPIO引脚号 触发方式> <12 2>
RISC-V PLIC 1 <中断号> <10>
常见错误:中断号填写错误是驱动调试中最常见的问题。例如,ARM GIC中SPI中断号从32开始编号,但设备树中填写的是相对于SPI基址的偏移量(即实际硬件中断号减去32)。务必查阅SoC手册确认中断号映射规则。
// 多中断描述符示例:一个设备使用两个中断
ethernet@f0003000 {
    compatible = "vendor,eth-controller";
    reg = <0x0 0xf0003000 0x0 0x1000>;
    interrupt-parent = <&gic>;
    interrupts = <0 45 4>,   // 发送完成中断
                 <0 46 4>;   // 接收完成中断
    interrupt-names = "tx", "rx";
};
interrupts 属性结构解析 设备节点 interrupts = ... interrupt-names = ... 中断描述符数组 描述符1: <0 45 4> 描述符2: <0 46 4> ... 单个描述符 Cell 0: 中断类型 (0=SPI) Cell 1: 中断号 (45) Cell 2: 触发方式 (4=高电平) 描述符格式由中断控制器的 #interrupt-cells 决定 GIC v2/v3 使用 3 个 cell:类型、编号、触发标志 GPIO 控制器通常使用 2 个 cell:引脚号、触发标志

4.3 interrupt-names:为中断描述符命名

interrupt-names 属性为 interrupts 中的每个描述符提供一个字符串名称,便于驱动代码通过名称而非索引来获取中断号,提高代码可读性和可维护性。

  • 语法:字符串列表,顺序与 interrupts 中的描述符一一对应。
  • 驱动接口:Linux内核提供 platform_get_irq_byname() 函数,驱动可通过名称获取中断号。
  • 最佳实践:即使只有一个中断,也建议命名,便于后续扩展和代码审查。
// 驱动代码中使用 interrupt-names
static int my_probe(struct platform_device *pdev)
{
    int tx_irq, rx_irq;

    // 通过名称获取中断号,不依赖索引顺序
    tx_irq = platform_get_irq_byname(pdev, "tx");
    if (tx_irq < 0) {
        dev_err(&pdev->dev, "failed to get tx irq\n");
        return tx_irq;
    }

    rx_irq = platform_get_irq_byname(pdev, "rx");
    if (rx_irq < 0) {
        dev_err(&pdev->dev, "failed to get rx irq\n");
        return rx_irq;
    }

    // 注册中断处理函数
    devm_request_irq(&pdev->dev, tx_irq, tx_handler, 0, "mydev_tx", dev);
    devm_request_irq(&pdev->dev, rx_irq, rx_handler, 0, "mydev_rx", dev);

    return 0;
}
对比:使用 platform_get_irq(pdev, 0) 依赖中断描述符在数组中的索引位置,一旦DTS中调整了顺序,驱动必须同步修改。而 platform_get_irq_byname() 通过名称匹配,顺序无关,更加健壮。

4.4 #interrupt-cells:定义中断描述符长度

#interrupt-cells 是中断控制器节点上的属性,用于声明该控制器所需的中断描述符包含多少个32位整数(cell)。这个属性决定了所有挂载在该控制器下的设备节点中 interrupts 属性的格式。

#interrupt-cells 值 典型用途 每个 cell 的含义
1 简单中断控制器(如RISC-V PLIC) Cell 0: 中断号
2 GPIO 控制器、级联中断控制器 Cell 0: 引脚/中断号;Cell 1: 触发标志
3 ARM GIC、RISC-V APLIC Cell 0: 中断类型;Cell 1: 中断号;Cell 2: 触发标志
// 定义 #interrupt-cells = 2 的 GPIO 中断控制器
gpio0: gpio@f0004000 {
    compatible = "vendor,gpio-controller";
    reg = <0x0 0xf0004000 0x0 0x1000>;
    gpio-controller;
    #gpio-cells = <2>;
    interrupt-controller;
    #interrupt-cells = <2>;   // 每个中断描述符使用2个cell
};

// 挂载在该GPIO控制器下的设备
button@0 {
    compatible = "gpio-keys";
    interrupt-parent = <&gpio0>;
    interrupts = <5 2>;        // GPIO5, 下降沿触发
};
配置陷阱:#interrupt-cells 的值必须与驱动解析代码严格一致。如果控制器声明了3个cell,但设备节点只提供了2个,内核在解析时会读取到无效数据,导致中断号计算错误。调试时可通过 /proc/interrupts 和 cat /sys/kernel/debug/irq/irqs 验证中断号是否正确。

4.5 interrupt-map:实现中断路由与映射

interrupt-map 是设备树中用于中断路由的高级属性,常见于PCIe总线、多级中断控制器级联、以及需要将子总线中断重新映射到父中断控制器的场景。它本质上是一个中断地址转换表。

  • 语法:interrupt-map = <子中断地址 子中断描述符 父中断控制器phandle 父中断地址 父中断描述符>;
  • 配套属性:interrupt-map-mask 用于对子中断地址进行掩码匹配。
  • 典型场景:PCIe桥片将下游设备的中断(INTA/INTB/INTC/INTD)映射到SoC的GIC中断线。
// PCIe 中断映射示例
pcie0: pcie@f0005000 {
    compatible = "vendor,pcie-controller";
    reg = <0x0 0xf0005000 0x0 0x1000>;
    #address-cells = <3>;
    #size-cells = <2>;
    interrupt-parent = <&gic>;
    interrupts = <0 120 4>;   // PCIe控制器自身中断

    // 下游设备中断映射表
    #interrupt-cells = <1>;
    interrupt-map-mask = <0xf800 0 0 7>;
    interrupt-map = <
        // 设备地址掩码  子中断  父控制器  父中断地址  父中断描述符
        0x0000 0 0 1 &gic 0 0 121 4   // INTA -> GIC SPI 121
        0x0000 0 0 2 &gic 0 0 122 4   // INTB -> GIC SPI 122
        0x0000 0 0 3 &gic 0 0 123 4   // INTC -> GIC SPI 123
        0x0000 0 0 4 &gic 0 0 124 4   // INTD -> GIC SPI 124
    >;
};
interrupt-map 中断路由流程 PCIe 下游设备 发出 INTA 中断 PCIe 中断映射器 interrupt-map 查找 INTA -> SPI 121 GIC 控制器 SPI 121 CPU 中断处理 interrupt-map 映射表 INTA -> &gic 0 0 121 4 INTB -> &gic 0 0 122 4 INTC -> &gic 0 0 123 4 INTD -> &gic 0 0 124 4 interrupt-map-mask = <0xf800 0 0 7>; 子中断请求 PCIe 设备发出 INTA/INTB/INTC/INTD

4.6 属性对比与调试要点

属性 所属节点 值类型 核心作用 调试检查点
interrupt-parent 设备节点 phandle 指定中断控制器 检查phandle是否指向有效的interrupt-controller节点
interrupts 设备节点 u32数组 定义中断描述符 验证cell数量与#interrupt-cells一致
interrupt-names 设备节点 字符串列表 为中断描述符命名 确保名称与驱动中platform_get_irq_byname匹配
#interrupt-cells 中断控制器节点 u32 声明描述符长度 与硬件手册和驱动解析逻辑对齐
interrupt-map 桥接/总线节点 复合表 中断路由映射 验证映射表条目与父控制器中断资源不冲突
调试命令速查:
  • cat /proc/interrupts — 查看系统中所有注册的中断及其触发次数。
  • cat /sys/kernel/debug/irq/irqs — 查看每个中断的详细属性(需要CONFIG_DEBUG_FS)。
  • dtc -I dtb -O dts -o output.dts input.dtb — 反编译DTB,验证中断属性是否被正确编译。
  • ls /sys/firmware/devicetree/base/ — 在运行时查看内核解析后的设备树结构。

4.7 综合配置示例

以下是一个完整的设备树片段,展示了多个中断属性协同工作的典型场景:一个SoC包含GIC和GPIO两个中断控制器,外设分别挂载到不同的控制器上。

/dts-v1/;

/ {
    compatible = "vendor,board";
    #address-cells = <2>;
    #size-cells = <2>;

    // 主中断控制器:ARM GIC
    gic: interrupt-controller@f0001000 {
        compatible = "arm,cortex-a15-gic";
        #interrupt-cells = <3>;
        interrupt-controller;
        reg = <0x0 0xf0001000 0x0 0x1000>;
    };

    // GPIO 控制器,同时也是中断控制器
    gpio0: gpio@f0004000 {
        compatible = "vendor,gpio-controller";
        reg = <0x0 0xf0004000 0x0 0x1000>;
        gpio-controller;
        #gpio-cells = <2>;
        interrupt-controller;
        #interrupt-cells = <2>;
    };

    // 外设1:挂载到GIC,使用命名中断
    uart0: serial@f0002000 {
        compatible = "ns16550a";
        reg = <0x0 0xf0002000 0x0 0x1000>;
        interrupt-parent = <&gic>;
        interrupts = <0 33 4>, <0 34 4>;
        interrupt-names = "rx", "tx";
    };

    // 外设2:挂载到GPIO中断控制器
    button@0 {
        compatible = "gpio-keys";
        interrupt-parent = <&gpio0>;
        interrupts = <5 2>;   // GPIO5, 下降沿触发
    };
};
工程要点:在实际项目中,建议在DTS中添加注释说明每个中断的硬件来源和触发条件,便于团队协作和后期维护。同时,在驱动probe函数中增加中断号打印日志,辅助验证DTS配置是否正确。