4. 设备树语法精讲(二):标准属性之compatible、model、status

好,咱们接着聊设备树的标准属性。上一讲我们把设备树的基本骨架搭起来了,这一讲要往骨架上填肉——也就是那些每个节点几乎都会用到的标准属性。

我个人习惯把标准属性分成两类:一类是「身份证」属性,告诉内核你是谁、你是什么;另一类是「状态」属性,告诉内核你现在能不能干活。compatible、model、status 正好各占一头。

4.1 compatible:驱动匹配的钥匙

compatible 属性,说白了就是设备与驱动之间的「接头暗号」。内核通过它来找到哪个驱动能伺候这个设备。

它的格式很简单:一个字符串列表,每个字符串描述一个兼容的设备型号。

compatible = "vendor,device", "fallback_device";

注意顺序很重要。最前面的是最精确的型号,后面的是兼容的型号。内核会按顺序尝试匹配驱动。

举个例子,我在项目中调过一款恩智浦的 I2C 控制器,设备树里是这样写的:

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

这里「fsl,imx6q-i2c」是精确匹配,如果内核里没有对应的驱动,就会尝试用「fsl,imx21-i2c」这个通用驱动来驱动它。嗯,这就是向后兼容的设计思路。

命名规范:vendor 部分一般是芯片厂商的缩写,比如 fsl(飞思卡尔)、ti(德州仪器)、allwinner(全志)。device 部分用芯片型号或 IP 核名称。中间用逗号隔开,不要用下划线或空格。

我曾经踩过一个坑:写 compatible 时把厂商名写成了「freescale」而不是「fsl」,结果驱动死活匹配不上。内核里驱动用的是「fsl,imx6q-i2c」,我写的是「freescale,imx6q-i2c」,对不上。查了两天才发现是命名不一致。所以一定要去内核源码里确认驱动用的字符串。

4.2 model:设备的自我介绍

model 属性比 compatible 简单得多。它就是一个字符串,描述设备的具体型号。比如:

model = "Freescale i.MX6 Quad SABRE Smart Device Board";

你可能会问:有了 compatible 为什么还要 model?

原因很简单:compatible 是给内核匹配驱动用的,它关心的是「这个设备能用哪个驱动」。而 model 是给人看的,它描述的是「这个板子具体叫什么名字」。

举个例子,同样是使用 i.MX6Q 芯片的板子,可能有十几种不同的型号。compatible 可能都是「fsl,imx6q-sabresd」,但 model 可以区分是「SABRE Lite」还是「SABRE Auto」。

属性 用途 匹配对象
compatible 驱动匹配 内核驱动
model 设备标识 用户/调试

我个人习惯在根节点里必写 model,这样 dmesg 里一眼就能看出当前跑的是哪个板子。调试时特别有用。

4.3 status:设备的开关

status 属性控制设备是否启用。它的取值不多,但每个都有明确的含义:

  • okay:设备正常启用
  • disabled:设备禁用
  • fail:设备检测到错误
  • fail-sss:带错误码的失败状态

最常见的用法是在 dtsi 文件里把设备默认设为 disabled,然后在具体的板级 dts 文件里改成 okay。比如:

// imx6q.dtsi 中
&uart1 {
    status = "disabled";
};

// myboard.dts 中
&uart1 {
    status = "okay";
    pinctrl-0 = <&pinctrl_uart1>;
};

这种设计的好处是:SoC 的 dtsi 文件可以一次性定义所有外设,板级文件只需要按需开启即可。不用每个板子都重新写一遍完整的节点。

小技巧:调试时如果某个外设不工作,先检查 status 是不是 okay。我遇到过好几次,同事把 status 写成 "ok"(少了个 a),内核直接忽略了这个设备。嗯,少一个字母都不行。

4.4 三个属性的配合使用

在实际项目中,这三个属性经常一起出现。我给大家看一个完整的例子:

/ {
    model = "MyCustomBoard v2.0";
    compatible = "mycompany,myboard", "fsl,imx6q";

    chosen {
        stdout-path = &uart1;
    };

    soc {
        uart1: serial@21e8000 {
            compatible = "fsl,imx6q-uart", "fsl,imx21-uart";
            reg = <0x021e8000 0x4000>;
            interrupts = <0 26 IRQ_TYPE_LEVEL_HIGH>;
            status = "okay";
        };

        gpio@209c000 {
            compatible = "fsl,imx6q-gpio", "fsl,imx35-gpio";
            reg = <0x0209c000 0x4000>;
            interrupts = <0 66 IRQ_TYPE_LEVEL_HIGH>;
            status = "disabled";
        };
    };
};

你看,根节点用 model 告诉人这是什么板子,用 compatible 告诉内核该用哪个平台驱动。每个外设节点用 compatible 匹配具体驱动,用 status 控制开关。分工明确,各司其职。

4.5 避坑指南

最后分享几个我实际项目中遇到的坑:

  1. compatible 字符串大小写敏感:Linux 内核是大小写敏感的,写错了就匹配不上。我习惯全部小写。
  2. status 不要写错:除了 "okay" 和 "disabled",其他值内核可能不认识。别写 "enable" 或 "on"。
  3. model 不是必须的:但建议写上。调试时 grep dmesg 找 model 字符串,比翻原理图快多了。
  4. 多个 compatible 的顺序:最精确的放最前面。内核匹配到第一个就停了,不会继续往后找。

嗯,这一讲的内容就这些。compatible、model、status 这三个属性虽然简单,但用好了能让设备树清晰很多。下一讲我们聊聊地址相关的属性——reg、ranges、dma-ranges,这些才是真正考验功力的地方。