1. 设备树基础概念

1.1 什么是设备树(Device Tree)

设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它使用树形节点(Node)和属性(Property)来向操作系统内核传递板级硬件信息。设备树源文件(.dts)经过设备树编译器(DTC)编译为二进制设备树块(.dtb),在系统启动时由引导加载程序传递给内核。

核心定义:设备树是一个硬件描述文件,它告诉内核“这块板子上有哪些设备、它们挂接在哪个总线上、使用什么资源(中断、地址、时钟等)”,从而让内核无需硬编码板级信息。

一个典型的设备树节点结构如下:

/dts-v1/;

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

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

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

    &i2c0 {
        status = "okay";
        clock-frequency = <100000>;

        eeprom@50 {
            compatible = "atmel,24c32";
            reg = <0x50>;
            pagesize = <32>;
        };
    };
};
关键语法:每个节点以 节点名@地址 标识,compatible 属性是驱动匹配的核心字段,reg 描述地址/中断等资源,status 控制设备启用/禁用。
设备树结构示意图 根节点 "/" cpus 节点 memory 节点 cpu@0 子节点 i2c0 节点 eeprom@50 子节点 设备树层级结构 根节点 → 总线/控制器节点 → 设备子节点,每个节点通过 compatible 属性与驱动绑定

1.2 为什么需要设备树

在设备树出现之前,Linux 内核使用大量的板级文件(board-*.c)来硬编码硬件信息。这种做法在 ARM 架构中导致了严重的代码膨胀和维护灾难。设备树的引入解决了以下核心问题:

对比维度 传统板级文件方式 设备树方式
硬件描述位置 硬编码在 arch/arm/mach-*/board-*.c 独立 .dts 文件,与内核源码分离
内核维护负担 每块新板子都要修改内核源码,重新编译 只需编写新的 .dts 文件,内核无需改动
多平台支持 一个内核镜像只能支持少数板子 同一内核镜像通过更换 .dtb 支持不同硬件
驱动与硬件解耦 驱动直接引用板级宏定义 驱动通过 compatible 匹配,资源从设备树获取
社区接受度 ARM 社区强烈反对,维护成本高 Linux 主线强制要求,所有新平台必须使用
重要里程碑:自 Linux 3.x 起,ARM 架构强制要求使用设备树。PowerPC、RISC-V、x86(ACPI 之外)等架构也广泛采用。设备树已成为嵌入式 Linux 硬件描述的事实标准。
设备树解决的核心问题 传统板级文件方式(问题) • 每块板子修改 arch/arm/mach-* • 内核镜像与板级硬件强耦合 • 代码膨胀,维护成本指数增长 设备树方式(解决方案) • 硬件描述独立为 .dts 文件 • 内核与板级硬件解耦 • 同一内核镜像支持多板,更换 .dtb 即可 设备树带来的关键收益 1. 硬件描述与内核源码分离,降低维护成本 2. 驱动通过 compatible 匹配,无需硬编码资源 3. 设备树覆盖中断、DMA、时钟、GPIO、电源管理等全部硬件资源

1.3 设备树与平台设备(Platform Device)的关系

平台设备(Platform Device)是 Linux 内核中用于表示“非可枚举总线设备”的抽象层。设备树与平台设备的关系可以概括为:设备树是硬件描述源,平台设备是内核中的运行时对象。设备树节点在系统启动时被内核解析,并自动创建对应的平台设备。

整个流程分为三个关键阶段:

  1. 设备树解析阶段:内核启动早期,unflatten_device_tree() 将 .dtb 展开为 device_node 树结构。
  2. 平台设备创建阶段:of_platform_populate() 遍历设备树节点,为每个 compatible 属性匹配的节点创建 platform_device。
  3. 驱动匹配阶段:平台总线(platform bus)根据 platform_device 的 name 或 compatible 与 platform_driver 进行匹配,调用 probe() 函数。
核心结论:设备树节点 → 平台设备(platform_device)→ 平台驱动(platform_driver)→ probe() 执行。设备树是“图纸”,平台设备是“零件”,平台驱动是“装配工”。

下表总结了设备树节点与平台设备之间的映射关系:

设备树元素 平台设备对应 说明
compatible = "vendor,device" platform_device.name / driver.of_match_table 驱动匹配的核心标识,优先级最高
reg = <addr size> platform_device.resource[] (IORESOURCE_MEM) 内存地址范围,驱动通过 platform_get_resource() 获取
interrupts = <num> platform_device.resource[] (IORESOURCE_IRQ) 中断号,驱动通过 platform_get_irq() 获取
status = "okay" / "disabled" 设备是否注册到平台总线 disabled 节点不会创建平台设备
自定义属性(如 clock-frequency) 通过 of_property_read_*() 在 probe 中读取 驱动使用设备树 API 获取自定义配置
设备树到平台设备的转换流程 .dts 源文件 设备树源文件 DTC 编译 设备树编译器 .dtb 二进制 设备树二进制块 内核解析 device_node unflatten_device_tree() 创建 platform_device of_platform_populate() 设备树 → 平台设备 转换要点 • 每个 compatible 匹配的节点 → 一个 platform_device • reg → IORESOURCE_MEM,interrupts → IORESOURCE_IRQ • 自定义属性通过 of_property_read_* 在 probe 中读取 • status = "disabled" 的节点不会创建设备 • SPI/I2C 子节点创建 spi_device/i2c_client,而非 platform_device
重要区分:并非所有设备树节点都创建平台设备。挂接在 SPI 总线下的子节点创建 spi_device,挂接在 I2C 总线下的子节点创建 i2c_client。平台设备主要用于“内存映射型”设备(如 GPIO 控制器、时钟控制器、DMA 控制器等)。本课程后续章节将详细讲解 SPI 和 I2C 设备节点的编写方法。