设备树入门:什么是设备树?为什么需要设备树?
大家好,我是你们的嵌入式Linux驱动讲师。今天咱们开始第一课——设备树入门。
说实话,我刚接触Linux驱动那会儿,还没有设备树这东西。那时候板子一换,整个BSP代码都得跟着改,那叫一个痛苦。后来设备树出现了,我第一反应是:这玩意儿能解决我的痛点吗?
答案是:能,而且解决得很彻底。
什么是设备树?
设备树,英文叫 Device Tree,简称 DT。它本质上是一个描述硬件的数据结构。
你想想看,一个嵌入式板子上有CPU、内存、I2C控制器、SPI控制器、GPIO、中断控制器……这些硬件怎么连接的?地址是多少?中断号是多少?
以前的做法是:把这些信息硬编码在C代码里。换一块板子,就得改代码、重新编译内核。
设备树的思路很简单:把硬件描述从内核代码中剥离出来,单独放在一个文本文件里。这个文件就是设备树源文件(.dts)。
我打个比方:
- 以前:硬件信息写在C代码里,像把菜谱刻在锅上
- 现在:硬件信息写在.dts里,像把菜谱写在便签纸上,锅还是那个锅
内核启动时,会解析这个.dts文件,动态构建出硬件拓扑。换板子?换.dts文件就行,内核不用重新编译。
核心要点:设备树是描述硬件的结构化数据,不是代码。它让内核与硬件描述解耦。
为什么需要设备树?
这个问题,我当年也问过自己。后来在项目里踩了坑,才真正理解。
原因主要有三个:
- 硬件多样性爆炸——同一个SoC可以搭配不同的外设、不同的引脚复用。如果每个组合都写一份内核代码,那维护成本就上天了。
- 内核与板级代码耦合——以前Linux内核里充斥着arch/arm/mach-xxx这样的板级文件。每次主线内核合并,这些文件都是维护噩梦。
- 启动参数传递困难——Bootloader需要告诉内核:内存多大、时钟多快、串口用哪个。没有统一标准,全靠约定。
设备树解决了这三个问题:
- 硬件描述统一放在.dts里,内核只认设备树
- 板级代码从内核中消失,迁移到设备树中
- Bootloader通过设备树二进制(.dtb)传递硬件信息,标准化了
我的经验:我在一个项目里接手过一块老板子,没有设备树支持。为了加一个新外设,我得翻遍arch/arm/mach-xxx下的代码,找到板级初始化函数,再手动注册platform_device。那感觉就像在迷宫里找钥匙。后来换成设备树,半小时搞定。
设备树在Linux内核中的角色
设备树在内核启动流程中扮演什么角色?我画个简化的流程给你看:
Bootloader → 加载内核 → 加载dtb → 内核解析dtb → 构建设备模型 → 驱动匹配 → 设备初始化
具体来说:
| 阶段 | 设备树的作用 |
|---|---|
| Bootloader阶段 | 将dtb文件加载到内存,把地址传给内核 |
| 内核启动早期 | 解析dtb,获取内存大小、CPU数量、中断控制器等基础信息 |
| 设备模型构建 | 根据dtb中的节点,创建platform_device、i2c_client等设备对象 |
| 驱动匹配 | 驱动通过compatible属性与设备节点匹配,触发probe |
| 运行时 | 驱动通过of_* API读取设备树中的属性,获取资源 |
说白了,设备树就是内核的「硬件说明书」。内核不看电路图,只看设备树。
注意:设备树不是万能的。它只描述硬件「是什么」「在哪里」,不描述「怎么用」。怎么用是驱动的事。我曾经见过有人试图在设备树里描述驱动逻辑,那是不对的。
一个简单的设备树例子
光说不练假把式。咱们看一个最简单的设备树片段:
/dts-v1/;
/ {
model = "我的开发板";
compatible = "mycompany,myboard";
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>;
};
uart0: serial@10000000 {
compatible = "ns16550";
reg = <0x10000000 0x1000>;
interrupts = <0 5 4>;
clock-frequency = <1843200>;
};
};
解释一下:
/dts-v1/;—— 版本声明,告诉编译器这是v1格式/—— 根节点,整棵树的起点model、compatible—— 板子标识,驱动匹配时用memory@80000000—— 内存节点,起始地址0x80000000,大小256MBuart0—— 串口节点,地址0x10000000,中断号5,时钟1.8432MHz
嗯,这里要注意:reg属性的格式是<地址 长度>,可以有多组。中断的格式取决于中断控制器,这个我们后面会细讲。
避坑指南
我刚开始写设备树时,犯过一个低级错误:忘记加/dts-v1/;。结果编译出来的dtb内核不认,启动卡死。查了半天才发现是版本声明丢了。
还有一次,我把compatible写成了compatiable,少了个i。驱动死活匹配不上。嗯,这种拼写错误,编译器不会报错,但内核就是找不到驱动。
所以我的建议是:
- 写设备树时,先写根节点,再写子节点,层次要清晰
- 每个节点都要有
compatible属性,除非是纯容器节点 - 地址和大小用十六进制,不要用十进制
- 养成用
dtc -I dts -O dtb编译检查的习惯
总结一下:设备树是嵌入式Linux的基石。它把硬件描述从内核代码中解放出来,让驱动开发更聚焦、更可移植。你不需要记住每个板子的内存地址,只需要会写设备树。
下一章,咱们会深入设备树的语法结构,包括节点、属性、标签、引用这些核心概念。到时候我会带你看懂一个完整的.dts文件。
今天就到这里。有问题欢迎交流。