3. DTS 语法基础:节点、属性、标签的定义与格式

设备树源文件(.dts)是一种结构化的文本描述语言,其核心由节点(Node)、属性(Property)和标签(Label)三大要素构成。理解这三者的定义与格式,是编写和解析设备树的基础。

核心概念速览:节点描述硬件设备或总线;属性描述节点的特性(如地址、中断号);标签为节点或属性提供可引用的别名,便于在 .dtsi 文件中进行覆盖或修改。

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

节点是设备树的基本组成单元,用于表示一个硬件设备、总线、控制器或功能模块。每个节点在树形结构中都有一个唯一的路径。

3.1.1 节点定义语法

[label:] node-name [@unit-address] {
    [property-list]
    [child-node-list]
};
  • node-name:节点名称,通常为通用名称(如 uart、i2c、gpio),长度 1-31 个字符。
  • @unit-address:可选,用于区分同一层级中相同名称的节点,通常为寄存器基地址或设备编号。
  • label:可选,为节点定义一个标签,便于引用。
  • property-list:零个或多个属性定义。
  • child-node-list:零个或多个子节点定义。

3.1.2 节点示例

/ {
    #address-cells = <1>;
    #size-cells = <1>;

    uart0: serial@101f0000 {
        compatible = "ns16550";
        reg = <0x101f0000 0x1000>;
        interrupts = <5>;
        clock-frequency = <1843200>;
    };

    i2c@101f2000 {
        compatible = "snps,designware-i2c";
        reg = <0x101f2000 0x1000>;
        #address-cells = <1>;
        #size-cells = <0>;

        eeprom@50 {
            compatible = "atmel,24c02";
            reg = <0x50>;
        };
    };
};
节点路径示例:
/ — 根节点
/serial@101f0000 — UART 节点
/i2c@101f2000/eeprom@50 — I2C 总线下的 EEPROM 子节点
设备树节点层级结构示例 / (根节点) #address-cells = <1> #size-cells = <1> uart0: serial@101f0000 compatible = "ns16550" reg = <0x101f0000 0x1000> interrupts = <5> i2c@101f2000 compatible = "snps,designware-i2c" #address-cells = <1>; #size-cells = <0> eeprom@50 compatible = "atmel,24c02" reg = <0x50> 节点 属性 父子关系

3.2 属性(Property)的定义与格式

属性用于描述节点的特征和配置信息,如硬件地址、中断号、时钟频率、兼容性等。属性由键(Key)和值(Value)组成。

3.2.1 属性值类型

值类型 语法格式 示例 说明
空值 property-name; status = "disabled"; 仅表示属性存在,无具体值
字符串 property-name = "string"; compatible = "ns16550"; 双引号包裹的文本
字符串列表 property-name = "str1", "str2"; compatible = "ti,am335x-uart", "ns16550"; 逗号分隔的多个字符串
32 位整数 property-name = <value>; interrupts = <5>; 尖括号包裹,支持十进制/十六进制
整数列表 property-name = <val1 val2 ...>; reg = <0x101f0000 0x1000>; 空格分隔的多个 32 位整数
字节序列 property-name = [hex-bytes]; local-mac-address = [00 11 22 33 44 55]; 方括号包裹的十六进制字节
混合值 property-name = <int>, "str", [bytes]; example = <0x1234>, "hello", [ab cd]; 用逗号分隔不同类型

3.2.2 常用标准属性

属性名 用途 值类型
compatible 指定设备驱动兼容性,格式为 "厂商,设备型号" 字符串 / 字符串列表
reg 描述设备地址空间(基地址 + 长度) 整数列表
interrupts 描述设备使用的中断号 整数列表
status 设备状态:"okay"、"disabled"、"reserved" 字符串
#address-cells 子节点 reg 属性中地址字段占用的 32 位单元数 32 位整数
#size-cells 子节点 reg 属性中长度字段占用的 32 位单元数 32 位整数
clock-frequency 设备时钟频率(单位 Hz) 32 位整数
常见错误:属性值类型不匹配是编译错误的常见原因。例如,将字符串写为整数格式:interrupts = "5";(错误)应写为 interrupts = <5>;(正确)。

3.3 标签(Label)的定义与使用

标签是设备树中的引用机制,允许为节点或属性定义一个别名,便于在 .dtsi 文件或同一文件的其他位置进行引用和覆盖。

3.3.1 标签定义语法

label: node-name {
    // 节点内容
};

标签位于节点名称之前,以冒号分隔。标签名必须唯一,遵循 C 语言标识符命名规则(字母、数字、下划线,不能以数字开头)。

3.3.2 标签引用方式

引用方式 语法 示例 说明
&label &label { ... } &uart0 { status = "okay"; }; 引用标签对应的节点,用于追加属性或覆盖
&label 在属性值中 property = &label; clocks = &clk_uart; 引用标签作为 phandle(指针句柄)
label 在路径中 &label/path &uart0/pinctrl 引用标签节点的子路径

3.3.3 标签使用示例

// 定义标签
uart0: serial@101f0000 {
    compatible = "ns16550";
    reg = <0x101f0000 0x1000>;
    status = "disabled";
};

// 在另一个文件中通过标签引用并修改
&uart0 {
    status = "okay";
    clock-frequency = <1843200>;
};
标签的编译原理:DTC 编译器在解析 .dts 文件时,会将标签转换为 phandle(指针句柄),这是一个唯一的 32 位整数标识符。在编译后的 .dtb 文件中,标签不再存在,取而代之的是 phandle 值。通过 dtc -I dtb -O dts 反编译时,DTC 会尝试将 phandle 恢复为标签名。
标签引用与覆盖流程 uart0: serial@101f0000 status = "disabled"; reg = <0x101f0000 0x1000>; &uart0 { ... } status = "okay"; clock-frequency = <1843200>; 编译后合并结果 status = "okay"; (覆盖) reg = <0x101f0000 0x1000>; clock-frequency = <1843200>; 标签机制允许在 .dtsi 中定义默认值,在 .dts 中通过 &label 覆盖或追加属性

3.4 节点、属性、标签的完整示例

以下是一个综合示例,展示三者在实际设备树中的协作关系:

// 根节点
/ {
    model = "TI AM335x Board";
    compatible = "ti,am335x-evm", "ti,am335x";
    #address-cells = <1>;
    #size-cells = <1>;

    // 带标签的 UART 节点
    uart0: serial@44e09000 {
        compatible = "ti,am335x-uart", "ns16550";
        reg = <0x44e09000 0x2000>;
        interrupts = <72>;
        status = "disabled";
    };

    // 带标签的 GPIO 控制器
    gpio0: gpio@44e07000 {
        compatible = "ti,am335x-gpio";
        reg = <0x44e07000 0x1000>;
        interrupts = <96>;
        #gpio-cells = <2>;
        gpio-controller;
    };
};

// 通过标签引用并启用 UART
&uart0 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&uart0_pins>;
};

// 通过标签引用 GPIO 并定义引脚
&gpio0 {
    uart0_pins: pinmux_uart0_pins {
        pinctrl-single,pins = <
            0x10 0x30  // P1_0: uart0_rxd
            0x14 0x10  // P1_1: uart0_txd
        >;
    };
};
最佳实践:
  • 节点名称使用小写字母,单词间用连字符分隔(如 serial@101f0000)。
  • 标签名使用有意义的名称,如 uart0、i2c1、mmc0,便于维护。
  • 属性值类型严格匹配,避免编译错误。
  • 在 .dtsi 文件中将设备状态设为 disabled,在具体板级 .dts 文件中通过标签引用启用。

3.5 常见错误与避坑指南

错误类型 错误示例 正确写法 说明
节点名称重复 uart@0 { ... } uart@0 { ... } 使用不同 unit-address 或不同名称 同一层级节点名称+地址必须唯一
属性值类型错误 interrupts = "5"; interrupts = <5>; 中断号应为整数,非字符串
标签未定义 &undefined_label { ... } 先定义标签再引用 引用未定义标签会导致编译错误
缺少分号 status = "okay" status = "okay"; 每个属性定义必须以分号结尾
花括号不匹配 node { property; } 缺少闭合 确保每个 { 有对应的 } 使用缩进对齐可减少此类错误
调试提示:使用 dtc -I dts -O dtb -o output.dtb input.dts 2>&1 编译时,DTC 会输出详细的错误信息,包括行号和错误类型。善用编译器的错误提示可以快速定位语法问题。