3. 设备树常用属性:compatible、reg、interrupts、status等属性的含义与用法

好,咱们今天聊聊设备树里最常用的一批属性。说实话,我刚接触设备树那会儿,看着满屏的compatiblereginterrupts,心里也犯嘀咕——这些到底怎么用?后来踩了不少坑,才慢慢摸清楚门道。

你想想看,设备树本质上就是个描述硬件的数据结构。那描述硬件,总得告诉内核几个关键信息吧:你是谁?你在哪?你怎么通知我?你现在能不能用?嗯,这四个问题,正好对应我们今天要讲的四个属性。

3.1 compatible:你是谁?

compatible属性,说白了就是设备的身份证。内核通过它来匹配对应的驱动。

它的格式很简单:

compatible = "厂商,型号";

举个例子:

uart0: serial@10000000 {
    compatible = "fsl,imx6q-uart", "fsl,imx21-uart";
    reg = <0x10000000 0x4000>;
    interrupts = <0 26 4>;
    status = "okay";
};

这里有个细节我特别想强调:兼容列表是有优先级的。内核会从左到右依次尝试匹配驱动。第一个匹配上了就用第一个,匹配不上再试第二个。

重要原则:越具体的型号放前面,越通用的放后面。这样既能精确匹配,又能保证兼容性。

我在项目中遇到过一件事:有个同事把compatible写反了顺序,结果内核总是匹配到通用驱动,导致一些特殊功能没法用。查了两天才发现是顺序问题。嗯,从那以后我每次写compatible都会多看一眼顺序。

3.2 reg:你在哪?

reg属性描述设备的地址空间信息。它告诉内核:你的寄存器在哪个地址范围。

格式是这样的:

reg = <地址1 长度1 地址2 长度2 ...>;

每个地址+长度对,就是一个地址区域。一个设备可以有多个地址区域。

看个实际例子:

i2c@21a0000 {
    compatible = "fsl,imx6q-i2c", "fsl,imx21-i2c";
    reg = <0x021a0000 0x4000>;
    interrupts = <0 36 4>;
    status = "okay";
};

这里reg = <0x021a0000 0x4000>表示:I2C控制器的寄存器从0x021a0000开始,占用0x4000字节的空间。

小技巧:我习惯在写reg之前,先翻翻芯片手册,确认寄存器基地址和范围。千万别凭感觉写,我见过有人把长度写错,导致驱动访问到别人的地址空间,系统直接崩溃。

你可能会问:为什么要有多个地址区域?举个例子,一个网卡设备,可能有控制寄存器区、数据缓冲区区、配置寄存器区。每个区域独立描述,驱动用起来更清晰。

3.3 interrupts:你怎么通知我?

设备要跟CPU通信,总不能一直轮询吧?太浪费CPU了。所以就有了中断机制。interrupts属性就是描述设备用哪个中断号、怎么触发中断的。

格式:

interrupts = <中断号 触发类型>;

触发类型常见的有:

含义
0 高电平触发
1 低电平触发
2 上升沿触发
3 下降沿触发
4 高电平触发(带中断控制器)

看个例子:

gpio@20a0000 {
    compatible = "fsl,imx6q-gpio", "fsl,imx21-gpio";
    reg = <0x020a0000 0x4000>;
    interrupts = <0 66 4>;
    status = "okay";
};

这里interrupts = <0 66 4>表示:中断号66,高电平触发。第一个0是中断控制器编号(通常为0),66是中断号,4是触发类型。

注意:中断号的编号方式因芯片而异。有的芯片用物理中断号,有的用逻辑中断号。我建议你写之前,先确认一下芯片手册里的中断映射表。我曾经因为搞混了中断号,调试了整整一个下午。

3.4 status:你现在能用吗?

status属性控制设备的启用状态。它只有几个取值:

含义
"okay" 设备可用,驱动会加载
"disabled" 设备禁用,驱动不会加载
"reserved" 保留,通常用于特殊场景
"fail" 设备检测失败

这个属性在板级适配时特别有用。比如一个芯片支持两个I2C控制器,但你的板子上只用了I2C1,那I2C2就可以设成disabled

&i2c1 {
    status = "okay";
};

&i2c2 {
    status = "disabled";
};

最佳实践:我个人的习惯是,在dtsi(芯片级设备树)里把所有外设都设成disabled,然后在dts(板级设备树)里按需打开。这样既清晰又安全,不会因为忘记关闭某个外设而出问题。

3.5 综合示例:一个完整的设备节点

好了,我们把上面四个属性串起来,看一个完整的例子:

/* 一个完整的UART设备节点 */
uart1: serial@21e8000 {
    compatible = "fsl,imx6q-uart", "fsl,imx21-uart";
    reg = <0x021e8000 0x4000>;
    interrupts = <0 27 4>;
    status = "okay";
    
    /* 其他可选属性 */
    clock-frequency = <24000000>;
    fsl,uart-has-rtscts;
};

这个节点告诉内核:

  • 我是飞思卡尔i.MX6Q的UART(兼容i.MX21的UART驱动)
  • 我的寄存器在0x021e8000,占16KB空间
  • 我用中断号27,高电平触发
  • 我现在是启用状态

避坑指南:我曾经在调试一个项目时,发现UART驱动死活不工作。查了半天,原来是status写成了"ok"而不是"okay"。内核只认"okay",少一个字母都不行。这种小错误,真的让人抓狂。

3.6 其他常用属性

除了上面四个,还有几个属性也经常用到:

  • clocks:描述设备的时钟源
  • resets:描述设备的复位信号
  • pinctrl:描述设备的引脚复用配置
  • dmas:描述设备的DMA通道

这些属性我们后面会专门讲,今天先有个印象就行。

3.7 小结

嗯,今天的内容就到这里。总结一下:

  • compatible:设备身份证,驱动匹配的关键
  • reg:设备地址,告诉内核寄存器在哪
  • interrupts:中断配置,设备怎么通知CPU
  • status:设备状态,控制启用还是禁用

这四个属性,是设备树里最基础也最重要的。你写设备树的时候,每个节点基本都离不开它们。下次我们聊聊更高级的属性,比如clockspinctrl,这些在实际项目中更常用。

有什么问题,欢迎随时交流。咱们下节课见。