第二节 设备树基础语法:节点、属性、值
好,咱们今天来聊聊设备树最核心的三个概念——节点、属性和值。说实话,我刚接触设备树那会儿,看着满屏的{};和=,心里直犯嘀咕:这玩意儿跟C语言的结构体有啥区别?后来写多了才发现,它其实比结构体灵活得多。
一、节点(Node)——设备树的骨架
节点是什么?说白了就是设备树里的一个"容器"。每个节点代表一个设备、一个总线、或者一个功能模块。我习惯把节点想象成一个文件夹——它可以包含子节点,也可以有自己的属性。
节点的写法很简单:
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
cpu@0 {
compatible = "arm,cortex-a7";
reg = <0x00 0x100>;
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x40000000>;
};
};
看到没?根节点是/,里面包了两个子节点:cpu@0和memory@80000000。每个节点都用花括号{}括起来,最后用分号;结束。
@地址不是必须的,但如果有reg属性,我建议加上。这样一眼就能看出这个节点对应哪个地址空间。
二、属性(Property)——节点的"身份证"
属性就是描述节点特征的东西。你可以把它理解成C语言里的变量,但写法更自由。属性值可以是字符串、数字、数组,甚至二进制数据。
常见的属性类型有这些:
| 属性类型 | 写法示例 | 说明 |
|---|---|---|
| 字符串 | compatible = "arm,cortex-a7"; |
用双引号括起来 |
| 32位整数 | reg = <0x80000000 0x40000000>; |
尖括号里放数字 |
| 64位整数 | reg = <0x00000000 0x80000000>; |
用两个32位拼一个64位 |
| 字符串列表 | compatible = "ti,am335x-uart", "ns16550"; |
逗号分隔多个字符串 |
| 二进制数据 | mac-address = [00 11 22 33 44 55]; |
方括号里放十六进制字节 |
| 混合类型 | foo = <0x1234>, "bar"; |
用逗号分隔不同类型 |
嗯,这里要注意:compatible属性是最重要的,没有之一。内核就是靠它来匹配驱动程序的。我在项目中遇到过有人把compatible写反了顺序,结果驱动死活加载不上——因为内核是按优先级匹配的,第一个匹配到就停了。
三、值(Value)——属性的具体内容
值就是属性后面的那一串东西。别看它简单,里面有不少门道。
先看整数值:
interrupts = <0 31 4>; // 三个32位整数
clock-frequency = <24000000>; // 24MHz
再看字符串值:
model = "TI AM335x BeagleBone Black";
status = "okay"; // 或者 "disabled"
还有phandle引用:
clocks = <&clk_uart>; // 引用另一个节点
reg地址写对了,但设备就是枚举不出来。查了半天,原来是#address-cells和#size-cells没配对。这两个属性决定了reg里每个地址和长度占几个32位字。你想想看,如果父节点说地址占2个cell,但你只写了1个,那解析出来的地址就全乱了。
四、节点与属性的组织规则
设备树不是随便写的,它有一套约定俗成的规则。我总结了几点:
- 根节点必须存在——所有设备都挂在
/下面 - 每个设备节点必须有
compatible——否则内核不认识它 - 地址相关的节点要有
reg——告诉内核它占哪段地址空间 - 中断控制器节点要有
interrupt-controller——这是个空属性,只起标记作用
举个例子,一个完整的UART节点长这样:
uart0: serial@44e09000 {
compatible = "ti,am335x-uart", "ns16550";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
clock-frequency = <48000000>;
status = "okay";
};
这里uart0:是标签(label),方便其他地方引用。比如在clocks属性里就可以写&uart0来引用这个节点。
uart0,节点名也写成uart0,结果编译报错。其实标签和节点名是两套命名空间,但为了可读性,我建议标签用全小写加下划线,节点名用全小写加@地址。
五、实战小技巧
最后分享几个我写设备树时的习惯:
- 先搭骨架,再填细节——先把所有节点列出来,再逐个加属性
- 多用
status = "disabled"——调试时先关掉不用的设备,减少干扰 - 善用
#include——把通用的部分抽成.dtsi文件,避免重复劳动 - 编译前先检查语法——用
dtc -I dts -O dtb test.dts编译,有错会直接报
说实话,设备树语法本身不难,难的是理解硬件和内核之间的映射关系。但只要你把节点、属性、值这三个概念吃透了,后面学中断映射、时钟绑定什么的,就会轻松很多。
下一节咱们聊聊设备树的地址映射——reg属性到底怎么用,为什么有时候写一个数,有时候写两个数。到时候我会拿一个实际项目里的例子来讲,保证你一听就懂。