4. 节点命名与路径:节点名称规范、unit-address、标签(label)与节点引用(&)的用法

设备树(Device Tree)通过树形结构描述硬件资源,每个节点(node)的命名、寻址与引用方式直接决定了驱动能否正确匹配设备。本章深入讲解节点名称的规范格式、unit-address 的作用、标签(label)的定义以及通过 & 符号进行节点引用的核心用法。

核心要点:节点名称 = 通用名称 + unit-address;标签是节点的“别名”,用于在 DTS 文件中跨文件引用;& 是编译阶段的节点追加/修改操作符。

4.1 节点名称规范

设备树规范要求每个节点必须遵循 通用名称@unit-address 的格式。通用名称描述设备类型,unit-address 描述设备在总线上的地址或寄存器偏移。

组件 说明 示例
通用名称 小写字母、数字、连字符组成,描述设备功能(如 uart、i2c、gpio) serial、ethernet、mmc
@unit-address 可选部分,表示设备在父总线上的地址(寄存器基址、片选号、端口号等) @ff000000、@1、@0
完整节点名 通用名称 + @ + unit-address(若存在) uart@ff000000、i2c@1
命名避坑:通用名称应使用标准设备类型名(如 gpio 而非 my_gpio),避免使用下划线;unit-address 必须与 reg 属性的第一个地址值一致,否则编译报错。
// 正确示例
uart0: serial@ff000000 {
    compatible = "ns16550";
    reg = <0x0 0xff000000 0x0 0x1000>;
};

// 错误示例:unit-address 与 reg 不匹配
uart0: serial@ff001000 {   // 编译警告/错误
    compatible = "ns16550";
    reg = <0x0 0xff000000 0x0 0x1000>;
};

4.2 unit-address 的寻址规则

unit-address 的格式由父节点的 #address-cells 和 #size-cells 决定。对于 32 位系统,通常使用 1 个 cell 表示地址;对于 64 位系统,使用 2 个 cells。

父节点属性 unit-address 格式 示例
#address-cells = <1> 单个 32 位十六进制数 @ff000000
#address-cells = <2> 两个 32 位十六进制数,用逗号分隔 @0,ff000000
无地址空间(如 chosen) 不使用 @unit-address chosen、aliases
// 64 位地址示例
bus@0 {
    #address-cells = <2>;
    #size-cells = <2>;
    
    memory@0,80000000 {
        device_type = "memory";
        reg = <0x0 0x80000000 0x0 0x80000000>;
    };
};
注意:如果节点没有 reg 属性(如 chosen、aliases),则不能包含 @unit-address 部分。编译时 dtc 会对此进行检查。

4.3 标签(label)的定义与作用

标签(label)是节点的一个“别名”,在节点名称前使用 标签名: 定义。标签的主要用途:

  • 跨文件引用:在多个 DTSI 文件中通过标签引用同一节点
  • 简化路径:避免书写冗长的绝对路径
  • 节点覆盖:通过 &标签 在其它文件中追加或修改节点属性
// 定义标签
uart0: serial@ff000000 {
    compatible = "ns16550";
    reg = <0x0 0xff000000 0x0 0x1000>;
    interrupts = <0 33 4>;
};

// 在另一个 DTSI 文件中通过标签引用
&uart0 {
    clock-frequency = <24000000>;
    status = "okay";
};
标签命名建议:使用有意义的名称,如 uart0、i2c1、mmc0,避免使用 node0、temp 等无意义名称。标签在全局范围内必须唯一。

4.4 节点引用(&)的用法

& 符号是设备树编译阶段的引用操作符,用于在 DTS 文件中引用已定义的标签,实现节点的属性追加、修改或覆盖。

用法 说明 示例
&标签 { ... }; 引用标签对应的节点,在大括号内添加/修改属性 &uart0 { status = "okay"; };
标签: 节点名@地址 { ... }; 定义标签并同时定义节点 uart0: serial@ff000000 { ... };
&标签 作为 phandle 在属性中引用另一个节点(如时钟、中断控制器) clocks = &clkc, &uart0;
// 引用作为 phandle 使用
clkc: clock-controller@ff760000 {
    compatible = "rockchip,rk3399-cru";
    reg = <0x0 0xff760000 0x0 0x1000>;
    #clock-cells = <1>;
};

uart0: serial@ff180000 {
    compatible = "rockchip,rk3399-uart";
    reg = <0x0 0xff180000 0x0 0x100>;
    clocks = &clkc, &clkc;  // 引用 clkc 节点
    clock-names = "baud", "module";
};
编译原理:&标签 在 dtc 编译时会被展开为节点的绝对路径(如 /soc/serial@ff180000),然后进行属性合并。如果引用的标签不存在,编译会报错。

4.5 节点路径与引用关系图

下图展示了设备树中节点名称、标签、路径与引用之间的完整关系:

节点命名、标签与引用关系图 设备树节点命名、标签与引用关系 / (根节点) soc@0 uart0: serial@ff000000 标签: uart0 i2c1: i2c@ff020000 标签: i2c1 引用文件: board.dts &uart0 { status = "okay"; } 引用文件: board.dts &i2c1 { clock-frequency = <100000>; } phandle 引用: clocks = &clkc; 标签作为句柄在属性中引用其他节点 普通节点 带标签节点 引用操作 phandle 引用

4.6 节点路径的完整语法

设备树中节点的路径使用 绝对路径 和 相对路径 两种方式:

路径类型 语法 示例 说明
绝对路径 以 / 开头,从根节点开始 /soc/serial@ff000000 唯一标识,可在任何位置使用
标签引用 &标签名 &uart0 编译时展开为绝对路径
别名路径 通过 aliases 节点定义 serial0 = &uart0; 在 chosen 或 bootargs 中使用
// 别名定义示例
/ {
    aliases {
        serial0 = &uart0;
        serial1 = &uart1;
        i2c0 = &i2c1;
    };
    
    chosen {
        stdout-path = "serial0:115200n8";  // 使用别名
    };
};
路径使用注意事项:
  • 绝对路径在 DTS 文件中只能用于 aliases、chosen 等特殊节点,不能用于属性追加
  • 标签引用 &标签 是推荐的方式,支持跨文件操作
  • 别名路径主要用于 bootargs 和 stdout-path,不用于节点属性修改

4.7 节点命名与引用的最佳实践

  1. 命名一致性:同一类设备使用相同的通用名称前缀(如所有 UART 使用 serial)
  2. 标签命名规范:使用 设备类型+序号 格式(如 uart0、i2c1、mmc0)
  3. 避免重复定义:同一标签只能定义一次,多次定义会导致编译错误
  4. 引用顺序无关:&标签 可以在标签定义之前或之后使用,dtc 会进行两遍扫描
  5. 属性合并规则:多次引用同一节点时,属性会按顺序合并,后定义的覆盖先定义的
// 最佳实践示例
// soc.dtsi - 芯片级定义
uart0: serial@ff000000 {
    compatible = "ns16550";
    reg = <0x0 0xff000000 0x0 0x1000>;
    interrupts = <0 33 4>;
    status = "disabled";  // 默认关闭
};

// board.dts - 板级使能
#include "soc.dtsi"

&uart0 {
    status = "okay";
    clock-frequency = <24000000>;
};
调试技巧:使用 dtc -I dtb -O dts 反编译 DTB 文件,可以查看编译后的节点路径和标签展开结果,帮助排查命名和引用问题。