2、设备树基础语法:节点、属性、标签、路径
好,咱们今天聊聊设备树的基础语法。说实话,我刚接触设备树那会儿,看着满屏的.dts文件,心里直犯嘀咕——这玩意儿跟C语言长得也太不一样了。但用久了你会发现,它其实挺简单的,就是一套描述硬件的“说明书”。
2.1 节点(Node)——设备树的“骨架”
节点是什么?说白了就是硬件设备在软件里的“化身”。每个节点代表一个硬件设备或总线,比如一个I2C控制器、一个GPIO引脚、一个串口。
节点的写法很简单:
/ {
cpu {
compatible = "arm,cortex-a7";
reg = <0x00 0x100>;
};
i2c0 {
compatible = "snps,designware-i2c";
reg = <0x1000 0x100>;
};
};
看到没?每个节点用花括号{}包起来,最后加分号。根节点是/,所有设备都挂在它下面。我刚开始写的时候,经常忘记加分号,编译报错半天找不到原因——嗯,这种低级错误我犯过不止一次。
节点命名规则:
- 节点名一般用设备类型+序号,比如
i2c0、uart1 - 可以包含字母、数字、连字符、下划线
- 不能以数字开头,不能包含空格
2.2 属性(Property)——节点的“血肉”
节点只有骨架不行,还得有血肉。属性就是描述节点具体信息的键值对。我习惯把属性分成三类:
| 属性类型 | 示例 | 说明 |
|---|---|---|
| 字符串 | compatible = "snps,designware-i2c"; |
用于匹配驱动 |
| 整数 | reg = <0x1000 0x100>; |
地址和大小 |
| 布尔 | status = "okay"; |
启用或禁用设备 |
这里有个坑,我当年踩过:compatible属性的值,顺序很重要!内核匹配驱动时,会按顺序尝试。如果你把通用的写在前面,专用的写在后面,可能会匹配到错误的驱动。我曾经因为这个,让一个I2C设备在某个板子上死活不工作,查了两天才发现是compatible顺序搞反了。
我的小技巧:写compatible时,把最具体的型号放最前面,通用的放后面。比如:"ti,lm75", "national,lm75"。这样内核会优先匹配你的专用驱动。
2.3 标签(Label)——给节点起个“外号”
标签是什么?说白了就是给节点起个外号。你想想看,如果节点名很长,比如i2c@ff010000,每次引用都要写全名,多累啊。标签就是用来解决这个问题的。
i2c0: i2c@ff010000 {
compatible = "snps,designware-i2c";
reg = <0xff010000 0x100>;
};
&i2c0 {
clock-frequency = <400000>;
};
看到i2c0:了吗?这就是标签。后面用&i2c0就能引用这个节点。我个人习惯给所有重要的节点都加上标签,方便后续扩展。比如你要在另一个文件里给这个I2C控制器加个时钟频率,直接&i2c0就搞定了。
注意:标签名不能重复!整个设备树里标签必须是唯一的。我见过有人把两个节点都叫i2c0,编译时直接报错。嗯,这种低级错误,新手期难免。
2.4 路径(Path)——设备树的“地址”
路径就是从根节点到某个节点的完整“地址”。比如:
/soc/i2c@ff010000/rtc@68
这个路径表示:根节点下的soc节点,里面的i2c@ff010000节点,再里面的rtc@68节点。路径用斜杠分隔,跟文件系统很像。
路径在哪儿用?最常见的是在chosen节点里指定内核启动参数:
/ {
chosen {
stdout-path = &uart0;
};
};
这里&uart0其实就是路径的简写形式。内核会把控制台输出重定向到UART0。我记得有次调试一个板子,串口死活没输出,最后发现是stdout-path写错了路径——嗯,这种问题排查起来特别费时间。
2.5 综合示例——一个完整的设备树片段
来,咱们看一个实际项目中用到的例子:
/dts-v1/;
/ {
model = "MyBoard V1.0";
compatible = "mycompany,myboard", "arm,vexpress";
chosen {
stdout-path = &uart0;
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x40000000>;
};
soc {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
uart0: serial@ff010000 {
compatible = "snps,dw-apb-uart";
reg = <0xff010000 0x100>;
interrupts = <0 33 4>;
clock-frequency = <24000000>;
status = "okay";
};
i2c0: i2c@ff020000 {
compatible = "snps,designware-i2c";
reg = <0xff020000 0x100>;
interrupts = <0 34 4>;
status = "okay";
rtc@68 {
compatible = "dallas,ds1307";
reg = <0x68>;
};
};
};
};
这个例子包含了咱们今天讲的所有概念:
- 节点:
/、chosen、memory、soc、uart0、i2c0、rtc - 属性:
compatible、reg、interrupts、status等 - 标签:
uart0:、i2c0: - 路径:
&uart0就是/soc/serial@ff010000的简写
避坑指南:我曾经在一个项目里,把reg属性的地址写成了32位,但实际硬件地址是64位的。结果内核访问外设时,地址被截断,系统直接崩溃。所以写reg时,一定要确认#address-cells和#size-cells的值对不对。
好了,这一章的内容就到这儿。设备树的基础语法其实不复杂,就是节点、属性、标签、路径这四个概念。你只要记住:节点是硬件,属性是参数,标签是外号,路径是地址。下一章咱们聊聊设备树如何与驱动代码交互,那才是真正有意思的地方。