本文主要是介绍对设备树、DTS语法、设备树中常用函数 的认识(简单总结),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
设备树(Device Tree)是一种数据结构,用于描述硬件的板级设备信息。它由一系列被命名的节点(node)和属性(property)组成,这些节点和属性共同构成了描述硬件设备的树形结构。设备树主要用于嵌入式系统和复杂硬件平台的开发中,特别是当硬件设备的种类和数量众多,且每个设备都有其独特的配置和特性时。
1、设备树的主要组成部分
-
DTS(Device Tree Source)文件:
- DTS文件是设备树的源码文件,采用ASCII文本格式编写,文件扩展名为.dts。
- DTS文件通过树形结构描述板级设备信息,如CPU数量、内存基地址、外设接口(如IIC、SPI)上连接的设备等。
- 每个设备在DTS文件中表示为一个节点,节点通过属性来描述设备信息,属性是键值对的形式。
-
DTSI(Device Tree Include)文件:
- DTSI文件类似于C语言中的头文件,用于描述SOC级别的内部外设信息,如CPU架构、主频、外设寄存器地址范围等。
- DTS文件可以引用DTSI文件,以减少代码冗余,便于维护和共享。
-
DTC(Device Tree Compiler)工具:
- DTC工具用于将DTS文件编译成二进制格式的DTB(Device Tree Blob)文件。
- DTC工具的源码位于Linux内核的scripts/dtc目录下。
-
DTB(Device Tree Blob)文件:
- DTB文件是DTS文件编译后的二进制文件,由Linux内核解析。
- DTB文件包含了设备树的完整信息,用于在系统启动阶段配置和初始化硬件设备。
2、设备树的作用
- 分离硬件描述与内核代码:设备树将硬件描述信息与内核代码分离,避免了硬编码方式带来的问题,如内核臃肿、修改硬件信息需重新编译内核等。
- 简化硬件适配:对于同一SOC的不同主板,只需更换设备树文件即可实现无差异支持,无需更换内核文件。
- 提高系统可移植性:设备树的使用使得Linux系统更容易移植到不同的硬件平台上。
3、设备树的使用方法
-
编写DTS文件:
- 根据硬件平台的实际情况,编写描述板级设备信息的DTS文件。
- 使用设备树语法定义节点和属性,确保信息的准确性和完整性。
-
编译DTS文件:
- 使用DTC工具将DTS文件编译成DTB文件。编译过程通常在系统编译阶段完成。
- 编译命令示例:
make dtbs
(在Linux内核源码根目录下执行)
-
加载DTB文件:
- 在系统启动阶段,bootloader负责加载启动镜像并提取其中的DTB文件。
- Bootloader将DTB文件传递给内核,内核根据其中的设备树信息来配置和初始化硬件设备。
-
内核解析DTB文件:
- 内核在接收到DTB文件后,会对其进行解析。
- 解析过程中,内核会根据设备树中的节点和属性来创建对应的设备对象,并为它们设置相应的属性和配置。
-
设备树匹配与驱动加载:
- 内核在解析设备树的过程中,会根据设备节点的“compatible”属性来匹配相应的驱动程序。
- 一旦找到匹配的驱动程序,内核就会将其加载并用于设备的初始化和管理。
综上所述,设备树是Linux系统中一种非常重要的硬件描述机制,它通过树形结构描述板级设备信息,简化了硬件适配过程,提高了系统的可移植性和灵活性。在使用设备树时,需要掌握DTS文件的编写方法、DTC工具的使用以及内核对DTB文件的解析过程。
4、DTS语法
DTS(Device Tree Source)是一种基于文本的描述硬件设备的语言,它允许开发者以一种结构化的方式来定义硬件的配置信息。DTS语法相对简单但严格,以下是对DTS语法的详细解析:
一、文件结构
DTS文件采用树状结构来组织设备信息,每个文件通常包含一个根节点(/),根节点下可以有多个子节点,子节点下还可以有更深层次的子节点,以此类推。
二、节点(Node)
节点是DTS文件的基本组成单元,用于表示硬件设备或总线。节点的基本语法如下:
[label:]node-name[@unit-address]{ [properties definitions]; [child nodes];
};
- label:可选的标签,用于方便地引用节点。
- node-name:节点名称,由数字、大小写字母、逗号(,)、点(.)、下划线(_)等组成,且必须以字母开头。
- unit-address:可选的设备地址,用于唯一标识具有相同节点名的不同设备。
- properties definitions:节点的属性定义,描述设备的特性和参数。
- child nodes:子节点,节点下可以嵌套子节点,形成树状结构。
三、属性(Property)
属性用于描述节点的特性和参数,其基本语法如下:
[label:]property-name = value;
- label:可选的标签,用于引用属性。
- property-name:属性名称。
- value:属性值,可以是字符串、整数、数组等类型。
属性值的具体表示方式如下:
- 字符串:用双引号(")括起来,如
compatible = "arm,cortex-a7";
。 - 整数:用尖括号(< >)括起来,如
reg = <0x1000 0x100>;
。64位整数由两个32位整数表示,如<0x00000001 0x00000000>
。 - 数组:用尖括号括起来的整数列表,如
interrupts = <17 0xc>;
。 - 字节序列:用方括号([ ])括起来的字节列表,每个字节用两个十六进制数表示,如
local-mac-address = [00 00 12 34 56 78];
。
四、特殊属性和节点
1. compatible属性
用于指定设备的兼容信息,通常由制造商和设备型号组成,如compatible = "fsl,imx6ull-gpmi-nand";
。这个属性是驱动和设备匹配的关键。
2. status属性
表示设备的运行状态,可选值包括okay
、disabled
、fail
等。
3. #address-cells和#size-cells属性
reg = <address1 length1 address2 length2 address3 length3……>
4. reg属性
用于描述设备的地址范围,格式通常为reg = <address length [address2 length2] ...>;
。
5.ranges属性
ranges属性的值可以为空,或者按照(child-bus-address, parent-bus-address, length)
的格式编写的数字矩阵。每个元素代表一个地址映射项,包含三部分:子地址(child-bus-address)、父地址(parent-bus-address)和地址空间长度(length)。
6. chosen节点
chosen
节点不表示一个实际的硬件设备,而是用于向内核传递启动参数,如bootargs
。
7. aliases节点
aliases
节点用于定义别名,方便在设备树中引用其他节点。
五、包含dtsi文件和宏定义
DTS文件可以包含dtsi文件,类似于C语言中的头文件包含。dtsi文件通常包含一些通用的设备树定义,可以在多个DTS文件中共享。此外,DTS文件还可以使用宏定义来简化属性的编写,这些宏定义通常定义在专门的头文件中。
六、编译与反编译
DTS文件需要通过设备树编译器(dtc)编译成设备树二进制文件(dtb),才能被内核解析。同时,dtb文件也可以通过dtc反编译回dts文件,以便于修改和调试。
七、DTS语法举例
/ { model = "Example Board with Example Device"; compatible = "generic-board", "vendor,example-device"; // Memory节点,描述物理内存的布局 memory@80000000 { device_type = "memory"; reg = <0x80000000 0x40000000>; // 起始地址0x80000000,大小0x40000000(1GB) }; // Cpus节点,描述CPU信息 cpus { #address-cells = <1>; #size-cells = <0>; cpu@0 { device_type = "cpu"; compatible = "arm,cortex-a9"; reg = <0>; // 标识CPU核心0 enable-method = "psci"; // 启用方法,使用PSCI协议 status = "okay"; }; }; // Chosen节点,用于传递系统指定的运行时参数给内核 chosen { bootargs = "console=ttyO0,115200n8 root=/dev/mmcblk0p2 rw rootfstype=ext4 rootwait"; }; // Aliases节点,定义别名以便更方便地引用设备树中的其他节点 aliases { serial0 = &uart0; ethernet0 = ð0; }; // 串口(UART)设备节点 uart0: uart@40002000 { compatible = "ns16550"; reg = <0x40002000 0x1000>; interrupts = <18>; clock-frequency = <1843200>; }; // 以太网设备节点 eth0: ethernet@40008000 { compatible = "smc,smc91111"; reg = <0x40008000 0x1000>; interrupts = <23>; mac-address = [00 12 34 56 78 9A]; fixed-link { speed = <100>; full-duplex; }; }; // I2C控制器节点 i2c1: i2c@40020000 { compatible = "i2c-compatible"; reg = <0x40020000 0x1000>; #address-cells = <1>; #size-cells = <0>; // I2C设备节点(EEPROM) eeprom@50 { compatible = "eeprom-compatible"; reg = <0x50>; }; }; // 另一个串口设备节点,用于展示不同的设备实例 serial@40080000 { compatible = "ns16550"; reg = <0x40080000 0x1000>; interrupts = <18>; clock-frequency = <1843200>; };
};
详细介绍
- 根节点(/`):
model
:设备的型号或名称。compatible
:设备的兼容性字符串,用于匹配驱动程序。
- 内存节点 (
memory@80000000
):device_type
: 指明了这是一个内存设备。reg
: 描述了内存的物理地址和大小,这里是起始地址0x80000000,大小为0x40000000(即1GB)。
- CPU节点 (
cpus
):#address-cells
和#size-cells
: 描述了CPU子节点中reg
属性的地址单元和大小单元的数量。cpu@0
: 描述了第一个CPU核心的信息。device_type
: 指明了这是一个CPU设备。compatible
: 描述了CPU兼容的类型,这里是“arm,cortex-a9”。reg
: 标识了CPU核心的编号,这里是0。enable-method
: 描述了启用CPU的方法,这里是使用PSCI协议。status
: 描述了CPU的状态,这里是“okay”,表示正常。
- Chosen节点 (
chosen
):bootargs
: 传递了系统指定的运行时参数给内核,包括控制台设置、根文件系统设置等
- Aliases节点 (
aliases
):- 定义了别名以便更方便地引用设备树中的其他节点,如
serial0
引用uart0
,ethernet0
引用eth0
。
- 定义了别名以便更方便地引用设备树中的其他节点,如
- 串口(UART)设备节点 (
uart0: uart@40002000
):compatible
: 描述了UART设备兼容的类型,这里是“ns16550”。reg
: 描述了UART设备的物理地址和大小。interrupts
: 描述了UART设备使用的中断号。clock-frequency
: 描述了UART设备的时钟频率。
- 以太网设备节点 (
eth0: ethernet@40008000
):compatible
: 描述了以太网设备兼容的类型,这里是“smc,smc91111”。reg
: 描述了以太网设备的物理地址和大小。interrupts
: 描述了以太网设备使用的中断号。mac-address
: 描述了以太网设备的MAC地址。fixed-link
: 描述了以太网设备的固定链接属性,如速度和是否全双工。
在这个DTS例子中,定义了设备的型号、兼容性、CPU、内存、串行通信和I2C控制器等硬件设备的信息。这些信息被编译成设备树二进制文件(DTB),并在系统启动时由内核解析,用于配置和初始化硬件设备。
八、DTS追加信息
在DTS(Device Tree Source)根节点外中追加信息,通常涉及对现有的DTS文件进行编辑,以添加新的设备节点、属性或修改现有节点的配置。以下是一些步骤和注意事项,用于在DTS中追加信息:
1. 确定追加位置
首先,需要确定在DTS文件中追加信息的具体位置。这通常取决于要添加的设备类型以及它在硬件系统中的位置。例如,如果要添加一个连接到I2C总线的传感器,你需要在I2C控制器的子节点下添加一个新的子节点。
2. 编辑DTS文件
使用文本编辑器打开DTS文件,然后按照设备树的语法规则添加新的节点或属性。在添加新节点时,需要注意节点命名、属性格式以及可能的依赖关系。
例如,要在I2C控制器下添加一个温度传感器节点,可以添加类似以下内容的代码:
&i2c1 { /* 假设i2c1是已经存在的I2C控制器节点 */ status = "okay"; temp_sensor@48 { compatible = "vendor,temp-sensor"; reg = <0x48>; interrupts = <&gpio 24 IRQ_TYPE_EDGE_FALLING>; /* 假设温度传感器通过GPIO触发中断 */ };
};
在这个例子中,temp_sensor@48
是新添加的温度传感器节点,@48
指定了它在I2C总线上的地址。compatible
属性用于指定温度传感器的兼容性字符串,以便内核能够找到正确的驱动程序。reg
属性指定了设备在I2C总线上的地址,而interrupts
属性(如果适用)指定了设备的中断配置。
3. 编译DTS文件
修改完DTS文件后,需要使用设备树编译器(DTC)将其编译成设备树二进制文件(DTB)。这通常通过内核的构建系统来完成,可以使用make dtbs
命令(在内核源码目录下)来编译所有DTS文件。
4. 部署DTB文件
编译生成的DTB文件需要被部署到目标系统的适当位置,以便在启动时由Bootloader加载到内存中,并由内核解析。
5. 验证和调试
在部署了修改后的DTB文件后,重新启动系统以验证新添加的设备是否按预期工作。可以使用/proc/device-tree
目录下的文件来检查内核是否正确地解析了设备树,并使用适当的驱动程序来配置和管理硬件设备。
注意事项
- 在修改DTS文件时,请确保遵循设备树的语法规则,以避免编译错误。
- 如果不确定某个属性的用途或格式,请参考相应的绑定文档或内核文档。
- 在修改设备树之前,最好备份原始文件,以便在出现问题时可以恢复。
- 某些设备可能需要特定的配置步骤或额外的驱动程序支持才能正常工作。请确保已经完成了所有必要的配置和安装步骤。
九、节点命名规范
一、命名格式
设备树中的节点名字通常遵循“node-name@unit-address”的格式,但并非所有节点都必须包含单元地址(unit-address)。这个格式允许节点名字清晰地描述节点的功能和位置。
- node-name:节点名字,为ASCII字符串,应该能够清晰地描述出节点的功能。例如,“uart1”就表示这个节点是UART1外设。
- @unit-address:单元地址,一般表示设备的地址或寄存器首地址。如果某个节点没有地址或者寄存器,这个部分可以省略。
此外,还有一种常见的格式是在节点名字前使用标签(label)和冒号(:)进行标识,即“[label:]node-name[@unit-address]”。引入标签的目的是为了方便访问节点,可以直接通过&label
来访问该节点,而不需要输入完整的节点名字。
二、字符限制
设备树中的节点名字长度通常为1到31个字符,且仅由特定的字符集组成。这些字符包括字母(A-Z, a-z)、数字(0-9)以及下划线(_)等。特殊字符(如空格、斜杠等)通常不被允许出现在节点名字中。
三、命名逻辑
节点名字的命名应遵循一定的逻辑,以便于理解和维护。以下是一些建议的命名逻辑:
- 描述性功能:节点名字应尽可能描述其功能或用途,如“uart”、“gpio”、“ethernet”等。
- 唯一性:在同一设备树中,节点名字应该是唯一的,以避免混淆。
- 继承性:如果节点之间存在父子关系,子节点的名字可以在一定程度上继承父节点的特性或功能描述,但应确保唯一性。
- 简洁性:在保持描述性功能的前提下,节点名字应尽量简洁明了,避免使用过长或过于复杂的名字。
四、其他注意事项
- 大小写敏感:在设备树中,节点名字是大小写敏感的,因此在命名和引用时需要注意大小写的一致性。
- 避免使用保留字:在命名节点时,应避免使用设备树语法中的保留字,如
/
(根节点路径)、#
(用于特殊属性名)等。 - 遵循惯例:在遵循上述规范的基础上,还可以参考行业内的命名惯例或标准,以提高设备树的兼容性和可移植性。
5、设备树常用 OF 操作函数
5.1. 查找节点
5.2 查找父/子节点的函数
5.3 提取属性值的函数
5.4其他常用的函数
小结
到此对于设备树的相关认识就大致总结到这里,在设备树的相关知识中,个人认为重点内容有如下几点:设备树语法、设备树的操作函数、等内容。
这篇关于对设备树、DTS语法、设备树中常用函数 的认识(简单总结)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!