一个设备树的全景视图
设备树和驱动
特性 | 设备树(Device Tree) | 驱动(Driver) | |
---|---|---|---|
职责 | 描述硬件配置 | 控制和管理硬件设备 | |
内容 | 硬件地址、中断号、时钟等 | 初始化代码、操作函数、中断处理等 | |
形式 | 文本文件(.dts)或二进制文件(.dtb) | 内核模块代码(.c 文件) | |
加载时机 | 在内核启动时由 Bootloader 传递给内核 | 在内核启动或模块加载时初始化 | |
可移植性 | 提高内核的可移植性,同一内核支持不同硬件 | 依赖设备树提供的硬件信息 |
设备树与驱动的联系
设备树为驱动提供硬件信息:
- 驱动通过设备树获取硬件的寄存器地址、中断号、时钟等资源。
- 设备树中的
compatible
属性用于匹配驱动和设备。
驱动依赖设备树:
- 在嵌入式 Linux 中,设备树是驱动获取硬件信息的主要方式。
- 驱动通过内核提供的 API(如
of_*
系列函数)从设备树中读取信息。
共同完成硬件管理:
- 设备树描述硬件,驱动操作硬件,二者协作完成硬件的初始化和控制。
例子_简单版
设备树(.dts)驱动(.c)1
2
3
4
5
6
7&i2c1 {
status = "okay";
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
};
};总结1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/of.h>
static int eeprom_probe(struct i2c_client *client, const struct i2c_device_id *id) {
printk("EEPROM probed!\n");
// 初始化硬件
return 0;
}
static int eeprom_remove(struct i2c_client *client) {
printk("EEPROM removed!\n");
// 释放资源
return 0;
}
static const struct of_device_id eeprom_match[] = {
{ .compatible = "atmel,24c02" },
{ }
};
MODULE_DEVICE_TABLE(of, eeprom_match);
static struct i2c_driver eeprom_driver = {
.probe = eeprom_probe,
.remove = eeprom_remove,
.driver = {
.name = "eeprom",
.of_match_table = eeprom_match,
},
};
module_i2c_driver(eeprom_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("EEPROM Driver");
设备树 描述硬件信息,驱动 控制硬件设备。
设备树通过 compatible 属性与驱动匹配,驱动通过设备树获取硬件资源。
二者共同协作,完成硬件的初始化和操作。例子_复杂版
举例子稍微复杂例子,能体现出,设备树的配置信息如何被驱动识别,解析,处理的。
- 设备树描述硬件,驱动操作硬件,二者协作完成硬件的初始化和控制。
这个例子将涉及一个假设的 多功能设备,它包含以下功能:
一个 I2C 接口 的传感器。
一个 GPIO 引脚 用于控制设备状态。
一个 中断引脚 用于触发事件。
设备树(.dts)
设备树描述硬件信息,包括 I2C 地址、GPIO 引脚、中断号等。
1 | { |
设备树解析
compatible:”mycompany,my-sensor”,用于匹配驱动。
reg:I2C 设备的地址为 0x50。
interrupt-parent:中断控制器是 gpio1。
interrupts:中断引脚是 gpio1 的第 5 个引脚,触发方式为上升沿。
vdd-supply:设备的电源由 vdd_3v3 提供。
gpios:设备的控制引脚是 gpio2 的第 3 个引脚,高电平有效。
驱动(.c)
驱动通过设备树获取硬件信息,并初始化设备。
1 | #include <linux/module.h> |
驱动解析设备树的过程
匹配设备:
驱动通过 of_device_id 中的 compatible 属性与设备树中的节点匹配。
匹配成功后,调用 probe 函数。
获取 GPIO:
使用 devm_gpiod_get 从设备树中获取 GPIO 控制引脚。
获取电源:
使用 devm_regulator_get 从设备树中获取电源。
获取中断:
使用 gpiod_to_irq 将 GPIO 引脚转换为中断号。
使用 devm_request_irq 注册中断处理函数。
初始化设备:
设置 GPIO 引脚状态。
使能电源。
总结
设备树 描述了硬件的详细信息(如 I2C 地址、GPIO 引脚、中断号等)。
驱动 通过内核提供的 API(如 devm_gpiod_get、devm_regulator_get、gpiod_to_irq 等)解析设备树中的信息,并初始化硬件。
设备树和驱动共同协作,完成硬件的配置和管理。
通过这个复杂一点的例子,可以更清楚地看到设备树和驱动如何配合工作,实现硬件的识别、解析和处理。
dts和硬件层的衔接
dts文件,靠软件层衔接的是驱动,那么另一层次呢?靠近硬件那一层和谁是关联(或者什么文档,也就是谁来决定dts的,或者dts应该根据谁来写类似这样的关联方)
1 | 在Linux系统中,设备树(Device Tree)用于描述硬件平台的配置信息,特别是对于嵌入式系统。设备树的描述文件通常以`.dts`(Device Tree Source)格式存在,并通过设备树编译器(DTC)编译为二进制格式的`.dtb`(Device Tree Blob),供内核在启动时使用。 |
举例_硬件和dts
硬件结构
1个双核ARM Cortex-A9
32位处理器;
ARM本地总线上的内存映射区域分布有两个串口(分别位于0x101F1000
和0x101F2000
)GPIO
控制器(位于0x101F3000
)SPI
控制器(位于0x10170000
)
中断控制器(位于0x10140000
)
外部总线桥上连接的设备如下:
SMC SMC91111
以太网(位于0x10100000
)I2C
控制器(位于0x10160000
)
64MB NOR Flash(位于0x30000000
)
外部总线桥上连接的I2C控制器所对应的I2C总线上又连接了Maxim DS1338
实时钟(I2C地址为0x58
)
具体如下图所示;
设备树dts文件
那么,如何将上面的硬件结构,通过设备树语言描述成dts
文件呢?具体我实现在下图,并且做出了详细的解释。其中需要注意的有以下几个属性:
compatitable
:兼容性属性;#address-cells
,#size-cells
:地址编码所采用的格式;节点名称@节点地址
:例如gpio@101f3000
,这里名称和地址要和实际的对应起来;标签
:例如interrupt-parent = <&intc>;
,这这里的intc
就是一个标签(label),通过&
可以获取它的值,这里可以简单的理解成一个变量,然后在下面需要对这个标签进行另外的解析,例如intc:interrupt-controller@10140000
;所以,这两个地方的intc
都是对应起来的。
最后,具体的实现可以参考下图;
参考
设备树与驱动的关系、设备树参数的介绍:https://zhuanlan.zhihu.com/p/8598598018
linux设备树dts文件详解:https://blog.csdn.net/weixin_42031299/article/details/125813060
linux 驱动简单案例:https://www.cnblogs.com/han-guang-xue/p/15769229.html
在Linux下写一个简单的驱动程序:https://www.cnblogs.com/kn-zheng/p/17168166.html
Linux 设备树语法(.dts)及如何从设备树获取节点信息:https://www.cnblogs.com/fortunely/p/16405592.html#a-of_get_property
Linux dts 设备树详解(二) 动手编写设备树dts:https://blog.csdn.net/u010632165/article/details/91488811
Device Tree (一) - dts基本概念和语法:https://blog.csdn.net/u011456016/article/details/136665769
Linux driver dts使用,实例驱动编写:https://blog.csdn.net/songyulong8888/article/details/78115512