2、设备树语法入门:节点(node)、属性(property)、标签(label)与路径(path)
好,咱们正式开始啃设备树的语法。说实话,我第一次看设备树文件时,感觉就像在看一本天书——一堆花括号、分号,还有各种奇怪的字符串。但别怕,它的核心语法其实特别简单,就那么几个概念。
设备树说白了,就是用一种树形结构来描述硬件。你想想看,一个电路板上有CPU、内存、I2C控制器、SPI控制器、GPIO……这些东西天然就是树状连接的。设备树就是把这个树给“写”出来。
2.1 节点(Node)—— 树上的每一个“挂件”
节点是设备树最基本的组成单元。每个节点代表一个硬件设备或总线。我习惯把节点想象成一个文件夹,里面可以放文件(属性),也可以再放子文件夹(子节点)。
节点的语法长这样:
/ {
node1 {
// 这是节点1的内容
};
node2 {
// 这是节点2的内容
};
};
注意看,根节点用 / 表示,所有设备都挂在这个根下面。每个节点用花括号 { } 包裹,最后用分号 ; 结束。嗯,这里要注意:分号千万别漏了,我刚开始写设备树时,经常因为漏了分号导致编译报错,找半天才发现。
节点可以无限嵌套。比如一个I2C控制器下面,可以挂多个I2C从设备:
/ {
i2c0: i2c@ff010000 {
compatible = "snps,designware-i2c";
reg = <0xff010000 0x1000>;
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
};
rtc@51 {
compatible = "nxp,pcf8563";
reg = <0x51>;
};
};
};
这里 i2c@ff010000 是一个节点,它下面又挂了 eeprom@50 和 rtc@51 两个子节点。每个节点都有自己的地址,用 @ 符号标识。
uart@ff000000、gpio@ff001000。这样一看就知道是什么设备、在哪个地址,特别清晰。
2.2 属性(Property)—— 节点的“身份证”
属性就是描述节点特征的数据。每个节点可以有多个属性,属性值可以是字符串、数字、数组等。
常见的属性类型:
| 属性类型 | 写法示例 | 说明 |
|---|---|---|
| 字符串 | compatible = "snps,dw-apb-uart"; |
用于匹配驱动,最重要属性之一 |
| 32位整数 | reg = <0xff010000 0x1000>; |
地址和大小,用尖括号包裹 |
| 布尔值 | status = "disabled"; |
用字符串表示,常见有"okay"和"disabled" |
| 字符串列表 | interrupt-names = "rx", "tx"; |
多个字符串用逗号分隔 |
| 二进制数据 | mac-address = [00 11 22 33 44 55]; |
用方括号包裹十六进制数 |
我个人觉得,compatible 属性是最关键的。它就像设备的“身份证号”,内核通过它来找到对应的驱动程序。比如 compatible = "snps,dw-apb-uart",内核就会去驱动列表里找能处理这个字符串的驱动。
2.3 标签(Label)—— 给节点起个“外号”
标签就是给节点起一个别名。为什么要用标签?因为设备树里节点的路径可能很长,比如 /soc/i2c@ff010000/eeprom@50,每次引用都写全路径太麻烦了。
标签的写法是在节点名前加一个名字和冒号:
/ {
soc {
i2c0: i2c@ff010000 {
// 这个节点的标签是 i2c0
};
};
};
有了标签,你就可以在其他地方用 &i2c0 来引用这个节点。比如在另一个文件里覆盖它的属性:
&i2c0 {
clock-frequency = <400000>; // 把I2C频率改成400kHz
status = "okay";
};
这种用法在设备树中特别常见。你想想看,芯片厂商提供了一个基础设备树,你只需要在自己的板级文件里用标签引用,然后修改或添加一些属性就行了,不用动原始文件。
2.4 路径(Path)—— 找到节点的“导航”
路径就是节点在设备树中的完整位置。从根节点 / 开始,用斜杠分隔每一级节点名。
比如:
/soc/i2c@ff010000/eeprom@50
这个路径表示:根节点 → soc节点 → i2c@ff010000节点 → eeprom@50节点。
路径在设备树中有两种使用场景:
- 引用节点: 用
&{/soc/i2c@ff010000}这种语法来引用一个节点。 - 覆盖节点: 在多个设备树文件(.dtsi和.dts)中,用路径来定位要修改的节点。
举个例子,假设芯片厂商的 .dtsi 文件里定义了一个节点:
/ {
soc {
uart0: uart@ff000000 {
compatible = "snps,dw-apb-uart";
reg = <0xff000000 0x1000>;
status = "disabled";
};
};
};
在你的板级 .dts 文件里,你可以这样启用它:
&uart0 {
status = "okay";
clock-frequency = <115200>;
};
这里 &uart0 就是通过标签引用。如果没有标签,你就得写全路径:
&{/soc/uart@ff000000} {
status = "okay";
};
说实话,全路径写法太啰嗦了,我基本不用。能用标签就用标签,代码看起来清爽很多。
2.5 实战:写一个简单的设备树
好了,理论说完了,咱们来写一个完整的例子。假设我们要描述一个开发板,上面有:
- 一个CPU(ARM Cortex-A72)
- 一个UART控制器(地址0xff000000)
- 一个I2C控制器(地址0xff010000),下面挂了一个温度传感器(地址0x48)
- 一个GPIO控制器(地址0xff020000)
设备树文件长这样:
/dts-v1/;
/ {
model = "MyDevBoard";
compatible = "mycompany,mydevboard";
#address-cells = <1>;
#size-cells = <1>;
cpu@0 {
compatible = "arm,cortex-a72";
reg = <0x0>;
};
uart0: uart@ff000000 {
compatible = "snps,dw-apb-uart";
reg = <0xff000000 0x1000>;
interrupts = <0 33 4>;
status = "okay";
};
i2c0: i2c@ff010000 {
compatible = "snps,designware-i2c";
reg = <0xff010000 0x1000>;
interrupts = <0 34 4>;
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
temp_sensor: temperature-sensor@48 {
compatible = "ti,tmp102";
reg = <0x48>;
};
};
gpio0: gpio@ff020000 {
compatible = "snps,dw-apb-gpio";
reg = <0xff020000 0x1000>;
interrupts = <0 35 4>;
#gpio-cells = <2>;
status = "okay";
};
};
- 每个节点都有
compatible属性,用于驱动匹配 reg属性描述地址和大小- 标签(如 uart0、i2c0)方便在其他地方引用
- 子节点(如 temperature-sensor)挂在父节点下面
status控制设备是否启用
嗯,到这里,设备树的四个核心概念就讲完了。节点是骨架,属性是血肉,标签是外号,路径是导航。把这四个搞明白,你就能看懂大部分设备树文件了。
下一章咱们会深入讲 compatible 和 reg 这两个最重要的属性,以及它们在内核里是怎么被解析的。到时候我会分享一个我调试驱动时遇到的真实案例,保证让你印象深刻。