3. DTS语法入门:DTS文件基本结构、节点(node)与属性(property)、标签(label)与引用(&)

设备树源文件(.dts)是描述硬件拓扑的领域特定语言(DSL)。理解其语法是排查DTB加载失败与驱动异常的基础。本章从文件骨架、节点/属性定义、标签与引用三个维度展开,配合内联SVG结构图与表格,帮助您建立完整的语法认知。

3.1 DTS文件基本结构

一个标准的DTS文件由根节点、子节点、属性和包含指令组成。其顶层结构如下:

/dts-v1/;

#include <dt-bindings/gpio/gpio.h>

/ {
    model = "TI AM335x BeagleBone Black";
    compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";
    #address-cells = <1>;
    #size-cells = <1>;

    chosen {
        stdout-path = &uart0;
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x10000000>;  /* 256 MB */
    };

    leds {
        pinctrl-names = "default";
        pinctrl-0 = <&led_pins>;

        led0 {
            label = "beaglebone:green:usr0";
            gpios = <&gpio1 21 GPIO_ACTIVE_HIGH>;
            linux,default-trigger = "heartbeat";
        };
    };
};
关键点: /dts-v1/; 声明DTS版本,必须位于文件第一行。#include 用于引入头文件(宏定义、绑定常量),与C语言预处理共享机制。

下表总结了DTS文件的组成要素:

要素 语法示例 说明
版本声明 /dts-v1/; 必须位于文件首行,标识使用v1语法
包含指令 #include "file.dtsi" 引入公共SoC定义或板级覆盖
根节点 / { ... }; 设备树的顶层容器,所有设备挂载其下
子节点 uart0: serial@44e09000 { ... }; 描述具体外设或总线,可嵌套
属性 reg = <0x44e09000 0x2000>; 键值对,描述地址、中断、GPIO等硬件参数
实践提示: 使用 dtc -I dts -O dtb -o output.dtb input.dts 编译DTS为DTB。若语法错误,dtc会报告行号与错误类型,是排查加载失败的第一道防线。
DTS文件结构层次图 根节点 / chosen memory@80000000 leds serial@44e09000 led0 属性示例: compatible = "ti,am335x-bone-black"; 属性示例: reg = <0x80000000 0x10000000>; 属性示例: gpios = <&gpio1 21 0>; 节点可嵌套,属性描述硬件参数;标签(如 uart0:)用于跨节点引用

3.2 节点(node)与属性(property)

3.2.1 节点定义规则

节点是设备树的核心单元,每个节点代表一个硬件设备、总线或功能块。节点名称格式为 [label:] name[@unit-address]:

  • label(可选):全局唯一标识符,用于跨文件引用,如 uart0:。
  • name:节点名称,描述设备类型,如 serial、gpio。
  • @unit-address(可选):设备在总线上的基地址,用于区分同名节点,如 serial@44e09000。
常见错误: 节点名称不能包含空格、#、? 等特殊字符。地址部分必须使用小写十六进制,且与 reg 属性中的首地址一致,否则dtc编译会报“node name unit address mismatch”警告。

3.2.2 属性类型与语法

属性是节点的键值对,值可以是以下五种类型之一:

类型 语法 示例 用途
字符串 "string" compatible = "ti,am335x-bone-black"; 设备兼容性标识
32位整数 <value> #address-cells = <1>; 地址/大小描述、时钟频率等
64位整数 <0x00000000 0xvalue> reg = <0x00000000 0x80000000>; 64位地址空间(高32位+低32位)
字节数组 [hex bytes] local-mac-address = [00 11 22 33 44 55]; MAC地址、UUID等二进制数据
布尔值 仅属性名,无值 status = "disabled"; 或 status; 启用/禁用设备(空属性表示true)
// 综合属性示例
uart0: serial@44e09000 {
    compatible = "ti,omap3-uart";       // 字符串
    reg = <0x44e09000 0x2000>;         // 32位整数对(地址+长度)
    interrupts = <72>;                  // 32位整数
    clocks = <&uart0_clk>;             // 引用(phandle)
    pinctrl-0 = <&uart0_pins>;         // 引用列表
    status = "okay";                    // 字符串状态
};
排查要点: 驱动通过 of_match_table 匹配 compatible 属性。若驱动加载失败,首先检查DTS中 compatible 字符串是否与驱动代码完全一致(包括大小写和逗号后空格)。
节点与属性关系图 节点:uart0: serial@44e09000 标签: uart0 | 名称: serial | 地址: 44e09000 字符串属性 compatible = "ti,omap3-uart"; status = "okay"; 整数属性 reg = <0x44e09000 0x2000>; interrupts = <72>; #address-cells = <1>; 引用属性 clocks = <&uart0_clk>; pinctrl-0 = <&uart0_pins>; (phandle引用) 属性值类型决定驱动解析方式:of_property_read_string / of_property_read_u32 / of_parse_phandle 属性名不能以数字开头,不能包含大写字母(推荐全小写+连字符)

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

3.3.1 标签定义与作用

标签(label)是附加在节点名称前的全局标识符,格式为 label: node-name。标签的主要用途:

  • 跨文件引用:在 .dtsi 中定义节点,在 .dts 中通过 &label 追加属性或覆盖。
  • 属性内引用:在 clocks、gpios、interrupt-parent 等属性中指向其他节点。
  • 编译时符号:dtc将标签编译为 phandle(指针句柄),驱动通过 of_find_node_by_phandle() 解析。
// 定义标签
uart0: serial@44e09000 {
    compatible = "ti,omap3-uart";
    reg = <0x44e09000 0x2000>;
};

// 引用标签(追加属性)
&uart0 {
    pinctrl-0 = <&uart0_pins>;
    status = "okay";
};

// 在属性中引用其他节点
gpio_keys {
    compatible = "gpio-keys";
    button@0 {
        label = "user_button";
        gpios = <&gpio1 15 GPIO_ACTIVE_LOW>;  // 引用 gpio1 节点
    };
};

3.3.2 引用操作符 & 的三种场景

场景 语法 编译效果 典型用途
追加属性 &label { property = value; }; 在标签对应节点中增加或覆盖属性 板级DTS覆盖SoC DTSI中的默认配置
属性值引用 clocks = <&label>; 生成phandle(32位整数),指向标签节点 时钟、中断控制器、GPIO控制器引用
属性值列表引用 pinctrl-0 = <&label1 &label2>; 生成phandle数组 多个pinctrl状态、多时钟源
调试技巧: 使用 dtc -I dtb -O dts -o decompiled.dts boot.dtb 反编译DTB,查看phandle是否被正确解析。若看到 phandle = <0x00000001>; 但缺少对应标签名,说明引用链可能断裂。

3.3.3 标签与phandle的编译映射

dtc编译时,每个标签会被分配一个唯一的32位phandle值。驱动通过 of_parse_phandle() 或 of_find_node_by_phandle() 将phandle还原为节点指针。映射规则如下:

  • 标签名在编译后丢失,DTB中只保留phandle数值。
  • 反编译时,dtc会尝试根据phandle值重建标签名(格式为 label_xx)。
  • 若多个标签指向同一节点,dtc会合并为同一个phandle。
标签与引用编译流程 DTS源码 uart0: serial@44e09000 { compatible = "..."; }; &uart0 { status = "okay"; }; dtc 编译器 标签 → phandle 引用 → phandle值 DTB 二进制 节点 serial@44e09000 { phandle = <0x00000001>; compatible = "..."; status = "okay"; 驱动运行时:of_parse_phandle(np, "clocks", 0) 根据phandle值查找节点,返回 struct device_node *

3.4 常见语法错误与排查

以下是在DTS编写和编译过程中最常遇到的语法问题:

错误类型 错误示例 dtc报错信息 解决方案
缺少分号 compatible = "foo" ERROR: syntax error 每条属性末尾加 ;
节点名地址不匹配 serial@44e09000 { reg = <0x44e00000 ...>; } WARNING: node name unit address mismatch 确保 @ 后地址与 reg 首地址一致
标签重复 两个节点使用相同标签 ERROR: Duplicate label 'uart0' 确保标签全局唯一
引用未定义标签 &undefined_label { ... }; WARNING: Reference to non-existent node or label 检查标签名拼写或是否在 .dtsi 中定义
属性值类型错误 reg = "0x44e09000"; ERROR: Expected integer, got string 地址使用 <> 包裹,字符串使用 ""
严重警告: 标签重复或引用未定义标签不会导致DTB编译失败(仅警告),但会导致驱动在运行时无法正确解析phandle,表现为 of_parse_phandle() 返回NULL,设备无法初始化。务必在编译时检查所有警告信息。

3.5 本章小结

DTS语法是设备树编程的基石,掌握以下要点可有效减少加载与驱动异常:

  • 文件结构:版本声明 + 包含指令 + 根节点 + 子节点 + 属性,层次清晰。
  • 节点定义:[label:] name[@unit-address],地址与 reg 对齐。
  • 属性类型:字符串、整数、字节数组、布尔值、引用(phandle),驱动解析方式不同。
  • 标签与引用:&label 用于追加属性或生成phandle,编译后标签名丢失。
  • 编译检查:使用 dtc -Werror 将警告升级为错误,避免运行时隐患。