4. 设备树中断控制器节点:interrupt-controller属性、#interrupt-cells、interrupt-parent

中断是嵌入式系统响应异步事件的核心机制。在设备树中,中断控制器节点通过三个关键属性描述中断的拓扑结构:interrupt-controller、#interrupt-cells 和 interrupt-parent。理解这三者的协作关系,是编写正确中断映射和调试中断相关驱动的基石。

4.1 中断控制器节点核心属性

设备树中,一个中断控制器节点必须声明自己能够接收和分发中断,并定义中断描述符的格式。以下是三个核心属性的详细说明:

属性名 类型 是否必需 描述
interrupt-controller 空属性(boolean) 是 标记该节点是一个中断控制器,可以接收中断请求。
#interrupt-cells u32 是 指定该中断控制器使用多少个 cell(32位整数)来描述一个中断源。例如 GIC 通常为 3。
interrupt-parent phandle 否(但常用) 指定该节点或子节点的中断请求连接到哪个中断控制器。若未指定,则继承父节点的值。
关键区别: interrupt-controller 是“我是中断控制器”的声明;#interrupt-cells 是“我如何描述中断”的格式定义;interrupt-parent 是“我连到谁”的连接关系。

4.2 中断描述符的组成:#interrupt-cells

#interrupt-cells 的值决定了设备节点中 interrupts 属性的格式。不同的中断控制器使用不同的 cell 数量来描述中断。以下是常见的中断控制器及其 cell 定义:

中断控制器类型 #interrupt-cells 中断描述符格式 示例
ARM GIC v2/v3 3 <中断类型 中断号 触发方式> <0 89 4>(SPI, 89号, 高电平触发)
简单中断控制器(GPIO) 2 <中断源引脚 触发方式> <5 2>(GPIO5, 下降沿触发)
老旧/自定义控制器 1 <中断号> <7>(硬件中断线7)
实践要点: 在编写设备树时,必须查阅对应中断控制器的绑定文档(binding document),确认 #interrupt-cells 的值以及每个 cell 的含义。错误的 cell 数量会导致内核无法正确解析中断号。

4.3 interrupt-parent:中断连接关系

interrupt-parent 属性通过 phandle(指向中断控制器节点的引用)建立设备与中断控制器之间的连接。它可以在以下位置出现:

  • 根节点:作为默认中断父节点,所有未显式指定 interrupt-parent 的子节点都继承此值。
  • 总线节点:覆盖根节点的默认值,该总线下的设备继承此值。
  • 设备节点:显式指定该设备的中断连接到哪个控制器,覆盖所有继承值。

以下是一个典型的中断连接关系示例:

/ {
    interrupt-parent = &gic;  /* 根节点默认中断父节点为 GIC */

    gic: interrupt-controller@f0001000 {
        compatible = "arm,gic-400";
        interrupt-controller;
        #interrupt-cells = <3>;
        reg = <0x0 0xf0001000 0x0 0x1000>;
    };

    uart0: serial@ff000000 {
        compatible = "arm,pl011";
        reg = <0x0 0xff000000 0x0 0x1000>;
        interrupts = <0 33 4>;  /* 继承根节点的 interrupt-parent,连接到 gic */
    };

    gpio0: gpio@ff001000 {
        compatible = "brcm,bcm2835-gpio";
        reg = <0x0 0xff001000 0x0 0x1000>;
        interrupt-controller;
        #interrupt-cells = <2>;
        /* 该 GPIO 控制器本身的中断连接到 GIC */
        interrupt-parent = <&gic>;
        interrupts = <0 89 4>;
    };
};

4.4 中断映射关系 SVG 架构图

下图展示了设备树中中断控制器、设备节点与 interrupt-parent 之间的连接关系:

设备树中断映射关系 根节点 (/) interrupt-parent = &gic GIC: interrupt-controller@f0001000 interrupt-controller; #interrupt-cells = <3>; UART0: serial@ff000000 interrupts = <0 33 4>; (继承根节点 interrupt-parent) → 连接到 GIC GPIO0: gpio@ff001000 interrupt-controller; #interrupt-cells = <2>; interrupt-parent = <&gic>; interrupts = <0 89 4>; 外部设备:interrupt-parent = &gpio0; interrupts = <5 2>; 说明:interrupt-parent 建立层级连接;#interrupt-cells 定义中断描述符格式;interrupt-controller 声明节点角色。

4.5 中断控制器节点完整示例

以下是一个包含两级中断控制器的完整设备树片段,展示了 interrupt-controller、#interrupt-cells 和 interrupt-parent 的协作:

/dts-v1/;

/ {
    compatible = "acme,custom-board";
    interrupt-parent = &gic;   /* 默认中断父节点 */

    gic: interrupt-controller@f0001000 {
        compatible = "arm,gic-400";
        interrupt-controller;
        #interrupt-cells = <3>;
        reg = <0x0 0xf0001000 0x0 0x1000>;
    };

    /* 第一级中断控制器:GPIO */
    gpio0: gpio@ff001000 {
        compatible = "acme,gpio-controller";
        reg = <0x0 0xff001000 0x0 0x1000>;
        interrupt-controller;
        #interrupt-cells = <2>;
        interrupt-parent = <&gic>;   /* GPIO 控制器本身连接到 GIC */
        interrupts = <0 89 4>;        /* GIC 描述符:SPI, 89, 高电平 */
    };

    /* 连接到 GPIO 控制器的设备 */
    button@0 {
        compatible = "acme,button";
        interrupt-parent = <&gpio0>;  /* 显式指定父中断控制器 */
        interrupts = <3 2>;            /* GPIO 描述符:引脚3, 下降沿 */
    };

    /* 直接连接到 GIC 的设备 */
    uart0: serial@ff000000 {
        compatible = "arm,pl011";
        reg = <0x0 0xff000000 0x0 0x1000>;
        interrupts = <0 33 4>;         /* 继承根节点 interrupt-parent,即 GIC */
    };
};

4.6 常见错误与调试技巧

常见错误 1: 忘记在中断控制器节点中添加 interrupt-controller; 属性。内核在解析时会忽略该节点,导致所有引用该节点的设备无法获取中断。
常见错误 2: #interrupt-cells 的值与设备节点中 interrupts 属性的 cell 数量不匹配。例如 GIC 要求 3 个 cell,但设备只提供了 2 个,内核会解析出错误的中断号。
调试技巧: 使用 cat /proc/interrupts 查看系统中断分配情况;使用 dtc -I dtb -O dts 反编译设备树,检查中断属性是否正确;在驱动中使用 devm_request_irq() 后检查返回值,确认中断号是否有效。

4.7 中断映射流程总结

设备树中断映射的完整流程可以归纳为以下步骤:

  1. 声明中断控制器:在节点中添加 interrupt-controller; 属性。
  2. 定义中断描述符格式:设置 #interrupt-cells 指定每个中断需要多少个 cell。
  3. 建立连接关系:在设备节点或父节点中设置 interrupt-parent,指向对应的中断控制器。
  4. 提供中断描述符:在设备节点的 interrupts 属性中,按照 #interrupt-cells 定义的格式填写中断信息。
  5. 内核解析:Linux 内核在启动时遍历设备树,通过 of_irq_parse_one() 等函数解析中断映射,最终注册中断号。
核心要点: 中断控制器节点是中断拓扑的锚点。interrupt-controller 声明角色,#interrupt-cells 定义语言,interrupt-parent 建立连接。三者缺一不可,共同构成了设备树中断系统的完整骨架。