4. 设备树基础:设备树(Device Tree)语法与结构、dts/dtsi文件编写、设备树与驱动匹配机制(compatible属性)
设备树(Device Tree)是嵌入式 Linux 系统中用于描述硬件资源的核心机制,尤其在 ARM、RISC-V 等非 x86 架构中,它取代了传统的 ACPI 或硬编码板级文件。本章将深入讲解设备树的语法结构、源文件组织方式以及驱动与设备树节点的匹配原理,为后续 WiFi/蓝牙模块的移植打下坚实基础。
4.1 设备树概述与结构
设备树是一种树形数据结构,每个节点代表一个硬件设备或总线,节点包含描述设备属性(如寄存器地址、中断号、时钟频率等)的键值对。内核在启动时解析设备树二进制文件(DTB),动态创建平台设备(platform_device),驱动则通过匹配机制绑定到对应设备。
核心概念:设备树将硬件描述与内核代码分离,同一份内核镜像可通过更换 DTB 文件适配不同硬件平台,极大提升了代码的可移植性。
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 匹配流程
- 内核启动时解析 DTB,为每个
status = "okay"的节点创建platform_device。 - 总线(platform_bus)注册后,遍历所有驱动,调用驱动的
of_match_table与设备节点的compatible属性进行匹配。 - 匹配成功则执行驱动的
probe函数,传递struct platform_device指针,驱动从中读取 reg、interrupts、GPIO 等资源。
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 = <®_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 = <®_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 驱动移植中的核心价值所在。