3. DTS 数据类型:字符串、整数、数组、字节序列、phandle、复合类型
设备树源文件(DTS)使用一套简洁但功能强大的数据类型来描述硬件资源。理解这些数据类型是编写和解析设备树的基础。本章将逐一剖析 DTS 中的核心数据类型,包括它们的语法、使用场景及注意事项。
核心要点: DTS 的数据类型设计遵循“人类可读、机器可解析”的原则。所有数据最终都会被编译成扁平化的二进制形式(DTB),但源文件中的表达方式必须清晰、无歧义。
3.1 数据类型概览
下表列出了 DTS 中最常用的数据类型及其典型用途:
| 数据类型 | DTS 表示法 | 典型用途 | 示例 |
|---|---|---|---|
| 字符串 | "..." |
设备名称、兼容性标识、状态标志 | compatible = "vendor,device"; |
| 整数 (32-bit) | <value> |
寄存器地址、中断号、时钟频率 | reg = <0x10000000 0x1000>; |
| 整数数组 | <val1 val2 ...> |
多段地址范围、多组 GPIO 引脚 | reg = <0x0 0x1000 0x2000 0x4000>; |
| 字节序列 | [hex bytes] |
MAC 地址、UUID、固件签名 | local-mac-address = [00 11 22 33 44 55]; |
| phandle | &label |
引用另一个节点(如中断控制器、时钟源) | interrupt-parent = &intc; |
| 复合类型 | 混合上述类型 | 描述复杂硬件属性(如 GPIO 映射、时钟绑定) | gpios = &gpio0 1 2 3; |
3.2 字符串 (String)
字符串是最基本的数据类型,用双引号包裹。它通常用于描述设备的兼容性、状态或厂商信息。
- 单字符串: 最常见的用法,如
compatible = "fsl,imx6ul-uart";。 - 字符串列表: 多个字符串用空格分隔,用于定义兼容性优先级。操作系统会从第一个字符串开始匹配驱动。
// 字符串列表示例:操作系统优先匹配 "snps,dw-apb-uart"
compatible = "snps,dw-apb-uart", "ns16550";
status = "okay"; // 另一个字符串属性
最佳实践: 字符串列表中的顺序至关重要。应将最具体、最精确的兼容字符串放在最前面,将通用兼容字符串放在后面。
3.3 整数与整数数组 (Integer & Integer Array)
整数在 DTS 中默认是 32 位无符号大端序。使用尖括号 <> 包裹。
- 单个整数:
clock-frequency = <50000000>; - 整数数组: 用于表示一组相关的数值,例如
reg属性中的地址和长度对。
// 整数数组:描述两段物理地址空间
// 第一段:起始地址 0x10000000,长度 0x1000
// 第二段:起始地址 0x20000000,长度 0x4000
reg = <0x10000000 0x1000
0x20000000 0x4000>;
注意: 在 64 位系统中,单个
<> 内的数值仍然是 32 位。要表示 64 位地址,需要使用两个连续的 32 位整数(高32位 + 低32位),或者使用 #address-cells = <2>; 来改变单元大小。
3.4 字节序列 (Byte Sequence)
字节序列用方括号 [] 包裹,内部是十六进制字节,字节之间用空格分隔。它用于存储非结构化的二进制数据。
// 字节序列示例:存储 MAC 地址
local-mac-address = [00 1A 2B 3C 4D 5E];
// 存储 UUID
uuid = [01 23 45 67 89 AB CD EF 01 23 45 67 89 AB CD EF];
3.5 phandle (Pointer Handle)
phandle 是设备树中实现节点间引用的机制。它本质上是一个指向另一个节点的指针。在 DTS 中,通常使用 &label 语法来生成 phandle。
- 定义标签: 在节点名前或内部定义
label:。 - 引用标签: 在属性值中使用
&label。
// 1. 定义带标签的中断控制器节点
intc: interrupt-controller@00A01000 {
compatible = "arm,gic-400";
reg = <0x00A01000 0x1000>;
interrupt-controller;
#interrupt-cells = <3>;
};
// 2. 在 UART 节点中通过 phandle 引用中断控制器
uart1: serial@01C28000 {
compatible = "snps,dw-apb-uart";
reg = <0x01C28000 0x400>;
interrupts = <0 33 4>;
interrupt-parent = <&intc>; // 使用 &intc 引用 intc 节点
};
3.6 复合类型 (Composite Type)
复合类型是指在一个属性中混合使用多种基本数据类型。这在描述复杂硬件映射时非常常见,例如 GPIO 引脚、时钟或 DMA 通道。
复合类型的典型语法是:phandle 整数 整数 ...。第一个元素通常是 phandle,指向提供资源的控制器节点;后续的整数是控制器内部的参数。
// 复合类型示例:描述 GPIO 引脚
// 语法:<&gpio-controller pin-number flags>
// 含义:使用 gpio0 控制器的第 15 号引脚,低电平有效
reset-gpios = <&gpio0 15 0x1>;
// 另一个示例:描述时钟
// 语法:<&clock-controller clock-id parent-id>
clocks = <&clks IMX6UL_CLK_UART1>;
解析技巧: 当内核解析复合类型时,它会根据
#gpio-cells、#clock-cells 等属性来确定 phandle 后面应该跟多少个整数。例如,如果 #gpio-cells = <2>,那么每个 GPIO 描述就由 phandle + 2个整数 组成。
3.7 数据类型对比与避坑
| 对比维度 | 字符串 | 整数数组 | 字节序列 | phandle |
|---|---|---|---|---|
| 定界符 | " |
<> |
[] |
& |
| 字节序 | ASCII | 大端 (Big-Endian) | 原始字节 | 大端 (Big-Endian) |
| 常见错误 | 忘记闭合引号 | 64位地址只用了一个 <> | 字节数不对齐 | 引用了不存在的 label |
| 调试方法 | 检查 compatible 拼写 | 使用 dtc -I dtb -O dts 反编译验证 | 核对二进制数据长度 | 确认 label 定义在引用之前 |
常见陷阱:
- 字符串 vs 整数:
"100"是字符串,<100>是整数。驱动解析方式完全不同。 - 数组长度: 整数数组
<0x1000 0x2000>占用 8 字节,而字节序列[10 20]只占用 2 字节。 - phandle 循环引用: 设备树不允许节点间形成循环引用,否则 DTC 编译器会报错。
3.8 实战:解析一个混合属性
下面是一个真实的设备树片段,它混合使用了字符串、整数数组、phandle 和复合类型:
// 一个完整的 I2C 设备节点示例
&i2c1 {
status = "okay"; // 字符串
clock-frequency = <100000>; // 整数
touch: touchscreen@38 {
compatible = "edt,edt-ft5406"; // 字符串列表
reg = <0x38>; // 整数
interrupt-parent = <&gpio1>; // phandle
interrupts = <13 2>; // 整数数组 (复合类型的一部分)
reset-gpios = <&gpio5 0 0x1>; // 复合类型 (phandle + 2个整数)
touchscreen-size-x = <800>; // 整数
touchscreen-size-y = <480>; // 整数
};
};
在这个例子中,interrupts 属性虽然看起来像整数数组,但它实际上是 interrupt-parent 所指向的中断控制器的参数,因此也属于复合类型的范畴。理解这种嵌套关系是掌握设备树的关键。