设备树中断绑定规范:中断描述的核心三要素
各位同学,今天我们来聊聊设备树里中断的描述方式。说实话,中断这块是设备树配置里最容易出问题的地方之一。我在早期做驱动开发时,就曾因为 interrupt-parent 写错,折腾了整整一个下午。嗯,咱们今天就把这块彻底讲透。
一、中断描述的基本框架
设备树里描述中断,说白了就三个核心属性:interrupt-parent、interrupts 和 interrupt-cells。你想想看,一个设备要使用中断,至少得告诉系统三件事:
- 中断控制器是谁?—— 由 interrupt-parent 指定
- 用哪条中断线?—— 由 interrupts 属性描述
- 中断描述格式是什么?—— 由 interrupt-cells 定义
这三者缺一不可。我见过不少新手,只写了 interrupts 却忘了 interrupt-parent,结果中断死活不触发。其实内核在解析时,如果找不到 interrupt-parent,会默认往父节点找,但这样容易出问题,我建议还是显式写清楚。
二、interrupt-parent:指向你的中断控制器
interrupt-parent 是一个 phandle,指向设备所连接的中断控制器节点。什么意思呢?就是告诉系统:「我的中断信号是连到那个中断控制器上的」。
重要原则:每个使用中断的设备节点,要么自己定义 interrupt-parent,要么继承父节点的 interrupt-parent。
来看个实际例子:
/ {
interrupt-parent = &&gic; // 根节点指定默认中断控制器
uart0: serial@10000000 {
compatible = "arm,pl011";
reg = <0x10000000 0x1000>;
interrupt-parent = &&gic; // 可以显式指定
interrupts = <0 33 4>;
};
gpio-keys {
compatible = "gpio-keys";
interrupt-parent = &&gpio1; // 也可以指向GPIO控制器
interrupts = <5 2>;
};
};
这里有个坑,我曾经踩过:如果一个 SoC 有多个中断控制器(比如 GIC 和 GPIO 控制器),每个设备必须明确指定自己连的是哪个。否则内核会沿着设备树向上查找,找到的第一个 interrupt-parent 可能不是你想要的。
三、interrupts:描述中断的具体信息
interrupts 属性是一个 u32 数组,具体包含几个数字,由中断控制器的 #interrupt-cells 决定。常见的格式有:
| 中断控制器类型 | #interrupt-cells | interrupts 格式 |
|---|---|---|
| ARM GIC (通用中断控制器) | 3 | <中断类型 中断号 触发方式> |
| 简单中断控制器 | 1 | <中断号> |
| GPIO 控制器作为中断源 | 2 | <GPIO引脚 触发方式> |
以 ARM GIC 为例,interrupts = <0 33 4> 的意思是:
- 第一个数 0:中断类型。0 表示 SPI(共享外设中断),1 表示 PPI(私有外设中断)
- 第二个数 33:中断号。对于 SPI,中断号从 32 开始算,所以 33 实际对应硬件中断号 65
- 第三个数 4:触发方式。1=上升沿触发,2=下降沿触发,4=高电平触发,8=低电平触发
我的小技巧:记不住触发方式的数值?没关系,内核头文件里定义了宏:IRQ_TYPE_EDGE_RISING = 1,IRQ_TYPE_LEVEL_HIGH = 4。我习惯在设备树里直接写数字,但会在注释里标明触发类型。
四、#interrupt-cells:定义中断描述的长度
#interrupt-cells 是在中断控制器节点中定义的,它告诉系统:我这个控制器需要几个 u32 来描述一个中断。这个值直接决定了 interrupts 属性的格式。
看个 GIC 控制器的定义:
gic: interrupt-controller@f0001000 {
compatible = "arm,gic-400";
reg = <0xf0001000 0x1000>,
<0xf0002000 0x2000>;
interrupt-controller; // 标记这是一个中断控制器
#interrupt-cells = <3>; // 每个中断用3个cell描述
#address-cells = <0>;
};
这里 interrupt-controller; 是个空属性,它的存在就表示「我是中断控制器」。而 #interrupt-cells = <3> 则声明了中断描述格式。
注意:#interrupt-cells 和 interrupt-parent 是不同节点上的属性。前者在中断控制器节点,后者在使用中断的设备节点。我见过有人把这两个搞混,在设备节点里写 #interrupt-cells,那肯定是不对的。
五、实际配置中的常见问题
讲到这里,我想分享几个实际项目中遇到的坑:
- 中断号偏移问题:不同厂商的 GIC 实现,中断号的起始值可能不同。有的从 0 开始,有的从 32 开始。我建议仔细看芯片手册的「Interrupt Mapping Table」。
- 中断类型搞错:SPI 和 PPI 的编号范围不同。SPI 是共享中断,多个 CPU 都能处理;PPI 是私有中断,只发给特定 CPU。把 SPI 写成 PPI,中断可能收不到。
- 触发方式不匹配:设备实际输出的是电平触发,你在设备树里配成边沿触发,那中断可能只触发一次,或者根本触发不了。
避坑指南:我曾经在一个项目中,GPIO 按键的中断总是丢失。排查了半天,发现是设备树里写的是上升沿触发,但硬件实际输出的是低电平有效。改成 level-low 后问题解决。所以,拿到硬件原理图后,一定要先确认中断信号的电气特性。
六、总结一下
中断绑定的三个核心属性,其实就对应了三个问题:
- interrupt-parent:谁管我的中断?
- interrupts:我用哪条中断线?怎么触发?
- #interrupt-cells:中断描述格式长什么样?
这三个属性配合好了,中断配置就成功了一大半。下一章我们会深入讲解 GIC 控制器的具体配置,包括 SPI、PPI 的区别,以及如何正确计算中断号。到时候我会拿一个实际项目的设备树文件来拆解,保证让你看得明明白白。
好,今天就到这里。有什么问题,咱们课程群里聊。