2. 设备树基础语法:节点、属性、值的表示方法、设备树源文件(.dts)结构
好,咱们正式开始啃设备树这块硬骨头。说实话,我刚接触 Android 底层时,看到 .dts 文件第一反应是——这玩意儿长得像 C 语言的结构体,又有点像 JSON,但又不完全是。后来被坑了几次才摸清楚门道。
设备树说白了就是一套描述硬件信息的「说明书」。内核通过它知道你的板子上有哪些外设、它们挂在哪条总线上、地址是多少、中断怎么配。嗯,就这么简单。
2.1 节点(Node)——硬件的最小单元
每个硬件设备或总线,在设备树里都是一个节点。节点用花括号 { } 包裹,里面可以嵌套子节点。
/ {
model = "My AOSP Board";
compatible = "vendor,board-v1";
cpu@0 {
device_type = "cpu";
reg = <0x00>;
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x40000000>;
};
};
看到没?根节点 / 下面挂了 cpu 和 memory。每个节点名字后面可以跟 @地址,这个地址通常是该设备在总线上的基地址。我个人习惯把地址写全,哪怕只有一个设备,也写上 @0,这样后面调试时一眼就能看出映射关系。
2.2 属性(Property)——节点的特征描述
属性就是键值对,用来描述节点的各种特征。常见的属性有:
- compatible:最重要的属性,驱动匹配全靠它。格式是
"厂商,设备型号"。 - reg:地址和长度信息,格式是
<地址 长度>。 - interrupts:中断号、触发方式等。
- status:设备状态,常用
"okay"或"disabled"。
uart@1c28000 {
compatible = "snps,dw-apb-uart";
reg = <0x1c28000 0x1000>;
interrupts = <0 0 4>;
clock-frequency = <24000000>;
status = "okay";
};
这里我多说一句:compatible 的命名一定要规范。我曾经接手过一个项目,同事把 "vendor,device" 写成了 "device-vendor",结果驱动死活匹配不上,查了两天才发现是字符串顺序反了。你想想看,这种低级错误多耽误事。
2.3 值的表示方法——三种基本类型
设备树的值有三种基本形式,搞懂了它们,写 .dts 就顺了。
| 类型 | 表示方式 | 示例 |
|---|---|---|
| 32位整数 | <值1 值2 ...> |
<0x1000 0x2000> |
| 字符串 | "字符串" |
"okay" |
| 字节数组 | [字节1 字节2 ...] |
[00 01 02] |
还有一种混合写法,比如 <&gpio1 0 GPIO_ACTIVE_HIGH>,这种在 GPIO 和中断里很常见。说白了就是引用一个 phandle(指针),再跟上参数。
2.4 设备树源文件结构——.dts 的骨架
一个完整的 .dts 文件通常包含这几部分:
- 头文件包含:
#include引入 .dtsi 文件(类似 C 语言的 .h) - 根节点:
/ { };定义整个板级信息 - 子节点:各种外设节点
- & 引用:通过
&节点标签修改或追加已有节点
// 引入 SoC 级设备树
#include "msm8998.dtsi"
/ {
model = "Qualcomm MSM8998 MTP";
compatible = "qcom,msm8998-mtp";
chosen {
bootargs = "console=ttyMSM0,115200";
};
};
// 引用 SoC 中的 uart 节点,追加板级配置
&uartblsp1 {
status = "okay";
pinctrl-0 = <&uart_console_active>;
};
这种 &引用 的写法非常实用。SoC 厂商提供 .dtsi 文件定义了所有外设的默认配置,板级 .dts 只需要引用并修改需要的部分。比如把某个 UART 从 disabled 改成 okay,或者调整时钟频率。
status = "disabled"。所以别以为写了节点就能用,一定要检查状态。
2.5 实战经验:一个典型的调试场景
我记得有一次调试 I2C 触摸屏,设备树里配了地址和中断,但内核就是报 no device found。后来用 cat /proc/device-tree 查看实际解析出来的设备树,发现 reg 属性里的地址是 0x38,但触摸屏芯片手册上写的是 0x38(7位地址)。
问题出在哪?I2C 地址在设备树里要写 7 位地址,但驱动里可能用了 8 位地址(左移一位)。说白了就是地址表示方式没对齐。从那以后,我每次配 I2C 设备都会先确认芯片手册上的地址格式。
嗯,这里再补充一个实用技巧:调试设备树时,可以用 dtc -I dtb -O dts 把编译好的 .dtb 反编译回 .dts,看看实际解析结果是不是你想要的。这个命令我几乎每天都要用。
2.6 小结
设备树语法其实不复杂,核心就是节点、属性、值这三板斧。但真正用好它,需要你对硬件有足够的了解。我见过太多人把设备树当成「玄学」,配错了就瞎试。其实你只要记住:
- 节点对应硬件模块
- 属性描述模块特征
- 值就是具体的参数
下次遇到设备树问题,先反编译看看实际解析结果,再对照芯片手册逐项检查。相信我,90% 的问题都能这样解决。