2. DTS语法入门
设备树源文件(.dts)是一种结构化的文本描述语言,用于向操作系统(尤其是 Linux 内核)描述不可枚举的硬件信息。本章将系统讲解 DTS 的四大核心语法要素:节点(Node)、属性(Property)、标签(Label)和包含文件(Include)。掌握这些基础是理解设备树覆盖(Overlay)和热插拔驱动的前提。
.dts 文件(如 Linux 内核源码中的 arch/arm/boot/dts/ 目录下的文件)进行练习,效果更佳。
2.1 节点(Node):硬件设备的树形抽象
节点是设备树的基本组成单元,用于表示一个硬件设备或总线。每个节点从根节点(/)开始,形成一棵树状结构。节点可以嵌套,子节点继承父节点的总线环境。
节点语法格式:
[label:] node-name[@unit-address] {
[property-list]
[child-node-list]
};
- node-name:节点名称,通常为通用设备类型(如
cpu,memory,i2c)。 - @unit-address:可选,用于区分同类型设备,通常为寄存器地址或设备编号。
- label:可选,为节点定义一个引用标签(详见 2.3 节)。
- 花括号
{ }:包含该节点的属性和子节点。 - 分号
;:节点定义结束符。
示例:一个简单的设备树根节点与子节点
/ {
#address-cells = <1>;
#size-cells = <1>;
cpu@0 {
compatible = "arm,cortex-a9";
reg = <0x00 0x100>;
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x40000000>; /* 1 GB RAM */
};
};
/ 是设备树的入口,每个系统有且只有一个根节点。子节点 cpu@0 和 memory@80000000 分别描述了 CPU 和内存设备。
2.2 属性(Property):描述设备的键值对
属性是附着在节点上的键值对,用于描述设备的特性、地址、中断、时钟等信息。属性值可以是空、整数、字符串、字节数组或这些类型的组合。
常见属性类型与格式:
| 属性类型 | 格式示例 | 说明 |
|---|---|---|
| 空属性 | status = "disabled"; |
布尔型标志,存在即表示真 |
| 32 位整数 | reg = <0x10000000 0x1000>; |
尖括号 <> 包裹,空格分隔 |
| 64 位整数 | clock-frequency = <0x00 0x5f5e100>; |
使用两个 32 位单元表示 |
| 字符串 | compatible = "ti,am335x-gpio"; |
双引号包裹,用于驱动匹配 |
| 字符串列表 | compatible = "ti,am335x-gpio", "ti,omap-gpio"; |
逗号分隔多个字符串 |
| 字节数组 | local-mac-address = [00 11 22 33 44 55]; |
方括号 [] 包裹,十六进制字节 |
| 混合类型 | interrupts = <0 42 4>; |
可包含整数、字符串等组合 |
最重要的属性:compatible
compatible 属性是设备与驱动之间的“握手协议”。内核通过匹配该属性的字符串列表来加载对应的驱动程序。格式通常为 "厂商,设备型号",例如 "ti,am335x-gpio"。
;,或字符串未使用双引号。DTS 编译器(DTC)对语法非常敏感,一个分号缺失会导致整个文件编译失败。
2.3 标签(Label):节点的引用别名
标签(Label)是为节点定义的别名,用于在设备树的其他位置引用该节点。标签在节点名称前定义,后跟冒号。使用 &label 语法可以引用标签指向的节点。
标签定义与引用示例:
/* 定义标签 */
uart0: serial@44e09000 {
compatible = "ti,omap-uart";
reg = <0x44e09000 0x2000>;
};
/* 在其他地方引用标签 */
&uart0 {
status = "okay";
clock-frequency = <48000000>;
};
标签的主要用途:
- 覆盖(Overlay):在设备树覆盖文件中,通过
&label向已有节点添加或修改属性。 - 引用(Phandle):在属性中通过
&label引用其他节点,形成设备间的连接关系(如中断控制器、时钟源)。 - 代码可读性:避免使用冗长的节点路径(如
/soc/serial@44e09000),使用简短标签更清晰。
&label 完成。因此,为关键设备节点(如 I2C、SPI、GPIO、UART)定义有意义的标签是良好的工程习惯。
2.4 包含文件(Include):模块化与复用
DTS 支持使用 C 语言风格的 #include 预处理指令,将公共定义、头文件或其它设备树片段包含到当前文件中。这极大地提高了代码的复用性和可维护性。
包含文件的使用方式:
/* 包含 .dtsi 文件(设备树包含文件,通常存放 SoC 公共定义) */
#include "am335x.dtsi"
/* 包含 .h 头文件(通常定义宏、引脚复用编号) */
#include "am335x-pinmux.h"
/ {
model = "TI AM335x BeagleBone Black";
compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am335x";
/* 使用头文件中定义的宏 */
&gpio0 {
pinctrl-0 = <&gpio0_pins>;
};
};
包含文件的典型层次结构:
| 文件类型 | 扩展名 | 用途 |
|---|---|---|
| 设备树源文件 | .dts |
具体板级的设备树文件,是编译的入口 |
| 设备树包含文件 | .dtsi |
SoC 公共定义、IP 核描述,被多个 .dts 包含 |
| C 语言头文件 | .h |
定义宏常量(如 GPIO 编号、时钟频率),供 DTS 使用 |
.dtsi 文件中,板级差异(如外设使能、引脚复用)放在 .dts 文件中。这样,同一 SoC 的不同板卡只需维护一个 .dtsi 和多个小型 .dts 文件。
2.5 综合示例:一个完整的 DTS 片段
以下示例综合展示了节点、属性、标签和包含文件的用法。该片段描述了一个基于 AM335x SoC 的板级设备树,包含 GPIO 控制器和 LED 子节点。
#include "am335x.dtsi"
#include "am335x-pinmux.h"
/ {
model = "TI AM335x Custom Board";
compatible = "ti,am335x-custom", "ti,am335x";
leds {
compatible = "gpio-leds";
pinctrl-0 = <&led_pins>;
led0: led@0 {
label = "heartbeat";
gpios = <&gpio1 21 GPIO_ACTIVE_LOW>;
linux,default-trigger = "heartbeat";
};
};
};
&gpio1 {
status = "okay";
};
&am33xx_pinmux {
led_pins: led_pins {
pinctrl-single,pins = <
AM33XX_IOPAD(0x848, PIN_OUTPUT | MUX_MODE7)
>;
};
};
#include "am335x.dtsi":引入 SoC 级定义,包含gpio1、am33xx_pinmux等节点。leds节点:定义了一个 GPIO LED 子节点,使用gpios属性引用&gpio1。&gpio1和&am33xx_pinmux:通过标签引用,在板级文件中使能 GPIO 并配置引脚复用。led_pins标签:为引脚复用子节点定义标签,供pinctrl-0引用。
通过本章的学习,读者应能够独立阅读和编写基础的 DTS 文件,理解节点如何组织硬件、属性如何描述细节、标签如何实现跨文件引用,以及包含文件如何实现模块化设计。这些知识是后续学习设备树覆盖(Overlay)和热插拔驱动的基石。