第四节:设备树绑定文档——内核文档解读与编写实战

设备树绑定文档,说白了就是一份「说明书」。

它告诉内核:某个硬件设备该怎么描述,哪些属性是必须的,哪些是可选的,值该怎么填。我刚开始接触设备树时,总觉得写设备树就是对着芯片手册抄寄存器地址,后来踩了坑才明白——没有绑定文档,设备树就是一堆没人看得懂的乱码。

4.1 内核文档 Documentation/devicetree/bindings/ 解读

先说说内核里那堆文档。你打开内核源码,找到 Documentation/devicetree/bindings/ 目录,里面按厂商、设备类型分门别类。比如 i2cgpioclockarm 等等。

每个子目录下,通常是一个个 .yaml 文件。嗯,现在内核社区已经全面转向 YAML 格式了。为什么?因为老式的纯文本绑定文档,机器没法自动校验,全靠人眼检查,太容易出错了。

举个例子,打开 Documentation/devicetree/bindings/i2c/i2c-imx.yaml,你会看到这样的结构:

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

title: Freescale i.MX I2C Controller

maintainers:
  - Oleksij Rempel <o.rempel@pengutronix.de>

properties:
  compatible:
    oneOf:
      - items:
          - enum:
              - fsl,imx1-i2c
              - fsl,imx21-i2c
          - const: fsl,imx1-i2c

  reg:
    maxItems: 1

  interrupts:
    maxItems: 1

required:
  - compatible
  - reg
  - interrupts

additionalProperties: false

看到没?properties 下面定义了每个属性的类型、约束。required 列出了必须填的属性。这就是绑定文档的核心——它是一份「契约」。

核心要点:绑定文档不是写给程序员看的说明书,而是写给设备树解析器和校验工具的「机器可读规范」。

4.2 如何读懂一份绑定文档

我个人的习惯是,拿到一份绑定文档,先看三样东西:

  1. compatible 字符串——这是设备树和驱动匹配的钥匙。比如 fsl,imx6ul-uart,驱动里必须写同样的字符串才能对上。
  2. required 列表——哪些属性是必须的?少一个,设备可能就起不来。
  3. reg 和 interrupts 的描述——地址、中断号,这些是硬件最核心的信息。

举个例子,我在项目中遇到过一块 SPI 闪存,设备树里只写了 compatible = "jedec,spi-nor",结果内核死活不认。后来查绑定文档才发现,reg 属性必须写,而且 spi-max-frequency 也是推荐的。加上去,问题就解决了。

小技巧:make dt_binding_check 可以自动校验你的设备树是否符合绑定文档。我每次提交设备树 patch 前,都会跑一遍这个命令,省得被 maintainer 怼回来。

4.3 如何编写绑定文档

假设你设计了一款新的 I2C 温度传感器,芯片型号叫 mycomp,tmp100。你要为它写一份绑定文档。

步骤其实不复杂:

  1. Documentation/devicetree/bindings/i2c/ 下新建一个 mycomp,tmp100.yaml 文件。
  2. 复制一份现有的类似文档作为模板,比如 ti,tmp102.yaml
  3. 修改 titlemaintainersproperties

下面是我写的一个示例:

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

title: MyComp TMP100 I2C Temperature Sensor

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

description: |
  The TMP100 is a digital temperature sensor with I2C interface.
  It supports 12-bit resolution and operates from -55°C to +125°C.

properties:
  compatible:
    const: mycomp,tmp100

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

  interrupts:
    description: |
      Optional interrupt line for alert functionality.
      If not provided, polling mode is used.
    maxItems: 1

  vcc-supply:
    description: Power supply regulator
    $ref: /schemas/types.yaml#/definitions/phandle

required:
  - compatible
  - reg

additionalProperties: false

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

        temperature-sensor@48 {
            compatible = "mycomp,tmp100";
            reg = <0x48>;
            interrupts = <5 2>;
            vcc-supply = <&reg_3v3>;
        };
    };

注意:写绑定文档时,additionalProperties: false 一定要加。不加的话,设备树里写什么乱七八糟的属性都不会报错,调试起来会非常痛苦。我曾经因为这个漏掉了一个拼写错误,查了整整两天。

4.4 绑定文档的校验与测试

写完了怎么验证?用 dt-schema 工具。内核里已经集成了:

# 安装依赖
pip install dtschema

# 校验单个绑定文档
make dt_binding_check DT_SCHEMA_FILES=Documentation/devicetree/bindings/i2c/mycomp,tmp100.yaml

# 校验设备树文件
make dtbs_check DT_SCHEMA_FILES=Documentation/devicetree/bindings/i2c/mycomp,tmp100.yaml

如果校验通过,恭喜你,你的绑定文档写对了。如果报错,别慌,仔细看错误信息,通常是缩进问题或者属性类型写错了。

4.5 避坑指南

写绑定文档这些年,我总结了几条血泪教训:

  • compatible 字符串要唯一——别用 generic,device 这种名字,一定要带上厂商前缀。我见过有人写 my-uart,结果跟别的驱动冲突了。
  • reg 的地址要写对——I2C 地址是 7 位,SPI 片选是 0 开始。别搞混了。
  • 中断类型要明确——interrupts = <0 2> 里的第二个数字代表触发方式,1 是上升沿,2 是下降沿,4 是高电平,8 是低电平。我建议写清楚注释。
  • 别忘了电源属性——很多设备需要 vcc-supplyvddio-supply,不写的话,驱动可能拿不到电源,设备直接不工作。

一句话总结:绑定文档是设备树和驱动之间的「翻译官」。写好了,事半功倍;写不好,调试到怀疑人生。

嗯,这一节就到这里。下一节我们会讲如何把绑定文档和驱动代码结合起来,实现真正的「设备树驱动开发」。到时候我会拿一个实际的项目案例来演示,保证你学完就能上手。