3. DTS 数据类型:字符串、整数、字节数组、phandle 等

设备树源文件(DTS)中的属性值并非随意填写,而是遵循严格的数据类型系统。理解这些数据类型是正确编写和解析 DTS 文件的基础。本章将详细解析 DTS 中五种核心数据类型:字符串、整数(32位/64位)、字节数组、phandle 以及 复合类型,并展示它们在 DTS 中的标准表示方法。

核心原则: DTS 中的数据类型本质上是二进制大端(Big-Endian)编码的字节序列。编译器(DTC)根据属性定义和上下文将文本表示转换为二进制 DTB 中的原始字节。

3.1 数据类型总览与对比

下表列出了 DTS 中最常用的数据类型及其在 DTS 源文件中的文本表示与在 DTB 二进制文件中的编码方式:

数据类型 DTS 文本表示 DTB 二进制编码 典型用途
字符串 (String) "compatible" 以 \0 结尾的 ASCII 字节序列 设备兼容性标识、节点名称、状态
32位整数 (u32) 0x12345678 或 305419896 4 字节大端序 寄存器地址、中断号、时钟频率
64位整数 (u64) 0x1122334455667788 8 字节大端序(两个连续 u32) 64位物理地址、大容量内存大小
字节数组 (Byte Array) [01 02 03 0a ff] 原始字节序列,无分隔符 MAC 地址、自定义硬件配置、加密密钥
phandle &node_label 一个 u32 整数,指向目标节点的偏移量 节点间引用(中断控制器连接、时钟源绑定)
复合类型 (Compound) <0x1000 0x2000> 或 "str", [01 02] 按顺序拼接的混合编码 reg 属性(地址+长度对)、多字段属性

3.2 字符串 (String)

字符串是 DTS 中最直观的数据类型,使用双引号 "..." 包裹。编译器自动在字符串末尾添加 \0 终止符。

// 单个字符串
compatible = "arm,cortex-a72";

// 字符串列表(多个字符串以空格分隔)
compatible = "ti,am335x-bone", "ti,am33xx";

// 设备状态
status = "okay";
status = "disabled";
字符串列表编码规则: 在 DTB 中,多个字符串连续存储,每个字符串以 \0 结尾。例如 "ti,am335x-bone\0ti,am33xx\0"。操作系统通过遍历 \0 分隔符来解析列表。

3.3 整数 (Integer)

整数支持十进制、十六进制(0x 前缀)和二进制(0b 前缀,DTC 1.4.7+ 支持)表示。默认类型为 32位无符号整数 (u32)。

// 32位整数(u32)
clock-frequency = <50000000>;      // 十进制:50 MHz
interrupts = <0 29 4>;             // 三个 u32 组成的数组

// 64位整数(u64)—— 使用两个 u32 拼接
reg = <0x00000000 0x80000000>;     // 表示 0x0000000080000000(2GB 起始地址)

// 带地址单元的 64 位地址(#address-cells = <2>)
reg = <0x00000008 0x00000000 0x0 0x10000000>;
// 解释:地址 = 0x00000008_00000000,长度 = 0x10000000
大端序陷阱: 在 DTB 二进制文件中,0x12345678 存储为字节序列 12 34 56 78(大端序)。如果手动解析 DTB 文件,必须注意字节序转换,否则读取的值会完全错误。

3.4 字节数组 (Byte Array)

字节数组使用方括号 [...] 包裹,内部以空格分隔的十六进制字节值。每个字节值必须是两位十六进制数(00 到 ff)。

// MAC 地址
local-mac-address = [00 11 22 33 44 55];

// 自定义硬件配置
custom-config = [01 02 03 0a ff];

// 混合使用:字符串 + 字节数组
mixed-prop = "prefix", [de ad be ef];
字节数组 vs 整数数组: [01 02] 占用 2 字节,而 <0x0102> 占用 4 字节(补齐到 u32)。选择哪种取决于硬件期望的精确字节布局。

3.5 phandle (Pointer Handle)

phandle 是设备树中实现节点间引用的核心机制。它使用 &label 语法引用一个已定义的节点标签,编译器自动将其转换为目标节点在 DTB 中的偏移量(一个 u32 整数)。

// 定义带标签的节点
clk_pll: clock-controller@44e00000 {
    compatible = "ti,am335x-pll";
    #clock-cells = <0>;
};

// 引用 phandle
uart0: serial@44e09000 {
    compatible = "ti,am335x-uart";
    clocks = <&clk_pll>;  // phandle 引用
    interrupt-parent = <&intc>; // 另一个 phandle 引用
};

// 带参数的 phandle(#clock-cells = <1> 时)
clocks = <&clk_pll 0 &clk_pll 1>;  // 引用同一个时钟源的不同输出
phandle 工作原理: 编译时,DTC 为每个节点分配一个唯一的 u32 标识符(phandle 值)。当遇到 &label 时,编译器将其替换为被引用节点的 phandle 值。操作系统通过遍历 DTB 的节点树来解析这个值对应的实际节点。

3.6 复合类型与 reg 属性

实际 DTS 中,属性值经常是多种数据类型的组合。最典型的例子是 reg 属性,它由 #address-cells 和 #size-cells 决定其结构。

// 父节点定义地址和长度单元
soc {
    #address-cells = <1>;  // 地址用 1 个 u32
    #size-cells = <1>;     // 长度用 1 个 u32

    uart@44e09000 {
        reg = <0x44e09000 0x2000>;  // 起始地址 + 长度
        // 二进制编码:44e09000 00002000(大端序)
    };

    gpu@18000000 {
        #address-cells = <1>;
        #size-cells = <0>;  // 子节点 reg 只有地址,没有长度

        gpu_mem: memory@0 {
            reg = <0x0>;  // 只有地址,无长度
        };
    };
};

3.7 数据类型选择流程图

下面的 SVG 流程图展示了在编写 DTS 属性时,如何根据硬件需求选择合适的数据类型:

DTS 数据类型选择决策流程 定义属性值 数据类型是什么? 字符串 "compatible" 整数 <0x1000> 字节数组 [01 02 03] phandle &node_label 复合类型 <addr len> 或 "str",[..] 提示:复合类型常用于 reg 属性,由 #address-cells 和 #size-cells 决定内部结构

3.8 常见错误与避坑指南

  • 整数数组 vs 字节数组混淆: <0x1234> 是 4 字节,[12 34] 是 2 字节。硬件期望的寄存器布局必须精确匹配。
  • 字符串缺少终止符: DTC 自动添加 \0,但手动拼接字符串时(如使用 /include/)可能导致终止符丢失。
  • phandle 循环引用: 节点 A 引用 B,B 引用 A,会导致 DTC 编译死循环或生成无效 DTB。
  • 64位地址截断: 当 #address-cells = <2> 时,必须提供两个 u32 表示完整 64 位地址,否则高 32 位默认为 0。
  • 大小写敏感: 十六进制 0x1A 和 0x1a 等效,但字符串 "okay" 和 "Okay" 不同。
调试建议: 使用 dtc -I dtb -O dts 反编译已知正确的 DTB 文件,观察其数据类型表示,这是学习正确语法的最高效方式。

3.9 本章小结

DTS 的数据类型系统虽然简单,但精确性要求极高。字符串用于标识和描述,整数用于数值参数,字节数组用于原始二进制数据,phandle 用于节点间引用,复合类型则灵活组合多种数据。掌握这些类型的表示方法和二进制编码规则,是后续学习设备树编译、反编译以及驱动开发的基础。