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引脚”,我得改代码、编译、烧录……折腾半天。

设备树解决了三个核心痛点:

  1. 硬件与驱动解耦:驱动代码不再包含硬件信息,只解析设备树
  2. 多板支持:同一份内核镜像,换一个设备树文件就能适配不同板子
  3. 减少内核编译:改硬件描述不用重新编译内核,只编译设备树就行

我的经验:在实际项目中,设备树最大的好处是“快速迭代”。硬件改版时,改几行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文件。