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 中各种数据类型在编译过程中的组织关系:

DTS 数据类型体系与编译映射 DTS 属性值 字符串 (String) 32位整数 (Cell) 字节数组 (Byte) 64位整数 (2 Cell) phandle (指针句柄) 复合类型 (混合) 编译后(DTB)统一为二进制格式 字符串 → ASCII字节 + null终止 | 整数 → 大端序4字节 | 字节数组 → 原始字节 典型应用场景 • 字符串:compatible = "vendor,device" • 整数 + phandle:interrupts = <&intc 0 4> • 字节数组:local-mac-address = [00 11 22 33 44 55]

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 实现节点间引用,是设备树“连接”的灵魂。
  • 复合类型将多种类型组合,满足复杂硬件描述需求。