4. 设备树机制:设备树语法、dts文件编写、设备树与驱动的匹配过程
设备树,英文叫 Device Tree,是嵌入式 Linux 里绕不开的一个东西。我刚开始接触它的时候,觉得这玩意儿就是个“硬件描述清单”,后来踩了几个坑才明白——它远不止是清单,它是操作系统和板级硬件之间的“翻译官”。
说白了,设备树就是用来告诉内核:我这块板子上挂了哪些外设,它们挂在哪个地址上,用哪个中断,时钟频率多少。没有它,内核就得靠一堆硬编码的板级文件来认硬件,那维护起来简直是灾难。
核心观点:设备树把硬件描述从内核源码里剥离出来,变成一份独立的数据结构。换板子改 dts,不用重新编译内核。
4.1 设备树语法基础
设备树的源文件是 .dts,编译后变成 .dtb(二进制),内核启动时解析它。语法其实不复杂,我总结一下最常用的几个要素。
4.1.1 节点(Node)
每个外设或总线都是一个节点。节点可以嵌套,形成树状结构。
/ {
model = "MyBoard v1.0";
compatible = "vendor,myboard";
cpu@0 {
device_type = "cpu";
reg = <0>;
};
serial@101f0000 {
compatible = "ns16550";
reg = <0x101f0000 0x1000>;
interrupts = <1 0>;
};
};
看到没?@ 后面跟的是地址或单元号,reg 属性描述地址范围。我习惯把 compatible 当作节点的“身份证”,驱动就是靠它来认领设备的。
4.1.2 属性(Property)
属性就是键值对。常见的有:
| 属性名 | 含义 | 示例 |
|---|---|---|
compatible |
设备兼容标识,驱动匹配的关键 | "vendor,device" |
reg |
地址/长度对,描述寄存器空间 | <0x1000 0x200> |
interrupts |
中断号与触发方式 | <0 2> |
clocks |
时钟句柄 | <&clk 1> |
status |
设备状态:okay 或 disabled |
"okay" |
我的习惯:写 dts 时,先把
compatible 和 reg 写对,其他属性可以慢慢补。这两个错了,驱动根本认不到设备。
4.2 dts 文件编写实战
写 dts 文件,说白了就是“对着原理图填参数”。我拿一个 I2C 控制器挂温度传感器的例子来说明。
/dts-v1/;
#include "soc.dtsi"
/ {
model = "MySensorBoard";
compatible = "vendor,sensorboard";
&i2c1 {
status = "okay";
clock-frequency = <100000>;
temp_sensor: tmp102@48 {
compatible = "ti,tmp102";
reg = <0x48>;
interrupt-parent = <&gpio1>;
interrupts = <15 2>;
};
};
};
这里有几个要点:
#include可以引用 SoC 厂商提供的.dtsi文件,省去重复定义。&i2c1是引用节点,用来覆盖或追加属性。- 子节点
tmp102@48的reg是 I2C 从机地址,不是内存地址。
我曾经踩过的坑:I2C 设备的
reg 写成了内存地址,结果驱动 probe 的时候读寄存器全返回 0xFF。排查了半天,才发现是地址类型搞混了。记住:reg 的含义由父节点的 #address-cells 和 #size-cells 决定。
4.3 设备树与驱动的匹配过程
这部分是重点。内核怎么把 dts 里的节点和驱动代码对应起来?我画了一张图,你看完就明白了。
匹配的核心就是 compatible 字符串的比较。内核里每个驱动都会声明一个 of_match_table,里面放一串它支持的设备名。当内核解析 dts 时,拿着节点的 compatible 去这个表里找,找到了就调用驱动的 probe()。
4.3.1 匹配的三种方式
| 匹配方式 | 原理 | 典型场景 |
|---|---|---|
| compatible 匹配 | 字符串精确匹配 | 绝大多数外设驱动 |
| 设备名匹配 | 节点名称匹配(已废弃) | 旧版内核兼容 |
| ACPI 匹配 | ACPI 表匹配(x86 平台) | 服务器、PC 平台 |
关键点:嵌入式平台几乎只用 compatible 匹配。你写驱动时,
of_match_table 里的字符串必须和 dts 里的 compatible 完全一致,大小写、逗号、空格都不能错。
4.4 避坑指南
- compatible 写反了:我见过有人把
"ti,tmp102"写成"tmp102,ti",内核匹配不上,驱动 probe 不执行。记住:厂商名在前,设备名在后。 - reg 的 cells 数不对:父节点定义了
#address-cells = <1>,子节点reg却写了两个地址,内核解析会错位。 - status 忘了写:很多 SoC 的
.dtsi里默认把外设节点status = "disabled",你必须在板级.dts里显式改成"okay"。
调试小技巧:内核启动时加
earlycon 和 dtoc 参数,可以打印设备树的解析过程。看到 "OF: ..." 开头的日志,就是设备树在干活。
嗯,设备树这部分内容不少,但核心就三件事:语法、写 dts、理解匹配流程。你只要把 compatible 和 reg 搞明白,再配合内核的打印信息,大部分问题都能定位。我当年第一次调设备树驱动,就是靠加打印一步步看匹配结果,最后发现是 dts 里少了个逗号——这种错误,犯过一次就再也不会忘了。