2. 设备树语法入门:节点、属性、标签、路径的规则
好,咱们直接进入正题。设备树(Device Tree)的语法,说白了就是一套描述硬件信息的「结构化语言」。你想想看,Linux 内核要支持那么多五花八门的板子,总不能每换一个板子就重新编译一次内核吧?设备树就是来解决这个问题的——它把硬件信息抽出来,变成一份独立的数据文件。
我个人习惯把设备树比作一张「硬件配置清单」。这张清单里,有节点、有属性、有标签、有路径。今天我就带你把这四个核心概念彻底搞明白。
2.1 节点(Node):设备树的基本单元
节点是设备树里最基本的组成单位。每个节点代表一个硬件设备或一个总线接口。比如一个 I2C 控制器、一个 GPIO 引脚、一个 SPI 设备,都可以用一个节点来描述。
节点的写法很简单:
/ {
cpu {
compatible = "arm,cortex-a7";
reg = <0x00>;
};
i2c0 {
compatible = "samsung,i2c";
reg = <0x13860000 0x1000>;
status = "okay";
};
};
看到没?每个节点用花括号 {} 包裹,里面可以嵌套子节点。根节点就是 /,所有设备都挂在它下面。
我的小经验: 我在项目中遇到过有人把节点名写成了数字开头,结果编译报错半天找不到原因。记住,节点名必须以字母或下划线开头,不能以数字开头。
2.2 属性(Property):描述节点的特征
属性就是节点的「身份证信息」。它告诉内核:这个设备是什么、用哪个驱动、地址是多少、中断号是多少等等。
常见的属性类型有几种:
| 属性类型 | 写法示例 | 说明 |
|---|---|---|
| 字符串 | compatible = "arm,cortex-a7"; |
用于匹配驱动 |
| 32位整数 | reg = <0x13860000 0x1000>; |
地址和大小 |
| 64位整数 | reg = <0x00000000 0x10000000>; |
高32位在前 |
| 布尔值 | status = "okay"; |
用字符串表示 |
| 数组 | interrupts = <0 31 4>; |
尖括号内多个值 |
这里有个坑,我必须要提醒你:compatible 属性是驱动匹配的关键。内核会拿这个字符串去驱动列表里找对应的驱动。如果你写错了,驱动就加载不上。
我曾经踩过的坑: 有一次我写 compatible = "samsung,i2c",但驱动里写的是 "samsung,i2c0",就多了一个 0,结果驱动死活不认。后来查了半天才发现是字符串不匹配。所以,compatible 一定要和驱动里的一模一样,多一个字符都不行。
2.3 标签(Label):给节点起个「外号」
标签,说白了就是给节点起个别名。为什么要起别名?因为设备树里经常要引用其他节点,比如某个 GPIO 控制器被多个设备共用,你总不能每次都写一长串路径吧?
标签的写法是在节点名前加一个冒号:
i2c0: i2c@13860000 {
compatible = "samsung,i2c";
reg = <0x13860000 0x1000>;
};
uart0: serial@13800000 {
compatible = "samsung,uart";
reg = <0x13800000 0x100>;
};
这里的 i2c0 和 uart0 就是标签。后面在其他地方引用时,直接用 &i2c0 或 &uart0 就行了。
你想想看,如果没有标签,你要引用一个深层嵌套的节点,就得写一长串路径,比如 /soc/bus@10000000/i2c@13860000,多麻烦。有了标签,一个 &i2c0 就搞定了。
重点: 标签是全局唯一的。你不能有两个节点用同一个标签名,否则编译会报错。我建议你给标签命名时,用设备类型加编号的方式,比如 i2c0、uart1、spi2,这样一目了然。
2.4 路径(Path):从根到节点的完整地址
路径就是设备树里从根节点到某个节点的完整「地址」。它和文件系统的路径很像,用斜杠 / 分隔。
比如:
/soc/bus@10000000/i2c@13860000
这个路径表示:从根节点 / 开始,进入 soc 节点,再进入 bus@10000000 节点,最后到达 i2c@13860000 节点。
路径在设备树覆盖(overlay)和引用时非常有用。比如你想在另一个设备树文件里引用某个节点,就可以用路径:
&/soc/bus@10000000/i2c@13860000 {
status = "okay";
};
不过说实话,我个人更推荐用标签而不是路径。为什么?因为路径太长了,而且如果中间某个节点的名字改了,你的引用就断了。标签更稳定,也更简洁。
2.5 节点名和单元地址的规则
节点名不是随便起的,它有一套约定俗成的规则。节点名一般由两部分组成:通用名称和单元地址。
格式是:name@unit-address
比如:
i2c@13860000 {
...
};
这里的 i2c 是通用名称,13860000 是单元地址(通常是设备的内存基地址)。
为什么要加单元地址?因为同一种设备可能有多个实例,比如两个 I2C 控制器,它们的基地址不同,用 i2c@13860000 和 i2c@13870000 就能区分开。
我的建议: 节点名尽量用标准名称,比如 i2c、uart、spi、gpio、mmc 等。不要自己发明名字,否则别人看你的设备树会一头雾水。内核文档里有一个 devicetree/bindings/ 目录,里面列出了所有标准节点名的规范,写之前可以去翻翻。
2.6 常见语法错误和避坑指南
写设备树的时候,有几个错误我几乎每次培训都会遇到。我列出来,你写的时候注意一下:
- 分号漏了: 每个属性后面都要加分号
;,漏一个就编译不过。 - 花括号不匹配: 节点用花括号包裹,左括号和右括号要一一对应。我建议你写的时候用缩进对齐,一眼就能看出括号匹配。
- 标签重复: 标签是全局唯一的,不能有两个节点用同一个标签。
- 路径写错: 路径里的节点名必须和实际节点名完全一致,包括大小写。
- 地址格式不对:
reg属性里的地址和大小要用尖括号<>包裹,多个值用空格分隔。
我曾经犯过的错: 有一次我写 reg = <0x13860000 0x1000>,但忘了加尖括号,写成了 reg = 0x13860000 0x1000。结果编译没报错,但内核解析出来的地址完全不对,设备根本没法用。所以,尖括号一定不能省。
2.7 小结
好了,今天的内容就到这里。我们讲了设备树的四个核心概念:
- 节点: 硬件设备的基本单元,用花括号包裹。
- 属性: 描述节点的特征,比如兼容性、地址、状态等。
- 标签: 节点的别名,方便引用,用
&符号。 - 路径: 从根到节点的完整地址,用斜杠分隔。
这些是设备树语法的基本功。你把这些搞懂了,后面学设备树绑定、覆盖、编译这些内容就会轻松很多。下一章我们讲设备树的编译和反编译,到时候我会教你如何把 .dts 文件编译成 .dtb,以及如何从 .dtb 反编译回 .dts。
嗯,今天就先到这里。有什么问题,欢迎随时交流。