4、设备树节点定义:节点名称、节点路径、节点标签(label)的使用

好,咱们今天聊聊设备树里最基础、也最容易被忽略的东西——节点定义。

很多新手刚接触设备树时,觉得不就是写几个花括号嘛,有什么难的?结果一上手,要么节点名字写错,要么路径引用搞混,编译报错半天找不出原因。我在项目中就见过一个同事,因为节点标签拼写错误,导致驱动死活加载不上,排查了整整两天。

说白了,节点名称、节点路径、节点标签这三样东西,就是设备树里的“身份证”、“家庭住址”和“外号”。搞懂了它们,你写设备树才能得心应手。

4.1 节点名称:设备树的“身份证”

每个设备树节点,都得有个名字。这个名字不是随便起的,它有一套规则。

节点名称的格式:

node-name@unit-address

其中 node-name 是通用名称,unit-address 是设备地址(通常是寄存器基地址)。

举个例子:

uart0: serial@101f1000 {
    compatible = "ns16550";
    reg = <0x101f1000 0x1000>;
};

这里 serial 是节点名称,@101f1000 是地址部分。为什么要有地址?因为同类型设备可能有多个,比如两个串口,用地址区分它们。

命名规则我总结了几点:

  • 节点名称应该反映设备类型,比如 i2cspigpioserial
  • 不要用具体芯片型号做节点名,比如别写 max3100@... ,应该写 serial@...
  • 地址部分用十六进制小写,前面不加 0x 前缀
  • 如果设备没有内存映射地址(比如 I2C 从设备),可以不写 @unit-address

我的个人习惯: 写节点名称时,尽量跟内核里的驱动名称保持一致。比如用 serial 而不是 uart,因为内核里串口驱动通常叫 serial。这样别人一看就懂。

4.2 节点路径:设备树的“家庭住址”

节点路径就是从根节点到目标节点的完整路径。它用斜杠 / 分隔,就像文件系统一样。

比如上面那个串口节点,它的完整路径是:

/soc/serial@101f1000

如果它挂在某个总线下面,路径会更长:

/soc/bus@40000000/serial@101f1000

路径的用途:

  • 在设备树源文件里,用 &phandlelabel 引用节点
  • 在运行时,内核通过路径查找设备节点
  • devicetreestruct device_node 里,full_name 字段就是路径

我记得有一次调试一个 PCIe 设备,设备树里路径写错了,少了一级总线节点,结果内核怎么都枚举不到设备。后来用 dtc -I fs /proc/device-tree 查看实际路径,才发现问题。

注意: 路径是区分大小写的!/SOC/serial@101f1000/soc/serial@101f1000 是两个完全不同的节点。我曾经见过有人把 soc 写成 SOC,编译不报错,但运行时就是找不到节点。

4.3 节点标签(label):设备树的“外号”

标签就是给节点起个简短好记的别名。它的语法很简单:

label: node-name@unit-address {
    // 节点内容
};

比如:

uart0: serial@101f1000 {
    compatible = "ns16550";
    reg = <0x101f1000 0x1000>;
};

uart1: serial@101f2000 {
    compatible = "ns16550";
    reg = <0x101f2000 0x1000>;
};

有了标签,其他地方引用节点就方便多了:

&uart0 {
    clock-frequency = <1843200>;
};

&uart1 {
    clock-frequency = <115200>;
};

你看,用 &uart0 比写 &/soc/serial@101f1000 简洁多了吧?

标签的命名规范:

  • 通常用设备类型缩写 + 编号,比如 uart0i2c1spi2
  • 也可以加前缀表示所属模块,比如 soc_uart0pcie_ep
  • 不要用数字开头,不要包含特殊字符(下划线可以)

避坑指南: 我曾经在一个项目里,把标签写成了 0uart(数字开头),结果 dtc 编译直接报语法错误。还有一次,标签名跟某个宏定义冲突了,编译通过但运行异常。所以标签名最好避开内核已有的宏名称。

4.4 三者的关系与使用场景

咱们用一张图来理清它们的关系:

/ (根节点) soc 节点 serial@101f1000 标签: uart0 三者关系说明: • 节点名称:serial@101f1000 —— 设备本身的“身份证” • 节点路径:/soc/serial@101f1000 —— 从根到节点的“家庭住址” • 节点标签:uart0 —— 方便引用的“外号”

从这张图可以清楚看到:节点名称是设备本身的标识,路径是它在设备树中的位置,标签则是我们为了方便引用而起的别名。

4.5 实际项目中的使用技巧

说了这么多理论,咱们看看实际项目中怎么用。

场景一:多个同类型设备

比如一个 SoC 有 4 个 I2C 控制器:

i2c0: i2c@1000 {
    // I2C0 配置
};

i2c1: i2c@2000 {
    // I2C1 配置
};

i2c2: i2c@3000 {
    // I2C2 配置
};

i2c3: i2c@4000 {
    // I2C3 配置
};

然后在板级文件里,可以单独覆盖某个 I2C 的配置:

&i2c1 {
    clock-frequency = <400000>;  // 改成 400KHz
    status = "okay";
};

场景二:引用其他节点的属性

有时候一个节点需要引用另一个节点的信息:

clocks {
    clk_uart: clock@0 {
        compatible = "fixed-clock";
        clock-frequency = <1843200>;
    };
};

uart0: serial@101f1000 {
    clocks = <&clk_uart>;  // 引用时钟节点
    clock-names = "uart_clk";
};

我的建议: 在写设备树时,尽量给所有可能被引用的节点都加上标签。哪怕当前没用到,也先加上。因为后续硬件改版、功能扩展时,你很可能需要引用它。我吃过这个亏——当初偷懒没加标签,后来改版时满世界找节点路径,改得头皮发麻。

4.6 常见错误与调试方法

错误类型 具体表现 解决方法
节点名称重复 dtc 编译报 "duplicate node name" 检查同一层级下是否有同名节点,用地址区分
标签重复 dtc 编译报 "duplicate label" 全局搜索标签名,确保唯一
路径引用错误 内核启动时打印 "OF: failed to find node" 用 dtc -I fs /proc/device-tree 查看实际路径
标签未定义 编译报 "reference to undefined label" 检查标签是否拼写正确,是否在 .dtsi 中定义

嗯,这里要注意一点:设备树编译器的错误信息有时候不太直观。比如标签重复,它可能只报个行号,不告诉你哪个标签重复了。我的做法是,用 grep -r "label:" *.dts *.dtsi 全局搜索,手动排查。

特别提醒: 节点路径中的 @unit-address 必须和 reg 属性中的地址一致。如果不一致,内核在匹配设备时会出问题。我曾经见过有人把 serial@101f1000reg 写成 0x101f2000,结果驱动加载时地址对不上,设备无法正常工作。

好了,关于节点名称、节点路径和节点标签,就讲这么多。这三样东西看似简单,但用好了能让你的设备树清晰易读,用错了则可能让你排查到怀疑人生。记住:名称要规范,路径要准确,标签要加全。做到这三点,你的设备树就成功了一半。