第一章:设备树的前世今生

为什么要有设备树?从平台设备到设备树的演进史

大家好,我是你们的讲师。今天咱们聊聊设备树的起源。

说实话,我刚入行那会儿,Linux内核里还没有设备树这玩意儿。那时候驱动怎么写的?直接在内核代码里硬编码硬件信息。你想想看,换一块开发板,就得改内核源码,重新编译。这日子,谁过谁知道。

一、平台设备时代的痛点

在设备树出现之前,Linux内核主要靠平台设备(Platform Device)来描述硬件。每个板子都有一份 board-xxx.c 文件,里面写满了寄存器地址、中断号、GPIO引脚。

典型代码长这样:

static struct resource uart_resources[] = {
    {
        .start = 0x10000000,
        .end   = 0x10000FFF,
        .flags = IORESOURCE_MEM,
    },
    {
        .start = 32,
        .end   = 32,
        .flags = IORESOURCE_IRQ,
    },
};

static struct platform_device uart_device = {
    .name = "my_uart",
    .id   = 0,
    .num_resources = ARRAY_SIZE(uart_resources),
    .resource = uart_resources,
};

嗯,这里要注意。每个板子都得写这么一份。我当年在项目里维护过6块不同板子的 board-*.c 文件,每次换平台,光改这些就够头疼的。

二、这种模式有什么问题?

说白了,就是硬件描述和内核代码强耦合。具体来说:

  • 代码膨胀:每个板子一份 board 文件,ARM 架构下这类文件一度超过 3000 个
  • 维护噩梦:改一个 GPIO 引脚,得重新编译整个内核
  • 碎片化严重:同样的 UART 控制器,不同板子要写不同的 platform_device
  • 启动慢:内核要枚举所有可能的设备,哪怕板子上根本没有

我记得有一次,客户换了一颗 Flash 芯片,只是引脚重映射了一下。结果呢?我得从内核源码里找到对应的 board 文件,改完再给客户发一个完整的内核镜像。你说这效率...

三、设备树是怎么解决这些问题的?

设备树(Device Tree)的思路其实很简单:把硬件描述从内核代码里抽出来,放到一个独立的数据结构里。这个数据结构就是 .dts 文件。

同样的 UART,用设备树描述就变成了:

uart0: serial@10000000 {
    compatible = "my_uart";
    reg = <0x10000000 0x1000>;
    interrupts = <32>;
    status = "okay";
};

你看,是不是清爽多了?硬件信息全在文本里,内核不用再硬编码了。

四、设备树的核心设计思想

我个人习惯把设备树理解成硬件配置单。它做了三件事:

  1. 描述硬件拓扑:CPU、内存、外设怎么连接的
  2. 提供配置参数:寄存器地址、中断号、时钟频率等
  3. 解耦驱动和硬件:驱动只认 compatible 字符串,不关心具体板子

一个小技巧:我在项目中经常用 dtc -I dtb -O dts 反编译设备树,看看实际运行时内核收到了什么配置。这招排查硬件配置问题特别管用。

五、设备树的演进时间线

咱们用一张图来梳理一下:

2005年 平台设备时代 board-*.c 硬编码 2007年 PowerPC 率先使用 Open Firmware 设备树 2011年 ARM Linux 开始迁移 Linus 怒批 ARM 代码 2012年 设备树成为 ARM 标准 Linux 3.7 强制要求 至今 RISC-V / ARM64 标配 几乎所有架构支持 设备树演进时间线 从平台设备硬编码 → 设备树统一描述 核心变化:硬件描述与内核代码解耦

六、设备树到底带来了什么好处?

咱们用表格对比一下:

对比项 平台设备时代 设备树时代
硬件描述位置 内核源码 board-*.c 独立 .dts 文件
换板子成本 改内核,重新编译 换 .dtb 文件即可
驱动复用性 低,与板级代码耦合 高,只认 compatible
内核镜像大小 大,包含所有板子代码 小,只包含驱动逻辑
启动速度 慢,需枚举所有设备 快,只初始化存在的设备
社区维护成本 极高,碎片化严重 低,统一标准

避坑指南:我曾经在一个项目里,直接把 x86 的 ACPI 思路套到 ARM 设备树上,结果发现设备树不支持动态加载。后来才搞清楚,设备树是静态描述,启动时一次性传递给内核。这个坑我踩过,大家注意。

七、设备树的核心文件结构

一个完整的设备树系统包含这些文件:

  • .dts:设备树源文件,你直接编写的内容
  • .dtsi:设备树头文件,公共部分放这里,比如 SoC 的定义
  • .dtb:编译后的二进制文件,内核实际使用的
  • .dtbo:设备树覆盖文件,用于运行时修改

编译命令也很简单:

dtc -I dts -O dtb -o myboard.dtb myboard.dts

反编译回来调试:

dtc -I dtb -O dts -o myboard.dts myboard.dtb

八、总结一下

设备树的出现,说白了就是 Linux 内核在硬件描述上的一次解耦革命。它让驱动开发者和板级开发者各司其职:

  • 驱动开发者:只关心 compatible 匹配,写通用驱动
  • 板级开发者:只关心 .dts 文件,描述硬件连接
  • 内核维护者:不用再为每个板子维护一份 board 文件

我个人觉得,理解设备树的关键就一句话:硬件是硬件,驱动是驱动,别混在一起。后面我们会深入讲解设备树的语法、绑定规则,以及怎么和驱动完美配合。


专注资料整理