4. 设备树中断控制器:interrupt-parent、interrupt-cells、interrupt-map 配置与排查
中断,是嵌入式系统里最核心的通信机制之一。设备树里的中断描述,说白了就是告诉内核:谁产生了中断,中断信号走哪条路,最终送到哪个处理单元。我见过太多工程师在中断配置上栽跟头——要么系统不响应中断,要么中断风暴把 CPU 打满。今天咱们就把这三个关键属性彻底讲透。
4.1 中断控制器基础:interrupt-parent 与 interrupt-cells
先看一个最简单的例子。假设你的 SoC 里有一个 GPIO 控制器,它本身也是一个中断控制器——GPIO 引脚可以产生中断。
gpio0: gpio@ff000000 {
compatible = "my-soc,gpio";
reg = <0xff000000 0x1000>;
interrupt-controller;
#interrupt-cells = <2>;
interrupts = <0 15 4>;
interrupt-parent = <&gic>;
};
这里有几个关键点:
- interrupt-controller:空属性,声明这个节点是一个中断控制器。
- #interrupt-cells:描述该中断控制器需要几个 cell 来描述一条中断。比如 2 个 cell 通常表示:中断类型(上升沿、下降沿等)+ 中断号。
- interrupt-parent:指向该节点自身的中断控制器——也就是谁给这个 GPIO 控制器提供中断服务。
- interrupts:描述该节点自身需要的中断。这里用了 3 个 cell,因为 GIC(通用中断控制器)定义了 3 个 cell。
核心原则:每个需要中断服务的设备节点,必须通过 interrupt-parent 指向它的中断控制器,并用 interrupts 描述具体的中断信号。
我在项目中遇到过一种情况:有人把 interrupt-parent 写成了自己节点的 phandle,结果内核在解析中断时死循环了。你想想看,一个 GPIO 控制器怎么可能给自己发中断?
4.2 中断映射:interrupt-map 的复杂场景
当系统里有多个中断控制器,或者需要做中断路由时,interrupt-map 就派上用场了。最常见的是 PCIe 总线场景——PCIe 设备的中断需要通过桥接芯片映射到 SoC 的中断控制器。
pcie@f0000000 {
compatible = "my-soc,pcie";
reg = <0xf0000000 0x1000000>;
#interrupt-cells = <1>;
interrupt-map-mask = <0xf800 0 0 7>;
interrupt-map = <
0x0000 0 0 1 &pcie_intc 0 1
0x0000 0 0 2 &pcie_intc 1 1
0x0000 0 0 3 &pcie_intc 2 1
0x0000 0 0 4 &pcie_intc 3 1
>;
pcie_intc: interrupt-controller {
interrupt-controller;
#interrupt-cells = <2>;
};
};
这个配置看起来有点绕,我拆开讲:
- #interrupt-cells:PCIe 总线域里,每个设备用 1 个 cell 表示中断引脚(INTA#、INTB#、INTC#、INTD#)。
- interrupt-map-mask:掩码,用于匹配设备地址和中断引脚。这里
0xf800 0 0 7表示只匹配高 5 位地址和低 3 位中断号。 - interrupt-map:映射表。每一行格式:
子设备地址 子设备中断 父中断控制器 phandle 父中断描述。
我的调试技巧:当 interrupt-map 不生效时,先检查 interrupt-map-mask 是否覆盖了所有需要匹配的位。我曾经因为掩码写错,导致所有 PCIe 设备都映射到了同一个中断上——系统直接崩溃。
4.3 中断描述格式:不同控制器的 cell 定义
不同的中断控制器,#interrupt-cells 的含义完全不同。我整理了一个对照表:
| 中断控制器类型 | #interrupt-cells | cell 含义 |
|---|---|---|
| GIC(ARM 通用中断控制器) | 3 | 中断类型(0=SPI, 1=PPI)、中断号、触发方式(4=高电平, 8=上升沿) |
| 简单 GPIO 中断控制器 | 2 | GPIO 引脚号、触发方式(0=低电平, 1=高电平, 2=上升沿, 3=下降沿) |
| PCIe 中断控制器 | 1 | 中断引脚编号(1=INTA, 2=INTB, 3=INTC, 4=INTD) |
| Xilinx AXI INTC | 2 | 中断号、触发方式(0=低电平, 1=高电平, 2=上升沿, 3=下降沿) |
嗯,这里要注意:触发方式的编码不是统一的。GIC 用 4 表示高电平,但 GPIO 控制器可能用 1 表示高电平。一定要看对应控制器的 binding 文档。
4.4 常见排查场景与实战案例
我挑三个最常踩的坑,每个都附上排查方法。
场景一:中断号不匹配
症状:设备注册成功,但中断从不触发。用 cat /proc/interrupts 看不到该设备的中断计数。
排查步骤:
- 检查设备节点的
interrupts属性,确认中断号是否正确。 - 用
dtc -I dtb -O dts反编译 DTB,查看实际解析后的值。 - 对比 SoC 数据手册里的中断号映射表——很多 SoC 的中断号是从 0 还是从 32 开始,容易搞混。
我曾经踩过的坑:某款 SoC 的 GIC 文档里写 SPI 中断号从 0 开始,但实际设备树里需要加 32 的偏移。我查了整整两天,最后发现是参考手册和实际硬件不一致。
场景二:interrupt-parent 指向错误
症状:内核启动时打印 irq: no irq domain found for ...。
原因:interrupt-parent 指向的节点没有声明 interrupt-controller,或者 phandle 写错了。
排查方法:
- 用
dtc检查 phandle 是否有效:dtc -O dts -I dtb -o /tmp/test.dts boot.dtb && grep interrupt-parent /tmp/test.dts。 - 确认目标节点确实有
interrupt-controller;属性。 - 检查是否有多个节点使用了相同的 phandle——这会导致内核解析混乱。
场景三:interrupt-map 映射失败
症状:PCIe 设备枚举成功,但中断分配失败,dmesg 里出现 irq: irq domain search failed。
排查步骤:
- 确认
interrupt-map-mask是否覆盖了设备地址和中断引脚的所有有效位。 - 检查映射表里的父中断控制器 phandle 是否正确。
- 验证父中断控制器的
#interrupt-cells与映射表里提供的 cell 数量一致。
我的调试习惯:在 interrupt-map 里只写一条映射做测试,确认通路后再补全其他条目。这样能快速定位是映射逻辑问题还是硬件问题。
4.5 知识体系总览
下面这张图总结了中断控制器配置的核心逻辑:
这张图把整个中断配置流程串起来了。从设备节点出发,通过 interrupt-parent 找到中断控制器,再根据 #interrupt-cells 解析中断描述。遇到复杂路由时,interrupt-map 负责做地址和中断的转换。
4.6 调试工具与命令
最后分享几个我常用的调试命令:
- 查看中断分配:
cat /proc/interrupts—— 看每个中断的触发次数和 CPU 亲和性。 - 查看设备树解析结果:
ls /proc/device-tree/配合cat查看具体属性值。 - 动态调试中断域:
echo 'file irqdomain.c +p' > /sys/kernel/debug/dynamic_debug/control—— 打开内核中断域调试信息。 - 反编译 DTB:
dtc -I dtb -O dts -o /tmp/debug.dts /sys/firmware/fdt—— 查看实际加载的设备树。
重要提醒:修改设备树后一定要重新编译并确认 DTB 被正确加载。我见过有人改了 .dts 文件但忘记编译,结果调试了一整天——最后发现内核用的还是旧 DTB。
中断配置说难不难,说简单也不简单。核心就是搞清楚三个问题:谁产生中断?中断怎么描述?中断路由到哪? 把这三个问题想明白,再配合上面的排查方法,大部分中断问题都能快速定位。