3. DTS语法入门:节点、属性、标签、路径

设备树源文件(.dts)使用一种树形结构来描述硬件,其核心语法围绕四个基本概念展开:节点(Node)、属性(Property)、标签(Label)和路径(Path)。掌握这四个要素是编写和理解设备树的基础。

核心认知:设备树本质上是一个键-值数据库,节点是层级目录,属性是具体的键值对,标签是节点的别名,路径是节点的唯一地址。

3.1 节点(Node)—— 设备树的骨架

节点是设备树的基本组成单元,代表一个硬件设备、总线或控制器。节点可以嵌套,形成父子层级关系。

3.1.1 节点定义语法

node-name {
    property1 = <value>;
    property2 = "string";
    child-node {
        ...
    };
};
  • 节点名称:由字母、数字、连字符(-)、逗号(,)和点(.)组成,必须以字母开头。
  • 节点单元地址:在节点名后可以跟 @ 加上地址(如 uart@fe001000),用于区分同一类型的不同实例。
  • 根节点:整个设备树的起点,用 / 表示,所有其他节点都是它的后代。
实战要点:节点名应尽量使用标准名称(如 uart、i2c、spi),@ 后的地址通常与寄存器基地址或设备编号一致,便于调试时快速定位。
设备树节点层级结构 / (根节点) soc@0 chosen memory@80000000 uart@fe001000 i2c@fe002000 gpio@fe003000 rtc@68 根节点 一级节点 二级节点 三级节点

3.2 属性(Property)—— 描述节点的特征

属性是附着在节点上的键值对,用于描述硬件的具体特征,如寄存器地址、中断号、时钟频率、设备兼容性等。

3.2.1 属性值类型

类型 语法格式 示例 说明
空值 property; status = "disabled"; 仅表示属性存在,无具体值
32位整数 <value> reg = <0xfe001000 0x1000>; 一个或多个32位值,用空格分隔
64位整数 <high low> reg = <0x0 0x80000000 0x0 0x10000000>; 高32位在前,低32位在后
字符串 "string" compatible = "snps,dw-apb-uart"; 双引号包裹
字符串列表 "str1", "str2" compatible = "ti,am335x-uart", "ns16550"; 逗号分隔多个字符串
字节数组 [hex bytes] local-mac-address = [00 11 22 33 44 55]; 十六进制字节,空格分隔
混合值 <...> "..." [...] foo = <0x10> "bar" [0xab]; 不同类型值可组合

3.2.2 标准属性速查

  • compatible:最重要的属性,用于操作系统匹配驱动。格式为 "厂商,型号",如 "ti,am335x-uart"。
  • reg:描述设备地址空间,格式为 <基地址 长度>。
  • interrupts:描述中断号,格式依赖 interrupt-parent。
  • status:设备状态,常用值 "okay"(启用)、"disabled"(禁用)。
  • #address-cells / #size-cells:定义子节点 reg 属性中地址和长度的单元数。
常见陷阱:compatible 属性中的厂商前缀必须使用内核维护的官方列表(如 ti、snps、fsl),不要随意编造。错误的 compatible 会导致驱动无法匹配。

3.3 标签(Label)—— 节点的别名

标签为节点提供一个全局唯一的别名,方便在其他地方引用该节点,尤其在 .dtsi 文件中覆盖或扩展节点时非常有用。

3.3.1 标签定义语法

uart0: uart@fe001000 {
    compatible = "snps,dw-apb-uart";
    reg = <0xfe001000 0x1000>;
    interrupts = <33>;
};

在上例中,uart0 就是标签,它指向 uart@fe001000 节点。标签名必须以字母或下划线开头,由字母、数字、下划线组成。

3.3.2 标签的典型用途

用途 示例 说明
引用节点 &uart0 { status = "okay"; }; 通过 &label 引用节点并修改属性
中断父节点 interrupt-parent = &gic; 指定中断控制器
时钟引用 clocks = &clk_uart0; 引用时钟提供者
GPIO引用 reset-gpios = &gpio0 15 0; 引用GPIO控制器
实战技巧:在 .dtsi 文件中为所有重要节点定义标签,然后在 .dts 文件中通过 &label 进行覆盖或启用。这种模式可以极大提高设备树的可维护性,避免重复定义整个节点。
标签引用与节点覆盖流程 soc.dtsi uart0: uart@fe001000 { compatible = "..."; reg = <0xfe001000 0x1000>; status = "disabled"; }; 定义标签 uart0 board.dts &uart0 { status = "okay"; clock-frequency = <24000000>; }; 通过 &uart0 覆盖 编译后 .dtb uart@fe001000 { compatible = "..."; reg = <0xfe001000 0x1000>; status = "okay"; clock-frequency = <24000000>; };

3.4 路径(Path)—— 节点的绝对地址

路径是从根节点到目标节点的完整层级描述,类似于文件系统中的绝对路径。路径以 / 开头,各节点名用 / 分隔。

3.4.1 路径语法

/soc@0/uart@fe001000

上述路径表示:根节点下的 soc@0 节点下的 uart@fe001000 节点。

3.4.2 路径的典型使用场景

  • aliases 节点:为常用路径定义简短别名。
  • chosen 节点:指定内核启动参数,如 bootargs。
  • phandle 引用:编译时自动为节点生成唯一句柄,通过 <&label> 引用。

3.4.3 路径 vs 标签 vs phandle

方式 语法 特点 适用场景
路径 /soc/uart@fe001000 完整、可读性强,但冗长 手动调试、aliases 定义
标签 &uart0 简洁、可维护,推荐使用 节点覆盖、属性引用
phandle <&uart0> 编译后为整数句柄,高效 中断、时钟、GPIO 等跨节点引用
最佳实践:在 .dts 文件中优先使用 标签 进行节点引用,避免硬编码路径。路径更适合在 aliases 节点中一次性定义,然后通过别名使用。

3.5 综合示例:一个完整的 UART 节点

下面是一个综合了节点、属性、标签和路径的完整示例:

/dts-v1/;

/ {
    model = "Example Board";
    compatible = "example,board-v1";

    aliases {
        serial0 = &uart0;
    };

    soc@0 {
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;

        uart0: uart@fe001000 {
            compatible = "snps,dw-apb-uart";
            reg = <0xfe001000 0x1000>;
            interrupts = <33>;
            interrupt-parent = <&gic>;
            clocks = <&clk_uart0>;
            status = "disabled";
        };

        gic: interrupt-controller@fe002000 {
            compatible = "arm,gic-400";
            reg = <0xfe002000 0x1000>;
            interrupt-controller;
            #interrupt-cells = <3>;
        };

        clk_uart0: clock@fe003000 {
            compatible = "fixed-clock";
            #clock-cells = <0>;
            clock-frequency = <24000000>;
        };
    };
};

&uart0 {
    status = "okay";
};
代码解析:
  • uart0 标签指向 uart@fe001000 节点。
  • aliases 中 serial0 = &uart0 为 UART 节点创建了路径别名。
  • interrupt-parent = <&gic> 通过 phandle 引用中断控制器。
  • clocks = <&clk_uart0> 通过 phandle 引用时钟提供者。
  • 文件末尾通过 &uart0 覆盖节点,将 status 改为 "okay"。

3.6 常见错误与避坑指南

错误类型 错误示例 正确写法 后果
缺少分号 status = "okay" status = "okay"; 编译报错
标签重复 两个节点使用相同标签 确保标签全局唯一 编译报错或引用混乱
路径错误 &/soc/uart &uart0 或 /soc/uart@fe001000 引用失败,驱动无法加载
属性值类型错误 reg = 0x1000; reg = <0x1000>; 编译报错或运行时异常
忘记 #address-cells 子节点 reg 使用多值 父节点定义 #address-cells 地址解析错误
调试建议:使用 dtc -I dts -O dtb -o output.dtb input.dts 编译设备树,dtc 会给出详细的语法错误提示。编译后使用 fdtdump output.dtb 查看二进制内容,验证节点和属性是否正确。