4、设备树节点定义:节点名称、节点路径、节点标签(label)的使用
好,咱们今天聊聊设备树里最基础、也最容易被忽略的东西——节点定义。
很多新手刚接触设备树时,觉得不就是写几个花括号嘛,有什么难的?结果一上手,要么节点名字写错,要么路径引用搞混,编译报错半天找不出原因。我在项目中就见过一个同事,因为节点标签拼写错误,导致驱动死活加载不上,排查了整整两天。
说白了,节点名称、节点路径、节点标签这三样东西,就是设备树里的“身份证”、“家庭住址”和“外号”。搞懂了它们,你写设备树才能得心应手。
4.1 节点名称:设备树的“身份证”
每个设备树节点,都得有个名字。这个名字不是随便起的,它有一套规则。
节点名称的格式:
node-name@unit-address
其中 node-name 是通用名称,unit-address 是设备地址(通常是寄存器基地址)。
举个例子:
uart0: serial@101f1000 {
compatible = "ns16550";
reg = <0x101f1000 0x1000>;
};
这里 serial 是节点名称,@101f1000 是地址部分。为什么要有地址?因为同类型设备可能有多个,比如两个串口,用地址区分它们。
命名规则我总结了几点:
- 节点名称应该反映设备类型,比如
i2c、spi、gpio、serial等 - 不要用具体芯片型号做节点名,比如别写
max3100@...,应该写serial@... - 地址部分用十六进制小写,前面不加
0x前缀 - 如果设备没有内存映射地址(比如 I2C 从设备),可以不写
@unit-address
我的个人习惯: 写节点名称时,尽量跟内核里的驱动名称保持一致。比如用 serial 而不是 uart,因为内核里串口驱动通常叫 serial。这样别人一看就懂。
4.2 节点路径:设备树的“家庭住址”
节点路径就是从根节点到目标节点的完整路径。它用斜杠 / 分隔,就像文件系统一样。
比如上面那个串口节点,它的完整路径是:
/soc/serial@101f1000
如果它挂在某个总线下面,路径会更长:
/soc/bus@40000000/serial@101f1000
路径的用途:
- 在设备树源文件里,用
&phandle或label引用节点 - 在运行时,内核通过路径查找设备节点
- 在
devicetree的struct 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 简洁多了吧?
标签的命名规范:
- 通常用设备类型缩写 + 编号,比如
uart0、i2c1、spi2 - 也可以加前缀表示所属模块,比如
soc_uart0、pcie_ep - 不要用数字开头,不要包含特殊字符(下划线可以)
避坑指南: 我曾经在一个项目里,把标签写成了 0uart(数字开头),结果 dtc 编译直接报语法错误。还有一次,标签名跟某个宏定义冲突了,编译通过但运行异常。所以标签名最好避开内核已有的宏名称。
4.4 三者的关系与使用场景
咱们用一张图来理清它们的关系:
从这张图可以清楚看到:节点名称是设备本身的标识,路径是它在设备树中的位置,标签则是我们为了方便引用而起的别名。
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@101f1000 的 reg 写成 0x101f2000,结果驱动加载时地址对不上,设备无法正常工作。
好了,关于节点名称、节点路径和节点标签,就讲这么多。这三样东西看似简单,但用好了能让你的设备树清晰易读,用错了则可能让你排查到怀疑人生。记住:名称要规范,路径要准确,标签要加全。做到这三点,你的设备树就成功了一半。