2. DTS语法详解:节点、属性、标签、包含文件的完整规则
好,咱们今天来啃DTS语法这块硬骨头。说实话,我刚接触设备树那会儿,看着那些花花绿绿的括号和分号,头都大了。但后来我发现,DTS的语法其实比C语言简单得多——它就是个结构化的描述语言,没有复杂的控制流,没有函数调用。
说白了,DTS就是用来描述硬件信息的。你告诉内核:我这儿有个I2C控制器,挂在哪个地址上,用哪个中断号。就这么简单。
2.1 节点(Node)——设备树的基本单元
节点是DTS的核心。每个节点代表一个设备、一个总线、或者一个控制器。我习惯把节点想象成硬件设备在软件世界里的身份证。
节点的基本语法长这样:
/dts-v1/;
/ {
model = "MyBoard";
compatible = "vendor,myboard";
cpu@0 {
device_type = "cpu";
reg = <0>;
compatible = "arm,cortex-a9";
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x40000000>;
};
};
看到没?每个节点用花括号包裹,以分号结尾。节点名后面可以跟一个@符号加地址——这个地址通常是寄存器基址或者设备编号。
节点命名规则:
- 节点名必须小写字母开头
- 可以包含数字、连字符和下划线
- @后面的地址是十六进制数,不带0x前缀
- 根节点用斜杠 / 表示
我在项目中遇到过一个问题:有人把节点名写成了大写开头,结果内核死活不认。嗯,DTS解析器对大小写是敏感的,这点要记住。
2.2 属性(Property)——描述节点的特征
属性就是节点的特征描述。你可以把它理解成C语言里的变量,只不过它的类型更丰富。
常见的属性类型有这些:
| 属性类型 | 语法示例 | 说明 |
|---|---|---|
| 字符串 | compatible = "vendor,device"; |
用双引号包裹 |
| 32位整数 | reg = <0x10000000 0x1000>; |
尖括号包裹,空格分隔 |
| 64位整数 | reg = <0x00000000 0x80000000>; |
用两个32位表示 |
| 字节数组 | local-mac-address = [00 11 22 33 44 55]; |
方括号包裹,十六进制 |
| 布尔值 | status = "disabled"; |
用字符串表示 |
| 混合类型 | foo = <0x1234>, "string"; |
逗号分隔不同类型 |
我最常用的是compatible属性。它就像设备的身份证号,内核通过它来匹配驱动。格式是"厂商,设备型号",比如"ti,am335x-uart"。
避坑指南:我曾经在reg属性里少写了一个地址单元,结果驱动读到的地址完全不对。调试了两天才发现是地址长度写错了。所以写reg的时候,一定要确认好地址和长度各占几个cell。
2.3 标签(Label)——给节点起个外号
标签就是节点的别名。你想想看,如果一个节点名特别长,比如"spi@48030000",每次引用它都要写全名,多麻烦。标签就是解决这个问题的。
// 定义标签
uart0: serial@44e09000 {
compatible = "ti,am335x-uart";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
};
// 引用标签
&uart0 {
status = "okay";
clock-frequency = <48000000>;
};
看到&uart0了吗?这就是通过标签引用节点。我习惯在写DTS的时候,给所有重要的设备都加上标签。这样后面要修改或者引用的时候,方便得很。
标签的命名规则:
- 以字母或下划线开头
- 可以包含字母、数字、下划线
- 不能是关键字(比如compatible、reg这些)
- 在整个设备树中必须唯一
注意:标签和节点名是两回事。标签只是引用方式,不会影响节点在设备树中的路径。比如上面的uart0,它的完整路径还是/serial@44e09000。
2.4 包含文件(Include)——模块化你的设备树
一个复杂的SoC可能有几十个外设,如果全部写在一个文件里,那简直是一场灾难。include指令就是用来解决这个问题的。
// 主文件:myboard.dts
#include "soc.dtsi"
#include "myboard.dtsi"
/ {
model = "MyBoard";
compatible = "vendor,myboard";
};
// soc.dtsi 文件
/ {
cpu@0 {
compatible = "arm,cortex-a9";
reg = <0>;
};
uart0: serial@44e09000 {
compatible = "ti,am335x-uart";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
status = "disabled";
};
};
include指令有两种写法:
#include "file.dtsi"—— 标准C预处理器语法/include/ "file.dtsi"—— DTS原生语法
我个人习惯用#include,因为大多数DTS文件都会经过C预处理器处理,这样还能用宏定义。
说到dtsi文件,它和dts文件有什么区别?dtsi是设备树包含文件,通常用来描述SoC内部的通用硬件。dts文件才是具体的板级文件。一个SoC的dtsi可以被多个板子的dts文件包含。
文件组织建议:
- soc.dtsi —— SoC通用外设定义
- soc-pinmux.dtsi —— 引脚复用配置
- myboard.dts —— 具体板级配置
- myboard-xxx.dtsi —— 板级扩展配置
2.5 节点引用与覆盖
这是DTS最强大的特性之一。你可以在一个地方定义节点,在另一个地方修改它。我经常用这个特性来覆盖dtsi文件里的默认配置。
// soc.dtsi 中定义
uart0: serial@44e09000 {
compatible = "ti,am335x-uart";
reg = <0x44e09000 0x2000>;
status = "disabled";
};
// myboard.dts 中覆盖
&uart0 {
status = "okay";
pinctrl-0 = <&uart0_pins>;
pinctrl-names = "default";
};
看到没?在myboard.dts里,我通过&uart0引用了之前定义的节点,然后只修改了status和pinctrl属性。其他属性保持不变。这就是设备树的继承和覆盖机制。
我曾经在调试一个I2C设备的时候,发现设备树里明明配了中断,但驱动就是收不到中断。后来发现是dtsi文件里把中断号写错了,而板级文件里没有覆盖这个属性。嗯,从那以后我每次都会检查所有覆盖是否正确。
2.6 特殊属性详解
有几个属性特别重要,我单独拿出来说说:
compatible: 驱动匹配的关键。内核通过它找到对应的驱动。格式是"厂商,设备型号"。比如"ti,am335x-uart"、"fsl,imx6q-uart"。
reg: 描述设备地址空间。格式是<地址1 长度1 地址2 长度2 ...>。每个地址和长度占几个32位单元,由父节点的#address-cells和#size-cells决定。
interrupts: 描述中断信息。格式取决于中断控制器的配置。通常是一个中断号加一个触发类型。
status: 设备状态。可选值有"okay"、"disabled"、"reserved"等。我习惯在dtsi里把所有外设都设为disabled,然后在板级文件里按需开启。
小技巧:写compatible属性的时候,我建议把最具体的型号写在前面,通用的写在后面。比如"ti,am335x-uart"、"ns16550a"。这样内核匹配的时候,会优先匹配最具体的驱动。
2.7 实战:一个完整的DTS片段
说了这么多,咱们来看一个完整的例子。这是我之前做的一个项目里的实际代码:
// 包含SoC通用定义
#include "am335x.dtsi"
/ {
model = "TI AM335x MyBoard";
compatible = "ti,am335x-myboard", "ti,am335x";
// 内存配置
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>; // 256MB
};
// 板级LED
leds {
compatible = "gpio-leds";
led0 {
label = "heartbeat";
gpios = <&gpio0 6 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
};
};
};
// 覆盖SoC默认配置
&uart0 {
status = "okay";
pinctrl-0 = <&uart0_pins>;
};
&i2c0 {
status = "okay";
clock-frequency = <400000>;
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
pagesize = <16>;
};
};
这个例子涵盖了节点、属性、标签、包含文件、节点覆盖等所有知识点。你可以看到,整个设备树结构清晰,层次分明。
2.8 知识体系图
下面这张图总结了DTS语法的核心知识体系:
嗯,DTS语法其实不复杂。你只要记住:节点是骨架,属性是血肉,标签是别名,包含文件是模块化工具。把这四样东西搞明白了,写设备树就跟搭积木一样简单。
我刚开始学的时候,总觉得设备树很神秘。后来发现,它就是个结构化的硬件描述语言。你只要按照规则把硬件信息写清楚,内核就能自动帮你匹配驱动、分配资源。
好了,这一章的内容就到这里。记住我强调的几个关键点:节点命名规则、属性类型、标签引用、文件组织。这些都是实际开发中天天要用到的东西。