第四节:设备树绑定文档——如何阅读内核 Documentation/devicetree/bindings 下的文档
说实话,我刚接触设备树那会儿,最头疼的就是看内核里那一堆 Documentation/devicetree/bindings/ 下的文档。密密麻麻的 YAML 和文本描述,看着就头大。但后来我发现,这些文档其实是设备树开发的「法律条文」——你写的每个节点、每个属性,都得按它说的来。
今天我就带你把这套文档读透。说白了,读懂了绑定文档,你写设备树就不会翻车。
4.1 绑定文档到底长什么样?
内核 5.x 以后,大部分绑定文档都改成了 YAML 格式,后缀是 .yaml。老一点的还是纯文本 .txt。我建议你优先看 YAML 格式的,因为它结构清晰,还能被工具自动校验。
随便打开一个,比如 Documentation/devicetree/bindings/gpio/gpio.txt(老格式)或者 .../gpio/gpio.yaml(新格式),你会发现它基本包含这几块:
- 标题和描述:这个 IP 是干什么的,用在什么芯片上
- 属性列表:必须有的属性(required)、可选的属性(optional)
- 示例:一个完整的设备树节点写法
- 兼容性字符串:比如
compatible = "ti,omap-gpio"
核心原则:绑定文档里写的「required」属性,一个都不能少。少了内核驱动就 probe 失败,或者直接 panic。
4.2 怎么快速定位你要找的文档?
内核的绑定文档是按子系统分类的。目录结构大概是:
Documentation/devicetree/bindings/
├── arm/ # ARM 平台相关
├── clock/ # 时钟控制器
├── gpio/ # GPIO 控制器
├── i2c/ # I2C 控制器
├── interrupt-controller/ # 中断控制器
├── memory/ # 内存控制器
├── mmc/ # SD/MMC 控制器
├── net/ # 网络设备
├── pinctrl/ # 引脚复用
├── regulator/ # 电源管理
├── serial/ # 串口
├── spi/ # SPI 控制器
├── timer/ # 定时器
└── vendor-prefixes.yaml # 厂商前缀列表
我个人习惯是:先确定你要写的设备属于哪个子系统,然后直接进对应目录。比如你要写一个 I2C 触摸屏,就去 bindings/input/touchscreen/ 下找。
小技巧:在源码根目录下用 grep -r "compatible" Documentation/devicetree/bindings/ 可以快速搜到你要的兼容字符串。我在移植 U-Boot 时经常这么干。
4.3 读懂 YAML 格式的绑定文档
YAML 格式的文档,其实比纯文本好读多了。咱们拿一个实际的例子来分析:
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/gpio/gpio.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: GPIO Controller
maintainers:
- Linus Walleij <linus.walleij@linaro.org>
properties:
compatible:
description: 兼容性字符串
items:
- enum:
- ti,omap-gpio
- nxp,lpc-gpio
reg:
description: 寄存器地址和长度
maxItems: 1
gpio-controller: true
"#gpio-cells":
const: 2
required:
- compatible
- reg
- gpio-controller
- "#gpio-cells"
examples:
- |
gpio0: gpio@44e07000 {
compatible = "ti,omap-gpio";
reg = <0x44e07000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
};
你看,结构非常清晰:
properties下面列出了所有属性及其约束required告诉你哪些是必须写的examples直接给你一个完整示例,照着抄就行
嗯,这里要注意:#gpio-cells 这个属性名在 YAML 里必须用引号括起来,因为 # 是 YAML 的注释符号。我刚开始写的时候没注意,结果 dtc 编译报错,找了半天才发现是这个问题。
4.4 纯文本格式的绑定文档怎么读?
虽然 YAML 是趋势,但很多老驱动还是纯文本格式。比如 Documentation/devicetree/bindings/i2c/i2c-imx.txt:
* Freescale i.MX I2C Controller
Required properties:
- compatible: Should be "fsl,imx1-i2c", "fsl,imx21-i2c" or "fsl,imx6q-i2c"
- reg: Physical address and size of the I2C controller registers
- interrupts: Should contain the I2C interrupt
Optional properties:
- clock-frequency: Desired I2C bus clock frequency in Hz (default 100000)
- fsl,use-gpio: Use GPIO for SDA/SCL lines
Example:
i2c@43f80000 {
compatible = "fsl,imx6q-i2c";
reg = <0x43f80000 0x4000>;
interrupts = <0 67 IRQ_TYPE_LEVEL_HIGH>;
clock-frequency = <100000>;
};
读这种文档,我有个习惯:先看 Required properties,再看 Example。把 Example 里的节点和 Required 属性一一对应,确认没漏掉。Optional 的属性,按需添加就行。
避坑指南:我曾经在移植一个 SPI 驱动时,漏掉了 spi-max-frequency 这个可选属性。结果 SPI 总线跑在默认频率上,导致通信不稳定。后来加上这个属性,指定了合适的频率,问题就解决了。所以可选属性也别随便忽略,尤其是跟时序相关的。
4.5 绑定文档里的「兼容性」到底怎么选?
这是新手最容易懵的地方。绑定文档里经常看到这样的写法:
compatible:
items:
- enum:
- ti,am335x-uart
- ti,omap3-uart
- const: ti,omap-uart
这表示兼容字符串可以是 "ti,am335x-uart","ti,omap-uart" 或者 "ti,omap3-uart","ti,omap-uart"。第一个是具体型号,第二个是通用型号。驱动匹配时,会先找具体型号,找不到再找通用型号。
我建议你:优先使用最具体的兼容字符串。比如你的芯片是 AM335x,就用 "ti,am335x-uart",而不是偷懒只用 "ti,omap-uart"。为什么?因为内核后续版本可能会为具体型号添加特殊处理,你用通用字符串就享受不到这些优化。
4.6 如何验证你的设备树是否符合绑定文档?
光读文档还不够,你得会验证。内核提供了一个工具叫 dt-bindings-check,或者直接用 make dt_binding_check:
# 检查所有绑定文档的语法
make dt_binding_check
# 检查某个具体的绑定文档
make dt_binding_check DT_SCHEMA_FILES=Documentation/devicetree/bindings/gpio/gpio.yaml
# 检查你的设备树文件是否符合绑定
make dtbs_check
我在项目中经常这么干:写完设备树后,先跑 make dtbs_check,看看有没有报错。如果有,就对照绑定文档逐条排查。这比手动检查靠谱多了。
个人经验:dtbs_check 的输出有时候比较隐晦,比如它可能只告诉你「属性类型不匹配」,但不告诉你具体哪个属性。这时候我会用 dtc -I dtb -O dts 把编译好的 dtb 反编译成 dts,然后对照绑定文档手动检查。虽然麻烦,但能发现一些自动化工具漏掉的问题。
4.7 绑定文档的常见陷阱
读多了绑定文档,你会发现一些容易踩坑的地方:
- 属性名大小写:设备树属性名都是小写+连字符,比如
clock-frequency,不是clockFrequency。我见过有人写成驼峰式,结果驱动匹配不上。 - 中断类型:
interrupts属性的第三个值(中断触发类型)在不同架构下含义不同。ARM 用IRQ_TYPE_LEVEL_HIGH,RISC-V 可能用别的。一定要看绑定文档里的说明。 - 地址和大小:
reg属性的地址和大小,单位是字节。但有些文档里写的是0x1000,实际可能是 4KB。别想当然,以文档为准。
我曾经踩过的坑:在写一个 MMC 控制器的设备树时,绑定文档里说 bus-width 属性可选,默认值是 1。我没写这个属性,结果 SD 卡只能以 1-bit 模式工作,速度慢得离谱。后来加上 bus-width = <4>,速度就正常了。所以可选属性里跟性能相关的,一定要根据硬件实际能力写上去。
4.8 总结一下我的阅读方法
读绑定文档,我一般按这个步骤来:
- 先看标题和描述:确认这个文档是不是你要找的
- 再看 Required properties:列出所有必须写的属性,一个都不能少
- 然后看 Optional properties:根据你的硬件需求,决定哪些要加
- 最后看 Example:照着示例写,但别忘了把地址、中断号改成你自己的
- 跑一遍 dtbs_check:让工具帮你验证
你想想看,设备树说白了就是硬件的「身份证」。绑定文档就是身份证的「填写规范」。按规范填,驱动就能认出来;不按规范填,驱动就不认识你。就这么简单。
下一节,我会讲怎么从零开始写一个设备树文件,到时候咱们会频繁用到今天讲的这些知识。记得把绑定文档的阅读方法练熟,后面会省很多事。