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

1.1 设备树的起源:从“硬编码”到“描述”

在早期 Linux 内核(2.6 及之前)中,板级硬件信息(如内存地址、中断号、GPIO 映射)被直接硬编码在 arch/arm/mach-xxx 下的 C 语言板文件中。每增加一款新硬件平台,就需要新增或修改大量 C 代码,导致内核源码膨胀、维护困难,且无法实现“同一内核镜像支持多款设备”。

设备树(Device Tree,DT)正是为解决此问题而生。它起源于 PowerPC 架构(Open Firmware),后被 ARM、RISC-V、x86 等架构广泛采用。其核心思想是:将硬件描述从内核代码中剥离,以结构化文本(DTS)的形式独立存在,内核在启动时解析该描述,动态创建平台设备。

关键转变: 从“代码定义硬件” → “数据描述硬件”。设备树是数据,不是代码。

1.2 设备树的作用:硬件拓扑的“数据清单”

设备树在 Linux 系统中扮演三个核心角色:

角色 说明 典型内容
硬件拓扑描述 描述 CPU、内存、总线、外设的层级连接关系 CPU 核心数、内存基地址与大小、总线类型
资源清单 声明每个设备使用的硬件资源 中断号、寄存器地址范围、DMA 通道、时钟频率
配置参数 提供设备运行所需的非通用参数 GPIO 默认电平、SPI 模式、I2C 地址

内核启动时,Bootloader(如 U-Boot)将编译后的设备树二进制(DTB)传递给内核;内核的 Device Tree Blob (DTB) 解析器 遍历节点,为每个匹配的节点创建 platform_device 或 i2c_client 等设备实例。

1.3 设备树的基本结构:节点、属性与层级

设备树源文件(.dts)采用树形层级结构,每个节点代表一个硬件设备或总线,节点内包含若干属性(键值对)。

// 最小设备树示例
/dts-v1/;

/ {
    model = "Example ARM Board";
    compatible = "vendor,example-board";
    #address-cells = <1>;
    #size-cells = <1>;

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x40000000>;  // 1GB RAM
    };

    soc {
        compatible = "simple-bus";
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;

        uart0: serial@10000000 {
            compatible = "vendor,uart";
            reg = <0x10000000 0x1000>;
            interrupts = <0 33 4>;
            clock-frequency = <24000000>;
        };
    };
};

核心语法要素:

  • 根节点:/,整个设备树的起点,包含 model、compatible 等全局属性。
  • 节点名:uart0: serial@10000000,格式为 [标签:]名称[@地址]。标签(label)用于引用,地址用于区分同类型设备。
  • 属性:compatible = "vendor,uart";,驱动匹配的关键字符串;reg 描述地址空间;interrupts 描述中断资源。
  • 地址单元:#address-cells 和 #size-cells 定义子节点 reg 属性的地址和长度字段宽度(32位或64位)。
设备树层级结构示意图 根节点 / model / compatible memory@80000000 reg = <0x80000000 0x40000000> soc compatible = "simple-bus" uart0: serial@10000000 reg / interrupts clock-frequency 每个节点代表一个硬件设备或总线,属性描述其资源与配置 内核解析 DTB 后,为每个 compatible 匹配的节点创建 platform_device

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

平台设备(Platform Device)是 Linux 内核中用于“非可枚举总线”上设备的抽象模型。设备树与平台设备的关系可概括为:设备树是“硬件描述源”,平台设备是“内核设备实例”。

维度 设备树(DTS/DTB) 平台设备(Platform Device)
存在位置 源码目录 arch/arm/boot/dts/,编译为 DTB 内核运行时动态创建,挂载在 platform_bus 上
生命周期 编译时生成,启动时由 Bootloader 加载 内核启动时由 of_platform_populate() 创建
匹配机制 节点中的 compatible 属性 驱动中的 of_match_table 与节点 compatible 匹配
资源传递 通过 reg、interrupts 等属性声明 内核解析 DTB 后填充 platform_device->resource 数组
典型场景 描述 SoC 内部外设(UART、SPI、I2C、DMA) 驱动绑定后提供 probe() 回调,完成设备初始化
调试要点: 当你在 /sys/devices/platform/ 下看到某个设备节点时,说明设备树解析成功并创建了对应的平台设备。如果设备未出现,请检查 DTB 是否被正确加载、compatible 是否匹配。
设备树到平台设备的转换流程 DTS 源文件 .dts 文本描述 dtc 编译 DTB 二进制 扁平化设备树 Bootloader 加载 内核 DTB 解析器 of_platform_populate() platform_device 实例 挂载在 platform_bus 上 驱动 probe() 回调 compatible 匹配 → 设备初始化 整个流程:文本描述 → 二进制数据 → 内核对象 → 驱动绑定

1.5 常见误区与避坑

  • 误区一: 认为设备树是内核驱动的一部分。实际上设备树是独立的数据文件,驱动通过 of_match_table 引用它,但两者编译和发布是分离的。
  • 误区二: 所有设备都必须出现在设备树中。对于 PCI、USB 等可枚举总线上的设备,设备树通常只描述总线控制器本身,子设备由总线枚举自动发现。
  • 误区三: 修改 DTS 后必须重新编译整个内核。实际上只需重新编译 DTB(make dtbs),然后替换启动分区中的 DTB 文件即可。
调试警告: 如果 DTB 与内核版本不匹配(例如使用旧内核解析新 DTB),可能导致 compatible 字符串无法识别,设备不会被创建。始终确保 DTB 与内核源码树同步编译。