第二章 设备树语法入门:节点、属性、标签与路径
好,咱们正式开始动手写设备树了。这一章我带你过一遍最核心的语法要素——节点、属性、标签和路径。说白了,设备树就是一个描述硬件拓扑的树形结构,你只要搞懂这四个东西,剩下的就是查手册填参数了。
2.1 节点(Node):硬件单元的化身
设备树里的每个硬件单元,都对应一个节点。比如一个UART、一个GPIO控制器、一个I2C总线,都是节点。节点用花括号 { } 包裹,里面可以嵌套子节点。
我个人习惯把节点想象成「硬件设备的身份证」。每个节点都有一个名字,格式是 node-name@unit-address。其中 unit-address 通常是设备在总线上的起始地址。
/ {
soc {
uart0: serial@10000000 {
compatible = "ns16550a";
reg = <0x10000000 0x1000>;
};
};
};
你看,serial@10000000 就是一个节点。名字是 serial,地址是 0x10000000。这里有个小细节——uart0: 是标签,后面会讲。
2.2 属性(Property):描述节点的关键信息
属性就是节点的「参数」。每个属性都有一个名字和一个值。常见的属性类型有:
- 字符串:
compatible = "ti,omap3-gpio"; - 32位整数:
reg = <0x48000000 0x1000>; - 布尔值:
status = "disabled";或status = "okay"; - 数组:
gpios = <&gpio1 15 0>;
嗯,这里要注意——compatible 属性是最重要的。它告诉内核这个设备用什么驱动来匹配。格式是 "厂商,型号",比如 "ti,omap3-gpio"。我建议你养成习惯,所有自定义设备都加上厂商前缀,避免和内核主线驱动冲突。
gpio1: gpio@48000000 {
compatible = "ti,omap3-gpio";
reg = <0x48000000 0x1000>;
interrupts = <29>;
gpio-controller;
#gpio-cells = <2>;
};
gpio-controller; 这个布尔属性,结果内核死活不认这个节点是GPIO控制器。查了两天才发现,原来这个属性是告诉内核「我是GPIO控制器」,没有它,gpios 引用就解析不了。
2.3 标签(Label):给节点起个外号
标签就是节点的别名。格式是 标签名: 放在节点名前。比如:
uart0: serial@10000000 {
...
};
有了标签,你就可以在其他地方通过 &uart0 来引用这个节点。这有什么用?你想想看,当你要在板级文件中修改某个SoC内部节点的属性时,总不能把整个节点复制一遍吧?用标签引用就优雅多了。
&uart0 {
status = "okay";
pinctrl-0 = <&uart0_pins>;
};
我个人习惯给所有重要的外设都加上标签,哪怕当前不需要引用。因为后续调试时,你很可能需要临时修改某个节点的属性。有标签在,改起来就是一行代码的事。
0uart 做标签,结果编译报错。标签名建议用 外设名+编号 的格式,比如 uart0、i2c1、gpio2。
2.4 路径(Path):从根到节点的完整路线
路径就是节点在设备树中的完整位置。从根节点 / 开始,用斜杠分隔。比如:
/soc/serial@10000000
路径主要用于 device_node 的查找。在内核代码中,你可以用 of_find_node_by_path() 函数通过路径找到节点。不过在实际的dts文件中,我们很少直接写路径,更多是用标签引用。
但路径有个重要用途——在 aliases 节点中给设备起短名:
/ {
aliases {
serial0 = &uart0;
serial1 = &uart1;
};
};
这样,uboot或内核就可以通过 serial0 这个别名快速找到对应的串口节点。我在调试早期启动时经常用这个技巧,省得记一堆地址。
2.5 实战:一个完整的GPIO控制器节点
来,咱们把上面学的串起来,写一个完整的GPIO控制器节点:
/ {
soc {
gpio0: gpio@48000000 {
compatible = "ti,omap3-gpio";
reg = <0x48000000 0x1000>;
interrupts = <29>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
status = "okay";
};
};
};
这个节点包含了:
- 标签:
gpio0,方便其他地方引用 - compatible:匹配驱动
- reg:寄存器基地址和大小
- interrupts:中断号
- gpio-controller:声明自己是GPIO控制器
- #gpio-cells:每个GPIO描述需要2个cell(引脚号和标志)
- status:默认启用
2.6 常见错误与调试技巧
最后分享几个我踩过的坑:
- 漏加分号:每个属性结尾必须有分号。编译报错时先检查这个。
- 标签重复:两个节点用了同一个标签名,编译会报重复定义。
- 路径写错:引用节点时路径大小写不对,内核找不到节点。
- 属性类型搞混:比如
reg应该是<地址 长度>,有人写成字符串。
调试时,我常用 dtc -I dtb -O dts 反编译dtb文件,看看实际解析出来的设备树长什么样。如果和你写的不一样,那肯定是语法有问题。
好了,这一章就到这里。节点、属性、标签、路径这四个概念,你只要动手写几个dts文件,很快就能掌握。下一章我们深入GPIO binding,看看怎么在设备树中描述GPIO的引脚复用和电气特性。