2、设备树语法精讲:节点、属性、标签、包含机制
设备树这东西,说白了就是一块描述硬件的数据结构。我刚开始接触Linux驱动时,总觉得它很神秘——为什么非得用这种格式来描述硬件?后来做多了才发现,它其实就是个「硬件说明书」,告诉内核你的板子上有什么、怎么访问。
今天咱们就把设备树的四个核心概念掰开揉碎讲清楚。嗯,我保证不绕弯子。
2.1 节点(Node)——设备树的骨架
节点是设备树最基本的组成单元。你可以把它想象成一颗树上的分叉点,每个节点代表一个硬件设备或总线。
节点长这样:
/ {
cpu {
compatible = "arm,cortex-a7";
reg = <0x00>;
};
memory {
device_type = "memory";
reg = <0x80000000 0x40000000>;
};
};
根节点是 /,它下面挂载了 cpu 和 memory 两个子节点。每个节点用花括号 { } 包裹,最后用分号结束。
关键点:节点名可以重复,但路径必须唯一。比如 /soc/i2c@1234 和 /soc/i2c@5678 是两个不同的节点。
我在项目中遇到过一个问题:两个外设的节点名写成了 uart 和 uart@1234,结果内核解析时把后者当成了前者的别名,导致驱动加载失败。嗯,节点名后面加 @地址 是规范写法,别偷懒。
2.2 属性(Property)——节点的灵魂
属性就是节点的「配置项」。它告诉驱动:这个设备用什么协议、地址是多少、中断号是什么。
常见的属性类型有四种:
| 类型 | 写法 | 示例 |
|---|---|---|
| 字符串 | "string" |
compatible = "ti,am335x-uart"; |
| 32位整数 | <value> |
reg = <0x44e09000 0x2000>; |
| 64位整数 | <0x00000000 0x80000000> |
用两个32位拼一个64位 |
| 字节数组 | [hex bytes] |
local-mac-address = [00 11 22 33 44 55]; |
我个人习惯把 compatible 属性放在每个节点的第一行。为什么?因为内核匹配驱动时第一个就看它。你想想看,如果驱动和设备树的 compatible 字符串对不上,后面写再多属性都是白搭。
避坑指南:我曾经在 reg 属性里少写了一个地址单元,导致驱动读到的内存地址完全错位。检查时发现 #address-cells 和 #size-cells 没配对。记住:父节点定义地址长度,子节点按这个长度写 reg。
2.3 标签(Label)——给节点起个外号
标签就是节点的别名。它让你在别处引用节点时不用写一长串路径。
uart0: serial@44e09000 {
compatible = "ti,am335x-uart";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
};
这里的 uart0 就是标签。在其他地方你可以直接用 &uart0 来引用它:
&uart0 {
status = "okay";
pinctrl-0 = <&uart0_pins>;
};
标签的作用说白了就两个:一是让引用更简洁,二是方便在多个文件中覆盖节点属性。我在做板级支持包时,经常把基础配置写在 .dtsi 文件里,然后在 .dts 中用标签覆盖某些属性——这样既保留了通用配置,又能针对具体板子做调整。
注意:标签名不能重复,也不能以数字开头。我见过有人用 0uart 做标签,结果编译直接报错。标签的命名规范和C语言变量名一样。
2.4 包含机制(Include)——模块化的基石
设备树支持 #include 指令,作用和C语言的头文件类似。它让你把通用的硬件描述拆分成多个文件,避免重复劳动。
举个例子,一个典型的设备树文件结构:
// am335x.dtsi
#include "skeleton.dtsi"
/ {
compatible = "ti,am335x";
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a8";
};
};
};
// myboard.dts
#include "am335x.dtsi"
/ {
model = "My Custom Board";
memory {
device_type = "memory";
reg = <0x80000000 0x10000000>; // 256MB
};
};
编译时,myboard.dts 会先展开 am335x.dtsi 的内容,然后再叠加自己的配置。最终生成一个完整的设备树。
这里有个细节:.dtsi 文件通常不包含根节点的闭合花括号,因为它只是「片段」,需要被 .dts 文件包含后才能形成完整的树结构。
实用技巧:我习惯把 SoC 公共部分、外设控制器、时钟配置等放到 .dtsi 里,只把板级特有的东西(如内存大小、GPIO 复用、外设使能)放到 .dts 中。这样换板子时只需要改一个文件,省心不少。
2.5 知识体系总览
下面这张图把四个概念的关系串起来了。你看一眼就能明白:节点是骨架,属性是血肉,标签是名字,包含是拼图。
你看,设备树的语法其实不复杂。节点搭框架,属性填细节,标签方便引用,包含实现复用。这四个概念搞明白了,写设备树就跟搭积木一样简单。
最后说一句:别怕报错。我刚开始写设备树时,编译错误能堆满一屏幕。但每次报错都是学习的机会——你想想看,编译器其实在帮你检查语法,比你自己肉眼检查靠谱多了。
个人经验:写设备树时,先把 compatible 和 reg 写对,这两个对了,驱动基本就能跑起来。其他属性可以慢慢加。我曾经为了调一个I2C设备,花了三天才发现是 reg 的地址偏移写错了——嗯,从那以后我每次写 reg 都会拿数据手册对照三遍。
可可精品资料库