4. 中断控制器属性:interrupt-controller, #interrupt-cells, interrupt-parent 属性解析

在设备树中,中断系统的描述是连接外设与中断控制器(Interrupt Controller)的关键桥梁。本章将深入解析三个核心中断属性:interrupt-controller、#interrupt-cells 和 interrupt-parent。理解这些属性是正确配置中断映射、编写设备树中断节点的基础。

核心概念:设备树通过“中断生产者-消费者”模型描述中断拓扑。中断控制器是生产者,外设是消费者。interrupt-controller 标记生产者身份,#interrupt-cells 定义中断描述格式,interrupt-parent 指定消费者连接的生产者。

4.1 interrupt-controller 属性:标记中断控制器节点

interrupt-controller 是一个布尔属性(无值),用于在设备树节点中声明该节点是一个中断控制器。只有被标记的节点才能接收和处理来自其他设备的中断请求。

  • 作用: 标识节点为中断资源的管理者,是中断路由的起点。
  • 使用位置: 通常出现在 SoC 内部的中断控制器节点(如 GIC、INTC、NVIC)或 GPIO 控制器节点(当 GPIO 可作为中断源时)。
  • 语法: 在节点内直接写入 interrupt-controller; 即可。
// 示例:ARM Generic Interrupt Controller (GIC) 节点
gic: interrupt-controller@f0001000 {
    compatible = "arm,cortex-a15-gic";
    reg = <0x0 0xf0001000 0x0 0x1000>,
          <0x0 0xf0002000 0x0 0x2000>;
    interrupt-controller;        // 声明本节点是中断控制器
    #interrupt-cells = <3>;      // 每个中断描述需要3个cell
    #address-cells = <0>;
    #size-cells = <0>;
};
常见错误: 如果一个节点被其他设备通过 interrupt-parent 引用,但自身未声明 interrupt-controller,内核在解析中断时会报错或忽略该中断映射。务必确保中断控制器节点包含此属性。

4.2 #interrupt-cells 属性:定义中断描述格式

#interrupt-cells 是一个整型属性,用于指定该中断控制器在描述一个中断源时需要多少个 32 位单元(cell)。每个 cell 通常包含中断类型、触发方式、优先级等信息,具体含义由中断控制器的绑定文档定义。

中断控制器类型 #interrupt-cells 值 cell 含义
ARM GIC (v2/v3) 3 cell[0]: 中断类型 (0=SPI, 1=PPI);cell[1]: 中断号;cell[2]: 触发标志 (如 IRQ_TYPE_EDGE_RISING)
RISC-V PLIC 1 cell[0]: 中断源编号 (1~N)
GPIO 控制器 (作为中断控制器) 2 cell[0]: GPIO 引脚号;cell[1]: 触发标志 (低电平、高电平、上升沿等)
Xilinx INTC 2 cell[0]: 中断号 (0~31);cell[1]: 触发类型 (边沿/电平)
// 示例:GPIO 控制器作为中断控制器
gpio0: gpio@ff000000 {
    compatible = "snps,dw-apb-gpio";
    reg = <0xff000000 0x1000>;
    #gpio-cells = <2>;
    gpio-controller;
    interrupt-controller;        // 该GPIO控制器也能处理中断
    #interrupt-cells = <2>;      // 中断描述:引脚号 + 触发标志
    interrupts = <0 32 4>;       // 该GPIO控制器本身连接到GIC的中断
};
关键理解: #interrupt-cells 的值决定了外设节点在 interrupts 属性中需要提供多少个 cell。例如,若父中断控制器的 #interrupt-cells = <3>,则外设的 interrupts = <0 42 4>; 表示 SPI 类型、中断号 42、高电平触发。

4.3 interrupt-parent 属性:指定中断连接关系

interrupt-parent 是一个 phandle 属性,用于显式指定当前设备节点连接到哪个中断控制器。它解决了中断路由的“寻路”问题,尤其是在存在多个中断控制器或中断层级嵌套时。

  • 默认行为: 如果设备节点没有显式定义 interrupt-parent,内核会沿着设备树向上查找父节点,直到找到一个包含 interrupt-controller 属性的节点作为默认中断控制器。
  • 显式覆盖: 当设备需要连接到非父节点的中断控制器时(如连接到 GPIO 控制器或专用中断控制器),必须使用 interrupt-parent 指定。
  • 语法: interrupt-parent = <&gic>; 或 interrupt-parent = <&gpio0>;
// 示例:外设连接到不同的中断控制器
// 1. 连接到 GIC(默认通过父节点继承)
uart0: serial@10000000 {
    compatible = "ns16550a";
    reg = <0x10000000 0x1000>;
    interrupts = <0 33 4>;       // 隐式使用父节点的 interrupt-parent
};

// 2. 连接到 GPIO 控制器(显式指定)
button: gpio-keys {
    compatible = "gpio-keys";
    interrupt-parent = <&gpio0>;  // 显式指定中断控制器为 gpio0
    interrupts = <5 2>;           // GPIO0 的引脚5,下降沿触发
    label = "User Button";
};

4.4 中断属性协同工作流程

以下 SVG 图展示了设备树中中断属性的完整解析流程,从外设触发中断到内核处理。

设备树中断属性解析流程 外设节点 interrupts = <...> 查找 interrupt-parent 有显式定义?→ 使用 无 → 向上遍历父节点 中断控制器节点 interrupt-controller; #interrupt-cells = <N> 解析 interrupts 属性 按 #interrupt-cells 拆分 cell 中断映射表 中断号 → 硬件中断线 注册中断处理函数 request_irq(irq, handler) 说明 interrupt-parent 决定路由方向 #interrupt-cells 决定解析格式

4.5 多级中断控制器与 interrupt-parent 嵌套

在复杂 SoC 中,可能存在多级中断控制器。例如,一个 GPIO 控制器本身连接到 GIC,而外设又连接到 GPIO 控制器。此时,interrupt-parent 的嵌套关系至关重要。

// 多级中断控制器示例
gic: interrupt-controller@f0001000 {
    interrupt-controller;
    #interrupt-cells = <3>;
};

gpio0: gpio@ff000000 {
    interrupt-controller;
    #interrupt-cells = <2>;
    interrupt-parent = <&gic>;       // GPIO控制器连接到GIC
    interrupts = <0 32 4>;           // GPIO控制器本身的中断
};

// 外设连接到GPIO控制器
keypad: keypad@0 {
    interrupt-parent = <&gpio0>;     // 显式指定父中断为GPIO控制器
    interrupts = <3 1>;              // GPIO0的引脚3,上升沿触发
};
解析过程: 当内核解析 keypad 的中断时,首先通过 interrupt-parent 找到 gpio0,读取其 #interrupt-cells = <2>,将 interrupts = <3 1> 解析为 GPIO 引脚 3、上升沿触发。然后 gpio0 再通过自身的 interrupt-parent 将中断路由到 gic,最终由 GIC 上报给 CPU。

4.6 属性对比与常见陷阱

属性 类型 所属节点 作用 是否必需
interrupt-controller 布尔 中断控制器节点 声明本节点是中断控制器 是(中断控制器节点)
#interrupt-cells 整型 中断控制器节点 定义中断描述所需的 cell 数量 是(中断控制器节点)
interrupt-parent phandle 任何需要中断的设备节点 指定该设备连接的中断控制器 否(默认向上查找)
常见陷阱:
  • 中断号冲突: 不同中断控制器可能使用相同的中断号,但含义不同。务必通过 interrupt-parent 明确指定。
  • 遗漏 interrupt-controller: 在 GPIO 控制器节点中,如果忘记添加 interrupt-controller,即使有 #interrupt-cells,外设也无法通过该节点申请中断。
  • cell 数量不匹配: 外设的 interrupts 属性提供的 cell 数量必须严格等于父中断控制器的 #interrupt-cells 值,否则解析会失败。
  • 循环依赖: 避免中断控制器之间形成循环的 interrupt-parent 引用,这会导致内核死锁或解析失败。

4.7 实战:编写一个完整的中断控制器节点

以下是一个基于 RISC-V PLIC(Platform-Level Interrupt Controller)的完整设备树节点示例,展示了三个属性的协同使用。

// RISC-V PLIC 中断控制器节点
plic: interrupt-controller@c000000 {
    compatible = "riscv,plic0";
    reg = <0x0 0xc000000 0x0 0x4000000>;
    interrupts-extended = <&cpu0_intc 11>,
                          <&cpu1_intc 11>;
    interrupt-controller;           // 标记为中断控制器
    #interrupt-cells = <1>;         // 每个中断用1个cell描述(中断源编号)
    riscv,ndev = <31>;              // 最大支持31个外部中断源
};

// 外设连接到 PLIC
uart0: serial@10000000 {
    compatible = "ns16550a";
    reg = <0x0 0x10000000 0x0 0x1000>;
    interrupt-parent = <&plic>;     // 连接到 PLIC
    interrupts = <10>;              // 中断源编号10
    clock-frequency = <3686400>;
};
调试技巧: 在 Linux 内核启动时,可以通过 cat /proc/interrupts 查看中断映射是否正确。如果设备的中断号显示为 0 或缺失,通常意味着 interrupt-parent 或 #interrupt-cells 配置有误。使用 dtc -I dtb -O dts 反编译设备树二进制文件,检查属性值是否符合预期。

通过本章的学习,你应该能够准确理解并配置设备树中的中断控制器属性。这三个属性共同构成了中断系统的“骨架”,为外设与中断控制器之间的通信提供了清晰的描述规范。在实际开发中,务必参考对应中断控制器的绑定文档,确保 cell 含义与硬件一致。