2. 设备树基础语法:节点、属性、值的定义与格式,dtsi与dts文件的关系
大家好,欢迎来到设备树实战的第二讲。今天咱们聊聊设备树最基础的东西——节点、属性、值,还有那两个容易搞混的文件:.dtsi 和 .dts。
说实话,我刚接触设备树那会儿,看着满屏的花括号和分号,第一反应是「这玩意儿是C语言吗?」后来才发现,它其实更像一个树形结构的配置文件。嗯,咱们慢慢拆解。
2.1 节点(Node)——设备树的骨架
设备树里,每个硬件设备或总线都用一个「节点」表示。节点从根(/)开始,一层层往下挂,就像文件系统的目录结构。
节点的基本格式长这样:
/dts-v1/;
/ {
node1 {
child-node {
// 子节点内容
};
};
node2 {
// 节点内容
};
};
每个节点用花括号 { } 包裹,末尾必须加分号 ;。节点名通常用硬件名称或功能描述,比如 cpu、memory、i2c0。
节点命名规则:
- 节点名可以包含字母、数字、连字符、下划线
- 建议用全小写,单词间用连字符分隔(比如
serial-port) - 同一层级下节点名不能重复
我在项目中遇到过有人把节点名写成 I2C_0(大写+下划线),虽然语法没错,但跟社区风格不搭。你想想看,内核里成千上万个设备树文件,统一风格多重要。
2.2 属性(Property)与值(Value)——节点的血肉
节点光有名字不行,还得有属性来描述硬件特性。属性就是键值对,格式是:
属性名 = <值>;
属性值有几种常见类型,我列个表方便你对照:
| 值类型 | 格式 | 示例 | 说明 |
|---|---|---|---|
| 空值 | 无等号 | interrupt-controller; |
表示布尔属性,存在即为真 |
| 32位整数 | <数字> |
reg = <0x10000000 0x1000>; |
尖括号包裹,多个值用空格隔开 |
| 64位整数 | <高32位 低32位> |
reg = <0x0 0x80000000 0x0 0x1000>; |
两个32位拼一个64位 |
| 字符串 | "字符串" |
compatible = "ti,am335x-gpio"; |
双引号包裹 |
| 字节数组 | [十六进制字节] |
local-mac-address = [00 11 22 33 44 55]; |
方括号包裹,每字节两位十六进制 |
| 混合值 | 组合使用 | foo = <0x1234>, "bar", [0xab]; |
逗号分隔不同类型 |
我个人习惯把 compatible 属性放在每个设备节点的第一行,因为它是驱动匹配的关键。内核通过这个字符串找到对应的驱动程序。
避坑指南:我曾经在 reg 属性里少写了一个地址单元,导致驱动读到的内存地址完全错位。调试了整整一下午才发现——嗯,从那以后我每次写 reg 都会数一遍括号里的数字个数。
2.3 一个完整的节点示例
咱们把上面说的串起来,看一个实际的UART节点:
uart0: serial@10000000 {
compatible = "ns16550a";
reg = <0x10000000 0x1000>;
interrupts = <0 33 4>;
clock-frequency = <1843200>;
status = "okay";
};
这里 uart0 是标签(label),方便其他地方引用。节点名 serial@10000000 中的 @10000000 是地址后缀,用于区分同类型设备。
属性说明:
compatible:告诉内核用哪个驱动reg:基地址和地址范围interrupts:中断号、触发类型clock-frequency:时钟频率status:设备状态,"okay"表示启用
2.4 dtsi 与 dts 文件的关系
说到这儿,咱们得聊聊 .dtsi 和 .dts 这对「兄弟文件」。说白了,.dtsi 是头文件(包含文件),.dts 是主文件。
为什么要有两个文件?
你想想看,一个芯片厂商(比如TI、NXP)会出很多款SoC,这些SoC有很多共同点——相同的内核、相同的外设IP、相同的内存映射。如果每款芯片都写一份完整的 .dts,那得累死,还容易出错。
所以就有了 .dtsi:
.dtsi存放公共部分:SoC的通用外设、中断控制器、时钟等.dts存放板级特有部分:具体型号的DDR大小、外设使能、引脚复用等
用 #include 指令把 .dtsi 包含进来:
// am335x-evm.dts
#include "am335x.dtsi"
/ {
model = "TI AM335x EVM";
compatible = "ti,am335x-evm", "ti,am335x";
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>; // 256MB DDR
};
};
&uart0 {
status = "okay";
};
这里 &uart0 的写法叫「引用」,用来修改 .dtsi 中定义的节点。我在项目中经常用这个技巧——先在 .dtsi 里把所有外设的 status 设为 "disabled",然后在板级 .dts 里逐个打开需要的。
注意:.dtsi 文件可以多层嵌套。比如 am335x.dtsi 可能又 #include 了 skeleton.dtsi(内核提供的基础模板)。但别搞成循环包含,编译器会报错的。
2.5 知识体系结构图
下面这张图帮你理清本章的核心逻辑:
这张图很直观:.dtsi 和 .dts 都包含节点、属性、值这三要素,但职责不同。一个管公共,一个管定制。
2.6 实战中的小建议
最后分享几个我自己的习惯:
- 善用
#include:别把公共代码复制粘贴到每个.dts里,那是给自己挖坑 - 标签命名要规范:比如
uart0、i2c1、mmc2,一看就知道是哪个外设 - 属性值写完整:
reg的地址和长度别漏,interrupts的触发类型别省略 - 多用
status:调试时把不用的外设设成"disabled",能减少很多干扰
好了,这一章的内容就到这儿。设备树的语法其实不复杂,多写几遍就熟了。下一章咱们聊聊如何用设备树描述具体的外设——到时候会用到今天讲的这些基础。