2、设备树(DTS)基础:设备树的作用、基本语法(节点、属性、标签)、dts/dtsi/dtb文件关系

2.1 设备树的作用与设计初衷

设备树(Device Tree)是一种描述硬件资源的数据结构,用于将硬件配置信息从内核代码中分离出来。在嵌入式Linux系统中,同一款SoC可能被用于成百上千种不同外设组合的板卡,传统方式需要为每种板卡编写大量板级文件(如 arch/arm/mach-xxx),导致内核代码臃肿且难以维护。

设备树的核心作用包括:

  • 硬件描述标准化:以统一格式描述CPU、内存、总线、外设、中断、GPIO、时钟等资源。
  • 驱动与硬件解耦:驱动通过设备树节点名称和兼容属性匹配硬件,无需硬编码地址或资源。
  • 多板卡支持:同一内核镜像通过更换设备树二进制文件(DTB)即可适配不同硬件。
  • 减少内核补丁:硬件变更只需修改DTS文件,无需修改内核C代码。
工程提示:在驱动移植过程中,超过70%的适配问题源于设备树节点描述错误或遗漏。掌握设备树语法是排查硬件资源冲突、中断号错误、时钟配置失败等问题的前提。

2.2 设备树基本语法

设备树源文件(DTS)采用树形层级结构,由节点(node)、属性(property)和标签(label)三要素构成。每个节点代表一个硬件设备或总线,属性描述该设备的配置参数。

2.2.1 节点(Node)

节点是设备树的基本单元,格式为 节点名@地址 { ... };。根节点为 /,所有设备节点均挂载在根节点下。

/dts-v1/;

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

    chosen {
        stdout-path = &uart0;
    };

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

    leds {
        compatible = "gpio-leds";
        led0 {
            label = "beaglebone:green:usr0";
            gpios = <&gpio1 21 GPIO_ACTIVE_HIGH>;
            linux,default-trigger = "heartbeat";
        };
    };
};

2.2.2 属性(Property)

属性以键值对形式存在,常见属性类型如下表:

属性类型 格式示例 说明
字符串 compatible = "ti,am335x-bone"; 驱动匹配标识,优先使用"厂商,型号"格式
32位整数 reg = <0x48000000 0x1000>; 地址和长度,可包含多个地址段
64位整数 reg = <0x00000000 0x80000000 0x00000000 0x10000000>; 高32位在前,低32位在后
布尔值 status = "disabled"; 存在即表示true,常用"okay"/"disabled"
数组 interrupts = <0 31 4>; 中断控制器、类型、触发方式
字节数组 local-mac-address = [00 11 22 33 44 55]; MAC地址等二进制数据
引用 clocks = <&clkc 12>; 通过标签引用其他节点

2.2.3 标签(Label)

标签为节点提供别名,方便在其他位置引用。标签定义在节点名之前,使用 &标签名 引用。

/* 定义标签 */
uart0: serial@44e09000 {
    compatible = "ti,am3352-uart", "ti,omap3-uart";
    reg = <0x44e09000 0x2000>;
    interrupts = <72>;
};

/* 在其他位置引用标签 */
chosen {
    stdout-path = &uart0;  /* 引用uart0节点 */
};

/* 通过标签追加属性 */
&uart0 {
    pinctrl-0 = <&uart0_pins>;
    status = "okay";
};
常见错误:标签引用时使用 &标签名,而非 &节点名。节点名可能重复(如不同总线下的同名设备),但标签在整个设备树中必须唯一。

2.3 dts/dtsi/dtb 文件关系

设备树涉及三种文件类型,它们之间的关系构成了从源码到二进制、从SoC通用描述到板级定制的完整链路。

文件类型 扩展名 作用 是否可编译
设备树源文件 .dts 板级硬件描述,包含完整设备树 是(编译为.dtb)
设备树头文件 .dtsi SoC通用描述或共用外设定义,可被.dts包含 否(被.dts包含后编译)
设备树二进制 .dtb 编译后的二进制文件,由Bootloader加载传递给内核 是(由.dts编译生成)

典型包含关系示例:

/* am335x-boneblack.dts */
#include "am33xx.dtsi"          /* SoC通用定义 */
#include "am335x-bone-common.dtsi" /* 板级共用定义 */

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

    /* 板级特有设备 */
    &lcdc {
        status = "okay";
    };
};
编译命令:
dtc -I dts -O dtb -o am335x-boneblack.dtb am335x-boneblack.dts
反编译查看二进制内容:
dtc -I dtb -O dts -o am335x-boneblack.dts am335x-boneblack.dtb
dts/dtsi/dtb 文件关系与编译流程 am33xx.dtsi SoC通用描述 am335x-boneblack.dts 板级完整描述 am335x-boneblack.dtb 二进制文件 #include dtc编译 Bootloader U-Boot / Barebox 加载到内存 Linux Kernel 传递DTB地址 工程要点 • .dtsi 可多层嵌套 • 同一SoC共用.dtsi • 内核启动参数 fdt_addr=0x88000000

2.4 设备树节点层级与地址编码

设备树通过 reg 属性和 #address-cells / #size-cells 描述设备地址空间。父节点定义地址编码规则,子节点使用 reg 描述具体地址和长度。

/* 地址编码示例 */
soc {
    compatible = "simple-bus";
    #address-cells = <1>;   /* 地址用1个32位单元表示 */
    #size-cells = <1>;      /* 长度用1个32位单元表示 */

    gpio0: gpio@44e07000 {
        compatible = "ti,omap4-gpio";
        reg = <0x44e07000 0x1000>;  /* 基地址0x44e07000,长度0x1000 */
        interrupts = <96>;
        gpio-controller;
        #gpio-cells = <2>;
    };

    uart0: serial@44e09000 {
        compatible = "ti,am3352-uart";
        reg = <0x44e09000 0x2000>;
        interrupts = <72>;
    };
};
设备树节点层级与地址映射关系 / (根节点) soc #address-cells=1 #size-cells=1 gpio0: gpio@44e07000 reg = <0x44e07000 0x1000> interrupts = <96> uart0: serial@44e09000 reg = <0x44e09000 0x2000> interrupts = <72> 地址映射规则 父节点 #address-cells=1 → 子节点 reg 中每个地址段占1个32位单元 父节点 #size-cells=1 → 每个地址段长度占1个32位单元

2.5 驱动匹配机制:compatible 属性

设备树驱动的核心匹配依据是 compatible 属性。内核通过设备树节点的 compatible 字符串与驱动中 of_match_table 进行匹配,匹配成功后调用驱动的 probe 函数。

/* 设备树节点 */
i2c0: i2c@44e0b000 {
    compatible = "ti,am3352-i2c", "ti,omap4-i2c";
    reg = <0x44e0b000 0x1000>;
    interrupts = <70>;
    #address-cells = <1>;
    #size-cells = <0>;
    status = "okay";
};

/* 驱动匹配表 */
static const struct of_device_id omap_i2c_of_match[] = {
    { .compatible = "ti,omap4-i2c", },
    { .compatible = "ti,am3352-i2c", },
    { },
};
MODULE_DEVICE_TABLE(of, omap_i2c_of_match);
匹配优先级:内核遍历 compatible 属性中的字符串列表,按顺序与驱动匹配表中的条目比较。通常将最具体的型号放在最前面(如 "ti,am3352-i2c"),通用型号放在后面(如 "ti,omap4-i2c")。

2.6 设备树排查常用命令

命令 用途 示例
dtc 编译/反编译设备树 dtc -I dtb -O dts -o output.dts input.dtb
ls /sys/firmware/devicetree/base/ 查看内核当前使用的设备树 遍历所有节点和属性
cat /proc/device-tree/model 查看板卡型号 TI AM335x BeagleBone Black
of_* API 驱动中解析设备树属性 of_property_read_u32()
fdtdump 以可读格式转储DTB fdtdump input.dtb
排查要点:当驱动无法加载时,首先检查 /sys/firmware/devicetree/base/ 下是否存在对应节点,其次确认 compatible 属性字符串与驱动匹配表完全一致(包括大小写和标点符号)。