2. 设备树语法入门:节点、属性、标签、别名的定义与规则
设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它使用树形层次结构将系统中的CPU、内存、外设、总线等硬件组件组织起来。在Linux内核驱动移植中,设备树是连接驱动代码与具体硬件配置的桥梁。本章将系统讲解设备树源文件(.dts)的核心语法要素:节点、属性、标签与别名。
学习目标:掌握设备树节点的定义与嵌套规则;理解属性(reg、interrupts、compatible等)的编码方式;学会使用标签(label)进行节点引用;掌握别名(aliases)节点的作用与配置方法。
2.1 设备树节点(Node)
节点是设备树的基本组成单元,每个节点代表一个硬件设备、总线、控制器或功能模块。节点通过树形层次结构组织,根节点(/)是整棵树的起点。
2.1.1 节点定义语法
/dts-v1/;
/ {
model = "TI AM335x BeagleBone Black";
compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a8";
device_type = "cpu";
reg = <0x00>;
};
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; /* 512 MB */
};
leds {
compatible = "gpio-leds";
led0 {
label = "beaglebone:green:usr0";
gpios = <&gpio1 21 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
};
};
};
节点命名规则:
- 节点名称:由字母、数字、连字符(-)、下划线(_)和点(.)组成,长度不超过31个字符。
- 单元地址(unit-address):可选,使用@符号与节点名连接,通常对应设备的基地址或编号,如
cpu@0、serial@44e09000。 - 节点路径:从根节点到目标节点的完整路径,如
/cpus/cpu@0、/leds/led0。
关键规则:同一层级下,节点名称必须唯一。若节点名相同,则必须通过不同的单元地址区分,例如
cpu@0 和 cpu@1。
2.1.2 节点嵌套与层次结构
设备树的层次结构通过花括号 { } 实现嵌套。子节点继承父节点的总线上下文,并可以访问父节点定义的地址空间和中断域。
2.2 属性(Property)
属性是节点的描述信息,以键值对(key-value)的形式存在。每个属性定义了硬件设备的一个特征,如地址范围、中断号、兼容性、时钟频率等。
2.2.1 属性值类型
| 属性值类型 | 语法格式 | 示例 | 说明 |
|---|---|---|---|
| 空值 | 属性名; |
interrupt-controller; |
表示布尔型真值,存在即有效 |
| 字符串 | "字符串" |
model = "TI AM335x BeagleBone Black"; |
双引号包裹的文本 |
| 字符串列表 | "str1", "str2", ... |
compatible = "ti,am335x-bone", "ti,am33xx"; |
逗号分隔的多个字符串 |
| 32位整数 | <数值> |
reg = <0x80000000 0x20000000>; |
尖括号包裹,可包含多个值 |
| 64位整数 | <高32位 低32位> |
reg = <0x00000000 0x80000000>; |
使用两个32位单元表示64位地址 |
| 字节数组 | [十六进制字节] |
local-mac-address = [00 11 22 33 44 55]; |
方括号包裹的十六进制字节序列 |
| 混合值 | <数值> "字符串" [字节] |
foo = <0x1234> "bar" [0xab]; |
多种类型组合,空格分隔 |
| 引用(phandle) | &标签 |
clocks = <&clk_mcasp0>; |
引用其他节点的标签 |
2.2.2 标准属性详解
设备树规范定义了一系列标准属性,驱动开发者必须熟悉这些属性的含义和用法。
| 属性名 | 用途 | 值类型 | 示例 |
|---|---|---|---|
compatible |
设备驱动匹配标识,格式为"厂商,设备型号" | 字符串列表 | "ti,am335x-uart", "ns16550" |
reg |
设备地址空间描述(基地址+长度) | 整数列表 | <0x44e09000 0x2000> |
#address-cells |
子节点reg地址字段的单元数(32位/单元) | 整数 | <1> |
#size-cells |
子节点reg长度字段的单元数 | 整数 | <1> |
interrupts |
设备使用的中断号及触发类型 | 整数列表 | <0 56 4> |
interrupt-parent |
指定中断控制器节点引用 | phandle | <&intc> |
clocks |
设备使用的时钟源引用 | phandle列表 | <&clk_uart0> |
status |
设备状态:"okay"、"disabled"、"reserved" | 字符串 | "okay" |
常见错误:compatible属性必须按照从具体到通用的顺序排列。内核驱动匹配时,会依次尝试列表中的每个字符串,匹配到第一个即停止。顺序错误可能导致驱动加载失败。
2.3 标签(Label)
标签是设备树中用于节点引用的标识符。通过为节点定义标签,可以在设备树的其他位置方便地引用该节点,而无需使用完整的节点路径。
2.3.1 标签定义语法
// 标签定义:在节点名称前添加 "标签名:"
uart0: serial@44e09000 {
compatible = "ti,am335x-uart", "ns16550";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
status = "okay";
};
// 标签引用:使用 &标签名 进行引用
&uart0 {
pinctrl-0 = <&uart0_pins>;
pinctrl-names = "default";
};
标签命名规则:
- 由字母、数字、下划线(_)组成,首字符不能为数字。
- 在同一设备树文件及其包含的文件中,标签必须唯一。
- 标签通常使用有意义的名称,如
uart0、i2c1、mmc0、gpio2。
2.3.2 标签的典型应用场景
| 场景 | 说明 | 示例 |
|---|---|---|
| 节点追加 | 在.dtsi文件中定义基础节点,在.dts文件中通过标签追加属性 | &uart0 { status = "okay"; }; |
| 属性引用 | 在属性值中引用其他节点,如时钟、中断、GPIO | clocks = <&clk_mcasp0>; |
| 覆盖配置 | 在设备树覆盖层(.dtso)中修改已有节点的属性 | &mmc1 { max-frequency = <50000000>; }; |
| pinctrl绑定 | 将引脚复用配置绑定到具体设备 | pinctrl-0 = <&uart0_pins &uart0_ctsrts_pins>; |
2.4 别名(Aliases)
别名节点(aliases)是根节点下的一个特殊子节点,用于为系统中的关键设备定义简短、易记的名称。别名在引导加载程序(如U-Boot)和内核初始化阶段有重要作用。
2.4.1 别名定义语法
/ {
aliases {
serial0 = &uart0;
serial1 = &uart1;
i2c0 = &i2c0;
i2c1 = &i2c1;
mmc0 = &mmc0;
mmc1 = &mmc1;
ethernet0 = &cpsw_emac0;
gpio0 = &gpio0;
gpio1 = &gpio1;
};
uart0: serial@44e09000 { /* ... */ };
uart1: serial@48022000 { /* ... */ };
i2c0: i2c@44e0b000 { /* ... */ };
};
别名命名规范:
- 别名通常使用
设备类型+序号的格式,如serial0、i2c1、mmc0。 - 别名值必须是对设备节点标签的引用(
&标签),不能直接写节点路径。 - 别名节点本身没有标准属性,所有子项均为属性名到节点引用的映射。
2.4.2 别名的核心作用
| 作用领域 | 具体用途 | 说明 |
|---|---|---|
| U-Boot引导 | 通过别名查找控制台串口、网络设备 | U-Boot使用 serial0 别名定位早期控制台输出设备 |
| 内核启动 | 解析 chosen 节点中的 stdout-path |
stdout-path = &serial0; 指定内核启动日志输出设备 |
| 设备编号 | 为设备分配逻辑序号(如ttyS0、mmcblk0) | 内核根据别名中的序号为设备注册对应的设备号 |
| 设备树覆盖 | 在覆盖层中通过别名快速定位目标节点 | 避免使用长路径,提高可读性和可维护性 |
实践要点:别名中的序号(如serial0、serial1)直接影响内核注册的设备名称。例如,
serial0 对应 /dev/ttyS0,mmc0 对应 /dev/mmcblk0。在移植过程中,必须确保别名序号与硬件设计一致,否则会导致用户空间程序无法找到正确的设备节点。
2.5 节点、标签、别名的关系与对比
2.6 实战案例:完整设备树片段解析
以下是一个基于TI AM335x平台的完整设备树片段,综合展示了节点、属性、标签和别名的实际应用:
/dts-v1/;
#include "am33xx.dtsi"
/ {
model = "TI AM335x BeagleBone Black";
compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";
aliases {
serial0 = &uart0;
serial1 = &uart1;
serial2 = &uart2;
i2c0 = &i2c0;
i2c1 = &i2c1;
i2c2 = &i2c2;
mmc0 = &mmc0;
mmc1 = &mmc1;
ethernet0 = &cpsw_emac0;
gpio0 = &gpio0;
gpio1 = &gpio1;
gpio2 = &gpio2;
gpio3 = &gpio3;
};
chosen {
stdout-path = &serial0;
bootargs = "console=ttyO0,115200 root=/dev/mmcblk0p2 rw";
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; /* 512 MB */
};
leds {
compatible = "gpio-leds";
pinctrl-names = "default";
pinctrl-0 = <&user_led_pins>;
led0 {
label = "beaglebone:green:usr0";
gpios = <&gpio1 21 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
default-state = "off";
};
led1 {
label = "beaglebone:green:usr1";
gpios = <&gpio1 22 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "mmc0";
default-state = "off";
};
};
};
/* 通过标签引用追加属性 */
&uart0 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&uart0_pins>;
};
&i2c0 {
status = "okay";
clock-frequency = <400000>;
pinctrl-names = "default";
pinctrl-0 = <&i2c0_pins>;
tps65217: tps@24 {
compatible = "ti,tps65217";
reg = <0x24>;
interrupt-parent = <&intc>;
interrupts = <7>; /* NMI */
};
};
&mmc0 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&mmc0_pins>;
bus-width = <4>;
cd-gpios = <&gpio0 6 GPIO_ACTIVE_LOW>;
};
代码解析:上述设备树片段展示了以下关键点:
•
• <
•
aliases 节点为所有关键外设定义了固定名称,确保U-Boot和内核能正确识别设备。• <