2. 设备树语法入门:节点、属性、标签、路径、别名

设备树(Device Tree,DT)是嵌入式 Linux 内核中用于描述硬件拓扑结构的一种数据结构。它使用树形层级结构将 CPU、内存、外设、总线等硬件资源组织起来,使内核能够在不硬编码硬件信息的情况下适配不同板级。本章将系统讲解设备树的五个核心语法要素:节点(node)、属性(property)、标签(label)、路径(path)和别名(aliases)。

学习提示:设备树语法是内核移植的基石。掌握这五个要素后,你将能够读懂绝大多数设备树源文件(.dts),并具备编写简单设备树的能力。

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

节点是设备树的基本组成单元,每个节点代表一个硬件设备、总线、控制器或功能模块。节点通过层级嵌套形成树状结构,根节点是整棵树的起点。

2.1.1 节点定义语法

/dts-v1/;

/ {
    // 根节点
    model = "Embedded Linux Board";
    compatible = "vendor,board";

    cpu@0 {
        // CPU 节点
        compatible = "arm,cortex-a7";
        reg = <0x0>;
    };

    serial@1c28000 {
        // 串口节点
        compatible = "ns16550";
        reg = <0x1c28000 0x1000>;
        interrupts = <0 38 4>;
    };
};

节点名称格式为 node-name@unit-address,其中 @unit-address 部分可选,通常用于标识设备在总线上的地址。

2.1.2 节点层级关系

关键概念:节点之间的父子关系决定了设备的从属关系。子节点继承父节点的总线环境,父节点通常提供地址映射、时钟、复位等资源。
设备树节点层级结构 / (根节点) cpu@0 memory@80000000 soc serial@1c28000 i2c@1c2b000 gpio@1c20800 根节点是唯一没有父节点的节点,所有硬件设备都挂载在根节点或其子节点下

2.2 属性(Property)—— 描述设备特征

属性是附着在节点上的键值对,用于描述设备的硬件特征、配置参数和连接关系。每个属性都有一个名称和一个值,值的类型可以是字符串、整数、字节数组或它们的组合。

2.2.1 常用属性类型

属性类型 语法格式 示例 说明
字符串 "string" compatible = "arm,cortex-a7"; 用于标识设备兼容性
整数 <value> reg = <0x1c28000 0x1000>; 地址、大小、中断号等
字节数组 [hex bytes] local-mac-address = [00 11 22 33 44 55]; MAC 地址、UUID 等
字符串列表 "str1", "str2" compatible = "vendor,device", "generic-device"; 多个兼容字符串
布尔值 仅属性名,无值 status = "disabled"; 表示设备启用/禁用状态

2.2.2 标准属性详解

核心属性:以下三个属性是每个设备节点必须或强烈建议包含的,内核通过它们识别和驱动硬件。
  • compatible:设备兼容性字符串,格式为 "厂商,设备型号",内核通过匹配该字符串加载对应的驱动程序。
  • reg:描述设备在总线上的地址空间,格式为 <地址 长度>,支持多组地址。
  • interrupts:描述设备使用的中断号、触发类型等信息,格式依赖中断控制器。
// 完整属性示例
uart0: serial@1c28000 {
    compatible = "ns16550a", "ns16550";  // 优先匹配前者
    reg = <0x1c28000 0x1000>;            // 基地址 0x1c28000,长度 4KB
    interrupts = <0 38 4>;               // SPI 中断号 38,高电平触发
    clock-frequency = <24000000>;         // 时钟频率 24MHz
    status = "okay";                      // 设备启用
};

2.3 标签(Label)—— 节点的引用标识

标签是附加在节点名称前面的标识符,用于在其他位置引用该节点。标签以冒号结尾,紧跟在节点名称之前。

2.3.1 标签定义与引用

// 定义标签
uart0: serial@1c28000 {
    compatible = "ns16550";
    reg = <0x1c28000 0x1000>;
};

// 在其他位置通过 & 引用标签
&uart0 {
    status = "okay";
    clock-frequency = <24000000>;
};
常见误区:标签不是节点名称的一部分,它只是一个引用别名。同一个节点可以有多个标签,但通常只定义一个。标签在整个设备树文件中必须唯一。

2.3.2 标签的典型应用场景

场景 说明 示例
板级覆盖 在 .dtsi 中定义节点,在 .dts 中通过标签修改 &uart0 { baud = <115200>; };
引脚复用 引用 pinctrl 节点配置引脚功能 pinctrl-0 = &uart0_pins;
时钟/电源引用 引用时钟或电源管理节点 clocks = &osc24M;

2.4 路径(Path)—— 节点的唯一地址

路径是从根节点到目标节点的完整层级描述,使用斜杠分隔节点名称。路径是设备树中定位节点的绝对方式,类似于文件系统中的绝对路径。

2.4.1 路径格式

/soc/serial@1c28000    // 从根节点到串口设备的路径
/cpus/cpu@0            // 从根节点到 CPU0 的路径
/                      // 根节点路径

2.4.2 路径与标签的对比

特性 路径(Path) 标签(Label)
定位方式 绝对路径,从根节点开始 符号引用,类似变量名
可读性 长路径不易读 短标签易读易记
唯一性 天然唯一 需保证不重复
使用场景 内核内部查找、调试 设备树源文件内引用
路径 vs 标签:两种节点定位方式 路径定位(绝对) /soc/serial@1c28000 从根节点逐级向下查找 适合:内核内部遍历、调试输出 缺点:路径长、易写错 of_find_node_by_path("/soc/serial@1c28000") 标签定位(符号) &uart0 通过标签名直接引用节点 适合:设备树源文件内修改、覆盖 优点:简洁、可读性强 &uart0 { status = "okay"; };

2.5 别名(Aliases)—— 简化路径的快捷方式

别名是在根节点的 aliases 子节点中定义的键值对,用于为常用设备路径提供简短名称。别名机制简化了设备树中的路径引用,尤其在 bootloader 与内核之间传递设备信息时非常有用。

2.5.1 别名定义语法

/ {
    aliases {
        serial0 = &uart0;
        serial1 = &uart1;
        ethernet0 = &gmac0;
        mmc0 = &mmc0;
    };

    uart0: serial@1c28000 { /* ... */ };
    uart1: serial@1c28400 { /* ... */ };
    gmac0: ethernet@1c30000 { /* ... */ };
};

2.5.2 别名的典型用途

  • bootloader 与内核通信:U-Boot 通过修改 chosen 节点中的 stdout-path 属性引用别名,如 stdout-path = "serial0:115200n8"。
  • 设备编号固定:确保系统启动后设备名称(如 /dev/ttyS0)与硬件对应关系稳定。
  • 简化设备树覆盖:在设备树覆盖(overlay)中通过别名快速定位目标节点。
最佳实践:建议为所有可能被 bootloader 或用户空间引用的设备定义别名,包括串口、以太网、MMC/SD 控制器、USB 控制器等。别名名称应遵循 设备类型+序号 的命名规范。

2.6 五要素综合示例

下面是一个完整的设备树片段,展示了节点、属性、标签、路径和别名的协同使用:

/dts-v1/;

/ {
    model = "ARM Embedded Development Board";
    compatible = "arm,vexpress";

    aliases {
        serial0 = &uart0;
        serial1 = &uart1;
        mmc0 = &mmc0;
    };

    chosen {
        stdout-path = "serial0:115200n8";
    };

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

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

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

        uart0: serial@1c28000 {
            compatible = "ns16550a";
            reg = <0x1c28000 0x1000>;
            interrupts = <0 38 4>;
            clock-frequency = <24000000>;
            status = "okay";
        };

        uart1: serial@1c29000 {
            compatible = "ns16550a";
            reg = <0x1c29000 0x1000>;
            interrupts = <0 39 4>;
            clock-frequency = <24000000>;
            status = "disabled";
        };

        mmc0: mmc@1c11000 {
            compatible = "arm,pl180", "arm,primecell";
            reg = <0x1c11000 0x1000>;
            interrupts = <0 42 4>;
            status = "okay";
        };
    };
};

2.7 五要素对比总结

要素 定义位置 主要用途 唯一性要求 引用方式
节点 设备树任意层级 表示硬件设备或功能模块 同级节点名称唯一 路径或标签
属性 节点内部 描述设备特征和配置 同一节点内属性名唯一 直接访问
标签 节点名称前 提供节点引用标识 全局唯一 &label
路径 从根节点开始 绝对定位节点 天然唯一 /node/subnode
别名 /aliases 节点内 简化路径引用 别名名称唯一 serial0 等短名称
核心记忆点:节点是硬件实体,属性是实体特征,标签是引用别名,路径是绝对地址,别名是快捷方式。五者配合使用,构成了设备树完整的数据描述和引用体系。
设备树五要素关系图 节点 (Node) 属性 (Property) 标签 (Label) 路径 (Path) 别名 (Aliases) 属性描述节点特征,标签/路径/别名提供三种不同的节点引用方式

2.8 常见错误与调试技巧

高频错误:
  • 节点名称重复(同级节点名称冲突)
  • 标签未定义就引用(编译报错)
  • 路径拼写错误(大小写敏感)
  • 属性值类型不匹配(如字符串写成了整数)
  • 别名与节点名称冲突

调试建议:使用 dtc -I dts -O dtb -o output.dtb input.dts 编译设备树,通过编译错误信息定位语法问题。编译成功后,使用 dtc -I dtb -O dts output.dtb 反编译验证生成的二进制文件是否符合预期。