设备树中断绑定规范:中断描述的核心三要素

各位同学,今天我们来聊聊设备树里中断的描述方式。说实话,中断这块是设备树配置里最容易出问题的地方之一。我在早期做驱动开发时,就曾因为 interrupt-parent 写错,折腾了整整一个下午。嗯,咱们今天就把这块彻底讲透。

一、中断描述的基本框架

设备树里描述中断,说白了就三个核心属性:interrupt-parentinterruptsinterrupt-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,那肯定是不对的。

五、实际配置中的常见问题

讲到这里,我想分享几个实际项目中遇到的坑:

  1. 中断号偏移问题:不同厂商的 GIC 实现,中断号的起始值可能不同。有的从 0 开始,有的从 32 开始。我建议仔细看芯片手册的「Interrupt Mapping Table」。
  2. 中断类型搞错:SPI 和 PPI 的编号范围不同。SPI 是共享中断,多个 CPU 都能处理;PPI 是私有中断,只发给特定 CPU。把 SPI 写成 PPI,中断可能收不到。
  3. 触发方式不匹配:设备实际输出的是电平触发,你在设备树里配成边沿触发,那中断可能只触发一次,或者根本触发不了。

避坑指南:我曾经在一个项目中,GPIO 按键的中断总是丢失。排查了半天,发现是设备树里写的是上升沿触发,但硬件实际输出的是低电平有效。改成 level-low 后问题解决。所以,拿到硬件原理图后,一定要先确认中断信号的电气特性。

六、总结一下

中断绑定的三个核心属性,其实就对应了三个问题:

  • interrupt-parent:谁管我的中断?
  • interrupts:我用哪条中断线?怎么触发?
  • #interrupt-cells:中断描述格式长什么样?

这三个属性配合好了,中断配置就成功了一大半。下一章我们会深入讲解 GIC 控制器的具体配置,包括 SPI、PPI 的区别,以及如何正确计算中断号。到时候我会拿一个实际项目的设备树文件来拆解,保证让你看得明明白白。

好,今天就到这里。有什么问题,咱们课程群里聊。