引言
在嵌入式Linux开发中,设备树(Device Tree)是一个非常重要的概念。它提供了一种描述硬件设备的方式,使得内核可以在不同的硬件平台上运行,而无需为每个平台编写特定的代码。本文将详细介绍设备树的基础知识、语法、编译过程以及如何在驱动中使用设备树。
1. 设备树概述
1.1 什么是设备树?
设备树(Device Tree)是一种描述硬件设备的数据结构,通常用于嵌入式系统中。它通过一种树形结构来描述系统中的硬件设备及其连接关系。设备树的主要目的是将硬件的描述与操作系统的代码分离,从而提高代码的可移植性和可维护性。
1.2 设备树的组成部分
设备树主要由以下几个部分组成:
- DTS(Device Tree Source):设备树源文件,通常以
.dts
为后缀。它是一个文本文件,描述了硬件设备的结构和属性。 - DTB(Device Tree Blob):设备树二进制文件,通常以
.dtb
为后缀。它是DTS文件编译后生成的二进制文件,供内核使用。 - DTC(Device Tree Compiler):设备树编译器,用于将DTS文件编译为DTB文件。
2. DTS、DTB 和 DTC 的区别与编译过程
2.1 DTS、DTB 和 DTC 的区别
- DTS:设备树源文件,是文本文件,便于人类阅读和编辑。
- DTB:设备树二进制文件,是DTS文件编译后生成的二进制文件,供内核使用。
- DTC:设备树编译器,用于将DTS文件编译为DTB文件。
2.2 如何将.dts 文件编译为.dtb 文件
要将DTS文件编译为DTB文件,可以使用DTC编译器。以下是一个简单的编译命令:
dtc -I dts -O dtb -o output.dtb input.dts
-I dts
:指定输入文件格式为DTS。-O dtb
:指定输出文件格式为DTB。-o output.dtb
:指定输出文件名。input.dts
:输入的DTS文件。
2.3 示例
假设我们有一个名为example.dts
的设备树源文件,内容如下:
/dts-v1/;
/ {
model = "Example Board";
compatible = "example,board";
cpus {
cpu@0 {
compatible = "arm,cortex-a9";
device_type = "cpu";
reg = <0>;
};
};
memory {
device_type = "memory";
reg = <0x80000000 0x10000000>;
};
};
我们可以使用以下命令将其编译为DTB文件:
dtc -I dts -O dtb -o example.dtb example.dts
编译完成后,将生成一个名为example.dtb
的二进制文件。
3. 设备树语法
设备树语法是设备树描述的基础,掌握设备树语法对于理解和修改设备树至关重要。以下是设备树语法的详细介绍。
3.1 设备树的基本结构
设备树的基本结构是一个树形结构,由节点(Node)和属性(Property)组成。每个节点可以包含子节点和属性。
3.1.1 节点
节点是设备树的基本组成单位,通常表示一个硬件设备或一个功能模块。节点的定义格式如下:
node-name {
property1 = value1;
property2 = value2;
...
child-node {
...
};
};
node-name
:节点的名称。property1
,property2
:节点的属性。child-node
:子节点。
3.1.2 属性
属性是节点的键值对,用于描述节点的特性。属性的定义格式如下:
property-name = value;
property-name
:属性的名称。value
:属性的值,可以是字符串、整数、数组等。
3.2 设备树的常用属性
以下是一些常用的设备树属性:
compatible
:用于匹配驱动程序的字符串列表。reg
:表示设备的寄存器地址和大小。interrupts
:表示设备的中断号。gpio
:表示设备的GPIO引脚。clock-frequency
:表示设备的时钟频率。
3.3 设备树的特殊节点
设备树中有一些特殊的节点,它们在设备树中具有特殊的意义。
3.3.1 /
根节点
根节点是设备树的顶级节点,所有其他节点都是根节点的子节点。根节点通常包含一些全局属性,如model
和compatible
。
/ {
model = "Example Board";
compatible = "example,board";
...
};
3.3.2 aliases
节点
aliases
节点用于定义节点的别名,方便在设备树中引用。
aliases {
serial0 = &uart0;
ethernet0 = ð0;
};
3.3.3 chosen
节点
chosen
节点用于传递内核启动参数或指定根文件系统。
chosen {
bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait";
};
3.4 设备树的示例
以下是一个完整的设备树示例:
/dts-v1/;
/ {
model = "Example Board";
compatible = "example,board";
cpus {
cpu@0 {
compatible = "arm,cortex-a9";
device_type = "cpu";
reg = <0>;
};
};
memory {
device_type = "memory";
reg = <0x80000000 0x10000000>;
};
uart0: serial@101f1000 {
compatible = "ns16550a";
reg = <0x101f1000 0x1000>;
interrupts = <0 0 4>;
clock-frequency = <1843200>;
};
eth0: ethernet@101f2000 {
compatible = "smc911x";
reg = <0x101f2000 0x1000>;
interrupts = <0 0 5>;
};
};
4. 设备树的OF操作函数
设备树最终是被驱动文件所使用的,驱动文件需要读取设备树中的属性信息,如内存信息、GPIO信息、中断信息等。为了在驱动中读取设备树的属性值,Linux内核提供了一系列的OF(Open Firmware)操作函数。
4.1 OF操作函数概述
OF操作函数是Linux内核提供的一组用于操作设备树的API。这些函数定义在include/linux/of.h
头文件中,常用的函数包括:
of_find_node_by_name
:根据节点名称查找节点。of_find_node_by_path
:根据路径查找节点。of_property_read_u32
:读取32位整数属性。of_property_read_string
:读取字符串属性。of_get_named_gpio
:获取GPIO引脚号。
4.2 OF操作函数的使用示例
以下是一个简单的驱动示例,展示了如何使用OF操作函数读取设备树中的属性。
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
static int example_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
int gpio;
u32 value;
const char *str;
if (!np) {
dev_err(&pdev->dev, "No device tree node found\n");
return -EINVAL;
}
/* 读取整数属性 */
if (of_property_read_u32(np, "example-value", &value)) {
dev_err(&pdev->dev, "Failed to read 'example-value'\n");
return -EINVAL;
}
dev_info(&pdev->dev, "example-value = %d\n", value);
/* 读取字符串属性 */
if (of_property_read_string(np, "example-string", &str)) {
dev_err(&pdev->dev, "Failed to read 'example-string'\n");
return -EINVAL;
}
dev_info(&pdev->dev, "example-string = %s\n", str);
/* 获取GPIO引脚号 */
gpio = of_get_named_gpio(np, "example-gpio", 0);
if (gpio < 0) {
dev_err(&pdev->dev, "Failed to get 'example-gpio'\n");
return gpio;
}
dev_info(&pdev->dev, "example-gpio = %d\n", gpio);
return 0;
}
static int example_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev, "Example device removed\n");
return 0;
}
static const struct of_device_id example_of_match[] = {
{ .compatible = "example,device", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, example_of_match);
static struct platform_driver example_driver = {
.probe = example_probe,
.remove = example_remove,
.driver = {
.name = "example",
.of_match_table = example_of_match,
},
};
module_platform_driver(example_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Example Device Tree Driver");
4.3 设备树中的属性
假设我们有一个设备树节点如下:
example_device: example@101f3000 {
compatible = "example,device";
example-value = <1234>;
example-string = "Hello, World!";
example-gpio = <&gpio0 5 0>;
};
在驱动中,我们可以使用OF操作函数读取这些属性:
example-value
:32位整数属性。example-string
:字符串属性。example-gpio
:GPIO引脚号。
4.4 编译和加载驱动
编译并加载上述驱动后,内核日志中将输出以下信息:
example-value = 1234
example-string = Hello, World!
example-gpio = 5
5. 总结
本文详细介绍了Linux设备树的基础知识、语法、编译过程以及如何在驱动中使用设备树。通过掌握设备树的基本概念和OF操作函数的使用,开发者可以更好地理解和修改设备树,从而编写出更加灵活和可移植的驱动程序。
设备树在嵌入式Linux开发中扮演着至关重要的角色,理解设备树的工作原理和使用方法,对于嵌入式Linux开发者来说是必不可少的技能。希望本文能够帮助读者更好地理解和应用设备树。