ARM嵌入式Linux设备树驱动DTS编写实战工程 - 第2节
2. 设备树语法入门
设备树(Device Tree)是一种描述硬件资源的树形数据结构,它使用文本文件(.dts)来描述系统中所有可发现的硬件设备及其连接关系。DTS 文件是设备树的源文件,经过 DTC(Device Tree Compiler)编译后生成 DTB(Device Tree Blob)二进制文件,供内核在启动时解析。
核心概念:设备树将硬件描述从内核代码中分离出来,使得同一份内核镜像可以支持多种硬件平台,只需更换不同的 DTB 文件即可。
2.1.1 DTS 文件的基本结构
一个典型的 DTS 文件由以下部分组成:
- 根节点(/):设备树的起点,所有其他节点都是根节点的子节点或后代节点。
- 节点(node):表示一个硬件设备或总线,每个节点可以有子节点和属性。
- 属性(property):描述节点的特性,如寄存器地址、中断号、时钟频率等。
- 标签(label):为节点或属性定义的别名,方便在其他地方引用。
- 引用(&):通过标签引用节点,用于在多个文件中扩展或修改节点内容。
// 一个简单的 DTS 文件示例
/dts-v1/;
/ {
model = "ARM Cortex-A9 开发板";
compatible = "arm,cortex-a9";
#address-cells = <1>;
#size-cells = <1>;
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>; // 256MB 内存
};
chosen {
bootargs = "console=ttyAMA0,115200 root=/dev/mmcblk0p2";
};
soc {
#address-cells = <1>;
#size-cells = <1>;
ranges;
uart0: serial@101f1000 {
compatible = "arm,pl011";
reg = <0x101f1000 0x1000>;
interrupts = <0 5 4>;
clock-frequency = <24000000>;
};
gpio0: gpio@101f2000 {
compatible = "arm,pl061";
reg = <0x101f2000 0x1000>;
interrupts = <0 6 4>;
gpio-controller;
#gpio-cells = <2>;
};
};
};
提示:DTS 文件使用 C 风格的注释(// 和 /* */),并且支持 #include 预处理指令,可以包含 .dtsi 头文件实现代码复用。
2.1.2 设备树的层级结构
设备树的层级结构反映了硬件系统的拓扑关系。根节点代表整个系统,其子节点代表 SoC 内部的总线或功能模块,再下一级节点代表具体的设备实例。
2.2 节点(Node)与属性(Property)
2.2.1 节点的定义与命名规则
节点是设备树的基本组成单元,每个节点代表一个硬件设备、总线或功能模块。节点的定义格式如下:
[label:] node-name[@unit-address] {
[property-list]
[child-node-list]
};
| 组成部分 | 说明 | 示例 |
|---|---|---|
| label | 可选的标签名,用于在其他地方引用该节点 | uart0: |
| node-name | 节点名称,描述设备类型,必须唯一 | serial |
| @unit-address | 可选的单元地址,通常为设备基地址 | @101f1000 |
| property-list | 属性列表,描述节点的特性 | reg = <...>; |
| child-node-list | 子节点列表,表示总线上的从设备 | i2c-bus { ... }; |
注意:节点名称应使用通用设备类型名称(如 serial、gpio、i2c),而不是具体芯片型号。具体型号通过
compatible 属性指定。
2.2.2 标准属性详解
设备树规范定义了一系列标准属性,用于描述设备的通用特性:
| 属性名 | 值类型 | 说明 | 示例 |
|---|---|---|---|
| compatible | 字符串列表 | 设备兼容性标识,用于驱动匹配 | "arm,pl011" |
| reg | 32位整数对列表 | 寄存器地址和长度 | <0x101f1000 0x1000> |
| interrupts | 32位整数列表 | 中断号、触发类型等 | <0 5 4> |
| #address-cells | 32位整数 | 子节点 reg 属性中地址字段的 cell 数 | <1> |
| #size-cells | 32位整数 | 子节点 reg 属性中长度字段的 cell 数 | <1> |
| ranges | 地址映射列表 | 子地址到父地址的映射关系 | <0 0x10000000 0x1000> |
| status | 字符串 | 设备状态(okay/disabled) | "okay" |
2.2.3 属性值类型
设备树属性支持多种数据类型,每种类型有其特定的编码方式:
- 字符串(string):用双引号括起来,如
compatible = "arm,pl011"; - 32位整数(u32):用尖括号括起来,如
reg = <0x101f1000 0x1000>; - 64位整数(u64):用两个 u32 表示,如
reg = <0x00000000 0x80000000 0x00000000 0x10000000>; - 字节数组(bytes):用方括号括起来,如
local-mac-address = [00 11 22 33 44 55]; - 布尔值(boolean):属性存在即为 true,如
gpio-controller; - 混合类型:可以组合多种类型,如
interrupts = <0 5 4>;
// 属性值类型示例
/ {
// 字符串
compatible = "arm,cortex-a9";
// 32位整数
#address-cells = <1>;
#size-cells = <1>;
// 字节数组
mac-address = [00 11 22 33 44 55];
// 布尔值
gpio-controller;
// 混合类型
interrupts = <0 5 4>;
};
2.3 标签(Label)与引用(&)
2.3.1 标签的定义与作用
标签(label)是为节点或属性定义的别名,用于在其他地方方便地引用该节点。标签定义在节点名称之前,使用冒号分隔:
uart0: serial@101f1000 {
compatible = "arm,pl011";
reg = <0x101f1000 0x1000>;
interrupts = <0 5 4>;
};
标签的主要作用包括:
- 简化引用:通过标签名引用节点,避免使用冗长的路径
- 节点扩展:在多个 .dtsi 文件中扩展同一个节点
- 跨文件引用:在包含多个文件的工程中引用其他文件定义的节点
- 覆盖属性:在 board 级别的 .dts 文件中覆盖 SoC 级别的默认配置
2.3.2 引用操作符 &
引用操作符 & 用于通过标签引用节点,实现对节点的扩展或修改。引用语法如下:
&label {
property = <new-value>;
child-node {
...
};
};
引用操作符的典型应用场景:
| 场景 | 说明 | 示例 |
|---|---|---|
| 添加属性 | 为已有节点添加新的属性 | &uart0 { clock-frequency = <24000000>; }; |
| 覆盖属性 | 修改已有节点的属性值 | &uart0 { status = "disabled"; }; |
| 添加子节点 | 为总线节点添加新的从设备 | &i2c0 { eeprom@50 { ... }; }; |
| 引用 phandle | 在属性中引用其他节点 | clocks = <&clk_uart0>; |
// 引用操作符的完整示例
// soc.dtsi - SoC 级设备树
/ {
soc {
uart0: serial@101f1000 {
compatible = "arm,pl011";
reg = <0x101f1000 0x1000>;
interrupts = <0 5 4>;
status = "disabled"; // 默认禁用
};
};
};
// board.dts - 板级设备树
#include "soc.dtsi"
// 引用 uart0 节点,启用并配置时钟
&uart0 {
status = "okay";
clock-frequency = <24000000>;
};
最佳实践:SoC 级 .dtsi 文件中的设备默认设置为
status = "disabled",板级 .dts 文件通过 &label 引用并设置为 status = "okay",这样可以避免未使用的设备占用资源。
2.3.3 标签与引用的工作流程
标签和引用在设备树编译过程中的工作流程如下:
2.4 实战案例:构建一个完整的设备树
下面通过一个完整的实战案例,综合运用节点、属性、标签和引用的知识:
// my-board.dts - 完整设备树示例
/dts-v1/;
#include "skeleton.dtsi"
/ {
model = "My ARM Development Board";
compatible = "my,board-v1", "arm,vexpress";
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; // 512MB
};
chosen {
bootargs = "console=ttyAMA0,115200 root=/dev/mmcblk0p2 rw";
};
soc {
#address-cells = <1>;
#size-cells = <1>;
ranges;
uart0: serial@101f1000 {
compatible = "arm,pl011";
reg = <0x101f1000 0x1000>;
interrupts = <0 5 4>;
clocks = <&clk_uart0>;
status = "disabled";
};
uart1: serial@101f2000 {
compatible = "arm,pl011";
reg = <0x101f2000 0x1000>;
interrupts = <0 6 4>;
clocks = <&clk_uart1>;
status = "disabled";
};
clk_uart0: clock@10000000 {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <24000000>;
};
clk_uart1: clock@10000010 {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <48000000>;
};
};
};
// 板级配置:启用 UART0,禁用 UART1
&uart0 {
status = "okay";
};
&uart1 {
status = "disabled";
};
关键要点:
- 根节点包含 model 和 compatible 属性,用于标识硬件平台
- memory 节点描述系统内存的起始地址和大小
- chosen 节点传递内核启动参数
- soc 节点作为总线容器,包含所有外设节点
- 标签(uart0、clk_uart0)用于在板级配置中引用和修改节点
- 引用操作符 & 用于启用或禁用设备
2.5 本章小结
| 知识点 | 核心内容 | 掌握程度 |
|---|---|---|
| DTS 文件结构 | 根节点、子节点、属性、标签、引用的组织方式 | 必须掌握 |
| 节点定义 | 节点名称、单元地址、子节点嵌套规则 | 必须掌握 |
| 标准属性 | compatible、reg、interrupts、status 等属性的含义和用法 | 必须掌握 |
| 属性值类型 | 字符串、整数、字节数组、布尔值的编码方式 | 必须掌握 |
| 标签与引用 | 标签定义、& 引用操作符、节点扩展与覆盖 | 必须掌握 |
| 实战应用 | 构建完整的设备树,实现 SoC 级与板级配置分离 | 重点练习 |
下节预告:第 3 章将深入讲解设备树中最重要的属性之一——
compatible 属性,以及驱动与设备树的匹配机制,包括 OF 匹配表、设备 ID 表等核心内容。