1. 设备树基础:起源、作用与基本概念

1.1 为什么Linux内核需要设备树?

在早期的Linux内核(2.6及更早版本)中,嵌入式平台的硬件描述信息被直接硬编码在板级文件(Board File)中。每个开发板都需要在 arch/arm/mach-xxx 目录下维护一个庞大的 board-xxx.c 文件,其中包含了内存地址、中断号、GPIO映射、时钟配置等硬件细节。这种模式带来了严重的维护灾难:

  • 代码冗余:同一款SoC的不同开发板,板级文件大量重复。
  • 耦合过高:驱动代码与硬件地址、中断号等具体参数深度绑定。
  • 社区分裂:每提交一个新板子,就需要合并大量板级代码,导致内核主线难以统一。
  • 启动困难:引导加载程序(Bootloader)无法向内核传递灵活的硬件拓扑信息。
核心痛点:内核需要一种与架构无关、与驱动解耦、可动态传递的硬件描述机制。设备树(Device Tree)正是为解决这一问题而诞生。

1.2 设备树的起源

设备树的概念源自Open Firmware(IEEE 1275标准),最初用于PowerPC架构。2011年左右,Linux ARM社区开始大规模引入设备树,并在Linux 3.x内核中逐步取代了传统的板级文件。如今,设备树已成为ARM、RISC-V、MIPS、x86(部分场景)等主流架构的标准硬件描述方式。

阶段 时间 描述
起源 1990s Open Firmware标准定义设备树概念,用于PowerPC/Mac
引入ARM 2011 Linux ARM社区开始将设备树作为板级描述的替代方案
成熟 2013+ Linux 3.10+ 内核中,设备树成为ARM平台的标准配置方式
扩展 2016+ RISC-V、MIPS、x86(ACPI替代)等架构广泛采用

1.3 设备树的基本概念

设备树是一种树形数据结构,用于描述硬件平台中所有可寻址的设备及其连接关系。它由三个核心部分组成:

  • 设备树源文件(.dts):人类可读的文本文件,使用设备树语法描述硬件。
  • 设备树源包含文件(.dtsi):公共的SoC级描述,可被多个.dts文件包含。
  • 设备树二进制文件(.dtb):由DTC(Device Tree Compiler)编译生成的二进制文件,供内核解析。
类比理解:.dts 相当于C语言源文件,.dtsi 相当于头文件,.dtb 相当于编译后的二进制文件。Bootloader负责将.dtb加载到内存,并将地址传递给内核。

1.4 设备树的核心作用

作用 说明 传统方式
硬件描述标准化 统一描述CPU、内存、外设、中断、GPIO、时钟等 板级文件硬编码
驱动与硬件解耦 驱动通过设备树节点名称和属性匹配硬件,不依赖具体地址 驱动直接引用板级宏定义
多板支持 同一内核镜像通过更换.dtb即可支持不同开发板 需重新编译内核
Bootloader传递 U-Boot等引导程序可动态修改.dtb(如内存大小、MAC地址) 内核启动参数有限
社区协作 SoC厂商只需提供.dtsi,板卡厂商补充.dts,减少重复工作 每个板子独立维护

1.5 设备树的基本结构

设备树采用单根树状结构,根节点为 /,所有设备节点都是根节点的子节点或后代节点。每个节点包含:

  • 节点名称:格式为 name@unit-address,如 uart@fe001000。
  • 属性(Properties):键值对,描述设备的配置参数,如 reg、interrupts、compatible。
  • 子节点:嵌套的子设备,如I2C总线下的从设备。
// 一个简化的设备树示例
/dts-v1/;

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

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x10000000>;  // 256MB内存
    };

    chosen {
        stdout-path = &uart0;
    };

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

        i2c0: i2c@44e0b000 {
            compatible = "ti,omap4-i2c";
            reg = <0x44e0b000 0x1000>;
            interrupts = <70>;
            #address-cells = <1>;
            #size-cells = <0>;
            status = "okay";

            eeprom@50 {
                compatible = "atmel,24c32";
                reg = <0x50>;
            };
        };
    };
};

1.6 设备树与内核probe流程的关系

设备树是驱动probe流程的起点。内核启动时,会解析.dtb并构建一个内存中的设备树(struct device_node树)。当驱动注册时,内核通过 compatible 属性进行匹配,匹配成功后调用驱动的probe函数。这一流程将在后续章节中详细展开。

设备树结构示意图 设备树结构示意图 根节点 "/" memory@80000000 chosen ocp uart0: serial@44e09000 i2c0: i2c@44e0b000 eeprom@50 关键属性说明 • compatible: 驱动匹配的核心标识,格式为 "厂商,型号" • reg: 地址和长度信息,格式为 <基地址 长度> • interrupts: 中断号,用于绑定中断控制器

1.7 设备树文件编译与使用流程

  1. 编写.dts/.dtsi文件:根据硬件原理图和数据手册,描述设备节点和属性。
  2. 编译为.dtb:使用DTC工具执行 dtc -I dts -O dtb -o output.dtb input.dts。
  3. 打包或烧录:将.dtb放入boot分区,或与内核镜像打包在一起。
  4. Bootloader加载:U-Boot等引导程序从存储介质读取.dtb到内存。
  5. 内核解析:内核启动早期,从Bootloader传递的地址解析.dtb,构建设备树内存结构。
  6. 驱动匹配与probe:驱动注册时,内核遍历设备树节点,通过compatible属性匹配,调用probe。
常见误区:设备树不是编程语言,不包含执行逻辑。它只是静态数据,所有硬件行为(如时序、初始化顺序)仍需由驱动代码实现。设备树只描述“有什么设备”和“设备怎么连接”,不描述“设备怎么工作”。

1.8 本章小结