3. 设备树基础:DTS/DTSI/DTB 文件格式、节点与属性定义

设备树(Device Tree)是嵌入式 Linux 内核中用于描述硬件拓扑结构的数据结构。它取代了传统架构中硬编码的 board-*.c 文件,实现了内核镜像与硬件描述的分离。本章将深入剖析设备树的三种文件格式(DTS、DTSI、DTB)、节点与属性的语法规则,以及它们在内核配置与驱动裁剪中的核心作用。

3.1 设备树文件体系:DTS / DTSI / DTB

设备树源文件采用文本格式编写,经过编译生成二进制格式供内核解析。整个流程涉及三种文件类型:

文件类型 扩展名 格式 作用 是否可包含
设备树源文件 .dts 文本(C 风格) 描述单板级硬件,是编译入口 可 #include 其他文件
设备树头文件 .dtsi 文本(C 风格) 存放 SoC 公共定义、外设 IP 模板 被 .dts 或其它 .dtsi 包含
设备树二进制 .dtb 二进制(FDT 格式) Bootloader 传递给内核的硬件描述 不可包含
关键认知: .dtsi 是“硬件 IP 库”,.dts 是“单板实例”。驱动裁剪时,修改 .dtsi 会影响所有使用该 SoC 的单板;修改 .dts 只影响当前单板。

3.2 从 DTS 到 DTB 的编译流程

设备树编译器(dtc)将 .dts 及其包含的 .dtsi 文件解析为扁平设备树(Flattened Device Tree, FDT),最终生成 .dtb 文件。内核在启动早期通过 unflatten_device_tree() 将 DTB 恢复为内存中的树状结构。

设备树编译与加载流程 board.dts soc.dtsi ip-core.dtsi dtc 编译器 board.dtb 内核解析 #include 预处理展开
编译命令示例: dtc -I dts -O dtb -o board.dtb board.dts。内核构建系统(Kbuild)会自动处理 arch/arm/boot/dts/ 或 arch/riscv/boot/dts/ 下的设备树编译。

3.3 节点(Node)定义与层级结构

设备树的核心是节点,每个节点代表一个硬件设备或总线。节点采用树形层级组织,根节点为 /。节点名称格式为 node-name@unit-address,其中 unit-address 通常为寄存器基地址或设备编号。

/dts-v1/;

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

    #address-cells = <1>;
    #size-cells = <1>;

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

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

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

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

        i2c@10002000 {
            compatible = "vendor,i2c-ip";
            reg = <0x10002000 0x1000>;
            #address-cells = <1>;
            #size-cells = <0>;

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

3.4 属性(Property)定义与数据类型

属性是节点的“键值对”,用于描述设备的特性。设备树规范定义了多种属性数据类型:

数据类型 语法示例 说明 典型用途
字符串 compatible = "vendor,device"; 双引号包裹的 ASCII 字符串 驱动匹配标识
32 位整数 reg = <0x1000 0x200>; 尖括号包裹,空格分隔 地址、大小、时钟频率
64 位整数 reg = <0x00000000 0x80000000>; 使用两个 32 位单元表示 64 位地址空间
字节数组 local-mac-address = [00 11 22 33 44 55]; 方括号包裹,十六进制字节 MAC 地址、UUID
布尔值 dma-coherent; 仅属性名,无值 开关型特性
字符串列表 compatible = "a", "b", "c"; 逗号分隔的多个字符串 驱动兼容性回退

3.5 核心属性详解

3.5.1 compatible —— 驱动匹配的关键

compatible 是设备树中最关键的属性。内核通过该属性查找对应的 platform_driver。格式为 "厂商,设备型号",支持多级回退:

compatible = "ti,am335x-uart", "ns16550a";

内核优先匹配 ti,am335x-uart,若未找到则回退到 ns16550a。

3.5.2 reg —— 地址与大小描述

reg 属性描述设备的寄存器地址范围。其格式由父节点的 #address-cells 和 #size-cells 决定:

#address-cells = <1>;   /* 地址用 1 个 32 位单元 */
#size-cells = <1>;      /* 大小用 1 个 32 位单元 */

uart@10000000 {
    reg = <0x10000000 0x1000>;  /* 基址 0x10000000,长度 0x1000 */
};

3.5.3 interrupts —— 中断描述

中断属性通常包含三个字段:中断控制器编号、中断号、触发类型。触发类型定义在 include/dt-bindings/interrupt-controller/irq.h 中:

interrupts = <0 33 4>;
/* 字段含义:中断控制器 0,中断号 33,触发类型 4(高电平触发) */

3.5.4 status —— 设备启用控制

status 属性是驱动裁剪的“开关”。在 .dtsi 中默认设为 disabled,在 .dts 中按需改为 okay:

/* soc.dtsi 中默认关闭 */
uart@10000000 {
    status = "disabled";
};

/* board.dts 中启用 */
#include "soc.dtsi"
&uart@10000000 {
    status = "okay";
};
裁剪陷阱: 仅将 status 设为 disabled 并不会减少内核镜像大小。驱动代码仍会被编译进内核,只是不执行 probe。真正的裁剪需要配合 Kconfig 的 CONFIG_ 选项,从编译层面移除驱动。

3.6 节点与属性的层级关系图

以下 SVG 图展示了一个典型嵌入式系统的设备树层级,包含根节点、SoC 总线、外设以及子设备:

设备树节点层级与属性示例 / (根节点) model = "Embedded Board v2.0" compatible = "vendor,board-v2" cpu@0 compatible = "arm,cortex-a7" reg = <0x0> memory@80000000 device_type = "memory" reg = <0x80000000 0x40000000> soc #address-cells = <1> uart@10000000 compatible = "vendor,uart-ip" reg = <0x10000000 0x1000> status = "okay" i2c@10002000 eeprom@50 根节点 设备节点 总线节点 外设节点 子设备

3.7 设备树在驱动裁剪中的实践要点

  • 模块化拆分原则: 将 SoC 公共 IP 定义放入 .dtsi,单板特有外设和引脚复用放入 .dts。裁剪时优先在 .dts 中 status = "disabled" 关闭不需要的外设。
  • 覆盖(Override)机制: 使用 &label 语法引用 .dtsi 中定义的节点,在 .dts 中修改属性值,无需复制整个节点。
  • 裁剪粒度: 设备树只能控制“设备是否被内核识别”,无法控制驱动代码是否编译。真正的二进制大小优化需要配合 Kconfig 的 CONFIG_ 选项。
  • 调试手段: 使用 dtc -I dtb -O dts board.dtb 反编译 DTB 验证修改是否正确;内核启动参数 earlycon 和 of_platform_dump 可打印设备树解析过程。