4. 设备树中断属性详解:interrupt-parent、interrupts、interrupt-names、interrupt-controller
在嵌入式 Linux 驱动开发中,中断是外设与 CPU 进行异步通信的核心机制。设备树(Device Tree)通过一组标准属性来描述硬件中断拓扑,驱动开发者必须准确理解这些属性才能正确申请和使用中断。本节将逐一剖析 interrupt-parent、interrupts、interrupt-names 和 interrupt-controller 四个关键属性,并结合实际案例说明其配置方法。
interrupts 属性声明自己需要的中断线,通过 interrupt-parent 指向提供中断服务的中断控制器。
4.1 interrupt-controller:声明中断控制器节点
interrupt-controller 是一个空属性(无值),用于标记某个节点为中断控制器。只有被标记为中断控制器的节点才能被其他节点通过 interrupt-parent 引用。通常,SoC 内部的 GIC(Generic Interrupt Controller)或 GPIO 控制器都会声明此属性。
此外,中断控制器节点还需要通过 #interrupt-cells 属性指定其 interrupts 属性中需要多少个 cell(32 位整数)来描述一条中断。例如,ARM GICv2 通常使用 3 个 cell:中断类型(SPI / PPI)、中断号、触发方式。
// ARM GIC 中断控制器节点示例
gic: interrupt-controller@f0001000 {
compatible = "arm,cortex-a9-gic";
reg = <0xf0001000 0x1000>,
<0xf0000100 0x0100>;
interrupt-controller; // 标记为中断控制器
#interrupt-cells = <3>; // 每个中断描述需要 3 个 cell
};
interrupt-controller 属性,则它不能作为 interrupt-parent 的目标。驱动代码通过 irq_of_parse_and_map() 解析设备树中断时,内核会自动沿 interrupt-parent 链向上查找中断控制器。
4.2 interrupt-parent:指定中断控制器
interrupt-parent 属性用于指定当前节点使用哪个中断控制器提供的中断线。其值为一个 phandle(指向中断控制器节点的指针)。如果某个节点没有显式声明 interrupt-parent,则继承其父节点的 interrupt-parent,直到根节点。
在驱动代码中,内核通过 of_irq_parse_one() 函数沿着 interrupt-parent 链向上查找,最终找到中断控制器并解析中断描述符。
// 外设节点显式指定 interrupt-parent
uart0: serial@10000000 {
compatible = "ns16550";
reg = <0x10000000 0x1000>;
interrupt-parent = <&gic>; // 指向 GIC 中断控制器
interrupts = <0 33 4>; // SPI 类型,中断号 33,高电平触发
};
// 子节点未指定 interrupt-parent,继承父节点
i2c@20000000 {
compatible = "snps,designware-i2c";
reg = <0x20000000 0x1000>;
interrupt-parent = <&gic>; // 父节点指定
interrupts = <0 45 4>;
touch@1a {
compatible = "edt,ft5x06";
reg = <0x1a>;
// 未指定 interrupt-parent,继承 i2c 节点的 &gic
interrupts = <0 46 4>;
};
};
interrupt-parent 指向的节点没有声明 interrupt-controller 属性,内核在解析中断时会报错并返回无效中断号。务必确保 interrupt-parent 的 phandle 指向正确的中断控制器节点。
4.3 interrupts:描述中断线
interrupts 属性是一个 u32 数组,用于描述设备需要的中断线。每个中断由若干个 cell 组成,cell 的数量由中断控制器的 #interrupt-cells 属性决定。常见的 cell 编码方式如下:
| 中断控制器类型 | #interrupt-cells | cell 含义 | 示例 |
|---|---|---|---|
| ARM GICv2 | 3 | cell[0]: 中断类型 (0=SPI, 1=PPI) cell[1]: 中断号 cell[2]: 触发方式 (1=上升沿, 4=高电平) |
<0 33 4> |
| ARM GICv3 | 3 | 与 GICv2 类似,但 SPI 中断号范围扩展 | <0 100 4> |
| GPIO 控制器 | 2 | cell[0]: GPIO 引脚号 cell[1]: 触发标志 (低电平/高电平/上升沿/下降沿) |
<12 2> |
| TI OMAP 中断控制器 | 2 | cell[0]: 中断线号 cell[1]: 触发类型 |
<56 1> |
// 多个中断描述示例
ethernet@30000000 {
compatible = "smsc,lan9115";
reg = <0x30000000 0x10000>;
interrupt-parent = <&gic>;
interrupts = <0 67 4>; // 一个中断:SPI 67,高电平触发
};
// 支持多个中断的外设
sound@40000000 {
compatible = "fsl,imx6q-sai";
reg = <0x40000000 0x4000>;
interrupt-parent = <&gic>;
interrupts = <0 97 4>, <0 98 4>; // 两个中断:TX 和 RX
interrupt-names = "tx", "rx"; // 为每个中断命名
};
4.4 interrupt-names:为中断线命名
interrupt-names 属性是一个字符串列表,为 interrupts 属性中的每个中断线指定一个名称。当外设拥有多个中断时,命名可以显著提高驱动代码的可读性和可维护性。驱动可以通过名称而不是索引来获取对应的中断号。
// 使用 interrupt-names 的多中断设备
dma@50000000 {
compatible = "ti,edma3";
reg = <0x50000000 0x10000>;
interrupt-parent = <&gic>;
interrupts = <0 12 4>, <0 13 4>, <0 14 4>;
interrupt-names = "ch0", "ch1", "err"; // 分别为三个中断命名
};
// 驱动代码中通过名称获取中断
static int dma_probe(struct platform_device *pdev)
{
int irq_ch0, irq_ch1, irq_err;
// 通过名称获取中断号,更直观
irq_ch0 = platform_get_irq_byname(pdev, "ch0");
irq_ch1 = platform_get_irq_byname(pdev, "ch1");
irq_err = platform_get_irq_byname(pdev, "err");
if (irq_ch0 < 0 || irq_ch1 < 0 || irq_err < 0) {
dev_err(&pdev->dev, "failed to get IRQs\n");
return -EINVAL;
}
// 注册中断处理函数
request_irq(irq_ch0, dma_ch0_handler, 0, "dma_ch0", dev);
request_irq(irq_ch1, dma_ch1_handler, 0, "dma_ch1", dev);
request_irq(irq_err, dma_err_handler, 0, "dma_err", dev);
return 0;
}
interrupt-names 时,驱动应优先使用 platform_get_irq_byname() 而非 platform_get_irq()。这样即使设备树中中断的顺序发生变化,驱动代码也无需修改,增强了兼容性。
4.5 综合示例:完整的中断配置
以下是一个完整的设备树中断配置示例,展示了中断控制器、外设节点和驱动代码之间的协作关系:
// 设备树文件(.dts)
/ {
#address-cells = <1>;
#size-cells = <1>;
// 中断控制器
gic: interrupt-controller@f0001000 {
compatible = "arm,cortex-a9-gic";
reg = <0xf0001000 0x1000>,
<0xf0000100 0x0100>;
interrupt-controller;
#interrupt-cells = <3>;
};
// 外设节点
uart1: serial@10001000 {
compatible = "ns16550";
reg = <0x10001000 0x1000>;
interrupts = <0 35 4>; // SPI 35,高电平触发
interrupt-parent = <&gic>; // 引用 GIC
clock-frequency = <24000000>;
};
// 带多个中断的外设
mmc0: mmc@20000000 {
compatible = "fsl,imx6q-usdhc";
reg = <0x20000000 0x4000>;
interrupts = <0 56 4>, <0 57 4>;
interrupt-names = "card", "dma";
interrupt-parent = <&gic>;
bus-width = <8>;
};
};
// 驱动代码(简化)
static int uart_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int irq;
// 获取中断号
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
// 申请中断
ret = devm_request_irq(dev, irq, uart_isr, 0, "uart1", port);
if (ret) {
dev_err(dev, "failed to request IRQ %d\n", irq);
return ret;
}
return 0;
}
4.6 属性对比总结
| 属性名 | 类型 | 作用 | 是否必需 | 使用场景 |
|---|---|---|---|---|
interrupt-controller |
空属性 | 标记节点为中断控制器 | 中断控制器节点必需 | GIC、GPIO 控制器、INTC 等 |
#interrupt-cells |
u32 | 指定 interrupts 属性中每个中断的 cell 数 | 中断控制器节点必需 | 与 interrupt-controller 配合使用 |
interrupt-parent |
phandle | 指定当前节点使用的中断控制器 | 外设节点推荐显式指定 | 所有需要中断的外设节点 |
interrupts |
u32 数组 | 描述中断线(类型、号、触发方式) | 外设节点必需 | 所有需要中断的外设节点 |
interrupt-names |
string 列表 | 为 interrupts 中的每个中断命名 | 可选(多中断时推荐) | 多中断外设(DMA、MMC、音频等) |
#interrupt-cells 编码规则,然后对照设备树绑定文档(Documentation/devicetree/bindings/interrupt-controller/)进行配置。驱动代码中优先使用 devm_request_irq() 和 platform_get_irq_byname() 以简化资源管理。