4. 设备树绑定文档:如何阅读和编写设备树绑定文档,理解 compatible 字符串的匹配规则

设备树绑定文档(Device Tree Binding Documentation)是连接硬件描述与内核驱动之间的“契约”。它定义了设备树节点中必须包含哪些属性、属性值的合法范围,以及最重要的——compatible 字符串的匹配规则。在嵌入式 Linux 开发中,无论是移植新硬件还是编写设备驱动,正确理解绑定文档都是避免“设备无法识别”或“驱动加载失败”的关键前提。

核心认知: 绑定文档不是内核代码,而是内核源码树中 Documentation/devicetree/bindings/ 目录下的 YAML 或文本文件。它告诉驱动开发者“设备树节点应该长什么样”,同时告诉设备树编写者“驱动期望看到哪些属性”。

4.1 绑定文档的结构与阅读方法

一个标准的设备树绑定文档通常包含以下五个部分。以 I2C 控制器绑定为例,其结构如下表所示:

章节 内容说明 示例(I2C 控制器)
标题与描述 声明该绑定适用的硬件模块名称和功能概述 NXP i.MX I2C Controller
compatible 字符串 列出所有合法的 compatible 值,通常按“厂商,型号”格式 "fsl,imx6q-i2c", "fsl,imx21-i2c"
属性列表 定义 required(必须)和 optional(可选)属性 reg, interrupts, clocks
子节点描述 说明子节点(如 I2C 从设备)的绑定规则 每个从设备必须有 reg 和 compatible
示例 提供一个完整的设备树节点片段 展示 i2c@021a0000 节点的完整写法
阅读技巧: 先看 compatible 字符串 和 required 属性 两个部分。前者决定驱动匹配,后者决定节点是否合法。如果节点缺少 required 属性,内核在解析时会直接跳过该节点。

4.2 compatible 字符串的匹配规则

compatible 字符串是设备树节点与驱动之间最核心的“握手协议”。内核通过 of_match_table 进行匹配,其规则遵循以下优先级:

  1. 精确匹配: 驱动中的 compatible 字符串与设备树节点中的第一个值完全一致时,立即匹配成功。
  2. 向后兼容匹配: 设备树节点可以列出多个 compatible 值(从最具体到最通用),内核会依次尝试匹配驱动表中的所有条目。
  3. 通配符不支持: 设备树绑定不识别 * 或 ? 等通配符,所有匹配都是字符串的精确比较。

下面这张 SVG 图展示了 compatible 字符串从设备树节点到驱动匹配的完整流程:

compatible 字符串匹配流程 设备树节点 compatible = "fsl,imx6q-i2c", "fsl,imx21-i2c" 内核匹配引擎 遍历 of_match_table 逐个字符串比较 匹配成功 驱动 probe 调用 of_match_device() 返回匹配的 of_device_id 匹配失败 驱动不加载 设备不可用 内核报 -ENODEV ⚠ 匹配顺序规则: 设备树节点中的第一个 compatible 值优先级最高;驱动表中的条目按声明顺序依次匹配。

4.3 编写绑定文档的规范与步骤

当你需要为新的硬件模块编写绑定文档时(例如一个自定义的 FPGA 外设),请遵循以下步骤:

  • 步骤 1:确定 compatible 字符串格式 —— 使用 "厂商名,设备型号" 格式,例如 "acme,my-fpga-ctrl"。厂商名必须是全小写,设备型号使用短横线连接。
  • 步骤 2:列出 required 属性 —— 至少包含 compatible 和 reg(如果设备有 MMIO 地址空间)。
  • 步骤 3:定义 optional 属性 —— 如 interrupts、clocks、dmas 等,并说明每个属性的类型和约束。
  • 步骤 4:描述子节点规则(如有) —— 例如总线控制器下的子节点必须包含 reg 和 compatible。
  • 步骤 5:提供完整示例 —— 示例节点必须包含所有 required 属性,并注释说明每个字段的含义。
常见错误: 不要将 compatible 字符串写成纯数字或过于简短的名称(如 "my-device")。缺少厂商前缀会导致与其他驱动冲突。内核社区要求所有新的 compatible 字符串必须经过 Documentation/devicetree/bindings/vendor-prefixes.yaml 中的厂商前缀注册。

4.4 驱动中的匹配实现

在 Linux 驱动代码中,compatible 字符串的匹配通过 of_device_id 结构体数组实现。下面是一个典型的驱动匹配表:

static const struct of_device_id my_driver_of_match[] = {
    { .compatible = "acme,my-fpga-ctrl-v2", .data = &v2_config },
    { .compatible = "acme,my-fpga-ctrl-v1", .data = &v1_config },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_driver_of_match);

当设备树节点包含 compatible = "acme,my-fpga-ctrl-v2", "acme,my-fpga-ctrl-v1" 时,内核会先尝试匹配 v2,匹配成功后直接返回,不会继续检查 v1。这种机制允许新版本的驱动优先处理新硬件,同时保持对旧硬件的向后兼容。

4.5 绑定文档的 YAML 格式(现代标准)

从 Linux 内核 5.x 开始,新的绑定文档推荐使用 YAML 格式(基于 json-schema)。相比传统的纯文本格式,YAML 格式具有自动校验能力。下面是一个简化的 YAML 绑定示例:

# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: ACME FPGA Controller

maintainers:
  - Jane Doe <jane.doe@acme.com>

properties:
  compatible:
    items:
      - enum:
          - acme,my-fpga-ctrl-v2
          - acme,my-fpga-ctrl-v1

  reg:
    maxItems: 1

  interrupts:
    maxItems: 1

required:
  - compatible
  - reg

additionalProperties: false

examples:
  - |
    fpga@40000000 {
        compatible = "acme,my-fpga-ctrl-v2", "acme,my-fpga-ctrl-v1";
        reg = <0x40000000 0x1000>;
        interrupts = <0 42 4>;
    };
YAML 绑定优势: 内核在编译设备树时,可以通过 dt-validate 工具自动检查绑定文档的合规性。如果节点缺少 required 属性或 compatible 值不在枚举列表中,编译会直接报错。这大大减少了运行时“设备不工作”的调试时间。

4.6 覆盖场景下的绑定注意事项

在使用设备树覆盖(Device Tree Overlay)时,绑定文档的规则同样适用,但需要额外关注以下几点:

场景 绑定要求 常见问题
覆盖新增节点 必须包含完整的 required 属性,compatible 必须与目标驱动匹配 遗漏 reg 属性导致驱动 probe 失败
覆盖修改节点 只能修改 optional 属性,不能修改 compatible 字符串 修改 compatible 后驱动无法重新匹配
覆盖删除节点 不需要绑定文档,但需确保删除操作不会破坏父节点约束 删除子节点后父节点仍引用已删除的资源
覆盖引用 phandle 引用的节点必须已在基树中定义,且 compatible 匹配 phandle 指向不存在的节点导致覆盖加载失败

下面这张 SVG 图展示了覆盖场景下绑定文档与设备树节点、驱动之间的三角关系:

覆盖场景下的绑定三角关系 绑定文档 定义属性规则 约束 compatible 值 设备树覆盖 提供节点实例 填写属性值 内核驱动 声明 of_match_table 实现 probe 函数 约束 匹配 实例化 ⚠ 覆盖加载时的校验顺序: 内核先检查覆盖节点是否符合绑定文档的 required 属性,再尝试匹配驱动。任何一步失败都会导致覆盖整体回滚。

4.7 实战:排查 compatible 不匹配问题

当设备无法被驱动识别时,可以按照以下步骤快速定位:

  1. 检查设备树节点: 使用 dtc -I dtb -O dts 反编译 DTB,确认节点中的 compatible 字符串是否与预期一致。
  2. 检查驱动匹配表: 在内核源码中搜索 of_match_table,确认驱动声明的 compatible 值是否与设备树节点中的第一个值完全一致。
  3. 检查绑定文档: 确认绑定文档中列出的 compatible 值是否包含你使用的字符串。如果使用 YAML 绑定,运行 dt-validate 检查节点合法性。
  4. 检查内核日志: 使用 dmesg | grep -i "of_match\|compatible\|probe" 查看内核是否打印了匹配失败的信息。
快速验证命令: 在目标板上执行 cat /proc/device-tree/<设备路径>/compatible 可以直接读取设备树节点中的 compatible 值,与驱动源码中的字符串进行逐字符对比(注意不可见字符和空格)。