2. 设备树基础:设备树(DTS)语法入门、节点与属性、标准属性详解
设备树(Device Tree,DT)是嵌入式 Linux 驱动开发中描述硬件拓扑结构的核心机制。它将硬件配置信息从内核代码中剥离,以树形结构的数据格式传递给内核,实现“硬件描述”与“驱动代码”的解耦。本章将系统讲解设备树的语法、节点与属性,并深入剖析三个最核心的标准属性:compatible、reg 和 interrupts。
2.1 设备树语法入门
设备树源文件(.dts)采用一种层次化的、人类可读的文本格式。其基本语法规则如下:
- 树形结构: 以根节点
/开始,内部包含若干子节点,子节点下可继续嵌套子节点。 - 节点定义:
节点名 { 属性; 子节点; }; - 属性赋值:
属性名 = 值;或属性名;(空属性)。 - 标签(Label): 用于引用节点,格式为
标签名: 节点名 { ... };。 - 包含文件: 使用
#include引入 .dtsi 头文件,实现复用。
/dts-v1/;
/ {
model = "Example Board";
compatible = "vendor,example-board";
cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-a9";
reg = <0x0>;
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x40000000>; // 1GB RAM
};
uart0: serial@10000000 {
compatible = "vendor,uart";
reg = <0x10000000 0x1000>;
interrupts = <0 33 4>;
};
};
上述代码定义了一个包含 CPU、内存和 UART 外设的简单系统。每个节点代表一个硬件设备或总线,属性则描述其配置信息。
2.2 节点与属性详解
设备树的每个节点都代表一个设备、总线或控制器。节点通过属性来描述其硬件特征、资源需求和配置参数。
2.2.1 节点命名规范
- 通用格式:
节点名@单元地址,例如serial@10000000。 - 单元地址: 通常为设备在总线上的起始地址,用于区分同一类型的不同实例。
- 特殊节点:
aliases、chosen、cpus、memory等具有特定语义。
2.2.2 属性类型与值格式
| 属性类型 | 值格式 | 示例 | 说明 |
|---|---|---|---|
| 字符串 | "string" |
compatible = "vendor,device"; |
用于标识设备兼容性 |
| 32位整数 | <value> |
reg = <0x10000000 0x1000>; |
地址、长度、中断号等 |
| 64位整数 | <0x00000000 0x80000000> |
reg = <0x00000000 0x80000000 0x00000000 0x40000000>; |
高32位在前,低32位在后 |
| 字节数组 | [byte1 byte2 ...] |
local-mac-address = [00 11 22 33 44 55]; |
MAC地址、UUID等 |
| 布尔值 | 仅属性名,无值 | status = "disabled"; |
通过存在与否表示 true/false |
| 字符串列表 | "str1", "str2" |
clock-names = "uart_clk", "baud_clk"; |
用于命名资源 |
<>,或字符串忘记加双引号。例如 reg = 0x10000000; 是错误的,必须写成 reg = <0x10000000>;。
2.3 标准属性详解
设备树规范定义了一系列标准属性,用于统一描述硬件资源。其中 compatible、reg 和 interrupts 是最基础、最常用的三个属性。
2.3.1 compatible 属性:驱动匹配的“身份证”
compatible 属性是设备树与驱动代码之间的桥梁。内核通过该属性查找并绑定对应的驱动程序。
- 格式:
compatible = "厂商,设备型号";或compatible = "通用型号", "厂商,设备型号";(降级兼容)。 - 匹配优先级: 内核优先匹配列表中的第一个字符串,失败后依次尝试后续字符串。
- 命名规范: 厂商名称为小写字母,设备型号为小写字母和数字,用逗号分隔。
// DTS 节点
uart0: serial@10000000 {
compatible = "vendor,uart-v2", "vendor,uart";
reg = <0x10000000 0x1000>;
interrupts = <0 33 4>;
};
// 驱动代码 (of_match_table)
static const struct of_device_id uart_of_match[] = {
{ .compatible = "vendor,uart-v2", .data = &uart_v2_data },
{ .compatible = "vendor,uart", .data = &uart_v1_data },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, uart_of_match);
当内核解析到 uart0 节点时,会先尝试匹配 vendor,uart-v2,若驱动不支持则回退到 vendor,uart。这种机制保证了硬件升级时驱动向后兼容。
2.3.2 reg 属性:描述设备地址空间
reg 属性用于描述设备在总线上的地址范围,包括起始地址和长度。它是驱动获取 MMIO 地址、DMA 区域等资源的关键。
- 格式:
reg = <地址1 长度1 地址2 长度2 ...>; - 地址单元: 由父节点的
#address-cells指定,通常为 1(32位)或 2(64位)。 - 长度单元: 由父节点的
#size-cells指定,通常为 1(32位)或 2(64位)。 - 多段地址: 一个设备可能拥有多个不连续的地址空间,如 FIFO 区域和配置寄存器区域。
// 32位地址空间
soc {
#address-cells = <1>;
#size-cells = <1>;
ethernet@20000000 {
reg = <0x20000000 0x10000>; // 起始 0x20000000,长度 0x10000
};
dma@30000000 {
reg = <0x30000000 0x1000 // 配置寄存器
0x40000000 0x2000>; // DMA 描述符区域
};
};
// 64位地址空间
bus@0 {
#address-cells = <2>;
#size-cells = <2>;
memory@0 {
reg = <0x00000000 0x80000000 // 高32位 低32位
0x00000000 0x40000000>; // 长度 1GB
};
};
驱动中通过 platform_get_resource(pdev, IORESOURCE_MEM, index) 获取 reg 属性定义的地址资源,index 对应多段地址的序号(从0开始)。
2.3.3 interrupts 属性:连接中断控制器
interrupts 属性描述设备的中断请求线,包括中断号、触发类型等信息。它必须与中断控制器节点配合使用。
- 格式:
interrupts = <中断控制器域 中断号 触发类型>; - 中断控制器域: 通常为 0(GIC 中断)、1(PPI 中断)等,由中断控制器定义。
- 中断号: 在中断控制器域内的编号。
- 触发类型: 如 0(上升沿)、1(下降沿)、4(高电平)、8(低电平)等。
- 父中断控制器: 通过
interrupt-parent属性指定,若未指定则继承父节点。
// 中断控制器定义
gic: interrupt-controller@f0001000 {
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0xf0001000 0x1000>,
<0xf0002000 0x100>;
};
// 使用中断的设备
uart0: serial@10000000 {
compatible = "vendor,uart";
reg = <0x10000000 0x1000>;
interrupt-parent = <&gic>; // 指定父中断控制器
interrupts = <0 33 4>; // 域0, 中断号33, 高电平触发
};
// 多个中断
dma@30000000 {
compatible = "vendor,dma";
reg = <0x30000000 0x1000>;
interrupt-parent = <&gic>;
interrupts = <0 40 4>, // 传输完成中断
<0 41 4>; // 错误中断
interrupt-names = "tx_done", "error"; // 命名中断
};
驱动中通过 platform_get_irq(pdev, index) 获取中断号,index 对应 interrupts 列表中的序号。使用 interrupt-names 属性可以为中断命名,提高代码可读性。
2.4 标准属性对比总结
| 属性 | 作用 | 驱动获取方式 | 典型值示例 |
|---|---|---|---|
compatible |
驱动匹配 | of_match_table |
"vendor,device-v2", "vendor,device" |
reg |
地址空间描述 | platform_get_resource(pdev, IORESOURCE_MEM, index) |
<0x10000000 0x1000> |
interrupts |
中断请求描述 | platform_get_irq(pdev, index) |
<0 33 4> |
reg 描述所有地址区域,使用 interrupts 描述所有中断线,并使用 interrupt-names 和 reg-names 提高可读性。这不仅能减少调试时间,还能让驱动代码更加健壮。