1. 设备树基础
各位同学好,我是你们的嵌入式Linux讲师。今天咱们来聊聊设备树——这个让无数驱动开发者又爱又恨的东西。
说实话,我刚入行那会儿,Linux内核里还没有设备树这玩意儿。那时候板级文件里全是密密麻麻的硬件初始化代码,一个平台文件动不动就上千行。后来我参与的一个项目要同时支持三款硬件略有差异的板子,那叫一个痛苦——代码复制粘贴,改一处漏三处。嗯,设备树就是来解决这个问题的。
1.1 设备树的起源:为什么要有它?
设备树,英文叫Device Tree,缩写DT。它最早源于PowerPC架构,后来被ARM社区大力推广,现在几乎成了所有嵌入式Linux平台的标配。
你想想看,在没有设备树之前,内核是怎么知道板子上有哪些硬件的?
- 每个板子都有一个对应的
board-xxx.c文件 - 里面硬编码了GPIO、中断、时钟、内存地址等信息
- 换一个板子就要重新编译内核
说白了,就是把硬件描述和内核代码死死绑在一起。维护起来?噩梦。
设备树的核心思想很简单:把硬件描述从内核源码中剥离出来。内核只负责解析设备树,然后根据描述去初始化硬件。这样,换板子只需要换一个设备树文件,内核本身不用动。
核心价值:设备树实现了「硬件描述」与「驱动代码」的解耦。这是嵌入式Linux走向通用化的关键一步。
1.2 基本语法结构:长什么样?
设备树的语法,其实挺直观的。它用树形结构来描述硬件,根节点是/,下面挂各种子节点。
我给大家看一个最简单的例子:
/dts-v1/;
/ {
compatible = "mycompany,myboard-v1";
cpu@0 {
compatible = "arm,cortex-a7";
reg = <0>;
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>;
};
uart0: serial@10000000 {
compatible = "ns16550";
reg = <0x10000000 0x1000>;
interrupts = <0 5 4>;
clock-frequency = <24000000>;
};
};
这里有几个关键点,我一个个说:
/dts-v1/;:版本声明,告诉编译器这是DTS v1格式。没有它,编译器会报错。/ { };:根节点,整个设备树的起点。compatible:兼容性字符串,内核用它来匹配驱动。格式一般是"厂商,型号"。reg:地址和大小信息。比如内存节点,地址0x80000000,大小0x10000000(256MB)。interrupts:中断描述,包含中断控制器、中断号、触发方式等。
个人习惯:我写设备树时,喜欢在节点名后面加个标签(比如uart0:),方便在其他地方引用。这个习惯帮我避免了很多低级错误。
1.3 dts、dtsi、dtb:三者的关系
这三个文件格式,初学者经常搞混。我简单梳理一下:
| 文件 | 全称 | 作用 | 是否可读 |
|---|---|---|---|
| .dts | Device Tree Source | 设备树源文件,描述具体板子的硬件 | 文本,可读 |
| .dtsi | Device Tree Source Include | 设备树头文件,存放通用或SoC级定义 | 文本,可读 |
| .dtb | Device Tree Blob | 编译后的二进制文件,内核直接使用 | 二进制,不可读 |
它们的关系,说白了就是:.dts和.dtsi是源码,.dtb是编译产物。
我举个例子。假设你用的是某款全志SoC,它的芯片级定义都在sun8i.dtsi里。然后你做了一块板子,叫myboard.dts,里面通过#include "sun8i.dtsi"引入芯片定义,再添加自己板子特有的外设。
// myboard.dts
/dts-v1/;
#include "sun8i.dtsi"
/ {
model = "MyBoard v1.0";
compatible = "mycompany,myboard", "allwinner,sun8i";
&uart0 {
status = "okay";
};
&mmc0 {
status = "okay";
bus-width = <4>;
};
};
你看,这里用了&uart0和&mmc0的引用语法,去修改.dtsi里定义好的节点。这种写法很常见,我个人非常推荐。
我曾经踩过的坑:有一次我忘了在.dts里包含.dtsi文件,结果编译出来的dtb只有根节点,内核启动时直接panic。排查了半天才发现是#include写漏了。所以,每次编译前我都会检查一下依赖关系。
1.4 编译流程:从dts到dtb
设备树的编译工具叫dtc(Device Tree Compiler)。内核源码里自带,路径在scripts/dtc/下。
基本用法:
dtc -I dts -O dtb -o myboard.dtb myboard.dts
反过来,也可以把dtb反编译成dts:
dtc -I dtb -O dts -o myboard.dts myboard.dtb
这个反编译功能,我在调试时经常用。有时候拿到一个别人给的dtb,想知道里面配了什么,反编译一下就看清楚了。
1.5 知识体系总览
为了让大家更直观地理解设备树的核心逻辑,我画了一张图:
这张图把设备树的三个核心模块串起来了。你从左边看起——为什么要用设备树?因为要解耦。中间是语法,怎么描述硬件。右边是文件关系,源码怎么变成内核能用的东西。
嗯,这一章的内容就到这里。设备树看起来东西多,但核心逻辑其实很清晰。后面几章我们会深入到时钟、电源管理这些实战场景中,到时候你会更深刻地理解设备树的价值。
给新手的建议:刚开始学设备树,别急着背语法。先找一块开发板的dts文件,打开看看,对照原理图,一行一行读。读懂了,你就入门了。
可可精品资料库