3、设备树中断绑定:interrupt-parent、interrupts、interrupt-cells 属性详解
好,咱们接着聊设备树里的中断绑定。说实话,这部分是很多嵌入式工程师的“噩梦”。我见过不少同事,设备树配得飞起,但一到中断就卡壳。其实没那么玄乎,说白了就是三个属性在唱戏:interrupt-parent、interrupts 和 interrupt-cells。今天我就带你把它们彻底捋清楚。
3.1 中断控制器与中断消费者
先搞清楚两个角色:谁产生中断?谁处理中断?
- 中断控制器(Interrupt Controller):比如 GIC(通用中断控制器),它是负责接收各路中断信号,然后统一上报给 CPU 的硬件模块。
- 中断消费者(Interrupt Consumer):就是那些需要中断服务的设备,比如网卡、串口、GPIO 按键等。
在设备树里,中断控制器用 interrupt-controller 属性标记,而消费者则通过 interrupt-parent 和 interrupts 来“绑定”到某个中断控制器上。
核心思想:设备树的中断绑定,本质上就是告诉内核——“我这个设备的中断信号,连到了哪个中断控制器的哪个引脚上”。
3.2 interrupt-parent:认准你的“顶头上司”
interrupt-parent 是一个 phandle(指针),指向设备所连接的中断控制器节点。每个需要中断服务的设备,都得明确告诉内核:我的中断归谁管。
举个例子:
// 中断控制器节点
gic: interrupt-controller@f0001000 {
compatible = "arm,cortex-a7-gic";
interrupt-controller;
#interrupt-cells = <3>;
reg = <0xf0001000 0x1000>;
};
// 使用中断的设备
uart0: serial@f0002000 {
compatible = "ns16550a";
reg = <0xf0002000 0x1000>;
interrupt-parent = <&gic>; // 指向 GIC
interrupts = <0 33 4>; // 具体中断描述
};
这里 interrupt-parent = <&gic> 就表示 uart0 的中断信号连到了 gic 上。嗯,这里要注意:如果设备没有显式指定 interrupt-parent,内核会尝试向上查找父节点的 interrupt-parent。但我的建议是——别偷懒,每个设备都显式写上,可读性更好,也避免踩坑。
我的经验:我曾经在一个项目里,因为某个子节点没写 interrupt-parent,结果内核默认用了父节点的中断控制器,但父节点根本没配中断,导致中断一直不触发。排查了整整一天……从那以后,我每个设备都老老实实写上 interrupt-parent。
3.3 interrupts:描述中断的“身份证”
interrupts 属性描述的是中断的具体信息。它的格式由中断控制器的 #interrupt-cells 决定。每个 cell 代表一个 32 位整数,组合起来描述中断号、触发类型等。
常见的 #interrupt-cells 取值:
| #interrupt-cells | 含义 | 示例 |
|---|---|---|
| 1 | 只包含中断号 | interrupts = <14>; |
| 2 | 中断号 + 触发类型 | interrupts = <0 14>; |
| 3 | 中断类型 + 中断号 + 触发类型(GIC 常用) | interrupts = <0 33 4>; |
以 GIC 的 3-cell 格式为例:
- 第一个 cell:中断类型。0 表示 SPI(共享外设中断),1 表示 PPI(私有外设中断)。
- 第二个 cell:中断号。SPI 从 32 开始编号,PPI 从 16 开始。
- 第三个 cell:触发类型。1 表示上升沿触发,4 表示高电平触发。
所以 interrupts = <0 33 4> 的意思是:这是一个 SPI 中断,中断号 33,高电平触发。
注意:不同中断控制器的 cell 含义完全不同。比如 GPIO 控制器作为中断控制器时,它的 cell 可能表示 GPIO 引脚号和触发极性。一定要看对应控制器的 binding 文档。
3.4 interrupt-cells:定义中断描述的长度
#interrupt-cells 是中断控制器节点的属性,它告诉内核:我这个控制器发出的中断描述,需要几个 32 位整数。
比如 GIC 用 3 个 cell,所以:
gic: interrupt-controller@f0001000 {
#interrupt-cells = <3>;
};
而一个简单的 GPIO 中断控制器可能只需要 2 个 cell:
gpio_controller: gpio@f0003000 {
#interrupt-cells = <2>;
interrupt-controller;
};
为什么需要这个属性?说白了,内核解析设备树时,得知道从 interrupts 里读几个数才算完。没有 #interrupt-cells,内核就不知道中断描述有多长,解析就会出错。
3.5 实战:一个完整的中断绑定示例
咱们来看一个稍微复杂点的例子,把三个属性串起来:
/dts-v1/;
/ {
model = "MyBoard";
compatible = "my,board";
// 主中断控制器:GIC
gic: interrupt-controller@f0001000 {
compatible = "arm,cortex-a7-gic";
interrupt-controller;
#interrupt-cells = <3>;
reg = <0xf0001000 0x1000>;
};
// 二级中断控制器:GPIO
gpio0: gpio@f0002000 {
compatible = "my,gpio-controller";
reg = <0xf0002000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
interrupt-parent = <&gic>;
interrupts = <0 50 4>; // GPIO 控制器本身的中断连到 GIC
};
// 使用 GPIO 中断的设备:按键
button {
compatible = "my,gpio-key";
gpios = <&gpio0 5 0>; // GPIO 5,默认极性
interrupt-parent = <&gpio0>; // 按键中断连到 GPIO 控制器
interrupts = <5 2>; // GPIO 5,下降沿触发
};
};
这个例子展示了中断的层级关系:
- GIC 是顶层中断控制器,用 3-cell 格式。
- GPIO 控制器本身也是中断消费者,它连到 GIC,用
interrupts = <0 50 4>描述自己的中断。 - 按键设备连到 GPIO 控制器,用 2-cell 格式描述中断。
你想想看,这种层级结构在复杂 SoC 里非常常见。我做过一个项目,中断控制器嵌套了四层,每一层的 #interrupt-cells 都不一样。当时调试的时候,我画了个树形图才理清楚。
3.6 避坑指南
最后,分享几个我踩过的坑:
- 中断号别搞混:GIC 的 SPI 中断号从 32 开始,但有些 SoC 的 TRM 里写的是“中断号 33”,实际上对应设备树里的 33。但也有厂商从 0 开始编号,一定要看 binding 文档。
- 触发类型别写错:电平触发和边沿触发,在 GIC 里用不同的值表示。写错了要么中断不触发,要么一直触发(电平没变)。
- 别忘了 interrupt-controller 属性:中断控制器节点必须加上
interrupt-controller;,否则内核不认。 - #interrupt-cells 必须和 interrupts 匹配:比如 GIC 用 3-cell,你写
interrupts = <33 4>,内核解析会错位,导致中断号完全不对。
总结一下:interrupt-parent 指明方向,interrupts 描述细节,#interrupt-cells 定义格式。三个属性配合好,中断绑定就稳了。
这张图把关系理得很清楚了。中断控制器在最上层,多个设备通过 interrupt-parent 指向它,每个设备用 interrupts 描述自己的中断细节。嗯,就是这么回事。
公众号:蓝海资料掘金营,微信deep3321