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 声明自己支持的设备,内核在解析设备树时逐项比对。
匹配规则遵循以下优先级:
- 内核遍历设备节点的
compatible字符串列表(从左到右)。 - 对每个字符串,在已注册驱动的
of_match_table中查找完全匹配项。 - 找到匹配项后,调用该驱动的
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= 1IRQ_TYPE_EDGE_FALLING= 2IRQ_TYPE_LEVEL_HIGH= 4IRQ_TYPE_LEVEL_LOW= 8
interrupt-names 属性为每个中断命名,驱动通过 platform_get_irq_byname() 获取,提高代码可读性。
2.6 设备树结构SVG图
下图展示了一个典型嵌入式系统的设备树层次结构,包含根节点、总线节点、设备节点以及关键属性的关联关系:
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语法实现硬件描述复用。