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

1.1 设备树的起源与设计动机

在早期的 Linux 内核中,板级硬件描述信息(如内存基址、中断号、GPIO 映射)被直接硬编码在 arch/arm/ 等平台目录下的 C 语言源文件中。这种“硬编码”方式导致每次硬件变更都需要重新编译内核,且不同厂商的板级代码相互耦合,维护成本急剧上升。

设备树(Device Tree)是一种描述硬件资源的数据结构,最早源于 Open Firmware(IEEE 1275)规范。Linux 从 2.6 内核开始逐步引入设备树,到 3.x 时代已成为 ARM、PowerPC、RISC-V 等架构的标准硬件描述方式。其核心思想是:将硬件配置从内核代码中剥离,以独立的数据文件形式存在,同一内核二进制可以适配多种硬件平台。

关键目标: 实现“内核不变,硬件可配”——通过更换设备树文件即可支持不同板级硬件,无需重新编译内核。

1.2 设备树的作用与核心价值

作用维度 具体说明 传统方式(无设备树) 设备树方式
硬件描述 描述 CPU、内存、外设、总线、中断控制器等硬件拓扑 板级 C 文件 + 宏定义 独立 .dts 文本文件
资源传递 向驱动传递寄存器地址、中断号、时钟频率、DMA 通道等 平台数据(platform_data) 设备树节点属性
驱动匹配 通过 compatible 字符串实现驱动与设备的绑定 设备 ID 表 + 板级注册 of_match_table 匹配
多平台支持 同一内核镜像支持不同硬件配置 需编译多个内核 更换 .dtb 文件即可
固件接口 Bootloader(如 U-Boot)将设备树传递给内核 ATAG 列表(ARM 旧方式) 设备树二进制块(DTB)

1.3 .dts / .dtsi / .dtb 文件关系

设备树体系包含三种文件形态,它们之间存在明确的编译与包含关系:

  • .dts(Device Tree Source):设备树源文件,是开发者直接编写或修改的文本文件,描述一个具体板级的硬件配置。
  • .dtsi(Device Tree Source Include):设备树包含文件,通常存放 SoC 公共定义、通用外设节点、头文件宏等,通过 #include 被 .dts 引用。
  • .dtb(Device Tree Blob):设备树二进制文件,由 .dts 经过 dtc(Device Tree Compiler)编译生成,是内核实际解析的二进制格式。
类比理解: .dtsi 相当于 C 语言的 .h 头文件(公共声明),.dts 相当于 .c 源文件(具体实现),.dtb 相当于编译后的 .o 或 .elf 二进制文件。
设备树文件编译流程与包含关系 .dts / .dtsi / .dtb 文件关系与编译流程 .dtsi SoC 公共定义 / 头文件 .dts 板级硬件描述源文件 .dtsi 外设 / 总线公共定义 #include #include dtc 编译器 Device Tree Compiler .dtb 设备树二进制(内核解析) 编译命令示例 dtc -I dts -O dtb -o board.dtb board.dts 反编译:dtc -I dtb -O dts

1.4 设备树基本语法结构

设备树源文件采用树形层级结构,以节点(node)和属性(property)为核心元素。以下是一个典型的设备树片段:

/dts-v1/;

#include "soc-common.dtsi"

/ {
    model = "EmbeddedAI Board v2.0";
    compatible = "vendor,board-v2", "vendor,board";

    chosen {
        stdout-path = &uart0;
    };

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

    soc {
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;

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

        gpio0: gpio@10001000 {
            compatible = "vendor,gpio-ctrl";
            reg = <0x10001000 0x100>;
            interrupts = <0 45 4>;
            gpio-controller;
            #gpio-cells = <2>;
            status = "okay";
        };
    };
};

1.5 语法要素详解

语法要素 表示方式 说明 示例
根节点 / { } 设备树的顶层容器,所有设备节点都挂在根下 / { model = "..."; };
节点名称 node-name@unit-address 节点名 + 可选的单元地址(寄存器基址) uart0: serial@10000000
标签(label) label: node-name 为节点起别名,便于引用或覆盖 uart0: serial@10000000
属性 key = value; 描述节点特性,值可以是字符串、数字、数组等 compatible = "vendor,uart-ip";
reg 属性 reg = <addr size>; 描述设备地址空间,格式由 #address-cells / #size-cells 决定 reg = <0x10000000 0x1000>;
interrupts interrupts = <type irq flags>; 描述中断号、触发类型等 interrupts = <0 33 4>;
compatible "vendor,device" 驱动匹配的关键字符串,优先使用具体型号 "vendor,uart-ip"
status "okay" / "disabled" 控制设备是否启用 status = "okay";
包含指令 #include "file.dtsi" 引入公共定义,支持 C 预处理宏 #include "soc-common.dtsi"

1.6 节点与属性的层级关系

设备树的树形结构通过节点嵌套体现硬件拓扑。每个节点可以包含子节点,形成从 SoC 总线到具体外设的完整路径。属性则附着在节点上,提供驱动所需的配置参数。

设备树节点层级结构 设备树节点层级结构示意 /(根节点) model / compatible / chosen memory / aliases soc 节点 #address-cells / #size-cells ranges / 总线定义 uart0: serial@10000000 gpio0: gpio@10001000 i2c0: i2c@10002000 compatible / reg interrupts clock-frequency status = "okay" compatible / reg interrupts gpio-controller #gpio-cells = <2> compatible / reg interrupts clock-frequency #address-cells = <1>

1.7 设备树覆盖(Device Tree Overlay)简介

设备树覆盖(DTO)允许在不修改原始 .dts 文件的情况下,动态添加或修改设备树节点。这在以下场景中尤为重要:

  • FPGA 动态加载:FPGA 逻辑加载后,对应的外设节点通过覆盖动态注入。
  • 扩展板 / 模块支持:树莓派等平台的 HAT(Hardware Attached on Top)通过覆盖描述扩展板硬件。
  • 运行时配置调整:在 Bootloader 或内核启动后,根据硬件检测结果动态修改设备树。
注意: 设备树覆盖使用 .dtbo 文件格式,编译命令为 dtc -@ -I dts -O dtb -o overlay.dtbo overlay.dts。其中 -@ 选项用于生成覆盖所需的符号信息。

1.8 本章小结