1、设备树基础:设备树(DTS)的起源与作用、基本语法结构、dts/dtsi/dtb文件关系
1.1 设备树从哪来?为什么要搞这么个东西?
做嵌入式Linux开发的朋友,一定绕不开设备树。我记得刚入行那会儿,Linux内核里还全是板级文件——每个开发板一个board-xxx.c,里面密密麻麻全是硬件寄存器地址、中断号、GPIO配置。那时候改个硬件,得改内核源码,重新编译,麻烦得要命。
后来Linus Torvalds发飙了,说这代码简直是一堆垃圾。于是设备树(Device Tree)被引入,说白了就是:把硬件描述从内核代码里剥离出来,变成一个独立的数据结构。
设备树的作用,我总结成一句话:告诉内核,你的板子上有哪些硬件,它们怎么连接的。你想想看,没有设备树的时候,内核得硬编码知道“I2C控制器在0x1000地址上”,有了设备树,内核启动时解析一下DTS文件,啥都知道了。
核心思想:设备树 = 硬件拓扑的配置文件。内核不再关心具体板子,只关心设备树描述的硬件。
1.2 基本语法结构——其实没那么玄乎
第一次看DTS文件,你可能会觉得像C语言的结构体,又像JSON。其实它有自己的规则,但上手很快。我习惯把它理解成:一个树形结构的键值对集合。
来看个最简单的例子:
/dts-v1/;
/ {
model = "MyCameraBoard";
compatible = "vendor,camera-board";
cpus {
cpu@0 {
compatible = "arm,cortex-a7";
reg = <0x0>;
};
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x40000000>;
};
i2c@1000 {
compatible = "vendor,i2c";
reg = <0x1000 0x100>;
#address-cells = <1>;
#size-cells = <0>;
camera@10 {
compatible = "vendor,camera-sensor";
reg = <0x10>;
clocks = <&clkc 15>;
reset-gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;
};
};
};
嗯,这里要注意几个关键点:
- 根节点:
/表示根,整个设备树的起点。就像文件系统的根目录。 - 节点名:
cpus、memory@80000000、i2c@1000。节点名后面可以跟@地址,用来区分同名的节点。 - 属性:
compatible、reg、clocks。属性就是键值对,值可以是字符串、数字、数组、二进制数据。 - reg属性:描述地址和大小。格式是
<地址 大小>,地址和大小分别由#address-cells和#size-cells决定。
我的小技巧:写设备树时,先把硬件框图画出来。CPU、内存、总线、外设,一层层嵌套。然后对着框图写节点,不容易漏。
1.3 dts、dtsi、dtb——三兄弟的关系
这三个文件,我刚开始也搞混过。其实很简单:
| 文件类型 | 全称 | 作用 | 类比 |
|---|---|---|---|
| .dts | Device Tree Source | 板级设备树源文件,描述具体开发板 | C源文件 (.c) |
| .dtsi | Device Tree Source Include | 公共设备树头文件,描述SoC通用部分 | C头文件 (.h) |
| .dtb | Device Tree Blob | 编译后的二进制文件,内核直接使用 | 可执行文件 (.o/.bin) |
说白了,.dtsi 是公共部分,.dts 是板级特有部分。我在项目中遇到过,有人把SoC的时钟配置写在了.dts里,结果换了个板子,同样的SoC,还得重新写一遍。正确的做法是:SoC厂商提供soc.dtsi,板子厂商在board.dts里#include它,然后只写自己板子特有的东西。
编译过程也很直接:
dtc -I dts -O dtb -o myboard.dtb myboard.dts
这个dtc工具(Device Tree Compiler)会把.dts和它include的所有.dtsi合并,生成一个.dtb二进制文件。内核启动时,bootloader把.dtb加载到内存,内核解析它,就知道硬件长什么样了。
避坑指南:我曾经因为.dtsi里定义了一个节点,.dts里又定义了一个同名节点,结果属性被覆盖了,摄像头死活不工作。后来才发现,设备树里同名节点是覆盖关系,不是合并关系。如果你要追加属性,得用&节点名的引用方式。
1.4 设备树在Camera驱动中的角色
讲到这里,你可能要问:设备树跟Camera驱动到底啥关系?
关系大了去了。一个Camera模组,通常包含:
- 传感器本身:挂在I2C总线上,需要描述地址、时钟、复位引脚
- MIPI接口:需要配置数据通道数、时钟频率
- 电源管理:需要描述AVDD、DVDD、IOVDD的电压和时序
- 控制信号:比如PWDN、RESET、STANDBY等GPIO
这些信息,全部写在设备树里。驱动代码通过of_系列API(比如of_property_read_u32、of_get_named_gpio)去读取设备树里的配置。这样,换一个Camera模组,只需要改设备树,不需要改驱动代码。
我个人习惯,在写Camera驱动前,先把设备树写好。因为设备树就是硬件的“身份证”,驱动只是去读这张身份证。身份证写错了,驱动再怎么写也白搭。
一句话总结:设备树是Camera驱动的“硬件配置清单”,驱动是“按清单干活的人”。清单写清楚,活就干得漂亮。
1.5 本章小结
这一章我们聊了设备树的起源——为了解决内核代码的混乱;基本语法——树形结构、节点、属性;以及dts/dtsi/dtb三兄弟的关系。下一章,我们会深入Camera驱动中具体的设备树节点怎么写,包括时钟、GPIO、电源、MIPI配置等。
嗯,设备树这东西,刚开始觉得麻烦,用习惯了你会发现——真香。