3. DTS 语法基础:节点、属性、值的定义规则与基本格式

设备树源文件(.dts)是一种人类可读的、用于描述硬件拓扑与配置的树形结构语言。其核心由三个基本元素构成:节点(Node)、属性(Property) 和 值(Value)。理解这三者的定义规则与基本格式,是编写正确 DTS 文件的基石。

核心概念速览: 节点是硬件设备或总线在树中的“位置”;属性是描述该设备特性的“键值对”;值则是属性对应的具体数据。三者组合,形成对硬件的完整描述。

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

节点是设备树的骨架,代表一个独立的硬件单元(如 CPU、I2C 控制器、GPIO 引脚)或一个逻辑分组(如 chosen、aliases)。

3.1.1 节点定义基本语法

[label:] node-name[@unit-address] {
    [property-definitions]
    [child-node-definitions]
};
  • node-name:节点名称,必须唯一标识节点类型,如 cpu、memory、i2c。名称应使用小写字母、数字和连字符。
  • @unit-address:可选,通常为设备在父总线上的地址(如寄存器基址、I2C 从机地址)。用于区分同一父节点下同名的多个子节点。
  • label::可选标签,用于在 DTS 文件的其他位置引用该节点(如通过 &label 进行覆盖或引用)。
  • 花括号 { }:包含该节点的所有属性和子节点定义。
  • 分号 ;:节点定义结束的标记。
示例: 定义一个 I2C 控制器节点,其基地址为 0x1000,并包含一个子节点(从设备)。
i2c@1000 {
    compatible = "vendor,i2c-controller";
    reg = <0x1000 0x100>;
    #address-cells = <1>;
    #size-cells = <0>;

    eeprom@50 {
        compatible = "atmel,24c02";
        reg = <0x50>;
    };
};

3.1.2 节点命名规范与常见误区

规范项 正确示例 错误示例 说明
名称通用性 serial@101f1000 uart1@101f1000 优先使用通用设备名(如 serial),而非具体型号(如 uart1)。
地址格式 memory@80000000 memory@0x80000000 unit-address 应为十六进制数,不包含 0x 前缀。
标签唯一性 i2c0: i2c@1000 i2c: i2c@1000 (重复) 标签(label)在整个 DTS 文件及包含的头文件中必须唯一。
根节点 / { ... }; 无 所有设备树的根节点,使用正斜杠 / 表示。
避坑指南: 节点名称中的 @unit-address 不是必需的,但若存在,其值必须与 reg 属性中的第一个地址值匹配。不匹配会导致内核解析时产生警告或错误。
设备树节点层级结构示例 / (根节点) cpu@0 memory@80000000 i2c@1000 eeprom@50 节点层级关系: 根节点包含 CPU、Memory、I2C 节点。 I2C 节点又包含 EEPROM 子节点。

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

属性是附着在节点上的“键-值”对,用于描述节点的特性,如兼容性、地址、中断信息等。属性名是字符串,值可以是多种数据类型。

3.2.1 属性定义基本语法

property-name = value;
  • property-name:由小写字母、数字、连字符和点号组成,通常以非数字开头。如 compatible、reg、interrupt-parent。
  • =:赋值操作符。
  • value:属性的具体数据,其格式取决于数据类型。
  • 分号 ;:属性定义结束的标记。

3.2.2 属性值的数据类型与表示法

数据类型 表示法 示例 说明
空值 无值 interrupt-controller; 用于表示布尔型 true 或标志性属性。
字符串 双引号 compatible = "vendor,device"; 用于描述设备兼容性、模型名等。
32位整数 尖括号 < > reg = <0x1000 0x100>; 用于地址、大小、中断号等。多个整数用空格分隔。
64位整数 尖括号 < > (两个32位) reg = <0x00000000 0x80000000 0x0 0x10000000>; 在 32 位系统上表示 64 位地址/大小,高32位在前。
字节序列 方括号 [ ] local-mac-address = [00 11 22 33 44 55]; 用于 MAC 地址、UUID 等原始二进制数据。
字符串列表 逗号分隔的字符串 clock-names = "uart", "spi"; 用于关联时钟、中断等资源的名称。
混合类型 组合使用 gpios = <&gpio0 1 0>; 常包含 phandle(节点引用)和整数。
关键属性示例:
serial@101f1000 {
    compatible = "ns16550a";          // 字符串
    reg = <0x101f1000 0x1000>;       // 32位整数列表(地址,大小)
    interrupts = <4 2>;              // 32位整数列表(中断号,触发类型)
    interrupt-parent = <&intc>;      // phandle(引用另一个节点)
    clock-frequency = <1843200>;     // 32位整数
    device_type = "serial";           // 字符串(已弃用,但仍常见)
};
属性与值类型映射关系 节点: uart@1000 属性定义 compatible = "ns16550a"; reg = <0x1000 0x100>; interrupts = <4 2>; interrupt-parent = <&intc>; clock-frequency = <1843200>; interrupt-controller; local-mac-address = [00 11 22]; clock-names = "uart", "spi"; 值类型 字符串 32位整数列表 32位整数列表 phandle (引用) 32位整数 空值 (布尔) 字节序列 字符串列表

3.3 值(Value)的详细定义规则

值的定义必须严格遵守其数据类型对应的格式,否则 DTC(Device Tree Compiler)将报错或产生非预期的二进制输出。

3.3.1 整数与地址值规则

  • 字节序(Endianness):DTS 中的整数默认使用大端序(Big-Endian)。例如 <0x12345678> 在内存中存储为 12 34 56 78。
  • 地址与大小组合:reg 属性通常由 #address-cells 和 #size-cells 决定每个地址和大小占用的 32 位整数个数。
    • 若 #address-cells = <1>,#size-cells = <1>,则 reg = <addr1 size1 addr2 size2 ...>;
    • 若 #address-cells = <2>,#size-cells = <1>,则 reg = <addr_high addr_low size ...>;

3.3.2 字符串与字符串列表规则

  • 字符串:必须使用双引号包裹。字符串内部支持转义字符,如 \n、\t。
  • 字符串列表:多个字符串用逗号分隔,整体作为一个属性值。例如 clock-names = "core", "bus", "ref";。

3.3.3 引用(Phandle)与标签

  • Phandle:使用 &label 语法引用另一个节点。DTC 会将其编译为指向该节点的 32 位整数句柄。
  • 示例:interrupt-parent = <&intc>; 表示该设备的中断父控制器是标签为 intc 的节点。
实战技巧: 在编写 DTS 时,优先使用 &label 引用节点,而不是手动填写 phandle 数值。这不仅能提高代码可读性,还能避免因节点顺序变化导致的 phandle 值改变。

3.4 节点、属性、值的综合示例

以下是一个完整的 DTS 文件片段,展示了节点、属性、值的协同工作方式:

/dts-v1/;

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

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

    uart0: serial@101f1000 {
        compatible = "ns16550a";
        reg = <0x101f1000 0x1000>;
        interrupts = <0 4 4>;
        interrupt-parent = <&intc>;
        clock-frequency = <1843200>;
    };
};
综合示例:节点、属性、值关系图 / (根节点) #address-cells = <1>; #size-cells = <1>; intc: interrupt-controller@10100000 compatible = "arm,gic-400"; reg = <0x10100000 0x1000>; uart0: serial@101f1000 compatible = "ns16550a"; reg = <0x101f1000 0x1000>; interrupt-parent = <&intc>; phandle 引用 关键点: 1. 根节点定义全局的 #address-cells 和 #size-cells。 2. uart0 通过 interrupt-parent 属性引用 intc 节点,形成中断依赖关系。

3.5 常见错误与调试方法

错误类型 错误示例 正确做法
缺少分号 compatible = "foo" 每行属性定义后加 ;
值类型不匹配 reg = "0x1000"; 地址值使用 <0x1000>
节点名称含大写 UART@1000 使用小写 uart@1000
标签重复 两个节点使用相同 label 确保 label 全局唯一
地址格式错误 memory@0x80000000 去掉 0x,写为 memory@80000000
调试建议: 使用 dtc -I dts -O dtb -o output.dtb input.dts 编译 DTS 文件。若编译失败,DTC 会输出具体的行号和错误原因。使用 dtc -I dtb -O dts -o output.dts input.dtb 可以反编译 DTB 文件,用于验证编译结果是否符合预期。