3. 设备树(DTS)基础:DTS语法、节点、属性、reg、interrupts、compatible
设备树(Device Tree)是嵌入式Linux系统中描述硬件拓扑结构的核心机制。对于蓝牙驱动开发而言,DTS是连接蓝牙芯片与内核驱动的“硬件描述语言”。本章将系统讲解DTS的语法结构、关键属性及其在蓝牙HID场景中的实际应用。
学习目标:掌握DTS基本语法与节点结构;理解compatible、reg、interrupts属性的含义与用法;能够为蓝牙HID设备编写正确的设备树节点。
3.1 设备树概述与作用
设备树是一种树形数据结构,用于向操作系统描述不可枚举的硬件设备信息。在嵌入式Linux中,DTS文件被编译为DTB(Device Tree Blob),由Bootloader传递给内核。
| 概念 | 说明 | 文件扩展名 |
|---|---|---|
| DTS | 设备树源文件,人类可读的文本格式 | .dts / .dtsi |
| DTC | 设备树编译器,将DTS编译为DTB | 工具 |
| DTB | 设备树二进制文件,内核解析的最终格式 | .dtb |
| DTBO | 设备树覆盖层,用于动态添加/修改节点 | .dtbo |
蓝牙驱动视角:蓝牙控制器通常连接在UART、USB、SDIO或SPI总线上。DTS节点告诉内核:“这里有一个蓝牙芯片,它挂在UART3上,使用中断线GPIO_17,兼容Broadcom BCM43438协议。”
3.2 DTS语法基础
DTS采用类似C语言的语法风格,包含节点、属性和值。每个设备树文件以/dts-v1/;开头,表示使用设备树v1规范。
3.2.1 节点定义
节点是设备树的基本组成单元,每个节点代表一个设备或总线。节点名称格式为node-name@unit-address,其中unit-address是设备在总线上的地址。
/dts-v1/;
/ {
model = "Embedded Bluetooth Board";
compatible = "vendor,board-name";
// 蓝牙UART控制器节点
uart3: serial@1000b000 {
compatible = "vendor,bluetooth-uart";
reg = <0x1000b000 0x1000>;
interrupts = <0 45 4>;
clock-frequency = <48000000>;
// 子节点:蓝牙芯片
bluetooth {
compatible = "brcm,bcm43438-bt";
reg = <0>;
interrupts-extended = <&gpio 17 IRQ_TYPE_EDGE_FALLING>;
vbat-supply = <&vcc_3v3>;
};
};
};
常见错误:节点名称中的
@unit-address必须与reg属性的第一个地址一致。例如节点serial@1000b000的reg首地址应为0x1000b000,否则DTC编译会报地址不匹配警告。
3.2.2 标签与引用
标签(label)是节点的别名,用于在树中其他位置引用该节点。标签以&符号引用。
| 语法 | 含义 | 示例 |
|---|---|---|
label: node-name |
定义标签 | uart3: serial@1000b000 |
&label |
引用标签 | &uart3 { ... } |
<&label> |
在属性值中引用 | interrupts-extended = <&gpio 17 2>; |
phandle = <num> |
显式句柄(通常自动生成) | phandle = <0x1>; |
3.3 核心属性详解
3.3.1 compatible 属性
compatible是设备树中最重要的属性,用于将设备节点与内核驱动进行匹配。其值为字符串列表,按优先级从高到低排列。
// 蓝牙控制器compatible示例
compatible = "brcm,bcm43438-bt", "brcm,bcm4330-bt", "simple-bluetooth";
// 匹配规则:内核驱动按顺序尝试匹配
// 1. 优先匹配 "brcm,bcm43438-bt"
// 2. 其次匹配 "brcm,bcm4330-bt"
// 3. 最后匹配 "simple-bluetooth"
驱动匹配流程:内核的of_match_table中列出驱动支持的compatible列表。当设备树节点被扫描时,内核遍历驱动的匹配表,找到第一个匹配的compatible字符串即绑定成功。蓝牙驱动通常使用
MODULE_DEVICE_TABLE(of, ...)导出匹配表。
3.3.2 reg 属性
reg描述设备在总线上的地址空间。格式为reg = <address1 length1 address2 length2 ...>,每个地址-长度对描述一个连续的地址区域。
| 总线类型 | reg含义 | 示例 |
|---|---|---|
| 内存映射(MMIO) | 基地址 + 寄存器范围长度 | reg = <0x1000b000 0x1000>; |
| SPI | 片选号 + 无长度(0) | reg = <0>; // CS0 |
| I2C | 7位从机地址 | reg = <0x29>; |
| USB | USB端口号 | reg = <1>; // 端口1 |
// SPI蓝牙控制器reg示例
spi_bt: bluetooth@0 {
compatible = "infineon,cyw43439-bt";
reg = <0>; // SPI片选CS0
spi-max-frequency = <48000000>;
interrupt-parent = <&gpio>;
interrupts = <19 IRQ_TYPE_LEVEL_LOW>;
};
3.3.3 interrupts 属性
interrupts描述设备的中断请求信息。其格式依赖于中断控制器类型,常见格式为interrupts = <中断号 触发类型>。
| 中断类型 | 宏定义 | 数值 | 说明 |
|---|---|---|---|
| 上升沿触发 | IRQ_TYPE_EDGE_RISING |
1 | 信号从低到高跳变时触发 |
| 下降沿触发 | IRQ_TYPE_EDGE_FALLING |
2 | 信号从高到低跳变时触发 |
| 高电平触发 | IRQ_TYPE_LEVEL_HIGH |
4 | 信号为高电平时触发 |
| 低电平触发 | IRQ_TYPE_LEVEL_LOW |
8 | 信号为低电平时触发 |
// 蓝牙HID中断配置示例
// 方案1:使用interrupt-parent引用
bluetooth {
compatible = "brcm,bcm43438-bt";
interrupt-parent = <&gpio>;
interrupts = <17 IRQ_TYPE_EDGE_FALLING>;
// GPIO17下降沿触发:蓝牙芯片拉低表示有数据
};
// 方案2:使用interrupts-extended(推荐)
bluetooth {
compatible = "brcm,bcm43438-bt";
interrupts-extended = <&gpio 17 IRQ_TYPE_EDGE_FALLING>;
// 直接指定中断控制器和参数
};
3.4 蓝牙HID设备树实战
以下是一个完整的蓝牙HID设备树配置示例,包含UART蓝牙控制器和中断映射。
// 完整蓝牙HID设备树配置
/dts-v1/;
#include "dt-bindings/interrupt-controller/irq.h"
#include "dt-bindings/gpio/gpio.h"
/ {
model = "Bluetooth HID Development Board";
compatible = "vendor,bt-hid-board";
// 电源管理
vcc_3v3: regulator-3v3 {
compatible = "regulator-fixed";
regulator-name = "3.3V";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
};
// UART控制器
uart0: serial@fe201000 {
compatible = "arm,pl011", "arm,primecell";
reg = <0xfe201000 0x1000>;
interrupts = <0 33 4>;
clock-frequency = <48000000>;
status = "okay";
// 蓝牙子节点
bluetooth {
compatible = "brcm,bcm43438-bt";
reg = <0>;
max-speed = <3000000>;
shutdown-gpios = <&gpio 16 GPIO_ACTIVE_HIGH>;
device-wakeup-gpios = <&gpio 17 GPIO_ACTIVE_HIGH>;
host-wakeup-gpios = <&gpio 18 GPIO_ACTIVE_HIGH>;
interrupts-extended = <&gpio 18 IRQ_TYPE_EDGE_FALLING>;
vbat-supply = <&vcc_3v3>;
clocks = <&clk_32k>;
clock-names = "lpo";
};
};
// 32KHz时钟
clk_32k: clk-32k {
compatible = "fixed-clock";
clock-frequency = <32768>;
};
};
蓝牙HID中断设计建议:
- host-wakeup:蓝牙芯片通知主机有数据,建议使用下降沿触发,避免电平触发导致中断锁死
- device-wakeup:主机通知蓝牙芯片唤醒,通常使用GPIO输出控制,不涉及中断
- shutdown:蓝牙芯片复位/使能控制,使用GPIO输出,高电平有效
- 中断优先级:蓝牙HID对实时性要求较高,建议配置为中等优先级(如Linux中的IRQ_DEFAULT_PRIORITY)
3.5 设备树编译与调试
编写完DTS文件后,需要使用DTC工具编译为DTB。以下是常用命令和调试技巧。
| 命令 | 说明 | 示例 |
|---|---|---|
dtc -I dts -O dtb |
DTS编译为DTB | dtc -I dts -O dtb -o board.dtb board.dts |
dtc -I dtb -O dts |
DTB反编译为DTS | dtc -I dtb -O dts -o board.dts board.dtb |
fdtdump |
查看DTB内容 | fdtdump board.dtb |
dt-validate |
验证DTS语法 | dt-validate board.dts |
// 编译与调试流程
# 1. 编译DTS为DTB
dtc -I dts -O dtb -o bcm2711-rpi-4-b.dtb bcm2711-rpi-4-b.dts
# 2. 检查编译错误
dtc -I dts -O dtb -o test.dtb test.dts 2>&1 | grep -i error
# 3. 反编译查看实际内容
dtc -I dtb -O dts -o decompiled.dts test.dtb
# 4. 在内核中查看设备树
# 挂载debugfs后
cat /sys/kernel/debug/device-tree/uart0/bluetooth/compatible
# 5. 查看中断映射
cat /proc/interrupts | grep bluetooth
调试陷阱:
- DTC编译时
-@选项用于生成符号表,支持设备树覆盖层,蓝牙模块动态加载时需要 - 内核启动参数
dtb=可指定使用外部DTB,方便调试 - 使用
of_*API在内核驱动中打印设备树信息:dev_info(dev, "compatible: %s\n", of_get_property(np, "compatible", NULL)); - 中断号在GIC中可能被重新映射,需通过
irq_of_parse_and_map()获取实际中断号
3.6 本章小结
设备树是嵌入式Linux蓝牙驱动开发的基石。本章重点内容包括:
- DTS语法:节点定义、标签引用、属性赋值的基本规则
- compatible属性:驱动匹配的核心机制,字符串列表按优先级匹配
- reg属性:描述设备地址空间,不同总线有不同的编码方式
- interrupts属性:中断映射配置,包括中断号、触发类型、中断控制器
- 蓝牙HID实战:host-wakeup/device-wakeup/shutdown的GPIO与中断配置
- 编译调试:DTC工具链使用、内核设备树查看方法