4. 设备树绑定(Binding):从文档到验证
设备树绑定,说白了就是一份“说明书”。它告诉内核:某个设备节点里,哪些属性是必须的,哪些是可选的,每个属性是什么类型、什么含义。没有绑定,设备树就是一盘散沙。
我刚接触设备树那会儿,总觉得写个节点、配几个属性就完事了。结果呢?驱动加载失败,内核报错说找不到资源。查了半天,原来是少了个 interrupt-parent。从那以后,我养成了一个习惯——先看绑定文档,再写设备树。
4.1 绑定文档长什么样
绑定文档通常放在内核源码的 Documentation/devicetree/bindings/ 目录下。每个子系统都有自己的子目录,比如 i2c/、gpio/、pinctrl/。
一个典型的绑定文档包含以下几部分:
- 标题和描述:说明这个设备是什么,用在什么场景。
- 兼容性字符串:列出所有支持的
compatible值。 - 属性列表:分“必需属性”和“可选属性”两类。
- 示例节点:一个完整的设备树节点示例。
举个例子,这是 I2C 控制器的一个简化绑定:
# 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:
enum:
- fsl,imx1-i2c
- fsl,imx21-i2c
- fsl,imx6q-i2c
reg:
maxItems: 1
interrupts:
maxItems: 1
required:
- compatible
- reg
- interrupts
additionalProperties: false
examples:
- |
i2c@53f8000 {
compatible = "fsl,imx6q-i2c";
reg = <0x53f8000 0x4000>;
interrupts = <0 56 4>;
};
嗯,这里要注意:现在的绑定文档已经全面转向 YAML 格式了。以前那种纯文本的 .txt 格式基本被淘汰了。YAML 的好处是结构清晰,还能自动校验。
4.2 自定义绑定:给自家芯片写说明书
如果你用的是非标设备,或者自己设计的 FPGA 外设,那就得写自定义绑定了。我个人习惯是:先写绑定文档,再写驱动,最后配设备树。顺序别搞反了,否则容易漏属性。
自定义绑定的步骤大致如下:
- 确定 compatible 字符串:格式是
厂商,型号,比如acme,my-uart。 - 列出所有属性:包括寄存器地址、中断号、时钟、GPIO 等。
- 写 YAML 绑定文件:放在对应子系统的目录下。
- 用 dt_binding_check 验证:确保语法正确。
来看一个我实际做过的例子。当时给一个 FPGA 上的 SPI 控制器写绑定:
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/spi/acme,spi-fpga.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: ACME FPGA SPI Controller
maintainers:
- Zhang San <zhangsan@acme.com>
properties:
compatible:
const: acme,spi-fpga
reg:
description: SPI controller registers
maxItems: 1
interrupts:
maxItems: 1
clocks:
description: SPI module clock
maxItems: 1
cs-gpios:
description: Chip select GPIOs
minItems: 1
maxItems: 4
required:
- compatible
- reg
- interrupts
- clocks
additionalProperties: false
examples:
- |
spi@40020000 {
compatible = "acme,spi-fpga";
reg = <0x40020000 0x1000>;
interrupts = <0 42 4>;
clocks = <&clk 100>;
cs-gpios = <&gpio1 10 0>,
<&gpio1 11 0>;
#address-cells = <1>;
#size-cells = <0>;
flash@0 {
compatible = "jedec,spi-nor";
reg = <0>;
spi-max-frequency = <50000000>;
};
};
additionalProperties: false 这个字段很重要。它告诉校验工具:除了列出的属性,不允许出现其他属性。这能帮你尽早发现拼写错误。
4.3 绑定验证:别让内核吃哑巴亏
绑定文档写完了,怎么知道对不对?内核提供了一套验证工具。
最常用的是 dt_binding_check 和 dtbs_check。前者检查绑定文档本身的语法,后者用绑定文档去校验实际的设备树。
运行方式很简单:
# 只检查绑定文档
make dt_binding_check DT_SCHEMA_FILES=acme,spi-fpga.yaml
# 检查设备树
make dtbs_check DT_SCHEMA_FILES=acme,spi-fpga.yaml
我曾经在一个项目里吃过亏。写了个自定义绑定,自认为没问题,结果 dtbs_check 报了一堆错。仔细一看,原来是属性名写错了——把 clocks 写成了 clock。少个 s,内核就不认。
常见的验证错误类型有:
| 错误类型 | 典型原因 | 解决办法 |
|---|---|---|
| 属性缺失 | required 里列了,但节点里没写 | 补上对应属性 |
| 类型不匹配 | 属性值类型与绑定定义不一致 | 检查类型:uint32、string、phandle 等 |
| 多余属性 | 节点里写了绑定未定义的属性 | 要么删掉,要么加到绑定里 |
| compatible 不匹配 | 节点 compatible 不在绑定枚举中 | 修正字符串或更新绑定 |
4.4 绑定与驱动的“契约”
绑定文档其实是驱动和设备树之间的“契约”。驱动通过 of_match_table 匹配 compatible 字符串,然后通过 of_property_read_* 系列函数读取属性。
举个例子,驱动里这样读属性:
static int my_spi_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource *res;
int irq;
u32 max_speed;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
irq = platform_get_irq(pdev, 0);
/* 读取自定义属性 */
of_property_read_u32(dev->of_node, "spi-max-frequency", &max_speed);
/* 读取 GPIO 属性 */
cs_gpio = of_get_named_gpio(dev->of_node, "cs-gpios", 0);
/* ... 初始化硬件 ... */
return 0;
}
static const struct of_device_id my_spi_of_match[] = {
{ .compatible = "acme,spi-fpga", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_spi_of_match);
static struct platform_driver my_spi_driver = {
.probe = my_spi_probe,
.driver = {
.name = "my-spi",
.of_match_table = my_spi_of_match,
},
};
你想想看,如果绑定文档里写了 cs-gpios 是必需的,但驱动里没读它,或者读的方式不对,那设备树配得再漂亮也没用。所以,绑定和驱动必须同步更新。
4.5 绑定文档的维护
绑定文档不是写一次就完事了。随着硬件版本迭代,属性可能会增减。我建议:
- 每次修改驱动,同步更新绑定文档。
- 提交补丁时,绑定文档和驱动代码放在同一个 patch 系列里。
- 使用版本管理,绑定文档的变更历史要清晰。
内核社区对绑定文档的审查很严格。我记得有一次提交,reviewer 要求我把一个属性名从 foo-gpios 改成 foo-gpio,理由是“内核惯例是单数”。虽然只是改个名字,但体现了社区对规范性的重视。
核心要点:绑定文档是设备树的“语法书”。写设备树之前,先看绑定;写驱动之前,先写绑定。验证工具是你的好帮手,但逻辑正确性还得靠你自己。
这张图展示了绑定文档在整个设备树开发流程中的位置。从绑定文档出发,经过验证,再到设备树和驱动,最终让设备正常工作。每一步都环环相扣,缺一不可。