4、设备树绑定文档:如何读懂和编写binding文档
设备树绑定文档,说白了就是一份“说明书”。
它告诉内核:某个硬件设备该怎么描述,哪些属性是必须的,哪些是可选的。我刚开始接触设备树时,觉得这玩意儿可有可无。直到有一次,我调试一个I2C触摸屏,死活驱动不起来。查了两天,最后发现是设备树里少写了一个 interrupts 属性。嗯,从那以后,我再也不敢小看binding文档了。
4.1 什么是设备树绑定?
设备树绑定(Device Tree Binding)是一份文档规范。它定义了:
- 某个硬件设备在设备树中应该有哪些节点
- 每个节点需要哪些属性
- 属性的数据类型是什么
- 属性值的取值范围
你想想看,如果没有这份文档,内核开发者怎么知道某个设备该怎么配置?全靠猜吗?显然不行。
我个人习惯,在写一个新的设备驱动之前,一定会先找到对应的binding文档。哪怕只是扫一眼,也能避免很多低级错误。
4.2 绑定文档的存放位置
在Linux内核源码中,binding文档通常存放在:
Documentation/devicetree/bindings/
这个目录下按设备类型分了子目录,比如:
i2c/— I2C设备gpio/— GPIO控制器clock/— 时钟控制器pinctrl/— 引脚控制display/— 显示设备sound/— 音频设备
每个设备类型下,通常是一个 .yaml 文件。现在内核社区已经全面转向YAML格式的绑定文档。老式的纯文本格式(.txt)正在被逐步淘汰。
重要提示:如果你在内核里找不到某个设备的binding文档,那说明这个设备可能还没有被主线支持。这时候你需要自己写一份binding文档,提交给社区。
4.3 如何读懂YAML格式的binding文档
YAML格式的binding文档,结构非常清晰。我来拆解一下:
4.3.1 基本结构
一个典型的YAML binding文档长这样:
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/i2c/foo,i2c-touch.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Foo I2C Touchscreen Controller
maintainers:
- Zhang San <zhangsan@foo.com>
description: |
This binding describes the Foo I2C touchscreen controller.
It supports up to 5-point multi-touch.
properties:
compatible:
const: foo,i2c-touch
reg:
maxItems: 1
description: I2C address of the device
interrupts:
maxItems: 1
description: Interrupt number for touch events
touchscreen-size-x:
$ref: /schemas/types.yaml#/definitions/uint32
description: Maximum X resolution
touchscreen-size-y:
$ref: /schemas/types.yaml#/definitions/uint32
description: Maximum Y resolution
required:
- compatible
- reg
- interrupts
additionalProperties: false
examples:
- |
i2c {
#address-cells = <1>;
#size-cells = <0>;
touchscreen@2a {
compatible = "foo,i2c-touch";
reg = <0x2a>;
interrupt-parent = <&gpio1>;
interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
touchscreen-size-x = <1024>;
touchscreen-size-y = <768>;
};
};
4.3.2 关键字段解读
| 字段 | 含义 | 说明 |
|---|---|---|
title |
设备名称 | 简洁描述设备是什么 |
maintainers |
维护者列表 | 出了问题找谁 |
description |
详细描述 | 设备的功能、特性等 |
properties |
属性定义 | 每个属性的类型、约束、说明 |
required |
必需属性列表 | 少了这些属性,设备树就是非法的 |
examples |
示例 | 一个完整的设备树节点示例 |
我的小技巧:看binding文档时,先看 required 字段。它告诉你哪些属性是“命根子”,缺一不可。然后看 examples,直接抄作业。最后再细看 properties,理解每个属性的含义。
4.4 如何编写binding文档
写binding文档,其实没那么玄乎。我总结了一套流程:
4.4.1 第一步:确定设备类型和兼容字符串
兼容字符串的格式是:厂商,设备型号。比如:
ti,ads1015— TI的ADS1015 ADC芯片nxp,pca9685— NXP的PCA9685 PWM控制器microchip,mcp23017— Microchip的MCP23017 GPIO扩展器
注意:厂商名要小写,设备型号要精确。我曾经见过有人把 ti,ads1015 写成 ti,ads1015-adc,结果驱动匹配不上。这种坑,踩一次就够了。
4.4.2 第二步:列出所有属性
把设备需要的所有属性列出来。分三类:
- 标准属性:比如
reg、interrupts、clocks、reset-gpios等。这些有通用的定义,直接引用即可。 - 设备特有属性:比如触摸屏的
touchscreen-size-x、音频编解码器的micbias-voltage等。这些需要你自己定义。 - 可选属性:比如电源管理相关的
vcc-supply、powerdown-gpios等。
4.4.3 第三步:确定数据类型
YAML binding中,常见的数据类型有:
| 类型 | YAML表示 | 说明 |
|---|---|---|
| 32位整数 | uint32 |
单个数值 |
| 32位整数数组 | uint32-array |
多个数值,如 <1 2 3> |
| 字符串 | string |
如 "foo,i2c-touch" |
| 布尔值 | boolean |
存在即为true |
| GPIO句柄 | phandle+gpio-specifier |
如 <&gpio1 5 GPIO_ACTIVE_LOW> |
4.4.4 第四步:写示例
示例很重要。它是binding文档的“活教材”。
我建议:示例要尽量完整。把必需属性和常用可选属性都写进去。这样别人抄作业时,不容易漏掉东西。
4.5 避坑指南
我曾经踩过的坑:
- 兼容字符串写错:大小写、连字符、下划线,一个都不能错。驱动匹配是精确匹配,错一个字符就匹配不上。
- 遗漏必需属性:比如I2C设备忘了写
reg,或者中断设备忘了写interrupts。内核在解析设备树时会报错,但错误信息往往不直观,排查起来很痛苦。 - 属性类型搞错:比如把
uint32写成了string,或者把phandle写成了uint32。驱动在读取属性值时,会得到错误的数据。 - 忘记更新binding文档:驱动代码改了,但binding文档没同步。别人看文档时,发现和实际代码对不上。这种问题在内核社区review时会被直接打回来。
4.6 知识体系图
下面这张图,帮你理清binding文档的核心逻辑:
4.7 小结
设备树绑定文档,是设备驱动开发的地基。地基没打好,上面的房子再漂亮也白搭。
我个人习惯,每次提交一个新的设备驱动时,都会把binding文档放在第一位。先写文档,再写代码。这样能逼着自己把设备的所有属性都想清楚,而不是边写代码边拍脑袋。
记住一句话:好的binding文档,能让后来者少掉一半头发。
实用工具推荐:
dt-doc-validate— 验证binding文档格式是否正确make dt_binding_check— 内核自带的binding检查工具make dtbs_check— 检查设备树文件是否符合binding规范
嗯,这一章就到这里。binding文档这东西,看多了自然就熟了。下次你拿到一个新设备的datasheet,不妨先试着写一份binding文档,你会发现,很多硬件细节在写文档的过程中就理清楚了。