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):负责路由、优先级管理、使能/屏蔽中断的节点。
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 号的过程。映射流程分为三个层级:
- 设备树解析层:内核遍历设备节点,读取
interrupt-parent找到中断控制器节点。 - 中断控制器域映射层:调用中断控制器的
.xlate回调函数,将设备树 cell 转换为硬件中断号(hwirq)和触发类型。 - IRQ 域分配层:通过 irq_domain 将 hwirq 映射到 Linux 全局 IRQ 号,并注册中断处理函数。
struct irq_domain 是中断映射的枢纽。每个中断控制器对应一个 irq_domain,负责维护 hwirq 到 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 号。
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 驱动的基础。在设备树覆盖场景下,中断配置的灵活性和可维护性得到极大提升,驱动开发者应充分利用这一特性,减少对板级代码的依赖。