3、DTS语法基础:节点、属性、值与标签引用

好,咱们今天来啃DTS的语法基础。说实话,这部分内容看着简单,但我在项目里见过太多人在这里栽跟头了。你想想看,设备树说白了就是一个描述硬件信息的树形结构,那它的基本组成单元就是节点、属性和值。搞懂了这三个东西,你就能看懂大部分DTS文件了。

3.1 节点(Node)的定义与格式

节点是什么?说白了就是树上的一个“挂点”。每个节点代表一个硬件设备或者一个总线控制器。比如CPU是一个节点,I2C控制器是一个节点,挂在I2C总线上的温度传感器也是一个节点。

节点的格式很简单:

node-name {
    property1 = <value1>;
    property2 = "string_value";
    child-node {
        child-property = <value>;
    };
};

嗯,这里要注意几点:

  • 节点名:一般用设备类型加上@地址的形式,比如 uart@fe010000
  • 花括号:节点内容用花括号包裹,最后用分号结束
  • 层级关系:节点可以嵌套,子节点缩进表示层级

重要提醒:节点名中的@地址部分不是必须的,但强烈建议加上。我在调试一个板子时,发现两个同名节点没加地址,结果编译报错,查了半天才发现是节点名冲突了。

3.2 属性(Property)与值(Value)

属性就是节点的“特征描述”。每个属性都有一个名字和一个值。值的格式有好几种,我给大家列个表:

值类型 格式 示例 说明
空值 属性名; interrupt-controller; 表示该节点是一个中断控制器
32位整数 <数字> reg = <0x10000000 0x1000>; 尖括号包裹,多个值用空格隔开
64位整数 <高32位 低32位> reg = <0x0 0x10000000 0x0 0x1000>; 两个32位拼成一个64位地址
字符串 "字符串" compatible = "ti,am335x-gpio"; 双引号包裹
字节数组 [十六进制] local-mac-address = [00 11 22 33 44 55]; 方括号包裹,每个字节两个十六进制数
混合值 组合使用 property = <0x1234>, "string", [0xAB]; 逗号分隔不同类型

个人经验:我建议初学者先记住整数和字符串两种格式,因为90%的场景都用这两个。字节数组我用的不多,但做网卡驱动时遇到过——MAC地址就是用字节数组存的。

3.3 标签(Label)与引用(&)

标签是什么?说白了就是给节点起个“别名”。为什么要用标签?因为有时候节点名很长,比如 i2c@44e0b000,每次引用都要写全名,太麻烦了。而且,如果你在多个地方引用同一个节点,万一节点名改了,你所有引用都得改——这谁受得了?

标签的用法:

// 定义标签
i2c0: i2c@44e0b000 {
    compatible = "ti,omap4-i2c";
    reg = <0x44e0b000 0x1000>;
    #address-cells = <1>;
    #size-cells = <0>;
    
    // 子节点也可以有标签
    eeprom: eeprom@50 {
        compatible = "atmel,24c02";
        reg = <0x50>;
    };
};

// 在其他地方引用
&i2c0 {
    status = "okay";
    clock-frequency = <400000>;
};

// 引用子节点
&eeprom {
    pagesize = <16>;
};

你看,标签用冒号跟在节点名前面。引用时用&符号加上标签名。这样写的好处是:

  • 解耦:节点定义和节点修改可以分开写
  • 复用:同一个节点可以在多个地方追加属性
  • 可读性:标签名可以用有意义的名称,比如i2c0、uart1、mmc0

避坑指南:我曾经在一个项目里,把标签名写成了数字开头,比如1st-i2c。结果编译直接报错。记住,标签名必须以字母或下划线开头,不能以数字开头。另外,标签名不能包含特殊字符,只能用字母、数字和下划线。

3.4 实战:一个完整的节点示例

咱们来看一个实际项目中用到的节点。这是我之前做的一个AM335x板子的GPIO控制器节点:

gpio0: gpio@44e07000 {
    compatible = "ti,omap4-gpio";
    reg = <0x44e07000 0x1000>;
    interrupts = <96>;
    gpio-controller;
    #gpio-cells = <2>;
    interrupt-controller;
    #interrupt-cells = <2>;
    ti,hwmods = "gpio1";
    status = "disabled";
};

这个节点里包含了:

  • 标签:gpio0,方便其他地方引用
  • 节点名:gpio@44e07000,表示GPIO控制器,地址是0x44e07000
  • compatible属性:字符串,告诉内核用哪个驱动
  • reg属性:两个32位整数,表示基地址和地址范围
  • interrupts属性:中断号
  • 空值属性:gpio-controller和interrupt-controller,表示这个节点是GPIO控制器和中断控制器
  • #gpio-cells和#interrupt-cells:表示引用时需要几个参数
  • status属性:字符串,表示设备状态,disabled表示禁用

嗯,这里有个小技巧。你看status属性,我习惯在开发阶段先设为disabled,等确认硬件没问题了再改成okay。这样万一硬件有问题,系统启动时不会因为加载这个驱动而卡住。

3.5 知识体系图

下面这张图总结了DTS语法的核心结构,我画出来方便大家理解:

DTS语法核心结构 根节点 / 子节点: cpu@0, i2c@44e0b000, ... 属性 (Property) 与值 (Value) 空值属性 interrupt-controller; 整数属性 reg = <0x1000>; 字符串属性 compatible = "..."; 标签 (Label): i2c0: i2c@44e0b000 { ... } 引用 (&): &i2c0 { status = "okay"; };

这张图把DTS的层级关系讲清楚了。从根节点出发,下面挂各种子节点,每个节点又包含若干属性。标签和引用则是在这个结构上加了“快捷方式”,方便我们在不同地方操作同一个节点。

我的建议:刚开始学DTS时,别急着写复杂的节点。先拿一个简单的设备树文件,比如arch/arm/boot/dts/am335x-boneblack.dts,对照着看。先找根节点,再看子节点,然后看每个节点有哪些属性。看个三五遍,语法自然就熟了。

好了,DTS语法基础就讲到这里。记住三个核心:节点是骨架,属性是血肉,标签和引用是工具。搞懂了这些,你就能读懂大部分DTS文件了。


公众号:蓝海资料掘金营,微信deep3321