4. 设备树绑定文档:从概念到实战

设备树绑定(Binding),说白了就是一份“说明书”。它告诉内核:某个硬件设备该怎么描述,驱动该怎么解析。我刚开始接触设备树时,总觉得这玩意儿可有可无——反正代码能跑就行。直到有一次,我写了一个自定义的传感器驱动,因为没有写绑定文档,三个月后自己都看不懂当初的节点是干嘛用的。嗯,从那以后,我再也不敢偷懒了。

4.1 什么是设备树绑定

设备树绑定是一套约定。它定义了:

  • 设备节点应该包含哪些属性(比如 reg、interrupts、compatible)
  • 每个属性的数据类型和取值范围
  • 节点之间的父子关系或引用关系
  • 驱动如何从设备树中获取这些信息

你想想看,内核里成百上千的驱动,每个驱动都需要知道它对应的设备节点长什么样。如果没有绑定文档,驱动开发者只能靠猜,或者翻看别人的代码。这效率太低了。

核心观点:绑定文档是设备树与驱动之间的“契约”。没有契约,代码就是一团乱麻。

4.2 如何阅读内核中的绑定文档

内核源码里已经包含了大量现成的绑定文档。它们位于 Documentation/devicetree/bindings/ 目录下。我建议你养成一个习惯:在写任何设备驱动之前,先去这个目录里翻一翻,看看有没有类似的设备可以参考。

4.2.1 绑定文档的结构

一个典型的绑定文档包含以下几个部分:

部分 内容 示例
标题 设备名称和兼容字符串 Binding for NXP PCA9685 PWM controller
描述 设备功能简介 This binding describes the PCA9685 I2C PWM controller...
属性 必选属性和可选属性列表 compatible, reg, #pwm-cells
示例 一个完整的设备树节点 pca9685@40 { ... };

我记得第一次看绑定文档时,被那些 YAML 格式的缩进搞得头晕。后来才发现,其实只要抓住三个关键点就够了:compatible 字符串、reg 地址、中断信息。其他的属性大多是锦上添花。

4.2.2 从 YAML 绑定中提取信息

现在内核推荐使用 YAML 格式的绑定文档。举个例子,我们来看一个 I2C 设备的绑定:

# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/i2c/nxp,pca9685.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#

title: NXP PCA9685 PWM Controller

maintainers:
  - Stefan Wahren <stefan.wahren@i2se.com>

properties:
  compatible:
    enum:
      - nxp,pca9685
      - nxp,pca9685-pwm

  reg:
    maxItems: 1
    description: I2C address of the device

  "#pwm-cells":
    const: 2

required:
  - compatible
  - reg
  - "#pwm-cells"

additionalProperties: false

examples:
  - |
    i2c {
        #address-cells = <1>;
        #size-cells = <0>;

        pwm@40 {
            compatible = "nxp,pca9685";
            reg = <0x40>;
            #pwm-cells = <2>;
        };
    };

看到 required 字段了吗?这就是“必填项”。如果你在设备树里漏掉了这些属性,驱动可能根本不会 probe。我曾经因为漏写 #pwm-cells 这个属性,折腾了一下午才找到原因。

阅读技巧:先看 required 列表,再看 examples。把示例里的节点直接复制到你的设备树里,再根据实际硬件修改地址和中断号。这是最快上手的方法。

4.3 编写自定义绑定文档

当你需要为一个新硬件编写驱动时,绑定文档就是你的“设计图纸”。我建议你在写驱动代码之前,先把绑定文档写好。这样做有两个好处:

  • 强迫自己理清硬件接口
  • 方便团队 review 和后续维护

4.3.1 绑定文档的编写步骤

  1. 确定 compatible 字符串:格式为 厂商,设备型号,比如 ti,ads1115。
  2. 列出所有属性:包括 reg、interrupts、时钟、GPIO 等。
  3. 区分必选和可选:用 required 和 optional 标记。
  4. 编写示例节点:确保示例能通过设备树编译器的检查。
  5. 添加描述:说明设备的功能和特殊用法。

举个例子,假设我们要为一个自定义的温湿度传感器编写绑定:

# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/i2c/mycom,th100.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#

title: MyCom TH100 Temperature and Humidity Sensor

maintainers:
  - Your Name <your.email@example.com>

description: |
  The TH100 is a digital temperature and humidity sensor
  communicating over I2C. It supports both polling and
  interrupt-driven modes.

properties:
  compatible:
    const: mycom,th100

  reg:
    maxItems: 1
    description: I2C slave address, typically 0x44 or 0x45

  interrupts:
    maxItems: 1
    description: |
      Optional interrupt line for data-ready notification.
      If omitted, the driver falls back to polling mode.

  vdd-supply:
    description: Regulator supplying the sensor (3.3V typical)

required:
  - compatible
  - reg

optional:
  - interrupts
  - vdd-supply

additionalProperties: false

examples:
  - |
    i2c {
        #address-cells = <1>;
        #size-cells = <0>;

        sensor@44 {
            compatible = "mycom,th100";
            reg = <0x44>;
            interrupt-parent = <&gpio1>;
            interrupts = <5 IRQ_TYPE_EDGE_RISING>;
            vdd-supply = <&vcc_3v3>;
        };
    };

注意:绑定文档中的 additionalProperties: false 意味着设备树节点里不能出现未定义的属性。这能帮你尽早发现拼写错误。我见过有人把 compatible 拼成 compatiable,结果驱动死活加载不上。

4.4 绑定文档的验证

写完了绑定文档,怎么知道它对不对?内核提供了一个验证工具:dt_binding_check。用法很简单:

# 在内核源码根目录执行
make dt_binding_check DT_SCHEMA_FILES=Documentation/devicetree/bindings/i2c/mycom,th100.yaml

如果输出没有错误,恭喜你,绑定文档写对了。如果有错误,工具会告诉你具体哪一行有问题。我个人习惯在提交代码前,把绑定文档和设备树源文件一起验证一遍,确保万无一失。

4.5 绑定文档与驱动的关联

绑定文档写好了,驱动怎么用呢?关键在于 of_match_table。驱动通过这个表格来匹配设备树中的 compatible 字符串:

static const struct of_device_id th100_of_match[] = {
    { .compatible = "mycom,th100", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, th100_of_match);

static struct i2c_driver th100_driver = {
    .probe = th100_probe,
    .id_table = th100_id_table,
    .driver = {
        .name = "th100",
        .of_match_table = th100_of_match,
    },
};

看到 of_match_table 了吗?它指向的数组里,compatible 字符串必须和绑定文档里定义的一模一样。大小写、标点符号都不能错。我曾经因为多写了一个空格,排查了整整两天。

经验之谈:绑定文档、设备树源文件、驱动代码,这三者必须保持同步。任何一方的修改,都要同步更新另外两方。否则,轻则驱动加载失败,重则系统崩溃。

4.6 绑定文档的常见陷阱

  • compatible 字符串冲突:不同厂商的设备不要用相同的字符串。我建议加上厂商前缀,比如 ti,、nxp,。
  • 属性类型错误:reg 是 uint32 数组,interrupts 是 uint32 或 uint64。类型写错了,驱动解析时会出错。
  • 遗漏必选属性:驱动里用到的属性,一定要在 required 里声明。否则别人用你的绑定文档时,可能会漏掉关键信息。
  • 示例节点不完整:示例节点应该包含所有必选属性,并且能通过编译。我见过有人只写了一个 compatible 就完事了,这种示例毫无意义。

4.7 绑定文档的维护

绑定文档不是写一次就完事了。随着硬件版本升级、驱动功能增加,绑定文档也需要更新。我建议你在每次修改驱动时,都问自己一个问题:这个修改是否需要更新绑定文档?

如果答案是“是”,那就立刻去更新。别想着“等代码稳定了再补文档”——相信我,你永远不会回头补的。

维护技巧:在 git commit message 里加上 dt-bindings: 前缀。比如 dt-bindings: i2c: Add TH100 sensor binding。这样别人看 git log 时,一眼就能知道哪些提交修改了绑定文档。

4.8 本章知识体系

下面这张图总结了设备树绑定的核心概念和流程:

设备树绑定知识体系 设备树绑定知识体系 绑定文档 (Binding) 硬件规格书 驱动接口需求 内核绑定规范 设备树源文件 (.dts) 驱动代码 (of_match) 验证脚本 (dt_binding_check) ✅ 设备正常工作 绑定文档是硬件、驱动、内核三者之间的桥梁。写好它,能让你的代码活得更久。

从这张图可以看出,绑定文档不是孤立存在的。它连接了硬件规格、驱动需求和内核规范。只有这三者对齐了,设备才能正常工作。