3. 设备树语法基础

设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它使用树形层次结构将系统中的CPU、内存、总线、外设等硬件信息传递给操作系统内核。本章将深入解析设备树源文件(.dts)的核心语法要素:节点、属性、标签和路径,为后续的驱动移植打下坚实基础。

学习提示:设备树语法是嵌入式Linux驱动开发的“硬件语言”。掌握本节内容后,你将能够读懂并修改任何主流开发板的设备树文件。

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

节点是设备树的基本组成单元,每个节点代表一个硬件设备、总线或控制器。节点通过父子关系形成一棵倒置的树,根节点是 /。

3.1.1 节点定义格式

node-name {
    property1 = <value>;
    property2 = "string";
    child-node {
        ...
    };
};
  • 节点名称:由字母、数字、连字符(-)、下划线(_)组成,必须以字母开头。
  • 节点体:用花括号 { } 包裹,内部包含属性和子节点。
  • 结束符:每个节点定义后必须加分号 ;。

3.1.2 节点命名规范

规则类别 说明 合法示例 非法示例
通用名称 使用小写字母、数字、连字符 i2c-controller I2C_Controller
地址后缀 总线设备节点名可包含@地址 uart@fe001000 uart@0xfe001000
长度限制 建议不超过31个字符 ethernet-phy@1 this-is-a-very-long-node-name-example
关键规则:同一层级下的节点名称必须唯一。如果两个节点同名但地址不同(如 uart@fe001000 和 uart@fe002000),则被视为不同节点。

3.1.3 节点层次结构示例

/ {
    model = "Embedded Learning Board v1.0";
    compatible = "vendor,board";

    cpu {
        compatible = "arm,cortex-a7";
        clock-frequency = <1000000000>;
    };

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

    soc {
        compatible = "simple-bus";
        #address-cells = <1>;
        #size-cells = <1>;

        uart@fe001000 {
            compatible = "vendor,uart";
            reg = <0xfe001000 0x1000>;
            interrupts = <0 33 4>;
        };

        i2c@fe002000 {
            compatible = "vendor,i2c";
            reg = <0xfe002000 0x1000>;
            #address-cells = <1>;
            #size-cells = <0>;

            eeprom@50 {
                compatible = "atmel,24c02";
                reg = <0x50>;
                pagesize = <8>;
            };
        };
    };
};
设备树节点层次结构图 根节点 / cpu memory@80000000 soc uart@fe001000 i2c@fe002000 eeprom@50 节点形成树形层次:根节点 → soc → i2c → eeprom,每个节点代表一个硬件设备或总线 图例: 根节点 顶层设备 总线节点 外设节点 子设备

3.2 属性(Property)—— 描述设备的语言

属性是附着在节点上的键值对,用于描述设备的特性、地址、中断、时钟等信息。每个属性由属性名和属性值组成。

3.2.1 属性值类型

值类型 语法格式 示例 说明
字符串 "string" compatible = "vendor,device"; 双引号包裹,用于设备兼容性标识
32位整数 <value> clock-frequency = <50000000>; 尖括号包裹,用于地址、频率等数值
64位整数 <high low> reg = <0x0 0x80000000>; 两个32位整数组合表示64位值
字节数组 [hex bytes] local-mac-address = [00 11 22 33 44 55]; 方括号包裹十六进制字节
字符串列表 "str1", "str2" compatible = "vendor,device", "generic,device"; 逗号分隔多个字符串
布尔值 仅属性名,无值 status = "disabled"; 存在即表示真,常用 status 属性

3.2.2 标准属性详解

  • compatible:最重要的属性,用于操作系统匹配驱动。格式为 "厂商,设备型号",如 "ti,am335x-uart"。
  • reg:描述设备地址空间,格式为 <基址 长度>,受 #address-cells 和 #size-cells 控制。
  • interrupts:描述中断信息,格式依赖 interrupt-parent 和 #interrupt-cells。
  • status:设备状态,常用值 "okay"(启用)、"disabled"(禁用)。
  • model:设备型号描述字符串。
常见错误:忘记在属性值后加分号 ; 是设备树编译错误的最常见原因。每个属性定义必须以分号结束。

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

标签是为节点定义的别名,允许在设备树的其他位置通过 &label 引用该节点。标签极大简化了设备树的修改和扩展。

3.3.1 标签定义语法

uart1: uart@fe001000 {
    compatible = "vendor,uart";
    reg = <0xfe001000 0x1000>;
};

上述代码中,uart1 是标签,uart@fe001000 是节点名称。标签必须放在节点名称之前,用冒号分隔。

3.3.2 标签引用与覆盖

// 在另一个 .dtsi 文件中引用并扩展
&uart1 {
    status = "okay";
    clock-frequency = <115200>;
};

通过 &uart1 引用标签,可以向原节点添加新属性或覆盖已有属性。这是设备树分层设计的核心机制。

3.3.3 标签命名规范

规范 说明 示例
前缀标识类型 使用设备类型缩写作为前缀 uart1, i2c0, spi2
避免关键字 不要使用设备树关键字 避免 status, reg, compatible
全局唯一 整个设备树中标签必须唯一 不能有两个 uart1
标签引用与覆盖机制图 原始节点定义(.dtsi) uart1: uart@fe001000 { compatible = "vendor,uart"; reg = <0xfe001000 0x1000>; }; 引用覆盖(.dts) &uart1 { status = "okay"; clock-frequency = <115200>; }; 编译后合并结果 uart@fe001000 { compatible = "vendor,uart"; reg = <0xfe001000 0x1000>; status = "okay"; clock-frequency = <115200>; }; 标签机制允许在 .dtsi 中定义基础节点,在 .dts 中通过 &label 引用并扩展,实现代码复用

3.4 路径(Path)—— 定位节点的地址

路径是从根节点到目标节点的完整路径字符串,用于在设备树中唯一标识一个节点。路径语法类似于文件系统的目录路径。

3.4.1 路径语法规则

  • 绝对路径:以根节点 / 开头,如 /soc/i2c@fe002000/eeprom@50。
  • 路径分隔符:使用斜杠 / 分隔各级节点名称。
  • 节点名称包含地址:如果节点名包含 @ 地址后缀,路径中必须包含完整名称。
  • 路径引用:在 .dts 文件中,可以使用 &{/path/to/node} 语法引用路径。

3.4.2 路径使用示例

// 路径引用示例
&{/soc/uart@fe001000} {
    status = "okay";
};

// 在设备树覆盖文件中使用路径
/fragment@0 {
    target-path = "/soc/i2c@fe002000";
    __overlay__ {
        eeprom@50 {
            compatible = "atmel,24c02";
        };
    };
};

3.4.3 路径与标签的对比

特性 标签(Label) 路径(Path)
定义方式 在节点名前定义,如 uart1: 由节点层次自动生成
唯一性 全局唯一,需手动保证 全局唯一,由树结构保证
可读性 简短,适合频繁引用 较长,但表达完整层次
使用场景 驱动匹配、设备树覆盖 设备树片段、动态加载
引用语法 &uart1 &{/soc/uart@fe001000}
最佳实践:在编写设备树时,优先使用标签引用节点,因为标签更简短且不易出错。路径引用通常用于设备树覆盖(DT Overlay)或无法预定义标签的场景。

3.5 综合示例:完整设备树片段

以下示例综合展示了节点、属性、标签和路径的协同使用:

// 基础定义文件:board.dtsi
/ {
    model = "Embedded Learning Board";
    compatible = "vendor,board";

    soc {
        compatible = "simple-bus";
        #address-cells = <1>;
        #size-cells = <1>;

        uart0: uart@fe001000 {
            compatible = "vendor,uart";
            reg = <0xfe001000 0x1000>;
            interrupts = <0 33 4>;
            status = "disabled";
        };

        i2c0: i2c@fe002000 {
            compatible = "vendor,i2c";
            reg = <0xfe002000 0x1000>;
            #address-cells = <1>;
            #size-cells = <0>;
            status = "disabled";
        };
    };
};

// 板级配置:board.dts
#include "board.dtsi"

&uart0 {
    status = "okay";
    clock-frequency = <115200>;
};

&i2c0 {
    status = "okay";
    clock-frequency = <100000>;

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

// 路径引用示例
&{/soc/i2c@fe002000/eeprom@50} {
    compatible = "atmel,24c02", "microchip,24c02";
};
设备树核心语法要素关系图 设备树节点 属性 描述设备特性 标签 节点别名 路径 唯一地址 子节点 层次扩展 应用场景 • compatible 属性匹配驱动 • reg 属性描述地址空间 • 标签实现设备树覆盖 • 路径用于动态加载 核心关系总结 节点是设备树的骨架,属性描述节点特征,标签提供便捷引用,路径确保唯一寻址。 四者结合构成了设备树完整的数据描述和访问体系。

3.6 常见错误与调试技巧

编译错误排查:使用 dtc -I dts -O dtb -o output.dtb input.dts 编译设备树时,常见错误包括:
  • 缺少分号 ; —— 每个属性和节点定义后必须加分号。
  • 括号不匹配 —— 花括号 {} 必须成对出现。
  • 标签重复 —— 整个设备树中标签必须唯一。
  • 路径错误 —— 路径中的节点名称必须与实际定义完全一致。
调试技巧:使用 dtc -I dtb -O dts -o decompiled.dts boot.dtb 反编译设备树二进制文件,可以验证编译结果是否符合预期。同时,fdtdump 工具可以快速查看设备树内容。