2、设备树语法入门:节点、属性、值的表示方法,dts/dtsi/dtb文件格式
好,咱们正式开始啃设备树的语法。说实话,我第一次看设备树文件时,觉得这东西长得像C语言和JSON的混血儿。但用久了你会发现,它其实挺朴素的——就是一套描述硬件信息的树形结构。
2.1 设备树长什么样?
设备树的源文件是 .dts 文件。我习惯叫它“硬件描述书”。你打开一个典型的 .dts 文件,会看到类似这样的结构:
/dts-v1/;
/ {
model = "TI AM335x BeagleBone Black";
compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am335x";
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a8";
device_type = "cpu";
reg = <0x0>;
};
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>;
};
};
嗯,看着有点眼熟吧?每个花括号 {} 就是一个节点,节点里可以嵌套子节点。说白了,这就是一棵树——根节点是 /,下面挂着 cpus、memory 这些大树枝,再往下是更细的树枝。
2.2 节点:设备树的骨架
节点是设备树最基本的组成单元。每个节点代表一个硬件设备或总线。节点名字的格式是 node-name@unit-address。
举个例子:
uart0: serial@44e09000 {
compatible = "ti,am3352-uart", "ti,omap3-uart";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
};
这里 serial@44e09000 是节点名,@ 后面的 44e09000 是它的地址。前面的 uart0: 是标签(label),方便其他地方引用。我在项目中经常用标签来引用节点,比如 &uart0 就能直接指向这个串口节点。
serial、i2c、spi。别用 my_uart 这种名字,否则别人看不懂,三个月后的你自己也看不懂。
2.3 属性:描述节点的特征
属性就是键值对,用来描述节点的各种特征。常见的属性有:
- compatible:兼容性字符串,告诉内核这个设备用什么驱动。格式是
"厂商,型号"。我踩过坑——字符串写反了,结果驱动死活匹配不上。 - reg:地址和长度信息。格式是
<地址 长度>。 - interrupts:中断号。
- status:设备状态,常用
"okay"或"disabled"。
看个实际例子:
gpio0: gpio@44e07000 {
compatible = "ti,am3352-gpio", "ti,omap4-gpio";
reg = <0x44e07000 0x1000>;
interrupts = <96>;
gpio-controller;
#gpio-cells = <2>;
status = "okay";
};
注意那个 gpio-controller;——这是个空属性,没有值。它就像个开关,告诉内核“我是GPIO控制器”。
2.4 值的表示方法
设备树里的值有几种表示方式,我整理了个表格:
| 类型 | 写法 | 示例 |
|---|---|---|
| 32位整数 | <值> |
<0x1000> |
| 64位整数 | 用两个32位表示 | <0x0 0x80000000> |
| 字符串 | "字符串" |
"ti,am3352-uart" |
| 字节数组 | [字节流] |
[00 01 02 03] |
| 字符串列表 | 逗号分隔 | "ti,am3352-uart", "ti,omap3-uart" |
你想想看,为什么地址要用 <> 括起来?因为设备树编译器(dtc)看到 <> 就知道里面是整数,看到 [] 就知道是字节。我曾经在调试时把 <0x1000> 写成了 [0x1000],结果地址解析全乱了——嗯,这种低级错误犯一次就够了。
2.5 dts、dtsi、dtb 文件格式
这三个后缀名,我刚开始也搞混过。简单说:
- .dts:设备树源文件。你写代码的地方。
- .dtsi:设备树包含文件。相当于C语言的
.h头文件。 - .dtb:编译后的二进制文件。U-Boot和内核真正读取的。
编译命令很简单:
dtc -I dts -O dtb -o myboard.dtb myboard.dts
反过来也能从 .dtb 反编译成 .dts:
dtc -I dtb -O dts -o myboard.dts myboard.dtb
.dtsi 文件用 #include 引入。比如 #include "soc.dtsi"。这样你可以把SoC公共部分放在 .dtsi 里,板级差异放在 .dts 里。我在项目中就是这么做的——一个 .dtsi 给同一系列的所有板子用,省事多了。
2.6 避坑指南
- 分号漏了——设备树里每个属性后面都要加分号
;。漏一个,编译就报错。我调试过半小时才发现少了个分号。 - 地址写错——
reg里的地址和长度,顺序不能乱。先地址后长度,这是铁律。 - compatible 字符串不匹配——驱动里写的
"ti,am3352-uart",设备树里写"ti,am335x-uart",差一个字符都匹配不上。
我个人习惯是,写完 .dts 文件后,先用 dtc -I dts -O dtb 编译一下,看看有没有语法错误。别等到烧到板子上才发现问题——那会儿调试起来就麻烦了。
2.7 小结
设备树语法其实不复杂。记住三点:
- 节点是树形结构,用
{}表示层级 - 属性是键值对,值有四种类型
.dts是源码,.dtsi是头文件,.dtb是二进制
下一章咱们会深入讲 #address-cells 和 #size-cells 这两个让人头疼的属性。说实话,我第一次看到这两个属性时也是一头雾水——但搞懂了它们,设备树的地址映射你就通了八成。