4. DTS 数据类型详解:字符串、整数、字节数组、64位整数与phandle
设备树源文件(DTS)中的属性值并非随意填写,而是遵循严格的数据类型系统。理解这些数据类型是正确编写和调试设备树的基础。本章将逐一剖析 DTS 中最核心的六种数据类型:字符串、32位整数、字节数组、64位整数、phandle 以及 复合类型。
核心原则: DTS 中的属性值最终会被编译为二进制格式(DTB),其数据类型决定了内核解析器如何解释这些字节。错误的类型会导致驱动无法正确获取硬件信息。
4.1 数据类型总览与对比
下表列出了 DTS 中最常用的数据类型及其语法特征和典型应用场景:
| 数据类型 | DTS 语法示例 | 编译后大小 | 典型用途 |
|---|---|---|---|
| 字符串 | "compatible" |
可变(含 null 终止符) | 设备兼容性标识、节点名称 |
| 32位整数 | 0x12345678 或 123 |
4 字节 | 寄存器地址、中断号、时钟频率 |
| 字节数组 | [12 34 56 78] |
按字节数 | MAC 地址、唯一 ID、原始配置 |
| 64位整数 | <0x00000000 0x12345678> |
8 字节 | 64位地址空间、大容量内存描述 |
| phandle | <&gpio1> |
4 字节 | 节点间引用、中断控制器连接 |
| 复合类型 | "string", <0x01>, [ab] |
各类型之和 | 多字段属性(如 GPIO 描述) |
4.2 字符串(String)
字符串是 DTS 中最直观的数据类型,使用双引号包裹。它主要用于描述设备的兼容性(compatible)和模型名称(model)。
- 语法:
"string_content" - 编码: ASCII 编码,编译时自动追加 null 终止符(
\0)。 - 多字符串: 使用逗号分隔,如
"vendor,device",内核会按优先级依次匹配。
// 单字符串属性
model = "ACME Corporation RD Board";
// 多字符串属性(最常用)
compatible = "acme,rd-board", "acme,common-board", "simple-bus";
// 字符串列表
clock-output-names = "cpu_clk", "bus_clk", "periph_clk";
实战要点: compatible 属性的字符串顺序非常重要。内核驱动匹配时,会从第一个字符串开始尝试,因此应将最具体的兼容性字符串放在最前面。
4.3 32位整数(Cell)
整数是 DTS 中最基础的数据单元,通常称为 cell。一个 cell 固定为 32 位(4 字节)。
- 语法: 使用尖括号
< >包裹,内部可以包含多个整数。 - 进制: 支持十进制(
123)、十六进制(0x1A)、八进制(077)和二进制(0b1010)。 - 表达式: 支持简单的算术运算,如
<(BASE + 0x100)>。
// 单个整数
reg = <0x10000000>;
// 多个整数(通常用于描述地址和大小对)
reg = <0x10000000 0x1000 // 第一个区域:基址 0x10000000,大小 0x1000
0x20000000 0x2000>; // 第二个区域:基址 0x20000000,大小 0x2000
// 使用表达式
interrupts = <(IRQ_NUM + 16) 0x4>;
4.4 字节数组(Byte Array)
字节数组用于表示原始的二进制数据,使用方括号 [ ] 包裹,内部以空格分隔十六进制字节。
- 语法:
[byte1 byte2 byte3 ...] - 特点: 每个值必须在
0x00到0xFF之间,且不进行字节序转换。 - 应用: 常用于存储 MAC 地址、设备唯一序列号、加密密钥等。
// 6 字节的 MAC 地址
local-mac-address = [00 11 22 33 44 55];
// 16 字节的 UUID
uuid = [de ad be ef 01 23 45 67 89 ab cd ef fe dc ba 98];
// 混合使用:字节数组与整数组合
custom-data = [ff 00], <0x12345678>;
注意: 字节数组不遵循 CPU 的字节序(Endianness)。写入的字节顺序就是 DTB 中的存储顺序。而整数类型(
< >)则遵循大端序(Big-Endian)存储,这是设备树规范的要求。
4.5 64位整数(64-bit Cell)
在 32 位系统中描述 64 位地址空间(如大于 4GB 的内存)时,需要使用 64 位整数。DTS 通过将两个连续的 32 位 cell 组合来表示一个 64 位值。
- 语法: 在
< >中连续写入两个整数,高 32 位在前,低 32 位在后。 - 依赖属性: 需要配合
#address-cells和#size-cells使用。
// 父节点声明地址和大小使用 2 个 cell(即 64 位)
/ {
#address-cells = <2>;
#size-cells = <2>;
memory@8000000000 {
// 基址:0x00000002 00000000(高32位=2,低32位=0)
// 大小:0x00000001 00000000(高32位=1,低32位=0,即4GB)
reg = <0x00000002 0x00000000
0x00000001 0x00000000>;
};
};
4.6 phandle(指针句柄)
phandle 是设备树中实现节点间引用的核心机制。它本质上是一个唯一的 32 位整数标识符,用于指向另一个节点。
- 显式语法:
&label,其中label是在节点定义时声明的标签。 - 隐式机制: 编译器自动为每个节点生成一个唯一的 phandle 值(通常从 1 开始递增)。
- 应用: 中断连接、时钟引用、GPIO 控制器绑定、DMA 通道指定。
// 定义节点并添加标签
intc: interrupt-controller@10000000 {
compatible = "acme,intc";
reg = <0x10000000 0x1000>;
interrupt-controller;
#interrupt-cells = <2>;
};
// 在其他节点中通过 &intc 引用
uart@20000000 {
compatible = "acme,uart";
reg = <0x20000000 0x100>;
interrupts = <&intc 0 4>; // 引用 intc 节点,指定中断号 0,触发类型 4
};
4.7 复合类型与实战案例
实际设备树中,属性往往由多种数据类型组合而成。例如 gpios 属性通常包含一个 phandle 和一个或多个整数 cell。
// 复合类型示例:GPIO 描述
// 格式:<&gpio_controller pin_number flags>
led-red {
gpios = <&gpio1 15 0x1>; // phandle + 两个 cell
};
// 复合类型示例:时钟描述
clocks = <&clk_pll 0>, <&clk_osc 1>; // 两个 phandle+cell 对
// 复合类型示例:中断描述
interrupts-extended = <&intc 0 4>, <&gpio2 3 1>; // 引用不同中断控制器
4.8 数据类型关系图
下图展示了 DTS 中各种数据类型在编译过程中的组织关系:
4.9 常见错误与避坑指南
常见错误 1: 忘记为 64 位地址设置
#address-cells = <2>。如果父节点声明了 1 个 cell,但子节点 reg 写了 2 个 cell,编译器会报错或截断地址。
常见错误 2: 混淆字节数组和整数。例如
reg = [00 00 00 10]; 与 reg = <0x10>; 在 DTB 中的存储方式完全不同,前者是 4 个独立字节,后者是一个 32 位整数。
调试技巧: 使用
dtc -I dtb -O dts 反编译 DTB 文件,可以直观地看到编译器如何解析各种数据类型。如果发现反编译结果与预期不符,通常是源文件中的数据类型定义有误。
4.10 本章小结
掌握 DTS 的数据类型是编写正确设备树的基石。记住以下要点:
- 字符串用双引号,用于兼容性匹配。
- 32位整数用尖括号,是地址和中断描述的基本单元。
- 字节数组用方括号,保持原始字节顺序。
- 64位整数用两个连续 cell,配合
#address-cells使用。 - phandle通过
&label实现节点间引用,是设备树“连接”的灵魂。 - 复合类型将多种类型组合,满足复杂硬件描述需求。