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 节点
};
phandle 节点引用关系 UART 节点 uart1: serial@01C28000 interrupt-parent = <&intc> 中断控制器节点 intc: interrupt-controller @00A01000 phandle 引用机制 UART 通过 &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个整数 组成。
复合类型解析流程 DTS 源码 reset-gpios = <&gpio0 15 0x1>; (phandle + 2 个整数) DTC 编译器 读取 #gpio-cells = <2> 确定参数数量 内核驱动解析 of_get_named_gpio_flags() 提取 phandle → 找到 gpio0 节点 复合类型 = phandle + 参数 参数个数由控制器节点的 #*-cells 属性决定

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 所指向的中断控制器的参数,因此也属于复合类型的范畴。理解这种嵌套关系是掌握设备树的关键。