2. DTS语法基础:DTS文件结构、节点、属性、标签与包含文件

设备树源文件(.dts)是一种人类可读的、结构化的文本描述语言,用于描述硬件平台的拓扑结构、内存布局、外设地址及中断连接等关键信息。理解DTS的语法是编写、调试和移植设备树的基础。本章将系统讲解DTS文件的整体结构、核心语法元素(节点、属性、标签)以及模块化组织方式(包含文件)。

2.1 DTS文件整体结构

一个典型的DTS文件由以下几个部分组成,它们共同构成了一个完整的硬件描述树:

  • 根节点(Root Node):使用 / 表示,是整个设备树的起点,包含所有其他节点。
  • 节点(Node):描述一个具体的硬件设备或总线,格式为 node-name@unit-address。
  • 属性(Property):描述节点的特性,如寄存器地址、中断号、时钟频率等。
  • 标签(Label):为节点或属性定义的别名,方便在其他地方引用。
  • 包含文件(Include):通过 #include 或 /include/ 引入其他 .dtsi 文件,实现代码复用。
关键概念: DTS文件本质上是一个以根节点为起点的树形结构。每个节点代表一个硬件设备或总线,节点内包含描述该设备具体信息的属性。这种结构清晰、层次分明,便于内核解析和管理。

2.2 节点(Node)

节点是设备树的核心元素,用于表示一个硬件设备、总线或功能单元。节点可以嵌套,形成父子关系,从而描述硬件之间的连接和从属关系。

2.2.1 节点定义语法

node-name@unit-address {
    [属性定义...]
    [子节点定义...]
};
  • node-name:节点名称,通常为设备类型或功能名称,如 cpu、uart、i2c。
  • unit-address:单元地址,通常为设备在总线上的起始地址或片选号,用于区分同类型设备。对于根节点下的顶层节点,此部分可选。
  • 花括号 { }:包含节点的属性和子节点。
  • 分号 ;:节点定义的结束符。

2.2.2 节点示例

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

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;

        cpu@0 {
            device_type = "cpu";
            compatible = "arm,cortex-a9";
            reg = <0x0>;
        };

        cpu@1 {
            device_type = "cpu";
            compatible = "arm,cortex-a9";
            reg = <0x1>;
        };
    };

    serial@101f0000 {
        compatible = "vendor,serial-ip";
        reg = <0x101f0000 0x1000>;
        interrupts = <0 12 4>;
    };
};
最佳实践: 节点名称应尽量使用标准化的名称(如 uart、mmc),而 compatible 属性则用于精确匹配具体的驱动。单元地址 @unit-address 应与 reg 属性中的第一个地址保持一致。

2.3 属性(Property)

属性是键值对,用于描述节点的具体特性。属性值可以是空、整数、字符串、字节数组或这些类型的组合。

2.3.1 属性值类型

类型 语法格式 示例 说明
空属性 property-name; interrupt-controller; 仅表示存在该属性,无值。
字符串 property-name = "string"; compatible = "arm,cortex-a9"; 用双引号包裹。
32位整数 property-name = <value>; reg = <0x10000000 0x1000>; 用尖括号包裹,多个值用空格分隔。
64位整数 property-name = <0x00000000 0x80000000>; reg = <0x00000000 0x80000000>; 用两个32位整数表示。
字节数组 property-name = [hex-bytes]; local-mac-address = [00 11 22 33 44 55]; 用方括号包裹,十六进制字节。
字符串列表 property-name = "str1", "str2"; compatible = "vendor,device", "generic-device"; 多个字符串用逗号分隔。
混合类型 property-name = <value>, "string", [bytes]; gpios = <&gpio0 1 0>, "irq-gpios"; 不同类型值用逗号分隔。

2.3.2 常用标准属性

  • compatible:最重要的属性,用于匹配驱动程序。格式为 "manufacturer,model"。
  • reg:描述设备地址空间,格式为 <address length>。
  • interrupts:描述设备的中断信息。
  • status:设备状态,常用值 "okay"、"disabled"、"fail"。
  • #address-cells 和 #size-cells:定义子节点中 reg 属性的地址和长度字段的位数。

2.4 标签(Label)

标签是为节点或属性定义的别名,使用 & 符号引用。标签可以简化对深层嵌套节点的访问,并支持在多个 .dtsi 文件中覆盖或扩展节点。

2.4.1 标签定义与引用

// 定义标签
uart0: serial@101f0000 {
    compatible = "vendor,serial-ip";
    reg = <0x101f0000 0x1000>;
    interrupts = <0 12 4>;
};

// 在其他地方引用标签
&uart0 {
    clock-frequency = <24000000>;  // 扩展 uart0 节点
    status = "okay";
};
常见错误: 标签名不能与节点名或属性名冲突。引用标签时,必须使用 & 前缀,否则编译器会将其视为普通字符串或节点名。

2.5 包含文件(Include)

为了复用和模块化,DTS支持包含其他文件。通常,SoC厂商会提供 .dtsi(Device Tree Source Include)文件,定义SoC内部通用的外设和总线。板级 .dts 文件通过包含 .dtsi 文件,并添加或覆盖板级特有的设备。

2.5.1 包含语法

// 使用 C 预处理器语法(推荐)
#include "soc.dtsi"
#include "board-common.dtsi"

// 使用设备树原生语法
/include/ "soc.dtsi"

2.5.2 包含文件示例

soc.dtsi(SoC通用定义):

/ {
    compatible = "vendor,soc-v1";

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
        cpu@0 {
            compatible = "arm,cortex-a9";
            reg = <0x0>;
        };
    };

    uart0: serial@101f0000 {
        compatible = "vendor,serial-ip";
        reg = <0x101f0000 0x1000>;
        interrupts = <0 12 4>;
        status = "disabled";
    };
};

board.dts(板级文件):

/dts-v1/;

#include "soc.dtsi"

/ {
    model = "My Board";
    compatible = "vendor,my-board", "vendor,soc-v1";

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x40000000>;  // 1GB RAM
    };
};

// 覆盖 SoC 中的 uart0 节点
&uart0 {
    status = "okay";
    clock-frequency = <24000000>;
};
模块化优势: 使用 .dtsi 文件可以避免在多个板级文件中重复定义相同的SoC外设。只需在板级文件中通过 &label 语法覆盖或启用特定设备即可,大大减少了代码冗余和维护成本。

2.6 DTS语法结构总览(SVG图)

下图展示了DTS文件从根节点到子节点、属性、标签以及包含文件的完整结构关系:

DTS文件结构总览 DTS文件结构总览 根节点 / #include "soc.dtsi" cpus 节点 serial@101f0000 memory@80000000 cpu@0 cpu@1 compatible = "..." reg = <...> compatible = "vendor,serial-ip" interrupts = <0 12 4> 标签: uart0: &uart0 { status = "okay"; } device_type = "memory" reg = <0x80000000 0x40000000> 说明:根节点包含子节点,子节点包含属性。标签(如 uart0:)为节点提供别名, 可通过 &uart0 引用并扩展。.dtsi 文件通过 #include 引入,实现模块化复用。

2.7 节点与属性关系详解(SVG图)

下图进一步细化了一个典型设备节点内部的结构,展示了节点、标签、标准属性以及如何通过 & 引用进行覆盖:

节点与属性关系详解 节点与属性关系详解 uart0: serial@101f0000 compatible = "vendor,serial-ip" reg = <0x101f0000 0x1000> interrupts = <0 12 4> status = "disabled" &uart0 { ... } status = "okay"; clock-frequency = <24000000>; 合并后:status = "okay" 说明:通过标签引用 &uart0,可以在不修改原始 .dtsi 文件的情况下,覆盖或添加属性。 原始节点中的 status = "disabled" 被覆盖为 "okay",并新增了 clock-frequency 属性。

2.8 常见语法错误与排查

错误类型 错误示例 正确写法 排查方法
缺少分号 compatible = "vendor,device" compatible = "vendor,device"; 编译器报错 syntax error,检查属性行尾。
花括号不匹配 node { ... } 缺少 ; node { ... }; 编译器报错 Expected ';',检查节点结束。
标签引用错误 uart0 { ... } 缺少 & &uart0 { ... } 编译器可能将其视为新节点,导致重复定义。
地址格式错误 reg = 0x1000; reg = <0x1000>; 编译器报错 bad cell value。
包含文件路径错误 #include "soc.dtsi" 文件不存在 确保路径正确或文件存在 编译器报错 No such file or directory。
调试建议: 使用 dtc -I dts -O dtb -o output.dtb input.dts 命令编译时,dtc 会输出详细的错误信息。重点关注行号和错误类型,通常可以快速定位问题。对于复杂的包含关系,可以使用 -E 选项展开预处理后的文件进行调试。