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。
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";
};
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
>;
};
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配置是否正确。