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 绑定文档的编写步骤
- 确定 compatible 字符串:格式为
厂商,设备型号,比如ti,ads1115。 - 列出所有属性:包括 reg、interrupts、时钟、GPIO 等。
- 区分必选和可选:用
required和optional标记。 - 编写示例节点:确保示例能通过设备树编译器的检查。
- 添加描述:说明设备的功能和特殊用法。
举个例子,假设我们要为一个自定义的温湿度传感器编写绑定:
# 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 本章知识体系
下面这张图总结了设备树绑定的核心概念和流程:
从这张图可以看出,绑定文档不是孤立存在的。它连接了硬件规格、驱动需求和内核规范。只有这三者对齐了,设备才能正常工作。