2. 设备树语法入门:节点、属性、标签与路径

设备树(Device Tree)是一种描述硬件资源的数据结构,它使用树形层次结构将系统中的CPU、内存、外设(如SPI控制器、I2C控制器、GPIO等)组织起来。本章将系统讲解设备树源文件(.dts)的核心语法要素:节点(node)、属性(property)、标签(label)与路径(path),为后续编写SPI与I2C设备节点打下坚实基础。

学习提示:设备树语法与C语言结构体初始化类似,但更强调层次嵌套和地址映射。掌握节点与属性的关系是理解设备树的关键。

2.1 节点(Node):设备树的骨架

节点是设备树的基本组成单元,每个节点代表一个硬件设备、总线或控制器。节点通过花括号 { } 定义,内部可以包含属性(描述设备特征)和子节点(描述下级设备)。

节点定义的基本格式:

节点名 {
    属性名 = 属性值;
    子节点名 {
        子节点属性;
    };
};

节点命名规则:

  • 节点名由小写字母、数字、连字符(-)和下划线(_)组成。
  • 节点名应反映设备类型,如 spi-controller、i2c-bus、gpio-keys。
  • 同一层级下节点名必须唯一。
  • 根节点固定为 /,所有设备节点都挂在根节点下。
示例:一个简单的设备树根节点与SPI控制器节点
/ {
    model = "Embedded Development Board";
    compatible = "vendor,board-v1";

    spi@ff110000 {
        compatible = "vendor,spi-controller";
        reg = <0xff110000 0x1000>;
        #address-cells = <1>;
        #size-cells = <0>;
        status = "okay";

        spi-device@0 {
            compatible = "vendor,spi-sensor";
            reg = <0>;
            spi-max-frequency = <5000000>;
        };
    };
};
设备树节点层次结构 根节点 / cpu@0 memory@40000000 spi@ff110000 device@0 device@1 device@2 节点层次说明 根节点 → 总线节点 → 设备节点(叶子节点)

2.2 属性(Property):描述设备特征

属性是附着在节点上的键值对,用于描述设备的硬件特征、配置参数和驱动绑定信息。每个属性由属性名和属性值组成,属性值可以是多种数据类型。

属性值的数据类型:

数据类型 语法格式 说明 示例
字符串 "string" 双引号包裹的文本 compatible = "vendor,device";
32位整数 <value> 尖括号包裹,支持十进制/十六进制 reg = <0xff110000 0x1000>;
64位整数 <0x00000000 0xvalue> 两个32位整数拼接表示64位 reg = <0x00000000 0x80000000>;
字节数组 [hex bytes] 方括号包裹的十六进制字节序列 local-mac-address = [00 11 22 33 44 55];
布尔值 仅属性名,无值 存在即为真,不存在为假 status = "disabled"; 或 status;
字符串列表 "str1", "str2" 逗号分隔的多个字符串 compatible = "vendor,spi-device", "generic,spi";

常用标准属性:

  • compatible:驱动匹配的关键属性,格式为 "厂商,设备型号"。
  • reg:描述设备地址空间,格式为 <基地址 长度>。
  • #address-cells 和 #size-cells:定义子节点中 reg 属性的地址和长度字段宽度。
  • status:设备状态,可选 "okay"、"disabled"、"reserved"。
  • interrupts:描述设备使用的中断号。
常见错误:忘记设置 #address-cells 和 #size-cells 会导致子节点 reg 解析错误。对于SPI总线,通常设置为 <1> 和 <0>;对于I2C总线,通常设置为 <1> 和 <0>。

2.3 标签(Label):节点的别名引用

标签是节点的别名,用于在设备树的其他位置引用该节点。标签定义在节点名之前,使用冒号 : 分隔。标签的主要用途包括:

  • 在设备树包含文件(.dtsi)中覆盖节点属性。
  • 在驱动代码中通过 of_find_node_by_label() 查找节点。
  • 在 phandle 引用中作为目标标识。

标签定义语法:

标签名: 节点名 {
    属性;
};

示例:

spi0: spi@ff110000 {
    compatible = "vendor,spi-controller";
    reg = <0xff110000 0x1000>;
    #address-cells = <1>;
    #size-cells = <0>;
};

i2c0: i2c@ff120000 {
    compatible = "vendor,i2c-controller";
    reg = <0xff120000 0x1000>;
    #address-cells = <1>;
    #size-cells = <0>;
};

标签引用方式:

  • 直接引用:使用 &标签名 在另一个节点中引用。
  • 覆盖属性:在 .dts 文件中通过 &标签名 { ... } 覆盖 .dtsi 中的节点属性。
// 在 board.dts 中覆盖 spi0 的属性
&spi0 {
    status = "okay";
    max-frequency = <50000000>;
};

// 在驱动代码中引用
struct device_node *np = of_find_node_by_label("spi0");
标签引用与属性覆盖流程 soc.dtsi(基础定义) spi0: spi@ff110000 { compatible = "..."; reg = <0xff110000 0x1000>; status = "disabled"; #address-cells = <1>; #size-cells = <0>; }; board.dts(板级覆盖) &spi0 { status = "okay"; max-frequency = <50000000>; spi-device@0 { compatible = "vendor,sensor"; reg = <0>; }; }; 编译结果 spi0 节点 status = "okay" max-frequency + 子设备节点 属性合并覆盖 标签机制允许在 .dtsi 中定义基础节点,在 .dts 中通过 &标签名 进行属性覆盖和扩展

2.4 路径(Path):节点的唯一地址

路径是从根节点到目标节点的完整层级描述,使用斜杠 / 分隔各级节点名。路径是设备树中定位节点的绝对地址,类似于文件系统中的文件路径。

路径格式:

/根节点名/中间节点名/目标节点名

路径示例:

/soc/spi@ff110000/spi-device@0
/soc/i2c@ff120000/i2c-device@1a

路径的用途:

  • 在设备树源文件中:使用 &{/路径} 语法引用节点。
  • 在驱动代码中:使用 of_find_node_by_path() 函数查找节点。
  • 在设备树覆盖(DT Overlay)中:通过路径指定目标节点。

路径与标签的对比:

特性 路径(Path) 标签(Label)
定位方式 绝对层级定位 别名引用
可读性 路径较长,可读性一般 简短易记,可读性好
唯一性 全局唯一 全局唯一(需避免重复)
适用场景 驱动代码查找、覆盖文件 属性覆盖、phandle引用
语法示例 &{/soc/spi@ff110000} &spi0
最佳实践:在 .dts 文件中优先使用标签进行属性覆盖,因为标签更简洁且不易出错。在驱动代码中,如果节点没有标签,则使用路径查找。

2.5 节点地址单元与大小单元

#address-cells 和 #size-cells 是总线节点中至关重要的属性,它们定义了子节点 reg 属性的地址和长度字段的宽度(以32位字为单位)。

规则说明:

  • #address-cells:指定子节点 reg 中地址字段占用的32位字数量。
  • #size-cells:指定子节点 reg 中长度字段占用的32位字数量。
  • 如果子节点没有 reg 属性,则这两个属性可以省略。

常见配置:

总线类型 #address-cells #size-cells reg 格式示例
SPI 总线 1 0 reg = <片选号>;
I2C 总线 1 0 reg = <设备地址>;
内存映射总线 1 1 reg = <基地址 长度>;
64位地址总线 2 2 reg = <高32位 低32位 高32位 低32位>;

SPI与I2C设备节点示例:

// SPI 控制器节点
spi0: spi@ff110000 {
    compatible = "vendor,spi-controller";
    reg = <0xff110000 0x1000>;
    #address-cells = <1>;   // 地址字段占1个32位字(片选号)
    #size-cells = <0>;      // 长度字段为0(无长度信息)
    status = "okay";

    // SPI 从设备:片选号0
    adc@0 {
        compatible = "vendor,spi-adc";
        reg = <0>;           // 片选号0
        spi-max-frequency = <10000000>;
    };

    // SPI 从设备:片选号1
    sensor@1 {
        compatible = "vendor,spi-sensor";
        reg = <1>;           // 片选号1
        spi-max-frequency = <5000000>;
    };
};

// I2C 控制器节点
i2c0: i2c@ff120000 {
    compatible = "vendor,i2c-controller";
    reg = <0xff120000 0x1000>;
    #address-cells = <1>;   // 地址字段占1个32位字(7位或10位I2C地址)
    #size-cells = <0>;      // 长度字段为0
    status = "okay";

    // I2C 设备:地址0x1a
    eeprom@1a {
        compatible = "vendor,i2c-eeprom";
        reg = <0x1a>;        // I2C 从设备地址
        pagesize = <64>;
    };

    // I2C 设备:地址0x48
    temperature@48 {
        compatible = "vendor,i2c-temp";
        reg = <0x48>;        // I2C 从设备地址
    };
};
SPI与I2C地址单元配置对比 SPI 总线节点 #address-cells = <1>; #size-cells = <0>; 子节点 reg 格式: reg = <片选号>; → 例如 reg = <0>; 片选号范围:0 ~ (spi-num-cs - 1) I2C 总线节点 #address-cells = <1>; #size-cells = <0>; 子节点 reg 格式: reg = <设备地址>; → 例如 reg = <0x1a>; 设备地址范围:0x03 ~ 0x77(7位地址)

2.6 节点合并与覆盖规则

设备树编译器(DTC)在编译时会自动合并相同路径的节点。这一特性允许在 .dtsi 文件中定义基础节点,在 .dts 文件中通过相同路径或标签引用进行属性补充和覆盖。

合并规则:

  1. 相同路径的节点自动合并:如果两个节点路径完全相同,编译器会将它们的属性合并到一个节点中。
  2. 属性覆盖:如果两个节点定义了同名属性,后定义的属性值覆盖先定义的属性值。
  3. 子节点合并:子节点也会按照路径规则递归合并。
  4. 标签引用覆盖:使用 &标签名 { ... } 语法,编译器会将花括号内的内容合并到标签对应的节点中。

覆盖示例:

// soc.dtsi - 基础定义
spi0: spi@ff110000 {
    compatible = "vendor,spi-controller";
    reg = <0xff110000 0x1000>;
    status = "disabled";
    #address-cells = <1>;
    #size-cells = <0>;
};

// board.dts - 板级覆盖
#include "soc.dtsi"

&spi0 {
    status = "okay";
    max-frequency = <50000000>;

    adc@0 {
        compatible = "vendor,spi-adc";
        reg = <0>;
        spi-max-frequency = <10000000>;
    };
};

// 编译后等效于:
spi0: spi@ff110000 {
    compatible = "vendor,spi-controller";
    reg = <0xff110000 0x1000>;
    status = "okay";                    // 被覆盖
    #address-cells = <1>;
    #size-cells = <0>;
    max-frequency = <50000000>;        // 新增属性

    adc@0 {                             // 新增子节点
        compatible = "vendor,spi-adc";
        reg = <0>;
        spi-max-frequency = <10000000>;
    };
};
开发技巧:将SoC级别的通用定义放在 .dtsi 文件中,板级特定的配置(如SPI设备、I2C设备、GPIO引脚复用)放在 .dts 文件中。这样可以在不同板卡之间复用SoC定义,只需修改板级文件即可。

2.7 本章小结

本章详细介绍了设备树语法的四个核心要素:

  • 节点(Node):设备树的骨架,通过树形层次结构组织硬件设备。
  • 属性(Property):描述设备特征的键值对,支持多种数据类型。
  • 标签(Label):节点的别名,用于简化引用和属性覆盖。
  • 路径(Path):节点的绝对地址,用于精确定位。