2. Linux 设备树基础:语法、节点与属性

设备树(Device Tree)是嵌入式 Linux 系统中描述硬件拓扑与资源分配的核心机制。对于蓝牙串口驱动而言,设备树负责向内核传递 UART 控制器地址、中断号、时钟频率、DMA 通道以及蓝牙电源控制 GPIO 等关键参数。本章从语法、节点结构、文件组织与编译工具四个维度展开,为后续蓝牙串口设备树配置奠定基础。

核心认知:设备树不是代码,而是数据。它用树形结构描述“硬件是什么、在哪里、怎么用”,驱动通过匹配 compatible 字符串获取这些数据,从而避免硬编码。

2.1 设备树语法基础

设备树源文件(.dts)采用层次化的键值对语法,每个节点代表一个硬件设备或总线,属性描述设备的特性。基本语法规则如下:

  • 节点定义:node-name@unit-address,其中 unit-address 通常为寄存器基址。
  • 属性赋值:property-name = <value>;,支持 32 位整数、字符串、字节数组、phandle 引用。
  • 标签(label):为节点起别名,便于引用,如 uart0: serial@fe201000。
  • 包含文件:#include "soc.dtsi",复用 SoC 级定义。
最小设备树示例:
/dts-v1/;

/ {
    model = "Bluetooth UART Board";
    compatible = "vendor,bt-board";

    chosen {
        stdout-path = &uart0;
    };

    uart0: serial@fe201000 {
        compatible = "vendor,bluetooth-uart";
        reg = <0xfe201000 0x1000>;
        interrupts = <0 33 4>;
        clocks = <&clk_uart0>;
        bluetooth-gpios = <&gpio1 12 GPIO_ACTIVE_HIGH>;
        status = "okay";
    };
};

上述示例中,uart0 节点描述了蓝牙串口控制器的寄存器地址、中断号、时钟源以及蓝牙使能 GPIO。驱动通过 of_match_table 匹配 compatible 字符串来获取这些属性。

设备树节点层次结构 / (根节点) chosen uart0: serial@fe201000 gpio1 compatible reg / interrupts bluetooth-gpios 每个节点可包含多个属性

2.2 节点与属性详解

设备树中的节点和属性是驱动获取硬件信息的唯一入口。下表列出蓝牙串口驱动最常用的节点属性及其含义:

属性名 格式 蓝牙串口驱动用途
compatible 字符串 驱动匹配标识,如 "brcm,bcm43438-uart"
reg <地址 长度> UART 控制器寄存器基址与映射范围
interrupts <类型 编号 触发> UART 中断号与触发方式(边沿/电平)
clocks phandle UART 模块时钟源引用
bluetooth-gpios phandle + 标志 蓝牙芯片使能/复位 GPIO
max-speed 整数 蓝牙串口最高波特率(如 4000000)
status 字符串 设备启用状态:"okay" / "disabled"
常见错误:忘记在 bluetooth-gpios 属性中指定 GPIO 标志(如 GPIO_ACTIVE_HIGH),导致蓝牙模块无法正确上电复位。务必在 dtsi 或 dts 头部 #include <dt-bindings/gpio/gpio.h>。

2.3 dts / dtsi 文件结构

设备树源文件分为两类:.dtsi(包含文件)和 .dts(板级文件)。.dtsi 存放 SoC 级通用定义,.dts 存放具体板卡的差异化配置。这种分离设计提高了复用性。

// soc.dtsi — SoC 级通用定义
/ {
    soc {
        uart0: serial@fe201000 {
            compatible = "vendor,uart-ip";
            reg = <0xfe201000 0x1000>;
            interrupts = <0 33 4>;
            clocks = <&clk_uart0>;
            status = "disabled";   // 默认关闭
        };
    };
};

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

&uart0 {
    status = "okay";
    pinctrl-0 = <&uart0_pins>;
    bluetooth-gpios = <&gpio1 12 GPIO_ACTIVE_HIGH>;
    max-speed = <4000000>;
};

上述代码中,board.dts 通过 &uart0 引用 soc.dtsi 中定义的节点,并覆盖 status、添加蓝牙专用属性。这种“引用 + 覆盖”机制是设备树配置的核心模式。

dts / dtsi 文件组织与编译流程 soc.dtsi SoC 通用 UART 定义 board.dts 板级蓝牙配置 dtc 编译器 Device Tree Compiler board.dtb 二进制设备树 Blob dtc -I dtb -O dts 反编译为 .dts #include 机制:board.dts 通过 #include "soc.dtsi" 继承 SoC 定义,再通过 &uart0 覆盖属性

2.4 编译与反编译

设备树编译器(dtc)将可读的 .dts 源文件编译为二进制 .dtb(Device Tree Blob),供内核在启动时解析。反编译则用于调试和逆向分析现有 .dtb 文件。

操作 命令 说明
编译 dtc -I dts -O dtb -o board.dtb board.dts 将 .dts 编译为 .dtb
反编译 dtc -I dtb -O dts -o board.dts board.dtb 将 .dtb 反编译为 .dts
检查 dtc -I dtb -O dts /sys/firmware/fdt 查看当前系统使用的设备树
验证 dtc -I dts -O dtb board.dts >/dev/null 仅检查语法错误,不输出文件
调试技巧:在板卡上执行 dtc -I dtb -O dts /sys/firmware/fdt 可导出当前内核使用的完整设备树,用于确认蓝牙节点是否被正确加载。若节点 status 为 "disabled",驱动将不会 probe。

编译时,dtc 会递归展开所有 #include 文件,并解析 /include/ 指令。最终生成的 .dtb 是扁平的二进制结构,包含所有节点、属性以及字符串表。内核启动时,bootloader 将 .dtb 加载到内存,并将地址传递给内核。

设备树编译前后结构对比 编译前 (.dts) 文本格式,可读 包含 #include 引用 节点层次清晰 编译后 (.dtb) 二进制格式,紧凑 所有引用已展开 含 FDT 头部 + 结构块 内核解析 of_* API 驱动匹配 资源获取

2.5 蓝牙串口设备树配置要点

针对蓝牙串口驱动,设备树配置需额外关注以下要点:

  • pinctrl 引脚复用:确保 UART TX/RX/CTS/RTS 引脚被正确配置为外设功能,而非 GPIO。
  • 蓝牙电源管理:通过 bluetooth-gpios 或 vbat-supply 属性控制蓝牙模块的电源与复位时序。
  • 波特率约束:部分蓝牙芯片(如 BCM43438)要求最高 4Mbps,需在 max-speed 中明确指定。
  • DMA 支持:若使用 DMA 传输,需添加 dmas 和 dma-names 属性,并确保 DMA 控制器节点已使能。
最佳实践:将蓝牙专用属性(如 bluetooth-gpios、max-speed)放在板级 .dts 中,而非 SoC 级 .dtsi。这样当蓝牙模块更换时,只需修改板级文件,不影响 SoC 通用定义。