1. 设备树起源:为什么需要设备树?从板级文件到设备树的演进历史

1.1 那个“硬编码”的年代

做嵌入式Linux开发的老手,应该都记得那个年代。

大概十年前,我刚开始接触内核移植。那时候每换一块板子,都得改内核源码。不是改驱动,是改一个叫 board-xxx.c 的文件。这种文件,我们叫它“板级文件”。

说白了,就是把硬件信息硬生生写死在C代码里。比如:

static struct resource i2c_resources[] = {
    {
        .start = 0x1000,
        .end   = 0x10FF,
        .flags = IORESOURCE_MEM,
    },
    {
        .start = IRQ_I2C,
        .end   = IRQ_I2C,
        .flags = IORESOURCE_IRQ,
    },
};

static struct platform_device i2c_device = {
    .name = "i2c-bus",
    .id   = 0,
    .num_resources = ARRAY_SIZE(i2c_resources),
    .resource = i2c_resources,
};

你看,内存地址、中断号、设备名称,全写在C语言结构体里。每次换一颗芯片,或者改一下引脚复用,就得重新编译整个内核。

我个人习惯,那时候维护一个BSP,手头至少有三四个 board-xxx.c 文件。改一个地方,得同步改好几个文件。稍不留神,就出bug。

1.2 为什么这条路走不通了?

为什么会这样?因为硬件越来越复杂了。

早期的ARM芯片,外设就那么几个。一个UART,一个I2C,一个GPIO。写死在代码里,问题不大。但到了Cortex-A系列,芯片内部集成了几十个外设。而且同一颗SoC,可以搭配不同的板子——有的用eMMC,有的用NAND Flash,有的用Nor Flash。

你想想看,如果每个板子都要改内核源码,那工作量有多大?

我在项目中遇到过最头疼的事:客户换了一颗DDR颗粒,时序参数变了。我得重新编译内核,给客户发一个新的uImage。客户那边还抱怨:“我就换个内存,你让我升级整个系统?”

嗯,这确实不合理。

核心痛点:

  • 硬件信息与驱动代码强耦合
  • 每款板子都需要独立的内核镜像
  • 硬件变更必须重新编译内核
  • 不同厂商的板级文件风格各异,难以维护

1.3 设备树:把硬件描述还给硬件

所以,Linux社区开始思考:能不能把硬件信息从C代码里抽出来?

答案就是设备树(Device Tree)。

设备树的核心思想很简单:用文本文件描述硬件,内核启动时解析这个文件,动态构建设备模型。驱动不再关心硬件接在哪根总线上、中断号是多少,它只负责“干活”。

说白了,就是“硬件描述”和“驱动逻辑”解耦。

/ {
    model = "MyBoard V1.0";
    compatible = "vendor,myboard";

    i2c@1000 {
        compatible = "vendor,i2c-bus";
        reg = <0x1000 0x100>;
        interrupts = <31>;
        status = "okay";
    };
};

你看,同样的I2C控制器,现在用文本描述。地址、中断号、状态,一目了然。换板子?改这个.dts文件就行,不用碰内核源码。

1.4 演进历史:从PowerPC到ARM

设备树其实不是Linux原创的。它最早来自PowerPC平台。PowerPC的硬件五花八门,IBM、Motorola、AMCC各搞一套,板级文件根本管不过来。所以PowerPC社区最早采用了Open Firmware的设备树概念。

ARM架构后来也遇到了同样的问题。2011年左右,ARM Linux社区开始大规模推进设备树。我记得那段时间,Linus Torvalds还发过火,说ARM的板级文件是“一团乱麻”。

从那以后,新加入的ARM SoC都强制要求使用设备树。到了2014年左右,老旧的板级文件基本被清理干净了。

一个小知识:

设备树的文件后缀名是.dts(Device Tree Source)。编译后的二进制文件是.dtb(Device Tree Blob)。内核启动时,由bootloader把.dtb加载到内存,内核解析后构建设备模型。

1.5 设备树到底解决了什么问题?

我总结一下,设备树带来的好处,其实就三点:

  1. 一次编译,到处运行——同一个内核镜像,配合不同的.dtb,可以适配不同的板子。
  2. 硬件变更无需改驱动——换DDR、改GPIO、调中断,改.dts就行。
  3. 社区统一标准——所有厂商都按同一套语法写设备树,维护成本大大降低。

避坑指南:

我曾经遇到过一个坑:某厂商的BSP里,设备树里写的中断号是硬件中断号,但内核驱动里用的是Linux中断号。两者差了32。结果中断一直不触发,查了两天才发现是设备树写错了。所以,设备树里的中断号,一定要看芯片手册,别想当然。

1.6 一张图看懂演进脉络

下面这张图,是我自己画的。它展示了从板级文件到设备树的演进过程:

设备树演进历史 板级文件时代 board-xxx.c 硬编码 过渡期 板级文件 + 设备树并存 设备树时代 .dts/.dtb 标准化 核心变化对比 板级文件 • 硬件信息写在C代码中 • 换板子必须重新编译内核 • 不同厂商风格不统一 • 维护成本高,容易出错 设备树 • 硬件信息用文本描述 • 换板子只需换.dtb文件 • 统一的语法标准 • 驱动与硬件解耦,易于维护 2005 2011 2014

1.7 我的个人体会

做嵌入式这么多年,我最大的感受是:设备树不是银弹。

它解决了硬件描述的问题,但带来了新的复杂度。比如设备树的覆盖(overlay)、引脚复用(pinctrl)的写法,刚开始接触时确实有点绕。但相比以前改板级文件的痛苦,这点学习成本,我觉得值。

嗯,这一章就到这里。设备树到底怎么写的?下一章我们直接上手。


专注资料整理