一、设备树基础:什么是设备树?为什么需要设备树?设备树在Linux内核中的角色
各位同学,今天我们来聊聊设备树。说实话,我刚入行那会儿,设备树这个概念也让我头疼了好一阵子。但搞明白之后,你会发现它其实没那么神秘。
1.1 什么是设备树?
设备树(Device Tree),说白了就是一套描述硬件信息的“说明书”。它用树形结构,把CPU、内存、外设、中断控制器、GPIO等等硬件资源,清清楚楚地列出来。
我习惯把它想象成一张“硬件地图”。内核启动时,拿着这张地图,就知道“哦,这里有个I2C控制器,那里有个SPI设备”。
来看一个最简单的例子:
/dts-v1/;
/ {
model = "TI AM335x BeagleBone Black";
compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am335x";
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>; // 256MB内存
};
cpus {
cpu@0 {
compatible = "arm,cortex-a8";
};
};
uart0: serial@44e09000 {
compatible = "ti,am3352-uart", "ti,omap3-uart";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
};
};
你看,这个树形结构,根节点是“/”,下面挂着memory、cpus、uart0等子节点。每个节点都有属性,比如compatible、reg、interrupts,这些就是硬件的“身份证”。
核心要点:设备树是一种数据结构,用于描述不可枚举的硬件。它把硬件信息从内核源码中剥离出来,变成独立的数据文件。
1.2 为什么需要设备树?
这个问题,我当年也问过自己。为什么不用传统的板级文件(board file)?
嗯,这里有个历史背景。在ARM Linux早期,每个板子都要写一个board-xxx.c文件,里面塞满了各种硬件初始化代码。比如:
// 传统方式:板级文件
static struct platform_device my_device = {
.name = "my-uart",
.id = 0,
.dev = {
.platform_data = &uart_platform_data,
},
};
static void __init my_board_init(void)
{
platform_device_register(&my_device);
// 还要注册时钟、GPIO、中断...
}
这样做有什么问题?我举个例子。我在项目中遇到过,一个SoC被用在几十种不同的板子上,每个板子都要写一份类似的board文件。内核源码里充斥着大量重复代码,维护起来简直是噩梦。
设备树解决了三个核心痛点:
- 硬件描述与内核解耦:换板子不用改内核,换一个.dtb文件就行
- 减少重复代码:同一款SoC,不同板子共用驱动,只改设备树
- 社区统一标准:PowerPC、ARM、RISC-V都用同一套描述方式
我的经验:有一次客户要快速适配一块新板子,我花了2小时改设备树,内核代码一行没动。要是放在以前,光改board文件就得折腾一整天。
1.3 设备树在Linux内核中的角色
设备树在内核启动过程中,扮演着“硬件信息传递者”的角色。它的生命周期是这样的:
- 编译阶段:.dts源文件 → dtc编译器 → .dtb二进制文件
- 启动阶段:Bootloader加载.dtb到内存,把地址传给内核
- 内核解析:内核从.dtb中读取硬件信息,创建platform_device、i2c_client等
- 驱动匹配:驱动通过compatible属性与设备节点匹配,完成初始化
下面这张图,是我自己画的设备树在内核中的流转过程:
你想想看,内核启动时,如果没有设备树,它怎么知道板子上有哪些外设?地址是多少?中断号是多少?
设备树就是内核的“眼睛”。它告诉内核:
- 内存有多大,起始地址在哪
- 哪个外设挂在哪个总线上
- 中断是怎么路由的
- GPIO的复用关系
- 时钟频率是多少
我曾经踩过的坑:有一次设备树里reg属性写错了地址,结果内核访问外设时直接挂死。查了两天才发现,原来是地址偏移量少写了一个0。所以,写设备树一定要仔细核对datasheet里的地址映射。
1.4 设备树的核心语法速览
这里我快速过一下设备树的基本语法,后面章节会详细展开:
| 语法元素 | 示例 | 说明 |
|---|---|---|
| 节点 | uart0: serial@44e09000 |
节点名+标签+地址 |
| 属性 | compatible = "ti,am3352-uart" |
驱动匹配的关键 |
| reg | reg = <0x44e09000 0x2000> |
地址+长度 |
| interrupts | interrupts = <72> |
中断号 |
| phandle | interrupt-parent = <&intc> |
引用其他节点 |
| include | #include "imx6ul.dtsi" |
包含头文件 |
我的习惯:写设备树时,我一般先看datasheet里的memory map,把地址范围搞清楚。然后对照参考板子的.dts文件,改改compatible和reg就行。千万别自己从头写,容易出错。
好了,这一章就到这里。设备树说白了就是硬件信息的“说明书”,它让内核和硬件解耦,让我们的开发工作更灵活。后面我们会深入每个细节,包括如何编写、如何调试、如何修复兼容性问题。
可可精品资料库