2. 设备树语法:设备树源文件(.dts)的语法结构

设备树源文件(.dts)是描述硬件拓扑结构的文本文件,采用树形层次结构组织节点(Node)和属性(Property)。本章将系统讲解 .dts 文件的语法要素,包括节点定义、属性赋值、标签引用、包含文件机制等核心概念,为后续解析设备树与驱动 probe 流程的关联打下基础。

2.1 设备树源文件的基本结构

一个典型的 .dts 文件由根节点(/)和若干子节点组成,每个节点可以包含属性(键值对)或进一步嵌套子节点。编译器(dtc)将 .dts 编译为二进制 .dtb,供内核在启动时解析。

核心语法规则:
  • 节点以 node-name { ... }; 形式定义,节点名可包含 @unit-address 后缀。
  • 属性以 property-name = value; 或 property-name;(空属性)形式出现。
  • 注释风格与 C 语言一致:// 单行、/* */ 多行。
  • 分号 ; 是语句结束符,花括号 {} 表示节点作用域。
// 最简单的 .dts 示例
/dts-v1/;

/ {
    compatible = "acme,coyote-revenge";
    #address-cells = <1>;
    #size-cells = <1>;

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

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

2.2 节点(Node)定义与层次结构

设备树的节点对应硬件总线、设备或功能单元。每个节点有唯一路径(如 /cpus/cpu@0),通过节点名和 reg 属性区分同级别节点。

节点元素 语法示例 说明
根节点 / { ... }; 设备树的顶层容器,所有节点均在其下
普通节点 uart@1e000 { ... }; 节点名 + 可选地址后缀,地址通常与 reg 属性对应
别名节点 aliases { serial0 = &uart1; }; 为其他节点提供简短引用名
空节点 status = "disabled"; 通过属性控制节点是否启用,而非删除节点
节点命名规范:节点名应使用小写字母、数字和连字符,地址后缀使用十六进制小写。例如 i2c@3ffd0000 优于 I2C@3FFD0000。
设备树节点层次结构图 根节点 / cpus memory@80000000 cpu@0 reg = <...> 节点层次关系说明 • 根节点是设备树的唯一入口,所有硬件节点均挂载其下。 • 节点路径由根到目标节点的名称序列组成,如 /cpus/cpu@0。 • 同级别节点通过节点名 + reg 地址区分,地址后缀必须与 reg 属性一致。 • 每个节点可包含子节点和属性,形成递归的树形结构。

2.3 属性(Property)类型与赋值

属性是设备树中描述硬件特征的关键载体,其值可以是空、整数、字符串、字节数组或它们的组合。内核通过 of_property_read_* 系列 API 读取这些属性。

属性类型 语法格式 示例 内核读取 API
空属性 property-name; interrupt-parent; of_find_property()
32 位整数 <value> reg = <0x1000 0x200>; of_property_read_u32()
64 位整数 <high low> reg = <0x0 0x80000000>; of_property_read_u64()
字符串 "string" compatible = "arm,cortex-a9"; of_property_read_string()
字符串列表 "str1", "str2" compatible = "ti,am335x-uart", "ns16550"; of_property_read_string_index()
字节数组 [hex bytes] local-mac-address = [00 11 22 33 44 55]; of_property_read_u8_array()
混合类型 <value> "string" gpios = <&gpio0 15 0>; of_parse_phandle_with_args()
属性赋值注意事项:
  • 整数默认采用大端序(Big-Endian),与 ARM 架构一致。
  • #address-cells 和 #size-cells 决定 reg 属性中地址和长度的单元数(32 位为 1 个 cell)。
  • compatible 属性是驱动匹配的核心,通常按“厂商,型号”格式组织,越具体的字符串越靠前。

2.4 标签(Label)与引用

标签(Label)为节点或属性提供符号名称,便于在 .dtsi 文件中通过 &label 语法引用和修改。这是设备树实现“增量覆盖”的关键机制。

// 定义标签
uart0: serial@1e000 {
    compatible = "ns16550";
    reg = <0x1e000 0x100>;
    interrupts = <33>;
};

// 在另一个文件中通过 &uart0 引用并追加属性
&uart0 {
    clock-frequency = <1843200>;
    status = "okay";
};
标签用途 语法 说明
定义标签 label: node-name { ... }; 标签名在前,节点名在后,用冒号分隔
引用节点 &label { ... }; 通过 & 符号引用已定义的标签
引用属性值 &label 作为 phandle 在属性值中引用其他节点,如 clocks = <&clk_osc>;
别名引用 &{/path/to/node} 直接使用节点路径引用(不推荐,可读性差)
标签命名建议:使用有意义的缩写前缀,如 uart0、i2c1、mmc0,避免使用 node1、temp_label 等无意义名称。标签名不能以数字开头。
标签引用与覆盖机制流程图 基板 .dtsi 文件 uart0: serial@1e000 { compatible = "ns16550"; reg = <0x1e000 0x100>; }; &uart0 引用 追加属性 产品 .dts 文件 &uart0 { clock-frequency = <1843200>; status = "okay"; }; 编译结果:uart0 节点合并了基板与产品的所有属性 标签机制允许 .dtsi 定义基础节点,.dts 通过 &label 增量修改,无需重写整个节点。 这种设计实现了 SoC 通用描述与板级定制描述的分离。

2.5 包含文件(Include)机制

设备树使用 C 预处理器(CPP)的 #include 指令来组织多文件结构。通常将 SoC 通用描述放在 .dtsi 文件中,板级特定描述放在 .dts 文件中。

// am335x-boneblack.dts
#include "am33xx.dtsi"           // SoC 级通用描述
#include "am335x-bone-common.dtsi" // 板级通用描述

/ {
    model = "TI AM335x BeagleBone Black";
    compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x20000000>;  // 512MB
    };
};

// 覆盖 SoC 级节点
&uart0 {
    status = "okay";
    pinctrl-0 = <&uart0_pins>;
};
文件类型 扩展名 用途 包含关系
设备树源文件 .dts 板级最终描述,包含所有硬件信息 可包含多个 .dtsi
设备树包含文件 .dtsi SoC 通用描述或板级公共描述 可嵌套包含其他 .dtsi
头文件 .h 定义宏、常量、GPIO 编号等 通过 #include 引入
包含文件最佳实践:
  • SoC 级 .dtsi 定义所有内部外设节点,但将 status 设为 "disabled"。
  • 板级 .dts 通过 &label 引用并启用实际使用的外设。
  • 使用 .h 头文件定义 GPIO 编号、时钟索引等常量,提高可读性。
  • 避免在 .dtsi 中定义板级专属属性(如内存大小、MAC 地址)。

2.6 常见属性详解:compatible、reg、interrupts

这三个属性是驱动 probe 流程中最常解析的核心属性,理解其格式对后续章节至关重要。

2.6.1 compatible 属性

用于设备与驱动的匹配,格式为 "厂商,型号" 字符串列表。内核按照列表顺序依次尝试匹配驱动。

compatible = "ti,am335x-uart", "ns16550";
// 内核先匹配 "ti,am335x-uart",失败后尝试 "ns16550"

2.6.2 reg 属性

描述设备地址空间,格式由父节点的 #address-cells 和 #size-cells 决定。

// #address-cells = <1>; #size-cells = <1>;
reg = <0x1e000 0x100>;  // 起始地址 0x1e000,长度 0x100

// #address-cells = <2>; #size-cells = <2>;
reg = <0x0 0x80000000 0x0 0x10000000>;  // 64 位地址 + 64 位长度

2.6.3 interrupts 属性

描述设备的中断号与触发类型,需要配合 interrupt-parent 使用。

interrupt-parent = <&gic>;  // 引用中断控制器节点
interrupts = <0 33 4>;       // 中断类型 0(SPI)、中断号 33、触发类型 4(高电平)
中断属性陷阱:中断号的编码方式因中断控制器而异。GIC(通用中断控制器)使用 <中断类型 中断号 触发类型> 三元素格式,而老式控制器可能只使用 <中断号 触发类型>。务必参考对应中断控制器的绑定文档。

2.7 设备树编译与反编译

设备树编译器 dtc 是处理 .dts/.dtb 文件的核心工具。掌握基本命令有助于调试和验证设备树配置。

操作 命令 说明
编译 .dts → .dtb dtc -I dts -O dtb -o output.dtb input.dts 生成二进制设备树
反编译 .dtb → .dts dtc -I dtb -O dts -o output.dts input.dtb 查看二进制设备树内容
检查 .dtb 语法 dtc -I dtb -O dts input.dtb > /dev/null 仅验证语法,不输出文件
生成 .dtb 时保留符号 dtc -@ -I dts -O dtb -o output.dtb input.dts -@ 保留符号表,用于动态覆盖
调试技巧:在 Linux 内核启动后,可以通过 /proc/device-tree 查看当前使用的设备树结构。每个节点对应一个目录,属性对应文件,直接 cat 即可查看属性值。
设备树编译流程 .dts 源文件 文本格式 dtc 编译器 设备树编译器 .dtb 二进制 扁平设备树 内核 解析 probe

2.8 本章小结

设备树源文件语法是驱动开发的基础技能,核心要点包括:

  • 节点结构:树形层次组织,根节点为 /,子节点通过路径唯一标识。
  • 属性赋值:支持整数、字符串、字节数组、phandle 引用等多种类型,compatible 是驱动匹配的关键。
  • 标签引用:通过 &label 实现增量覆盖,实现 SoC 与板级描述的分离。
  • 包含机制:#include 组织多文件,.dtsi 用于通用描述,.dts 用于板级定制。
  • 编译工具:dtc 编译器将 .dts 转换为 .dtb,内核在启动时解析并创建设备节点。