4. 设备树中断控制器:interrupt-parent、interrupts、interrupt-controller 属性与中断映射机制

中断是嵌入式系统响应异步事件的核心机制。在设备树中,中断的描述不再依赖板级代码的硬编码,而是通过标准化的属性与映射逻辑,将硬件中断控制器、中断引脚与设备驱动解耦。本章深入解析设备树中断模型的三个核心属性——interrupt-parent、interrupts、interrupt-controller,并详细阐述中断映射的完整流程。

4.1 中断控制器与设备树角色

在设备树中,中断控制器是负责接收、管理并分发中断信号的硬件单元。一个 SoC 可能包含多个中断控制器(如 GIC、INTC、GPIO 中断扩展)。设备树通过以下角色区分:

  • 中断生产者(Interrupt Producer):发起中断请求的设备节点,如 UART、I2C、DMA。
  • 中断消费者(Interrupt Consumer):接收并处理中断的驱动,通常与生产者是同一设备。
  • 中断控制器(Interrupt Controller):负责路由、优先级管理、使能/屏蔽中断的节点。
关键概念: 设备树中的中断描述是“声明式”的。驱动只需声明设备使用哪个中断引脚,中断控制器如何连接由设备树覆盖和映射机制自动解析,驱动代码无需关心物理中断号与 CPU 中断号之间的转换细节。

4.2 核心属性详解

4.2.1 interrupt-controller 属性

该属性是一个布尔属性,标注在中断控制器节点上,声明该节点是一个中断控制器。没有该属性的节点不会被内核视为中断路由的终点。

// 示例:GIC 中断控制器节点
gic: interrupt-controller@f0001000 {
    compatible = "arm,cortex-a15-gic";
    reg = <0x0 0xf0001000 0x0 0x1000>;
    interrupt-controller;          // 声明本节点为中断控制器
    #interrupt-cells = <3>;        // 每个中断描述占用 3 个 cell
};

4.2.2 #interrupt-cells 属性

该属性定义中断控制器节点中,每个中断描述需要多少个 32 位整数(cell)来描述。不同中断控制器类型有不同的编码格式:

中断控制器类型 #interrupt-cells cell 含义
ARM GIC v2/v3 3 中断类型(0=SPI, 1=PPI)、中断号、触发类型与极性
RISC-V PLIC 1 中断源序号
GPIO 控制器作为中断控制器 2 GPIO 引脚号、触发类型
Xilinx INTC 2 中断序号、触发类型

4.2.3 interrupt-parent 属性

该属性指定当前设备节点连接到的中断控制器节点的 phandle。如果设备节点没有显式定义 interrupt-parent,则继承其父节点的 interrupt-parent,直到根节点。根节点通常不设置该属性,由板级设备树覆盖指定。

// 设备节点显式指定中断父控制器
uart0: serial@f0002000 {
    compatible = "ns16550a";
    reg = <0x0 0xf0002000 0x0 0x1000>;
    interrupt-parent = <&gic>;      // 连接到 GIC 中断控制器
    interrupts = <0 33 4>;          // SPI 类型,中断号 33,高电平触发
};

4.2.4 interrupts 属性

该属性包含一个或多个中断描述,每个描述由 #interrupt-cells 指定数量的 cell 组成。多个中断描述之间用空格分隔。驱动可以通过 platform_get_irq() 或 of_irq_get() 按索引获取。

// 多中断描述示例:DMA 控制器使用两个中断
dma: dma-controller@f0003000 {
    compatible = "my,dma-controller";
    reg = <0x0 0xf0003000 0x0 0x1000>;
    interrupt-parent = <&gic>;
    interrupts = <0 45 4>,          // 中断索引 0:传输完成
                 <0 46 4>;          // 中断索引 1:错误中断
};

4.3 中断映射机制

中断映射是设备树将设备节点的 interrupts 属性转换为 Linux 内核 IRQ 号的过程。映射流程分为三个层级:

  1. 设备树解析层:内核遍历设备节点,读取 interrupt-parent 找到中断控制器节点。
  2. 中断控制器域映射层:调用中断控制器的 .xlate 回调函数,将设备树 cell 转换为硬件中断号(hwirq)和触发类型。
  3. IRQ 域分配层:通过 irq_domain 将 hwirq 映射到 Linux 全局 IRQ 号,并注册中断处理函数。
映射核心数据结构: struct irq_domain 是中断映射的枢纽。每个中断控制器对应一个 irq_domain,负责维护 hwirq 到 Linux IRQ 的映射表。设备树覆盖时,如果中断控制器节点发生变化,irq_domain 会重新创建或更新映射。
设备树中断映射三层架构 第一层:设备树解析层 读取设备节点 interrupts 属性 通过 interrupt-parent 找到中断控制器节点 第二层:中断控制器域映射层 调用 .xlate 回调:cell → hwirq + trigger type 校验中断类型、极性、有效性 第三层:IRQ 域分配层 irq_domain 分配 Linux IRQ 号,注册中断处理函数 设备树覆盖时,若中断控制器节点变更,irq_domain 会重新创建映射表

4.4 中断映射的代码实现路径

内核中中断映射的核心函数调用链如下:

// 设备树中断映射核心路径(Linux 内核源码)
of_irq_parse_one()          // 解析设备节点 interrupts 属性
  └─ of_irq_parse_raw()     // 根据 interrupt-parent 找到中断控制器
      └─ irq_create_of_mapping()  // 创建中断映射
          └─ irq_domain_translate()   // 调用 .xlate 回调
              └─ irq_domain_alloc_irqs()  // 分配 Linux IRQ 号
常见陷阱: 如果设备节点没有设置 interrupt-parent,且父节点也没有继承关系,内核会报错“no irq domain found”。在设备树覆盖时,如果新增设备节点,必须确保其 interrupt-parent 指向一个已存在的、带有 interrupt-controller 属性的节点。

4.5 多级中断控制器与级联映射

实际系统中,中断控制器可能形成级联结构。例如,GPIO 控制器作为一级中断控制器,其输出连接到 GIC 的某个中断输入。设备树通过 interrupts 属性描述这种级联关系:

// GPIO 控制器作为中断控制器,其自身输出连接到 GIC
gpio0: gpio@f0004000 {
    compatible = "my,gpio-controller";
    reg = <0x0 0xf0004000 0x0 0x1000>;
    gpio-controller;
    #gpio-cells = <2>;
    interrupt-controller;          // GPIO 也是中断控制器
    #interrupt-cells = <2>;
    interrupt-parent = <&gic>;     // GPIO 控制器输出连接到 GIC
    interrupts = <0 50 4>;         // GPIO 控制器使用 GIC 中断号 50
};

// 外部设备连接到 GPIO 中断引脚
button@0 {
    compatible = "my,gpio-key";
    gpios = <&gpio0 15 0>;
    interrupt-parent = <&gpio0>;   // 中断父控制器是 GPIO
    interrupts = <15 2>;           // GPIO 引脚 15,下降沿触发
};

级联映射时,内核会递归解析:先通过 GPIO 控制器的 irq_domain 将 <15 2> 映射为 GPIO 域的中断号,再通过 GIC 的 irq_domain 将 GPIO 控制器的输出中断映射为 CPU 可见的 IRQ 号。

多级中断控制器级联映射 外部设备 interrupts = <15 2> GPIO 中断控制器 irq_domain 映射 hwirq=15 #interrupt-cells = <2> GIC 中断控制器 irq_domain 映射 hwirq=50 #interrupt-cells = <3> 级联映射流程 ① 设备树解析:设备 → GPIO 控制器(interrupt-parent = &gpio0) ② GPIO 域映射:hwirq=15 → GPIO 域中断号 ③ GIC 域映射:GPIO 输出中断 hwirq=50 → 全局 IRQ 号 设备树覆盖时,级联关系通过 interrupt-parent 链自动维护

4.6 中断触发类型与极性编码

中断触发类型在 interrupts 属性的最后一个 cell 中编码。不同中断控制器可能使用不同的编码方式,但 ARM GIC 的编码是业界最通用的参考:

触发类型 GIC cell 值 描述
上升沿触发 1 IRQ_TYPE_EDGE_RISING
下降沿触发 2 IRQ_TYPE_EDGE_FALLING
高电平触发 4 IRQ_TYPE_LEVEL_HIGH
低电平触发 8 IRQ_TYPE_LEVEL_LOW
驱动优化建议: 在设备树覆盖中,如果硬件设计变更导致中断触发类型变化(例如从电平触发改为边沿触发),只需修改设备树中 interrupts 属性的最后一个 cell 值,驱动代码无需重新编译。这是设备树覆盖相比硬编码中断配置的最大优势。

4.7 设备树覆盖中的中断注意事项

  • 中断控制器节点必须存在:覆盖新增设备节点时,其 interrupt-parent 引用的中断控制器节点必须在基设备树或覆盖层中已定义。
  • 中断号冲突检测:内核在映射时会检测 hwirq 是否已被占用,如果冲突会返回 -EBUSY。覆盖时需确保新增设备的中断号不与现有设备冲突。
  • 中断控制器覆盖:如果覆盖层修改了中断控制器的 #interrupt-cells,所有引用该控制器的设备节点必须同步更新其 interrupts 属性格式。
  • 级联中断控制器覆盖:修改级联中断控制器的输出中断号时,需要同时更新该控制器自身的 interrupts 属性和其父控制器的映射关系。
调试技巧: 使用 cat /proc/interrupts 查看当前系统中所有中断的映射情况。如果设备树覆盖后设备中断不工作,首先检查 /proc/interrupts 中是否出现了预期的中断号,以及中断触发次数是否在增加。如果没有出现,则问题可能出在设备树解析或中断映射阶段。

4.8 本章小结

属性/概念 作用 关键约束
interrupt-controller 声明节点为中断控制器 必须与 #interrupt-cells 配合使用
#interrupt-cells 定义中断描述需要的 cell 数量 不同控制器类型有不同编码约定
interrupt-parent 指定设备连接的中断控制器 可继承父节点,覆盖时必须显式指定
interrupts 描述设备使用的中断引脚与触发类型 格式必须与父控制器的 #interrupt-cells 一致
中断映射机制 将设备树中断描述转换为 Linux IRQ 号 通过 irq_domain 三层映射实现

掌握设备树中断控制器的属性与映射机制,是编写正确、可移植的嵌入式 Linux 驱动的基础。在设备树覆盖场景下,中断配置的灵活性和可维护性得到极大提升,驱动开发者应充分利用这一特性,减少对板级代码的依赖。