第一章:设备树基础
1.1 什么是设备树?
设备树,英文叫 Device Tree。说白了,它就是一个描述硬件的数据结构。
我刚开始做嵌入式时,内核里全是板级文件。换一个板子,就要改一堆 C 代码。后来 ARM Linux 社区受不了了,干脆搞了个设备树出来。嗯,这玩意儿就是用来替代那些硬编码的板级信息的。
设备树用树形结构来描述硬件。CPU、内存、中断控制器、GPIO 控制器、I2C 设备……统统挂在树上。每个硬件节点都有属性,比如地址、中断号、时钟频率等。
核心思想:把硬件描述从内核代码中剥离出来,变成独立的数据文件。内核启动时解析这个文件,就知道板子上有什么硬件了。
1.2 为什么需要设备树?
你想想看,以前没有设备树的时候,内核是怎么知道硬件的?
每个板子都要写一个 board.c 文件。里面注册平台设备、定义资源、配置中断……代码量巨大。而且不同厂商的板子,代码风格还不一样。维护起来简直是噩梦。
我记得有一次,帮客户移植内核。他们换了颗 Flash 芯片,结果要改三个文件:板级文件、NAND 驱动、分区表。改完还要重新编译整个内核。折腾了两天。
设备树解决了三个核心问题:
- 硬件描述标准化:所有硬件都用同一种语法描述,不再有五花八门的板级文件
- 内核与硬件解耦:换硬件只需要改 dts 文件,不用改内核代码
- 启动参数灵活:内存大小、串口波特率、MAC 地址等,都可以在设备树里配
我的经验:做产品开发时,我习惯把设备树和内核分开管理。内核用主线版本,设备树自己维护。这样升级内核时,只需要改少量设备树节点,省心很多。
1.3 设备树文件结构
设备树涉及三种文件:dts、dtsi、dtb。很多人一开始搞混,我简单说说它们的区别。
| 文件类型 | 扩展名 | 作用 | 谁在用 |
|---|---|---|---|
| 源文件 | .dts | 描述具体板子的硬件 | 开发者编写 |
| 头文件 | .dtsi | 存放通用硬件描述,可被多个 dts 引用 | SoC 厂商提供 |
| 二进制文件 | .dtb | 编译后的设备树,内核直接使用 | Bootloader 加载 |
dtsi 就像 C 语言的 .h 头文件。SoC 厂商会把 CPU、中断控制器、GPIO 控制器等通用部分放在 dtsi 里。板级开发者只需要 include 进来,再添加自己板子特有的外设即可。
举个例子:
// 这是 SoC 厂商提供的 .dtsi 文件
// 描述了一个通用的 ARM 处理器
/ {
#address-cells = <1>;
#size-cells = <1>;
cpu@0 {
compatible = "arm,cortex-a7";
reg = <0x0>;
};
interrupt-controller@1e00000 {
compatible = "arm,gic-400";
reg = <0x1e00000 0x1000>;
interrupt-controller;
#interrupt-cells = <3>;
};
gpio@1c00000 {
compatible = "snps,dw-apb-gpio";
reg = <0x1c00000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
};
};
然后板级 dts 文件这样写:
// 这是具体板子的 .dts 文件
// 引用了 SoC 的 dtsi,再添加板级外设
#include "soc.dtsi"
/ {
model = "MyCustomBoard v1.0";
compatible = "mycompany,myboard";
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; // 512MB
};
&uart0 {
status = "okay";
clock-frequency = <115200>;
};
&i2c0 {
status = "okay";
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
};
};
};
编译时,dtc(Device Tree Compiler)会把 dts 和 dtsi 合并,生成一个 dtb 文件。Bootloader 把 dtb 加载到内存,内核启动时解析它。
注意:我曾经遇到过一个坑——dtsi 里定义了某个外设,但板级 dts 里忘了设置 status = "okay"。结果内核根本不初始化这个设备。查了两天才发现是 status 默认是 "disabled"。所以,添加外设后一定要检查 status 属性。
1.4 设备树的核心语法
设备树的语法其实很简单。我总结几个关键点:
- 节点:用 {} 表示,节点名格式为 name@address
- 属性:key = value; 的形式,value 可以是字符串、数字、数组
- 标签:用 & 引用已有节点,方便追加属性
- 包含:用 #include 引入 dtsi 文件
这里有个小技巧:属性值里的数字,默认是 32 位。如果地址是 64 位,需要设置 #address-cells = <2>。我见过有人把 64 位地址截断了,结果内存只认到 4GB。
1.5 设备树与中断控制器、GPIO 的关系
设备树里,中断控制器和 GPIO 控制器是两个非常重要的节点。它们负责描述硬件的中断路由和 GPIO 映射。
中断控制器节点需要声明:
- interrupt-controller:表明这是一个中断控制器
- #interrupt-cells:描述中断描述符需要几个 cell
GPIO 控制器节点需要声明:
- gpio-controller:表明这是一个 GPIO 控制器
- #gpio-cells:描述 GPIO 描述符需要几个 cell
外设节点通过 interrupts 和 gpios 属性来引用这些资源。这部分内容,我们会在后面的章节详细展开。
我的建议:刚开始学设备树,不要急着看复杂的 dts 文件。先找一块简单的开发板,读它的 dts 文件。对照原理图,一个一个节点看。看懂了,再自己写一个最小系统。实践出真知。
1.6 本章知识体系
下面这张图,概括了设备树的核心概念和文件结构:
这张图展示了设备树的完整知识体系。从源文件到二进制文件,从核心概念到应用场景。后面的章节,我们会逐一深入每个细节。
好了,第一章就到这里。设备树的基础概念,说白了就是:用树形结构描述硬件,把硬件信息从内核代码中分离出来。dts、dtsi、dtb 三种文件各司其职,共同构成了设备树的完整生态。
下一章,我们会深入设备树的语法细节。包括节点命名规则、属性类型、地址编码等。这些都是写设备树必须掌握的基本功。
公众号:蓝海资料掘金营,微信deep3321