第1章:设备树基础语法——节点、属性与.dts格式
大家好,我是你们的讲师。今天咱们聊聊设备树的基础语法。说实话,我刚接触设备树那会儿,也被它那套“树形结构”搞得有点懵。但后来我发现,这东西说白了就是个描述硬件的“配置文件”。你想想看,UEFI要启动操作系统,总得知道板子上接了哪些外设吧?设备树就是干这个的。
1.1 节点(Node)——设备树的“骨架”
设备树的结构,就像一棵倒着长的树。根节点是“/”,下面挂着一堆子节点。每个节点代表一个硬件设备或总线。我习惯把节点想象成“硬件设备的身份证”。
来看个最简单的例子:
/ {
cpu {
compatible = "arm,cortex-a72";
reg = <0x0 0x100>;
};
memory {
device_type = "memory";
reg = <0x80000000 0x40000000>;
};
};
这里“/”是根节点,“cpu”和“memory”是它的子节点。每个节点用花括号包裹,最后用分号结束。嗯,这里要注意:节点名不能重复,否则解析器会报错。我在项目中遇到过有人把两个I2C控制器都叫“i2c”,结果系统只认了第一个。
1.2 属性(Property)——节点的“血肉”
节点光有名字不行,还得有属性来描述它。属性就是键值对,格式是:
属性名 = 值;
值的类型有几种:
- 字符串:用双引号包着,比如
compatible = "ti,am335x-uart"; - 32位整数:用尖括号包着,比如
reg = <0x48020000 0x1000>; - 64位整数:用两个尖括号,比如
reg = <0x0 0x80000000 0x0 0x40000000>; - 字节数组:用方括号,比如
local-mac-address = [00 11 22 33 44 55]; - 布尔值:只有属性名,没有值,比如
status = "disabled";或者直接写status;(表示true)
我个人习惯把属性分成三类:标准属性、自定义属性和可选属性。标准属性是内核或UEFI固件必须识别的,自定义属性是驱动开发者自己加的,可选属性嘛,有最好,没有也能凑合。
1.3 标准属性详解
这部分是重点,我建议你记牢。标准属性就像硬件的“通用语言”,不管哪个平台都得懂。
1.3.1 compatible——设备匹配的“钥匙”
compatible 属性是设备树里最重要的属性之一。它告诉操作系统:“我是谁,我该用哪个驱动”。格式是 "厂商,设备型号"。比如:
compatible = "ti,am335x-uart", "ns16550";
这里有两个字符串,第一个是精确匹配,第二个是通用匹配。操作系统会按顺序尝试,直到找到能驱动的。我在项目中遇到过一个问题:有人把顺序写反了,结果系统用了通用驱动,没用到厂商优化过的驱动,性能差了一大截。所以,顺序很重要!
1.3.2 reg——地址与大小
reg 属性描述设备的地址空间。格式是 <地址 大小>,可以有多组。比如:
reg = <0x48020000 0x1000>;
这表示设备从0x48020000开始,占用0x1000字节。如果设备有多个地址段,可以写多组:
reg = <0x48020000 0x1000>, <0x48021000 0x100>;
嗯,这里要注意:地址和大小都是32位整数。如果地址超过4GB,就得用ranges属性配合#address-cells和#size-cells来处理。我曾经在调试一个64位平台时,忘了改#address-cells,结果地址被截断了,折腾了两天才发现。
1.3.3 interrupts——中断配置
中断属性描述设备怎么触发中断。格式取决于中断控制器。常见写法:
interrupts = <0 31 4>;
interrupt-parent = <&gic>;
这里interrupt-parent指向中断控制器节点,interrupts里的三个数字分别是:中断类型(0=SPI,1=PPI)、中断号、触发方式(1=上升沿,2=下降沿,4=高电平,8=低电平)。
我建议你写中断属性时,一定要查清楚中断控制器的spec。不同厂商的中断控制器,格式可能不一样。比如有些用interrupt-cells = <2>,有些用<3>。搞错了,中断就收不到。
1.3.4 其他标准属性
| 属性名 | 作用 | 示例 |
|---|---|---|
status |
设备状态,"okay"表示启用,"disabled"表示禁用 |
status = "okay"; |
device_type |
设备类型,通常用于内存节点 | device_type = "memory"; |
model |
设备型号,用于区分同一厂商的不同版本 | model = "TI AM335x EVM"; |
phandle |
节点句柄,用于其他节点引用 | phandle = <1>; |
ranges |
地址转换,用于桥接不同地址空间 | ranges = <0x0 0x80000000 0x10000>; |
1.4 设备树源文件(.dts)格式
.dts文件是设备树的“源代码”。它用C语言风格的预处理器,支持#include、#define等宏。我习惯把公共部分放在.dtsi文件里,然后通过#include包含进来。比如:
// am335x.dtsi
#include <dt-bindings/interrupt-controller/irq.h>
/ {
model = "TI AM335x";
compatible = "ti,am335x";
ocp {
#address-cells = <1>;
#size-cells = <1>;
ranges;
uart0: serial@48020000 {
compatible = "ti,am335x-uart", "ns16550";
reg = <0x48020000 0x1000>;
interrupts = <72>;
status = "disabled";
};
};
};
然后在具体板子的.dts里:
// am335x-evm.dts
#include "am335x.dtsi"
/ {
model = "TI AM335x EVM";
compatible = "ti,am335x-evm", "ti,am335x";
&uart0 {
status = "okay";
clock-frequency = <48000000>;
};
};
看到&uart0了吗?这是设备树的“引用语法”。它允许你在不破坏原有结构的情况下,修改某个节点的属性。我个人觉得这个设计非常巧妙,就像面向对象里的“继承”一样。
1.5 设备树编译与反编译
.dts文件不能直接给UEFI或内核用,得编译成.dtb(设备树二进制)文件。工具是dtc(Device Tree Compiler)。常用命令:
# 编译
dtc -I dts -O dtb -o am335x-evm.dtb am335x-evm.dts
# 反编译
dtc -I dtb -O dts -o am335x-evm.dts am335x-evm.dtb
我曾经在调试一个UEFI启动问题,发现设备树没生效。反编译一看,原来编译时忘了加-@选项,导致overlay功能没打开。嗯,细节决定成败啊。
1.6 知识体系结构图
下面我用一张SVG图来总结本章的知识结构:
核心要点:设备树就是一棵描述硬件的树。节点是树枝,属性是树叶。标准属性是“通用语言”,让不同操作系统都能读懂硬件。
我的小技巧:写.dts文件时,先用dtc -I dts -O dtb编译一下,看看有没有语法错误。别等到烧到板子上才发现问题,那太浪费时间了。
注意:设备树里的地址、中断号等数字,都是十进制或十六进制。我建议统一用十六进制,避免混淆。另外,reg属性的地址和大小顺序不能搞反,否则设备会访问到错误的内存区域。
好了,这一章的内容就到这里。设备树的基础语法其实不难,关键是多写、多编译、多调试。下一章我们会深入设备树的“地址映射”和“中断映射”,那才是真正考验功力的时候。
可可精品资料库