1. 设备树基础:什么是设备树?为什么需要设备树?设备树与ACPI的区别

各位同学,咱们今天聊点实在的。设备树(Device Tree)这东西,说白了就是一套描述硬件信息的“数据手册”。你想想看,一个嵌入式板子上面有CPU、内存、串口、I2C控制器、GPIO……这些东西怎么让内核知道?总不能让人家内核工程师每换一块板子就改一遍代码吧?

设备树就是来解决这个问题的。它用一套树形结构,把硬件信息写成文本,内核启动时直接解析它,就知道“哦,我这块板子上有个UART3,地址在0x1000A000,中断号是42”。

核心一句话:设备树是描述硬件的结构化数据,让同一份内核镜像能适配不同硬件平台。

1.1 为什么需要设备树?

我刚开始做嵌入式那会儿,用的还是老办法——直接在C代码里写死硬件信息。比如你想加个网卡,就得改 board-xxx.c 文件,然后重新编译内核。换一块板子?对不起,得重新来一遍。

这有什么问题呢?

  • 代码膨胀:每个板子都要维护一份 board 文件,Linux 内核里曾经有上千个这样的文件
  • 维护噩梦:厂商升级内核时,这些板级代码得跟着改,工作量巨大
  • 碎片化:同样的外设,不同板子的初始化代码可能写得五花八门

设备树出现后,情况就变了。硬件描述从C代码里剥离出来,变成纯文本的 .dts 文件。内核启动时,通过解析这个文件来动态初始化硬件。换板子?换一个 .dts 文件就行,内核代码不用动。

我的经验:我在一个项目里遇到过,客户要求把同一套系统移植到三块不同的板子上。如果没有设备树,我得维护三份板级代码,每份几千行。用了设备树,只需要改几十行的 .dts 文件,省了至少两周的工作量。

1.2 设备树长什么样?

咱们直接看个例子。这是一个简单的设备树片段:

/dts-v1/;

/ {
    model = "TI AM335x BeagleBone Black";
    compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;

        cpu@0 {
            compatible = "arm,cortex-a8";
            device_type = "cpu";
            reg = <0x0>;
        };
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x20000000>;  /* 512 MB */
    };

    uart0: serial@44e09000 {
        compatible = "ti,am3352-uart", "ti,omap3-uart";
        reg = <0x44e09000 0x2000>;
        interrupts = <72>;
        clock-frequency = <48000000>;
    };
};

你看,结构很清晰:

  • 根节点 / 描述整个板子
  • cpus 节点描述CPU信息
  • memory 节点描述内存布局
  • uart0 节点描述串口外设

每个节点里都有 compatible 属性,这是用来匹配驱动的。内核驱动里会声明自己支持哪些 compatible 字符串,设备树里写对了,驱动就能自动绑定上。

注意:compatible 字符串的命名有讲究。一般格式是 "厂商,型号",比如 "ti,am3352-uart"。我曾经见过有人随便写了个 "my_uart",结果内核驱动死活匹配不上,查了半天才发现是命名不规范。

1.3 设备树与ACPI的区别

说到这,可能有同学会问:那ACPI(高级配置与电源管理接口)不也是描述硬件的吗?它俩有啥区别?

嗯,这个问题问得好。我简单说说:

对比项 设备树 (DT) ACPI
主要应用场景 ARM、RISC-V 等嵌入式平台 x86、x86_64 服务器/PC
数据格式 纯文本 .dts,编译为二进制 .dtb AML(ACPI Machine Language)字节码
灵活性 高,可随意修改 .dts 文件 低,通常由BIOS/UEFI固件提供
运行时修改 支持设备树覆盖(overlay) 支持 DSDT 替换,但较复杂
电源管理 功能有限,需额外驱动支持 原生支持,功能强大
热插拔 支持有限 原生支持(PCIe、USB等)

说白了,设备树是“嵌入式专用”的轻量方案,ACPI是“PC/服务器”的全功能方案。为什么会有这种差异?

你想想看,嵌入式设备硬件是固定的,板子出厂后基本不会变。设备树简单直接,一个文本文件就能搞定。而PC上呢?你可能今天插个显卡,明天换个网卡,后天加个NVMe硬盘。ACPI需要处理这些动态变化,所以设计得更复杂。

关键区别:设备树是“静态描述”,ACPI是“动态管理”。设备树告诉内核“我有什么”,ACPI告诉内核“我有什么,以及怎么管理它们”。

我记得有一次,一个做x86移植的同事问我:“能不能在x86上用设备树?”理论上可以,但实际没人这么干。因为x86的BIOS/UEFI已经提供了ACPI,你再搞一套设备树,等于重复造轮子,而且ACPI的电源管理、热插拔功能设备树根本替代不了。

反过来,ARM平台上用ACPI的也有,但很少。因为ARM的固件生态不像x86那么统一,设备树更灵活,更适合嵌入式场景。

1.4 小结

这一节咱们讲了三个核心问题:

  • 设备树是什么:描述硬件信息的树形数据结构
  • 为什么需要它:把硬件描述从内核代码中分离出来,提高可移植性
  • 和ACPI的区别:设备树轻量静态,ACPI复杂动态,各有所长

下一节,咱们会深入设备树的语法细节,包括节点、属性、标签、引用这些概念。到时候我会拿一个实际项目里的设备树文件来拆解,保证你看完就能自己写 .dts 文件。

课后小建议:找一块你手头的开发板,看看它的 .dts 文件。别怕看不懂,先熟悉一下结构。我当年就是这么入门的——对着树莓派的设备树文件,一行一行地查文档,慢慢就摸清楚了。