第三节:设备树绑定文档——从规范到实战

设备树绑定文档,说白了就是一份「说明书」。它告诉内核:某个硬件设备该怎么描述、资源怎么配置、驱动怎么匹配。我刚开始接触设备树时,总觉得这玩意儿可有可无,直到有一次调试一个 SPI 外设,死活挂不上驱动……后来才发现,是 compatible 属性写错了。嗯,从那以后,我再也不敢轻视绑定文档了。

3.1 绑定文档的结构

绑定文档通常放在内核源码的 Documentation/devicetree/bindings/ 目录下。每个设备或设备家族,都有一个对应的 YAML 或文本文件。我个人习惯先看 YAML 格式的绑定文档,因为它结构清晰、校验严格。

一个典型的绑定文档包含以下几部分:

  • 描述(Description):简单说明这个设备是干什么的。
  • 属性(Properties):列出所有必需的、可选的属性,比如 compatible、reg、interrupts 等。
  • 示例(Example):给出一段完整的设备树节点示例。

核心原则:绑定文档不是写给机器看的,是写给开发者看的。你写的每一行属性,都应该有明确的语义和用途。

3.2 compatible 属性——驱动的「身份证」

compatible 属性是设备树里最重要的属性之一。它告诉内核:这个设备是什么型号、哪个厂商生产的。驱动通过匹配这个字符串,来决定要不要绑定这个设备。

格式通常是 "厂商,型号"。比如:

compatible = "ti,omap3-uart", "ns16550";

这里有两个字符串,第一个是精确匹配,第二个是兼容匹配。内核会按顺序尝试匹配驱动。我在项目中遇到过一个问题:某款芯片的 UART 模块,厂商写的是 "vendor,uart",但驱动里只写了 "vendor,uart-v2",结果死活匹配不上。后来我加了个兼容字符串才搞定。

我的建议:写 compatible 时,尽量把最精确的型号放在最前面。这样即使将来有新的兼容驱动,也不会影响旧设备的匹配顺序。

3.3 reg 属性——地址与空间的「地图」

reg 属性描述设备的寄存器地址范围。它由一组或多组 (地址, 长度) 对组成。比如:

reg = <0x48020000 0x1000>;

这表示设备从 0x48020000 开始,占用 0x1000 字节的地址空间。如果设备有多个地址段,可以这样写:

reg = <0x48020000 0x1000>, <0x49000000 0x100>;

嗯,这里要注意:reg 的顺序必须和驱动里读取的顺序一致。我曾经见过一个案例,某工程师把两个地址段写反了,结果驱动读到的全是垃圾数据。排查了整整两天……

避坑指南:reg 属性的长度单位是字节,不是字。别写成 0x100 就以为是 256 个字,那是 256 字节。我刚开始也犯过这个错。

3.4 中断属性——让设备会「说话」

中断属性描述设备如何向 CPU 发送中断信号。常用的中断属性有:

  • interrupts:指定中断号、触发类型等。
  • interrupt-parent:指定中断控制器。
  • interrupt-names:给中断起个名字,方便驱动里引用。

举个例子:

interrupt-parent = <&gpio1>;
interrupts = <15 IRQ_TYPE_EDGE_RISING>;
interrupt-names = "button-press";

这里表示设备使用 GPIO1 的第 15 号引脚,上升沿触发中断。我个人习惯给每个中断都起个名字,这样驱动里用 platform_get_irq_byname() 来获取,代码可读性会好很多。

你想想看,如果设备有多个中断,比如一个用于接收、一个用于发送、一个用于错误处理,没有名字的话,你只能靠索引来区分。万一哪天索引顺序变了,驱动就崩了。所以,命名是个好习惯

3.5 GPIO 属性——控制引脚的「开关」

GPIO 属性描述设备使用了哪些 GPIO 引脚,以及这些引脚的功能。常用的有:

  • gpios:指定 GPIO 控制器、引脚号和标志。
  • gpio-names:给每个 GPIO 起个名字。
  • xxx-gpios:比如 reset-gpios、enable-gpios,表示特定功能的 GPIO。

看个例子:

reset-gpios = <&gpio2 5 GPIO_ACTIVE_LOW>;
enable-gpios = <&gpio3 10 GPIO_ACTIVE_HIGH>;
gpio-names = "reset", "enable";

这里 reset 引脚是 GPIO2 的第 5 号,低电平有效;enable 引脚是 GPIO3 的第 10 号,高电平有效。我在项目中遇到过一个问题:某款 LCD 屏的复位引脚,设备树里写的是高电平有效,但实际电路是低电平复位。结果每次开机屏幕都花屏……后来我查了原理图才找到原因。

关键点:GPIO 的标志位(如 GPIO_ACTIVE_LOW)一定要和硬件电路一致。别想当然,一定要看原理图。

3.6 实战中的常见问题

说了这么多,我总结几个实战中容易踩的坑:

  1. compatible 字符串大小写敏感:Linux 内核是大小写敏感的,写错了就匹配不上。我曾经见过有人把 "ti,omap" 写成 "Ti,Omap",结果驱动加载失败。
  2. reg 属性地址对齐:很多硬件要求寄存器地址按 4 字节或 8 字节对齐。写错了可能导致访问异常。
  3. 中断号重复:如果两个设备用了同一个中断号,又没有正确共享中断,系统可能会死锁。
  4. GPIO 引脚冲突:同一个 GPIO 引脚被多个设备使用,会导致功能异常。检查原理图时一定要仔细。

我的经验:写设备树绑定文档时,最好先画一张资源分配表。把地址、中断、GPIO 都列出来,看看有没有冲突。这一步虽然麻烦,但能省下后面大量的调试时间。

3.7 小结

绑定文档是设备树的「说明书」,也是驱动开发的「地图」。compatible 属性决定了驱动怎么找到设备,reg 属性描述了设备的地址空间,中断属性和 GPIO 属性则让设备能和 CPU 交互。写的时候,一定要仔细核对硬件手册和原理图,别凭感觉写。

嗯,这一节就到这里。下一节我们会聊聊设备树的编译与调试,到时候我会分享一些我常用的调试技巧,保证让你少走弯路。