2、设备树基础概念:DTS/DTC/DTB的关系、设备树语法入门、节点与属性

2.1 设备树(Device Tree)的起源与作用

在早期的嵌入式 Linux 系统中,硬件描述信息被硬编码在板级文件(如 arch/arm/mach-xxx)中。每更换一款 SoC 或外设,都需要修改内核源码并重新编译,导致内核镜像与硬件高度耦合,维护成本极高。

设备树(Device Tree)是一种描述硬件资源的数据结构,它将硬件信息从内核源码中剥离出来,以文本形式独立描述。同一份内核镜像可以通过加载不同的设备树文件来适配不同的硬件平台,实现了“内核不变,硬件可配”的架构解耦。

核心价值:设备树是 Linux 内核与板级硬件之间的“契约”,驱动通过设备树获取硬件资源(地址、中断、GPIO、时钟等),无需在驱动代码中硬编码。

2.2 DTS / DTC / DTB 三者的关系

设备树从源文件到内核可识别的二进制数据,经历了三个关键角色:

术语 全称 格式 作用
DTS Device Tree Source 文本文件(.dts) 开发者编写的设备树源文件,使用设备树语法描述硬件
DTC Device Tree Compiler 编译工具 将 DTS 编译为 DTB 的编译器,也支持反编译(DTB → DTS)
DTB Device Tree Blob 二进制文件(.dtb) Bootloader 传递给内核的硬件描述数据,内核解析后生成设备节点

三者的转换流程如下:

DTS 源文件(文本)  ──→  DTC 编译器  ──→  DTB 二进制(.dtb)
                              ↑
                     DTSI 头文件(.dtsi,公共包含)
实践提示:在 Linux 内核源码中,arch/arm/boot/dts/ 或 arch/arm64/boot/dts/ 目录下存放了各平台的 DTS 文件。编译内核时,DTC 会自动将选中的 DTS 编译为 DTB。
DTS → DTC → DTB 转换流程 DTS 源文件 .dts 文本 DTC 编译器 dtc 工具 编译 / 反编译 DTB 二进制 .dtb 二进制 Linux 内核解析 DTSI 头文件通过 #include 包含

2.3 设备树语法入门

设备树语法基于树形结构,以“/”为根节点,每个硬件外设对应一个子节点。节点可以包含属性(键值对)和子节点。

2.3.1 基本语法规则

  • 节点定义:节点名@地址 { ... };,地址通常为寄存器基址。
  • 属性格式:属性名 = 值;,值可以是字符串、32位整数数组、64位整数数组、二进制数据等。
  • 标签(Label):在节点前加 标签名:,便于在其他地方引用。
  • 包含文件:使用 #include "xxx.dtsi" 引入公共头文件。
  • 注释:支持 C 风格 /* */ 和 C++ 风格 //。

2.3.2 一个完整的 DTS 示例

/dts-v1/;

#include "imx6ul.dtsi"

/ {
    model = "Freescale i.MX6 UltraLite EVK Board";
    compatible = "fsl,imx6ul-evk", "fsl,imx6ul";

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

    chosen {
        stdout-path = &uart1;
    };

    leds {
        compatible = "gpio-leds";
        led0: led-red {
            label = "red-led";
            gpios = <&gpio1 4 GPIO_ACTIVE_LOW>;
            linux,default-trigger = "heartbeat";
        };
    };

    &uart1 {
        status = "okay";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_uart1>;
    };
};
常见错误:忘记写分号 ; 或花括号不匹配是 DTS 编译失败的最常见原因。DTC 对语法严格,建议使用 dtc -I dts -O dtb -o test.dtb test.dts 验证语法。

2.4 节点与属性详解

2.4.1 节点(Node)

节点是设备树的基本组成单元,每个节点代表一个硬件设备或总线。节点路径从根节点 / 开始,如 /soc/aips-bus@02000000/spi@02010000。

节点类型 说明 示例
根节点 设备树的顶层节点,用 / 表示 / { ... };
设备节点 描述具体外设,通常包含 compatible 和 reg 属性 uart@02010000 { ... };
总线节点 描述总线控制器,子节点为挂载在该总线上的设备 i2c@021a0000 { ... };
别名节点 通过 aliases 节点为其他节点提供短名称 aliases { serial0 = &uart1; };
chosen 节点 传递 Bootloader 参数给内核,如启动参数、控制台 chosen { bootargs = "..."; };

2.4.2 属性(Property)

属性是节点的“键值对”,用于描述设备的硬件特征。驱动通过 of_match_table 匹配 compatible 属性后,再通过 of_property_read_* 系列 API 读取其他属性。

属性名 值类型 作用
compatible 字符串列表 设备驱动匹配的关键,格式为 "厂商,设备型号"
reg 32位整数数组 描述设备地址空间,格式为 <基址 长度>
interrupts 32位整数数组 描述中断号、触发类型等
gpios phandle + 整数 引用 GPIO 控制器并指定引脚号和标志
status 字符串 设备状态:"okay"、"disabled"、"reserved"
pinctrl-* phandle 列表 引脚复用配置,引用 pinctrl 子节点
clock-frequency 32位整数 时钟频率,单位 Hz
关键理解:compatible 是设备树匹配驱动的“身份证”。驱动通过 of_match_table 声明支持的 compatible 字符串列表,内核在启动时遍历设备树节点,找到匹配的节点后调用驱动的 probe 函数。
设备树节点与属性结构 根节点 / model = "i.MX6UL EVK" compatible = "fsl,imx6ul-evk" memory@80000000 device_type = "memory" reg = <0x80000000 0x20000000> uart1: serial@02010000 compatible = "fsl,imx6ul-uart" reg = <0x02010000 0x4000> interrupts = <0 26 4> leds led-red label = "red-led" gpios = <&gpio1 4 0> 节点 属性 父子关系

2.5 设备树匹配驱动的流程

当内核启动时,设备树匹配驱动的完整流程如下:

  1. Bootloader 加载 DTB:U-Boot 从存储介质读取 DTB 文件,将其放入内存指定地址。
  2. 内核解析 DTB:内核启动早期调用 unflatten_device_tree(),将 DTB 解析为 device_node 树。
  3. 创建 platform_device:对于每个 compatible 匹配的节点,内核创建对应的 platform_device。
  4. 驱动匹配:当驱动注册时,内核遍历 platform_device 的 compatible 列表,与驱动的 of_match_table 比对。
  5. 调用 probe:匹配成功后,内核调用驱动的 probe 函数,驱动通过 of_* API 读取设备树属性获取硬件资源。
调试技巧:在 Linux 内核启动时,可以通过 of_platform_populate() 的日志或 /sys/firmware/devicetree/base 目录查看设备树解析结果。使用 dtc -I dtb -O dts -o dump.dts /sys/firmware/fdt 可以反编译当前使用的 DTB。

2.6 本章小结

  • DTS 是开发者编写的硬件描述源文件,DTC 将其编译为 DTB 二进制,内核解析 DTB 生成设备节点。
  • 设备树语法以 节点 和 属性 为核心,compatible 属性是驱动匹配的关键。
  • 节点通过 reg 描述地址空间,通过 interrupts、gpios 等属性描述资源。
  • 驱动通过 of_match_table 与设备树节点匹配,匹配成功后调用 probe 函数。