3、DTS语法基础:节点、属性、值与标签引用
好,咱们今天来啃DTS的语法基础。说实话,这部分内容看着简单,但我在项目里见过太多人在这里栽跟头了。你想想看,设备树说白了就是一个描述硬件信息的树形结构,那它的基本组成单元就是节点、属性和值。搞懂了这三个东西,你就能看懂大部分DTS文件了。
3.1 节点(Node)的定义与格式
节点是什么?说白了就是树上的一个“挂点”。每个节点代表一个硬件设备或者一个总线控制器。比如CPU是一个节点,I2C控制器是一个节点,挂在I2C总线上的温度传感器也是一个节点。
节点的格式很简单:
node-name {
property1 = <value1>;
property2 = "string_value";
child-node {
child-property = <value>;
};
};
嗯,这里要注意几点:
- 节点名:一般用设备类型加上@地址的形式,比如
uart@fe010000 - 花括号:节点内容用花括号包裹,最后用分号结束
- 层级关系:节点可以嵌套,子节点缩进表示层级
重要提醒:节点名中的@地址部分不是必须的,但强烈建议加上。我在调试一个板子时,发现两个同名节点没加地址,结果编译报错,查了半天才发现是节点名冲突了。
3.2 属性(Property)与值(Value)
属性就是节点的“特征描述”。每个属性都有一个名字和一个值。值的格式有好几种,我给大家列个表:
| 值类型 | 格式 | 示例 | 说明 |
|---|---|---|---|
| 空值 | 属性名; | interrupt-controller; |
表示该节点是一个中断控制器 |
| 32位整数 | <数字> | reg = <0x10000000 0x1000>; |
尖括号包裹,多个值用空格隔开 |
| 64位整数 | <高32位 低32位> | reg = <0x0 0x10000000 0x0 0x1000>; |
两个32位拼成一个64位地址 |
| 字符串 | "字符串" | compatible = "ti,am335x-gpio"; |
双引号包裹 |
| 字节数组 | [十六进制] | local-mac-address = [00 11 22 33 44 55]; |
方括号包裹,每个字节两个十六进制数 |
| 混合值 | 组合使用 | property = <0x1234>, "string", [0xAB]; |
逗号分隔不同类型 |
个人经验:我建议初学者先记住整数和字符串两种格式,因为90%的场景都用这两个。字节数组我用的不多,但做网卡驱动时遇到过——MAC地址就是用字节数组存的。
3.3 标签(Label)与引用(&)
标签是什么?说白了就是给节点起个“别名”。为什么要用标签?因为有时候节点名很长,比如 i2c@44e0b000,每次引用都要写全名,太麻烦了。而且,如果你在多个地方引用同一个节点,万一节点名改了,你所有引用都得改——这谁受得了?
标签的用法:
// 定义标签
i2c0: i2c@44e0b000 {
compatible = "ti,omap4-i2c";
reg = <0x44e0b000 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
// 子节点也可以有标签
eeprom: eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
};
};
// 在其他地方引用
&i2c0 {
status = "okay";
clock-frequency = <400000>;
};
// 引用子节点
&eeprom {
pagesize = <16>;
};
你看,标签用冒号跟在节点名前面。引用时用&符号加上标签名。这样写的好处是:
- 解耦:节点定义和节点修改可以分开写
- 复用:同一个节点可以在多个地方追加属性
- 可读性:标签名可以用有意义的名称,比如i2c0、uart1、mmc0
避坑指南:我曾经在一个项目里,把标签名写成了数字开头,比如1st-i2c。结果编译直接报错。记住,标签名必须以字母或下划线开头,不能以数字开头。另外,标签名不能包含特殊字符,只能用字母、数字和下划线。
3.4 实战:一个完整的节点示例
咱们来看一个实际项目中用到的节点。这是我之前做的一个AM335x板子的GPIO控制器节点:
gpio0: gpio@44e07000 {
compatible = "ti,omap4-gpio";
reg = <0x44e07000 0x1000>;
interrupts = <96>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
ti,hwmods = "gpio1";
status = "disabled";
};
这个节点里包含了:
- 标签:gpio0,方便其他地方引用
- 节点名:gpio@44e07000,表示GPIO控制器,地址是0x44e07000
- compatible属性:字符串,告诉内核用哪个驱动
- reg属性:两个32位整数,表示基地址和地址范围
- interrupts属性:中断号
- 空值属性:gpio-controller和interrupt-controller,表示这个节点是GPIO控制器和中断控制器
- #gpio-cells和#interrupt-cells:表示引用时需要几个参数
- status属性:字符串,表示设备状态,disabled表示禁用
嗯,这里有个小技巧。你看status属性,我习惯在开发阶段先设为disabled,等确认硬件没问题了再改成okay。这样万一硬件有问题,系统启动时不会因为加载这个驱动而卡住。
3.5 知识体系图
下面这张图总结了DTS语法的核心结构,我画出来方便大家理解:
这张图把DTS的层级关系讲清楚了。从根节点出发,下面挂各种子节点,每个节点又包含若干属性。标签和引用则是在这个结构上加了“快捷方式”,方便我们在不同地方操作同一个节点。
我的建议:刚开始学DTS时,别急着写复杂的节点。先拿一个简单的设备树文件,比如arch/arm/boot/dts/am335x-boneblack.dts,对照着看。先找根节点,再看子节点,然后看每个节点有哪些属性。看个三五遍,语法自然就熟了。
好了,DTS语法基础就讲到这里。记住三个核心:节点是骨架,属性是血肉,标签和引用是工具。搞懂了这些,你就能读懂大部分DTS文件了。
公众号:蓝海资料掘金营,微信deep3321