4. 设备树基础:设备树(Device Tree)语法与结构、dts/dtsi文件编写、设备树与驱动匹配机制(compatible属性)

设备树(Device Tree)是嵌入式 Linux 系统中用于描述硬件资源的核心机制,尤其在 ARM、RISC-V 等非 x86 架构中,它取代了传统的 ACPI 或硬编码板级文件。本章将深入讲解设备树的语法结构、源文件组织方式以及驱动与设备树节点的匹配原理,为后续 WiFi/蓝牙模块的移植打下坚实基础。

4.1 设备树概述与结构

设备树是一种树形数据结构,每个节点代表一个硬件设备或总线,节点包含描述设备属性(如寄存器地址、中断号、时钟频率等)的键值对。内核在启动时解析设备树二进制文件(DTB),动态创建平台设备(platform_device),驱动则通过匹配机制绑定到对应设备。

核心概念:设备树将硬件描述与内核代码分离,同一份内核镜像可通过更换 DTB 文件适配不同硬件平台,极大提升了代码的可移植性。
设备树层次结构示例 / (根节点) soc chosen memory i2c0 uart0 mmc0 usb0 wifi@0x20 bt@0x21 说明 每个节点可包含 reg、interrupts、 compatible 等属性

4.2 设备树语法与 dts/dtsi 文件编写

设备树源文件(.dts)使用一种类似 C 语言的宏定义语法,通过 #include 引入公共头文件和 .dtsi 文件。.dtsi 文件通常存放 SoC 级通用硬件描述,而 .dts 文件则包含具体板级的差异化配置。

4.2.1 基本语法规则

  • 节点定义:[label:] node-name [@unit-address] { ... };
  • 属性赋值:property-name = <value>; 或 property-name = "string";
  • 引用其他节点:&label { ... }; 用于在 .dts 文件中覆盖 .dtsi 中的节点属性。
  • 包含文件:#include "soc.dtsi" 或 #include "dt-bindings/gpio/gpio.h"

4.2.2 典型 dtsi 文件示例(SoC 级)

// soc.dtsi - 描述 SoC 内部外设
/ {
    compatible = "vendor,soc-model";
    #address-cells = <1>;
    #size-cells = <1>;

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

        uart0: serial@10000000 {
            compatible = "vendor,uart";
            reg = <0x10000000 0x1000>;
            interrupts = <33>;
            status = "disabled";
        };

        i2c0: i2c@10001000 {
            compatible = "vendor,i2c";
            reg = <0x10001000 0x1000>;
            interrupts = <34>;
            #address-cells = <1>;
            #size-cells = <0>;
            status = "disabled";
        };
    };
};

4.2.3 典型 dts 文件示例(板级)

// board.dts - 具体板级配置
/dts-v1/;
#include "soc.dtsi"
#include "dt-bindings/gpio/gpio.h"

/ {
    model = "MyBoard v1.0";
    compatible = "vendor,myboard", "vendor,soc-model";

    chosen {
        bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2";
    };

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

&uart0 {
    status = "okay";
    clock-frequency = <24000000>;
};

&i2c0 {
    status = "okay";
    clock-frequency = <100000>;

    wifi@0x20 {
        compatible = "vendor,wifi-chip";
        reg = <0x20>;
        interrupts = <45 2>;
        reset-gpios = <&gpio0 10 GPIO_ACTIVE_LOW>;
    };

    bt@0x21 {
        compatible = "vendor,bt-chip";
        reg = <0x21>;
        interrupts = <46 2>;
        shutdown-gpios = <&gpio0 11 GPIO_ACTIVE_LOW>;
    };
};
编写技巧:将 SoC 级通用外设(如 UART、I2C、MMC)放在 .dtsi 中,并默认 status = "disabled";板级 .dts 通过 &label 引用并设置 status = "okay",同时添加板级特有设备(如 WiFi/蓝牙芯片)。

4.3 设备树与驱动匹配机制(compatible 属性)

compatible 属性是设备树节点与内核驱动之间匹配的“钥匙”。驱动通过 of_match_table 声明一组兼容字符串,内核在创建设备时遍历该表,找到匹配项后调用驱动的 probe 函数。

4.3.1 匹配流程

  1. 内核启动时解析 DTB,为每个 status = "okay" 的节点创建 platform_device。
  2. 总线(platform_bus)注册后,遍历所有驱动,调用驱动的 of_match_table 与设备节点的 compatible 属性进行匹配。
  3. 匹配成功则执行驱动的 probe 函数,传递 struct platform_device 指针,驱动从中读取 reg、interrupts、GPIO 等资源。
设备树与驱动匹配流程 内核解析 DTB 创建 platform_device 遍历驱动 of_match_table 匹配? 是 调用 probe 函数 否 继续下一个驱动 匹配依据:驱动 of_match_table 中的 .compatible 字符串与设备节点 compatible 属性值顺序比对

4.3.2 驱动侧匹配表实现

内核驱动通过 of_match_table 宏定义匹配表,示例如下:

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>

static const struct of_device_id wifi_of_match[] = {
    { .compatible = "vendor,wifi-chip", },
    { .compatible = "vendor,wifi-chip-v2", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, wifi_of_match);

static int wifi_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct resource *res;
    int irq;

    // 读取寄存器地址
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res)
        return -ENXIO;

    // 读取中断号
    irq = platform_get_irq(pdev, 0);
    if (irq < 0)
        return irq;

    dev_info(dev, "WiFi chip probed at 0x%llx, irq %d\n",
             (unsigned long long)res->start, irq);
    return 0;
}

static struct platform_driver wifi_driver = {
    .probe  = wifi_probe,
    .driver = {
        .name = "wifi",
        .of_match_table = wifi_of_match,
    },
};
module_platform_driver(wifi_driver);

4.3.3 匹配优先级与注意事项

匹配方式 优先级 说明
compatible 精确匹配 最高 驱动表中的字符串与设备节点 compatible 属性第一个值完全一致
compatible 模糊匹配 中 驱动表字符串与设备节点 compatible 后续值匹配(较少使用)
platform ID 表匹配 低 通过 id_table 中的 name 字段匹配,用于非设备树平台
驱动名称回退 最低 当以上均不匹配时,使用 driver.name 与设备名比较(不推荐)
常见陷阱:compatible 属性值应遵循“厂商,型号”命名规范,如 "broadcom,bcm43438"。多个值按从具体到通用的顺序排列,驱动优先匹配第一个值。若驱动表与设备树字符串不匹配,probe 函数将不会被调用,导致设备无法工作。

4.4 设备树在 WiFi/蓝牙模块移植中的典型应用

在移植 WiFi 和蓝牙模块时,设备树主要用于描述模块的连接方式(SDIO、UART、USB、I2C 等)、电源管理 GPIO、中断引脚以及时钟配置。以下是一个基于 SDIO 接口的 WiFi+蓝牙组合模块的设备树示例:

// 在板级 dts 中追加
&mmc1 {
    status = "okay";
    vmmc-supply = <&reg_vmmc>;
    bus-width = <4>;
    non-removable;
    cap-power-off-card;

    wifi_bt: wifi-bt@1 {
        compatible = "brcm,bcm43438";
        reg = <1>;
        interrupt-parent = <&gpio0>;
        interrupts = <37 IRQ_TYPE_EDGE_FALLING>;
        interrupt-names = "host-wake";
        reset-gpios = <&gpio0 38 GPIO_ACTIVE_LOW>;
        vddo-supply = <&reg_1v8>;
        clocks = <&clk32k>;
        clock-names = "lpo";
        pinctrl-names = "default";
        pinctrl-0 = <&wifi_bt_pins>;
    };
};
关键点解析:
  • compatible = "brcm,bcm43438":驱动通过此字符串匹配到 brcmfmac 或 hci_uart 驱动。
  • interrupts:定义主机唤醒中断,用于低功耗模式。
  • reset-gpios:模块复位引脚,驱动在 probe 时控制。
  • clocks:提供 32.768kHz 低功耗时钟,蓝牙协议栈需要。
  • pinctrl:配置引脚复用功能,确保 SDIO 或 UART 引脚正确连接。

通过合理编写设备树,开发者无需修改内核代码即可适配不同型号的 WiFi/蓝牙模块,只需调整 compatible 字符串、GPIO 编号和时钟配置即可。这也是设备树在嵌入式 Linux 驱动移植中的核心价值所在。