1. 设备树基础概念
大家好,我是你们的嵌入式Linux讲师。今天咱们来聊聊设备树——这个在Linux内核开发中绕不开的话题。
说实话,我刚入行那会儿,设备树还是个新鲜玩意儿。那时候我还在用古老的板级文件,每次换一个外设都要改一堆C代码,改完还得重新编译内核,烦得很。后来设备树普及了,我才发现——原来硬件描述可以这么优雅。
什么是设备树?
设备树,英文叫Device Tree,简称DT。它本质上是一种数据结构,用来描述硬件资源。
你想想看,一个嵌入式板子上有CPU、内存、GPIO控制器、I2C控制器、SPI控制器……这些硬件怎么连接?地址是多少?中断号是多少?设备树就是干这个的。
它用树形结构来描述硬件。根节点是板子本身,子节点是各种外设。每个节点都有属性,比如寄存器地址、中断号、时钟频率等等。
核心要点:设备树是硬件描述文件,不是驱动代码。它告诉内核“有什么硬件”,而不是“怎么驱动硬件”。
来看一个最简单的例子:
/dts-v1/;
/ {
model = "MyBoard";
compatible = "vendor,myboard";
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>;
};
uart0: serial@10000000 {
compatible = "ns16550";
reg = <0x10000000 0x1000>;
interrupts = <0 5 4>;
};
};
这段代码描述了一个板子,有256MB内存,一个UART串口。嗯,就是这么简单直接。
为什么需要设备树?
这个问题我经常被问到。说白了,就是为了解决硬件描述和内核代码的耦合问题。
在设备树出现之前,Linux内核是怎么描述硬件的?
- 板级文件(Board File):每个板子一个C文件,里面写满硬件信息
- ACPI:x86平台用的,但嵌入式不适用
- 平台数据(Platform Data):结构体满天飞,改一个引脚就要改代码
我当年维护过一个项目,有5个不同型号的板子。每个板子都有一个mach-xxx.c文件,加起来上千行。最痛苦的是,客户说“换个LED引脚”,我得改代码、编译、烧录……折腾半天。
设备树解决了三个核心痛点:
- 硬件与驱动解耦:驱动代码不再包含硬件信息,只解析设备树
- 多板支持:同一份内核镜像,换一个设备树文件就能适配不同板子
- 减少内核编译:改硬件描述不用重新编译内核,只编译设备树就行
我的经验:在实际项目中,设备树最大的好处是“快速迭代”。硬件改版时,改几行dts文件,比改C代码快得多。我曾经在半小时内适配了一块新板子,全靠设备树。
设备树的历史与演进
设备树的历史,其实是一部嵌入式Linux的进化史。
| 时期 | 方案 | 特点 |
|---|---|---|
| 2000年代初 | 板级文件 | 每个板子一个C文件,硬编码硬件信息 |
| 2005年左右 | 平台设备模型 | 引入platform_device,但仍有大量板级代码 |
| 2011年 | ARM开始采用设备树 | Linux 3.0开始,ARM架构逐步迁移 |
| 2014年 | 设备树全面普及 | Linux 3.16之后,新板子基本都用设备树 |
| 至今 | 设备树+ACPI | 嵌入式用设备树,服务器用ACPI |
我记得2011年那会儿,Linus Torvalds发了一封邮件,痛批ARM架构的板级文件乱象。他说:“每次合并窗口,ARM的板级代码都是一堆垃圾。” 从那以后,ARM社区开始认真推动设备树。
为什么会选择设备树?其实它借鉴了PowerPC和SPARC架构的经验。这些架构早就用设备树来描述硬件了。Linux内核社区觉得这玩意儿靠谱,就移植了过来。
注意:设备树不是万能的。它只描述硬件,不描述硬件行为。比如,它可以说“这个GPIO引脚是输出”,但不能说“这个引脚用来控制LED闪烁频率”。行为逻辑还是要写在驱动里。
到了2014年左右,设备树基本成了嵌入式Linux的标准。现在你随便拿一块开发板,比如BeagleBone、Raspberry Pi,它们的硬件描述都是设备树。
嗯,这里有个小故事。我有个朋友,他做一款智能家居网关,用了全志的芯片。硬件改版了三次,每次都是改dts文件,内核镜像一次都没重新编译。他说:“设备树救了我的命。” 虽然夸张了点,但确实省了不少事。
设备树文件格式
设备树文件有两种格式:
- .dts:文本格式,人类可读,用来编辑
- .dtb:二进制格式,内核直接使用,由dts编译而来
编译工具叫dtc(Device Tree Compiler)。用法很简单:
dtc -I dts -O dtb -o myboard.dtb myboard.dts
反过来,也可以把dtb反编译成dts:
dtc -I dtb -O dts -o myboard.dts myboard.dtb
这个技巧我经常用。有时候拿到一块新板子,没有dts源码,只有dtb文件。反编译一下,就能看到硬件布局了。
避坑指南:我曾经遇到过一个问题——反编译出来的dts和原始dts不完全一样。因为dtc在编译时会做一些优化,比如删除注释、重新排序属性。所以反编译只能作为参考,不能完全还原。
设备树的核心概念
最后,咱们快速过一下设备树的几个核心概念:
- 节点(Node):树形结构中的每个元素,用{}表示
- 属性(Property):节点的特征,用key-value表示
- 标签(Label):给节点起别名,方便引用
- 包含(Include):用#include引入其他dts文件
- 覆盖(Overlay):动态修改设备树,用于模块化硬件
举个例子:
// 标签:uart0
// 节点:serial@10000000
// 属性:compatible, reg, interrupts
uart0: serial@10000000 {
compatible = "ns16550";
reg = <0x10000000 0x1000>;
interrupts = <0 5 4>;
};
这些概念后面会详细讲,今天先有个印象就行。
好了,第一章就到这里。设备树不是什么高深的东西,说白了就是一个硬件描述文件。但它的设计思想——解耦、复用、标准化——值得我们学习。
下一章,咱们深入设备树的语法和结构,手把手教你写一个完整的dts文件。