2. 设备树基础

2.1 设备树(DTS)概述

设备树(Device Tree)是一种描述硬件资源的数据结构,它使用树形节点来记录系统中所有可寻址设备的属性信息。在嵌入式Linux系统中,设备树源文件(.dts)经过编译生成设备树二进制文件(.dtb),由bootloader传递给内核,内核解析后完成设备驱动与硬件资源的绑定。

核心作用: 将硬件描述从内核代码中剥离,实现“同一内核镜像支持不同硬件平台”的目标。

设备树的基本语法遵循以下规则:

  • 每个设备树文件以 /dts-v1/; 开头,表示使用DTS v1版本。
  • 根节点使用 / { }; 表示,所有设备节点都挂在根节点下。
  • 节点名称格式为 node-name@unit-address,其中 unit-address 通常为设备寄存器基地址。
  • 属性使用 key = value; 的形式赋值,支持字符串、整数数组、二进制数据等类型。
提示: 设备树是驱动开发者与硬件工程师之间的“契约文档”,驱动通过设备树获取硬件资源,无需硬编码地址和中断号。

2.2 节点与属性结构

设备树的每个节点代表一个硬件设备或总线,节点内包含描述该设备的关键属性。下表列出了最常用的属性及其用途:

属性名 数据类型 用途说明 示例
compatible 字符串列表 设备驱动匹配的关键标识,格式为"厂商,型号" "ti,am335x-uart"
reg 整数数组 描述设备寄存器地址空间,格式为(地址, 长度)对 <0x48020000 0x1000>
interrupts 整数数组 描述设备使用的中断号及触发类型 <0 12 4>
interrupt-parent phandle 指定该设备连接的中断控制器 <&gic>
clocks phandle列表 指定设备使用的时钟源 <&clk_uart0>
status 字符串 设备状态,"okay"表示启用,"disabled"表示禁用 "okay"

以下是一个典型的UART设备节点示例:

uart0: serial@48020000 {
    compatible = "ti,am335x-uart", "ns16550";
    reg = <0x48020000 0x1000>;
    interrupts = <0 72 4>;
    interrupt-parent = <&intc>;
    clocks = <&clk_uart0_fck>;
    clock-names = "fck";
    status = "okay";
};

2.3 compatible属性与驱动匹配机制

compatible 是设备树中最关键的属性,它建立了硬件节点与内核驱动之间的桥梁。驱动通过 of_match_table 声明自己支持的设备,内核在解析设备树时逐项比对。

匹配规则遵循以下优先级:

  1. 内核遍历设备节点的 compatible 字符串列表(从左到右)。
  2. 对每个字符串,在已注册驱动的 of_match_table 中查找完全匹配项。
  3. 找到匹配项后,调用该驱动的 probe 函数,并传入设备节点信息。
注意: compatible 字符串的命名规范为 "厂商名,设备型号",例如 "fsl,imx6q-uart"。避免使用过于通用的名称(如 "uart"),防止驱动误匹配。

驱动侧匹配表声明示例:

static const struct of_device_id my_uart_of_match[] = {
    { .compatible = "ti,am335x-uart", },
    { .compatible = "ns16550", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_uart_of_match);

2.4 reg属性与地址资源描述

reg 属性用于描述设备占用的地址空间,通常包含一个或多个(基地址, 长度)对。驱动通过 platform_get_resource() 系列函数从设备树中提取这些信息。

地址空间的解析依赖于父节点的 #address-cells 和 #size-cells 属性:

  • #address-cells:表示地址字段占用的32位整数个数,通常为1或2。
  • #size-cells:表示长度字段占用的32位整数个数,通常为1或2。

例如,对于32位系统:

#address-cells = <1>;
#size-cells = <1>;

spi@48030000 {
    reg = <0x48030000 0x1000>;  // 基址0x48030000,长度0x1000
};

对于64位系统:

#address-cells = <2>;
#size-cells = <2>;

dma@10000000 {
    reg = <0x0 0x10000000 0x0 0x1000>;  // 基址0x10000000,长度0x1000
};

2.5 interrupts属性与中断资源描述

interrupts 属性描述设备使用的中断资源,其格式由父节点(中断控制器)的 #interrupt-cells 决定。常见的中断描述格式如下:

中断控制器类型 #interrupt-cells 字段含义 示例
GIC (ARM通用中断控制器) 3 中断类型(0=SPI, 1=PPI)、中断号、触发类型 <0 72 4>
简单中断控制器 1 中断号 <15>
带触发类型 2 中断号、触发类型(1=上升沿, 2=下降沿, 4=高电平, 8=低电平) <15 4>

中断触发类型常用宏定义:

  • IRQ_TYPE_EDGE_RISING = 1
  • IRQ_TYPE_EDGE_FALLING = 2
  • IRQ_TYPE_LEVEL_HIGH = 4
  • IRQ_TYPE_LEVEL_LOW = 8
最佳实践: 在设备树中尽量使用 interrupt-names 属性为每个中断命名,驱动通过 platform_get_irq_byname() 获取,提高代码可读性。

2.6 设备树结构SVG图

下图展示了一个典型嵌入式系统的设备树层次结构,包含根节点、总线节点、设备节点以及关键属性的关联关系:

设备树层次结构与关键属性关系图 设备树层次结构与关键属性关系图 根节点 / #address-cells = <1> #size-cells = <1> axi@0 apb@0 dma@10000000 compatible = "arm,pl330" gpu@20000000 compatible = "arm,mali" uart@48020000 compatible = "ns16550" i2c@48030000 compatible = "snps,designware-i2c" spi@48040000 compatible = "ti,omap2-mcspi" uart@48020000 属性详情 reg = <0x48020000 0x1000> interrupts = <0 72 4> clocks = <&clk_uart0_fck> touch@1a (I2C子节点) compatible = "edt,edt-ft5406" 图例说明 根节点 总线节点 AXI设备节点 APB设备节点 I2C子设备 属性说明框 父子关系/连接线

2.7 设备树编译与反编译

设备树源文件(.dts)需要编译为二进制文件(.dtb)才能被内核使用。常用工具命令如下:

操作 命令 说明
编译DTS dtc -I dts -O dtb -o output.dtb input.dts 将.dts编译为.dtb
反编译DTB dtc -I dtb -O dts -o output.dts input.dtb 将.dtb反编译为.dts,用于调试
查看设备树 dtc -I fs -O dts /proc/device-tree 从运行中的内核导出设备树
调试技巧: 在开发阶段,可以使用 fdtdump 工具快速查看DTB文件内容,或通过 ls /proc/device-tree/ 查看内核解析后的设备树结构。

2.8 设备树包含与复用

设备树支持通过 #include 指令引入头文件和公共片段,实现硬件描述的复用:

// 主设备树文件:board.dts
#include "soc.dtsi"          // 引入SoC公共定义
#include "pinmux.dtsi"       // 引入引脚复用配置
#include <dt-bindings/gpio/gpio.h>  // 引入GPIO宏定义

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

    &uart0 {
        status = "okay";
        pinctrl-0 = <&uart0_pins>;
        pinctrl-names = "default";
    };
};

通过 &uart0 语法,可以在根节点下引用并覆盖其他文件中定义的节点属性,这是设备树实现“SoC通用描述 + 板级定制”的核心机制。

常见错误: 忘记在设备树中设置 status = "okay" 是导致设备无法被驱动的常见原因。SoC级.dtsi文件通常将外设默认设为 disabled,板级文件需显式启用。

2.9 本章小结

设备树是嵌入式Linux驱动开发的基石,掌握以下要点即可顺利进入后续的Probe流程学习:

  • 节点结构: 树形层次,每个节点代表一个硬件设备或总线。
  • compatible属性: 驱动匹配的唯一标识,格式为"厂商,型号"。
  • reg属性: 描述地址空间,配合 #address-cells 和 #size-cells 解析。
  • interrupts属性: 描述中断资源,格式由中断控制器定义。
  • 包含与覆盖: 使用 #include 和 &node 语法实现硬件描述复用。