2. 设备树基础语法:节点、属性、值的定义与规范
设备树,说白了就是一套描述硬件信息的「说明书」。内核通过它来知道你的板子上接了哪些外设、内存多大、中断怎么连。我刚开始接触设备树时,觉得它不过是个配置文件,后来踩了几个坑才明白——语法细节没搞对,内核可能连启动都起不来。
这一章,我们就来拆解设备树最核心的三个要素:节点、属性、值。搞懂了它们,你就能自己写一个简单的设备树了。
2.1 节点:设备树的骨架
节点是设备树的基本组成单元。每个节点代表一个硬件设备或总线。你可以把节点想象成文件系统里的目录,一层套一层。
节点的语法很简单:
/dts-v1/;
/ {
node1 {
child-node {
...
};
};
node2 {
...
};
};
根节点用 / 表示,所有设备都挂在它下面。每个节点用花括号 {} 包裹,最后用分号 ; 结束。
节点命名规范:
- 节点名由字母、数字、逗号、点、短横线、下划线组成
- 推荐格式:
name@address,比如uart@fe001000 @address部分通常写设备在总线上的基地址
我在项目中遇到过有人把节点名写成 UART@fe001000(大写),结果内核解析时报错。记住,节点名是区分大小写的,统一用小写最保险。
2.2 属性:描述设备的特征
属性就是节点的「参数」。每个属性都有一个名字和一个值。属性写在节点内部,格式是:
property-name = <value>;
常见的属性类型有:
| 属性类型 | 示例 | 说明 |
|---|---|---|
| 字符串 | compatible = "vendor,device"; |
用于匹配驱动,格式固定 |
| 32位整数 | reg = <0xfe001000 0x1000>; |
地址和长度,用尖括号包裹 |
| 64位整数 | reg = <0x00000000 0x80000000>; |
高32位在前,低32位在后 |
| 布尔值 | status = "disabled"; |
用字符串表示,常见值:okay/disabled |
| 字节数组 | local-mac-address = [00 11 22 33 44 55]; |
用方括号包裹,每个字节用空格隔开 |
我的习惯:写 compatible 属性时,一定要遵循「厂商,设备」的格式。比如 compatible = "fsl,imx6q-uart", "ns16550";。这样内核驱动匹配时,会先找精确匹配,找不到再回退到通用驱动。
2.3 值的表示方法
设备树里的值有几种写法,每种都有它的使用场景。我刚开始时经常搞混,后来总结了一个规律:
- 尖括号
<>:放整数,一个或多个。比如<0x1000 0x2000> - 方括号
[]:放字节数据。比如[01 02 03] - 双引号
"":放字符串。比如"okay" - & 符号:引用其他节点。比如
&uart1
举个例子,一个完整的 UART 节点可能是这样的:
uart1: serial@fe001000 {
compatible = "fsl,imx6q-uart", "ns16550";
reg = <0xfe001000 0x1000>;
interrupts = <0 26 4>;
status = "disabled";
};
这里 uart1: 是节点的标签(label),方便其他地方引用。比如在根节点里可以写 &uart1 { status = "okay"; }; 来使能它。
我曾经踩过的坑:写 reg 属性时,地址和长度的顺序搞反了。比如 reg = <0x1000 0xfe001000>;,内核会认为基地址是 0x1000,长度是 0xfe001000,这显然不对。记住:先地址,后长度。
2.4 特殊属性:标准化的约定
设备树规范定义了一些特殊属性,它们有固定的含义。我挑几个最常用的说说:
#address-cells和#size-cells:定义子节点中reg属性的地址和长度用几个 32 位整数表示。比如#address-cells = <1>; #size-cells = <1>;表示地址和长度各占一个 32 位整数。model:描述设备型号,比如model = "Freescale i.MX6 Quad SABRE Board";phandle:节点的唯一标识符,通常由编译器自动生成,用于节点间的引用。
你想想看,如果没有 #address-cells,内核怎么知道 reg 里的数字哪个是地址哪个是长度?这就是规范的重要性。
2.5 知识体系图
下面这张图总结了设备树语法的核心结构,我建议你把它存下来,写设备树时对照着看:
2.6 实战:写一个简单的设备树片段
光说不练假把式。我们写一个实际点的例子——描述一个 I2C 控制器和它下面挂载的温度传感器:
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
i2c@fe050000 {
compatible = "fsl,imx6q-i2c", "fsl,imx21-i2c";
reg = <0xfe050000 0x4000>;
interrupts = <0 33 4>;
#address-cells = <1>;
#size-cells = <0>;
temp-sensor@48 {
compatible = "ti,tmp102";
reg = <0x48>;
};
};
};
注意看,I2C 控制器节点里又定义了 #address-cells 和 #size-cells,这是因为 I2C 子节点的地址只有 7 位,所以 #size-cells = <0>。嗯,这里要特别注意——每个总线节点都要重新定义这些属性,因为它们的作用域是局部的。
我的建议:写设备树时,先画一张硬件拓扑图,把每个设备的地址、中断号、依赖关系列清楚。然后按「根节点 → 总线节点 → 设备节点」的顺序逐层写。这样不容易漏掉东西。
2.7 常见错误与避坑
最后,我总结几个新手最容易犯的错误:
- 忘记分号:每个属性声明和节点结束都要有
;。少一个分号,整个设备树解析失败。 - 花括号不匹配:节点用
{}包裹,一定要成对出现。我建议用带缩进的编辑器写,一眼就能看出括号是否对齐。 - 地址长度单位搞错:
reg里的地址和长度单位是 32 位整数,不是字节。比如reg = <0x1000 0x2>;表示地址 0x1000,长度是 2 个 32 位整数(8 字节),而不是 2 字节。 - 引用节点时标签写错:用
&uart1引用节点时,确保标签uart1:已经定义过。
我曾经在一个项目里,因为少写了一个分号,导致内核启动时直接 panic。查了整整半天才找到问题。从那以后,我写设备树都会用 dtc -I dts -O dtb test.dts 先编译一遍,检查语法错误。
好了,这一章的内容就到这里。设备树的语法其实不复杂,但细节很多。多写几遍,自然就熟练了。
公众号:蓝海资料掘金营,微信 deep3321