当前位置: 首页 > news >正文

linux驱动开发-设备树

设备树的历史背景

背景:

在早期的嵌入式系统中,硬件配置信息通常硬编码在内核源码中,这导致了内核代码的冗长和难以维护。

为了解决这个问题,设备树(Device Tree)被引入,使得硬件描述与内核代码分离,从而提高了内核的可移植性和可维护性。

引入时间:

设备树最初在PowerPC架构中引入,后来被ARM架构广泛采用,并逐渐成为Linux内核的标准配置方式。

设备树的基本概念

节点(Node):
设备树中的每个节点代表一个硬件设备或一个硬件组件。
节点可以包含子节点,形成树状结构。属性(Property):
节点中的键值对,用于描述硬件设备的特性或配置。
属性可以是字符串、整数、布尔值、数组等。路径(Path):
设备树中的节点路径表示节点在树中的位置。
例如,/cpus/cpu@0 表示根节点下的 cpus 节点下的 cpu@0 节点。

设备树的常用节点和属性

常用节点:

/:根节点,表示整个系统。/cpus:包含所有CPU节点的父节点。/memory:描述系统内存配置。/chosen:用于传递启动参数,如 bootargs。/soc:描述片上系统(SoC)的硬件配置。/aliases:定义设备树中的别名,方便引用。

常用属性:

compatible:描述设备的兼容性,通常用于匹配驱动程序。reg:描述设备的寄存器地址和大小。interrupts:描述设备的中断信息。clock-frequency:描述时钟频率。status:描述设备的状态,如 "okay" 或 "disabled"。

设备树的编译与使用

编译设备树:使用 dtc(Device Tree Compiler)工具将DTS文件编译为DTB文件。
dtc -I dts -O dtb -o my_device_tree.dtb my_device_tree.dts加载设备树:在启动Linux内核时,可以通过引导加载程序(如U-Boot)将DTB文件传递给内核。
bootm <kernel_image> - <dtb_file>内核解析设备树:
内核在启动时会解析DTB文件,并根据设备树中的描述配置硬件设备。
内核驱动程序可以通过设备树节点和属性来识别和配置硬件设备。

设备树与驱动程序


驱动程序与设备树的交互:
驱动程序可以通过设备树节点和属性来获取硬件信息,并进行相应的初始化和配置。
例如,驱动程序可以通过 of_get_property() 函数获取设备树中的属性值。设备树与设备模型的关系:
Linux内核使用设备模型(Device Model)来管理硬件设备。
设备树节点会被转换为设备模型中的设备节点(Device Node),驱动程序通过这些设备节点与硬件交互。

如何书写设备树

设备树的基本语法:

节点定义:使用大括号 {} 定义节点。

属性定义:使用键值对的形式定义属性,键和值之间用等号 = 连接。

注释:使用 // 或 /* */ 进行注释。

/dts-v1/;  // 声明设备树文件的版本为v1/ {// 根节点,表示整个系统model = "My Custom Board";  // 描述系统的型号compatible = "my,custom-board";  // 描述系统的兼容性,用于匹配驱动程序cpus {// 定义CPU节点#address-cells = <1>;  // 定义地址单元的数量,用于描述寄存器地址#size-cells = <0>;  // 定义大小单元的数量,用于描述寄存器大小cpu@0 {// 定义第一个CPU节点compatible = "arm,cortex-a9";  // 描述CPU的兼容性reg = <0>;  // 描述CPU的寄存器地址};cpu@1 {// 定义第二个CPU节点compatible = "arm,cortex-a9";  // 描述CPU的兼容性reg = <1>;  // 描述CPU的寄存器地址};};memory@80000000 {// 定义内存节点device_type = "memory";  // 描述设备的类型为内存reg = <0x80000000 0x10000000>;  // 描述内存的起始地址和大小(256MB)};chosen {// 定义chosen节点,用于传递启动参数bootargs = "console=ttyS0,115200";  // 设置启动参数,配置串口终端};soc {// 定义SoC节点,表示片上系统compatible = "simple-bus";  // 描述SoC的兼容性#address-cells = <1>;  // 定义地址单元的数量#size-cells = <1>;  // 定义大小单元的数量serial@101f1000 {// 定义串口节点compatible = "ns16550a";  // 描述串口的兼容性reg = <0x101f1000 0x100>;  // 描述串口的寄存器地址和大小interrupts = <1 1>;  // 描述串口的中断信息clock-frequency = <1843200>;  // 描述串口的时钟频率};ethernet@10108000 {// 定义以太网节点compatible = "smc91x";  // 描述以太网的兼容性reg = <0x10108000 0x1000>;  // 描述以太网的寄存器地址和大小interrupts = <2 1>;  // 描述以太网的中断信息};};aliases {// 定义别名节点,方便引用serial0 = "/soc/serial@101f1000";  // 定义串口0的别名ethernet0 = "/soc/ethernet@10108000";  // 定义以太网0的别名};
};

示例

my_device_tree.dts

/dts-v1/;  // 声明设备树文件的版本为v1/ {// 根节点,表示整个系统model = "My Custom Board";  // 描述系统的型号compatible = "my,custom-board";  // 描述系统的兼容性,用于匹配驱动程序cpus {// 定义CPU节点#address-cells = <1>;  // 定义地址单元的数量,用于描述寄存器地址#size-cells = <0>;  // 定义大小单元的数量,用于描述寄存器大小cpu@0 {// 定义第一个CPU节点compatible = "arm,cortex-a9";  // 描述CPU的兼容性reg = <0>;  // 描述CPU的寄存器地址};};memory@80000000 {// 定义内存节点device_type = "memory";  // 描述设备的类型为内存reg = <0x80000000 0x10000000>;  // 描述内存的起始地址和大小(256MB)};chosen {// 定义chosen节点,用于传递启动参数bootargs = "console=ttyS0,115200";  // 设置启动参数,配置串口终端};my_custom_device {// 定义自定义设备节点compatible = "my,custom-device";  // 描述自定义设备的兼容性status = "okay";  // 描述设备的状态,表示设备可用my_property = "Hello, Device Tree!";  // 自定义字符串属性my_int_property = <42>;  // 自定义整数属性my_array_property = <0x11 0x22 0x33>;  // 自定义数组属性};
};

my_custom_device_module.c

#include <linux/module.h>  // 包含内核模块相关的头文件
#include <linux/of.h>  // 包含设备树相关的头文件
#include <linux/of_device.h>  // 包含设备树设备相关的头文件MODULE_LICENSE("GPL");  // 声明模块的许可证为GPL
MODULE_AUTHOR("Your Name");  // 声明模块的作者
MODULE_DESCRIPTION("A simple module to read custom device tree properties");  // 描述模块的功能// 设备探测函数,当设备匹配时调用
static int my_custom_device_probe(struct platform_device *pdev)
{struct device_node *node = pdev->dev.of_node;  // 获取设备树节点const char *my_property;  // 定义字符串属性变量u32 my_int_property;  // 定义整数属性变量u32 my_array_property[3];  // 定义数组属性变量int ret;  // 定义返回值变量// 获取字符串属性ret = of_property_read_string(node, "my_property", &my_property);if (ret) {pr_err("Failed to read my_property\n");  // 打印错误信息return ret;  // 返回错误码}pr_info("my_property: %s\n", my_property);  // 打印字符串属性值// 获取整数属性ret = of_property_read_u32(node, "my_int_property", &my_int_property);if (ret) {pr_err("Failed to read my_int_property\n");  // 打印错误信息return ret;  // 返回错误码}pr_info("my_int_property: %u\n", my_int_property);  // 打印整数属性值// 获取数组属性ret = of_property_read_u32_array(node, "my_array_property", my_array_property, 3);if (ret) {pr_err("Failed to read my_array_property\n");  // 打印错误信息return ret;  // 返回错误码}pr_info("my_array_property: 0x%x 0x%x 0x%x\n", my_array_property[0], my_array_property[1], my_array_property[2]);  // 打印数组属性值return 0;  // 返回成功
}// 设备移除函数,当设备移除时调用
static int my_custom_device_remove(struct platform_device *pdev)
{return 0;  // 返回成功
}// 设备树匹配表,用于匹配设备树节点
static const struct of_device_id my_custom_device_of_match[] = {{ .compatible = "my,custom-device", },  // 匹配自定义设备节点{ /* sentinel */ }  // 结束标记
};
MODULE_DEVICE_TABLE(of, my_custom_device_of_match);  // 声明设备树匹配表// 平台驱动结构体
static struct platform_driver my_custom_device_driver = {.probe = my_custom_device_probe,  // 设置探测函数.remove = my_custom_device_remove,  // 设置移除函数.driver = {.name = "my_custom_device",  // 设置驱动名称.of_match_table = my_custom_device_of_match,  // 设置设备树匹配表},
};// 注册平台驱动
module_platform_driver(my_custom_device_driver);

device_node 结构体

device_node 结构体是设备树节点的核心数据结构,表示设备树中的一个节点。

struct device_node {const char *name;           // 节点名称const char *type;           // 节点类型phandle phandle;            // 节点的句柄const char *full_name;      // 节点的完整路径struct fwnode_handle fwnode;// 固件节点句柄struct  property *properties; // 节点的属性链表struct  property *deadprops;  // 已删除的属性链表struct  device_node *parent;  // 父节点struct  device_node *child;   // 子节点struct  device_node *sibling; // 兄弟节点struct  kobject kobj;         // 内核对象void    *data;                // 私有数据
};

设备树操作API

2.1 of_find_node_by_path()
根据路径查找设备树节点。struct device_node *of_find_node_by_path(const char *path);
path:设备树节点的路径。返回值:找到的设备树节点指针,如果未找到则返回 NULL。示例:struct device_node *node = of_find_node_by_path("/my_custom_device");
if (node) {pr_info("Found node: %s\n", node->full_name);
} else {pr_info("Node not found\n");
}2.2 of_find_node_by_name()
根据节点名称查找设备树节点。struct device_node *of_find_node_by_name(struct device_node *from, const char *name);
from:从哪个节点开始查找(可选,传 NULL 表示从根节点开始)。name:节点名称。返回值:找到的设备树节点指针,如果未找到则返回 NULL。示例:struct device_node *node = of_find_node_by_name(NULL, "my_custom_device");
if (node) {pr_info("Found node: %s\n", node->full_name);
} else {pr_info("Node not found\n");
}2.3 of_find_node_by_type()
根据节点类型查找设备树节点。struct device_node *of_find_node_by_type(struct device_node *from, const char *type);
from:从哪个节点开始查找(可选,传 NULL 表示从根节点开始)。type:节点类型。返回值:找到的设备树节点指针,如果未找到则返回 NULL。示例:struct device_node *node = of_find_node_by_type(NULL, "memory");
if (node) {pr_info("Found node: %s\n", node->full_name);
} else {pr_info("Node not found\n");
}2.4 of_find_compatible_node()
根据 compatible 属性查找设备树节点。struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible);
from:从哪个节点开始查找(可选,传 NULL 表示从根节点开始)。type:节点类型(可选)。compatible:compatible 属性值。返回值:找到的设备树节点指针,如果未找到则返回 NULL。示例:struct device_node *node = of_find_compatible_node(NULL, NULL, "my,custom-device");
if (node) {pr_info("Found node: %s\n", node->full_name);
} else {pr_info("Node not found\n");
}2.5 of_get_property()
获取设备树节点的属性值。const void *of_get_property(const struct device_node *np, const char *name, int *lenp);
np:设备树节点指针。name:属性名称。lenp:属性值的长度(可选)。返回值:属性值的指针,如果未找到则返回 NULL。示例:const char *prop_value;
int prop_len;prop_value = of_get_property(node, "my_property", &prop_len);
if (prop_value) {pr_info("my_property: %s\n", prop_value);
} else {pr_info("my_property not found\n");
}2.6 of_property_read_string()
读取字符串类型的属性值。int of_property_read_string(const struct device_node *np, const char *propname, const char **out_string);
np:设备树节点指针。propname:属性名称。out_string:输出字符串指针。返回值:0表示成功,负数表示失败。示例:const char *my_property;if (of_property_read_string(node, "my_property", &my_property) == 0) {pr_info("my_property: %s\n", my_property);
} else {pr_info("my_property not found\n");
}2.7 of_property_read_u32()
读取32位整数类型的属性值。int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value);
np:设备树节点指针。propname:属性名称。out_value:输出整数值。返回值:0表示成功,负数表示失败。示例:u32 my_int_property;if (of_property_read_u32(node, "my_int_property", &my_int_property) == 0) {pr_info("my_int_property: %u\n", my_int_property);
} else {pr_info("my_int_property not found\n");
}2.8 of_property_read_u32_array()
读取32位整数数组类型的属性值。int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz);
np:设备树节点指针。propname:属性名称。out_values:输出整数数组。sz:数组大小。返回值:0表示成功,负数表示失败。示例:u32 my_array_property[3];if (of_property_read_u32_array(node, "my_array_property", my_array_property, 3) == 0) {pr_info("my_array_property: 0x%x 0x%x 0x%x\n", my_array_property[0], my_array_property[1], my_array_property[2]);
} else {pr_info("my_array_property not found\n");
}2.9 of_property_count_elems_of_size()
获取属性值的元素数量。int of_property_count_elems_of_size(const struct device_node *np, const char *propname, int elem_size);
np:设备树节点指针。propname:属性名称。elem_size:元素大小(字节)。返回值:元素数量,负数表示失败。示例:int count = of_property_count_elems_of_size(node, "my_array_property", sizeof(u32));
if (count > 0) {pr_info("my_array_property has %d elements\n", count);
} else {pr_info("my_array_property not found\n");
}2.10 of_property_read_variable_u32_array()
读取可变长度的32位整数数组类型的属性值。int of_property_read_variable_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz_min, size_t sz_max);
np:设备树节点指针。propname:属性名称。out_values:输出整数数组。sz_min:最小数组大小。sz_max:最大数组大小。返回值:实际读取的元素数量,负数表示失败。示例:u32 my_array_property[3];
int count = of_property_read_variable_u32_array(node, "my_array_property", my_array_property, 1, 3);
if (count > 0) {pr_info("my_array_property: 0x%x 0x%x 0x%x\n", my_array_property[0], my_array_property[1], my_array_property[2]);
} else {pr_info("my_array_property not found\n");
}

设备树绑定API


设备树绑定API用于将设备树节点与驱动程序绑定。以下是一些常用的API函数:3.1 of_device_is_compatible()
检查设备树节点是否与指定的 compatible 属性匹配。int of_device_is_compatible(const struct device_node *device, const char *compat);
device:设备树节点指针。compat:compatible 属性值。返回值:非零表示匹配,零表示不匹配。示例:if (of_device_is_compatible(node, "my,custom-device")) {
pr_info("Node is compatible with 'my,custom-device'\n");
} else {
pr_info("Node is not compatible with 'my,custom-device'\n");
}3.2 of_device_is_available()
检查设备树节点是否可用(即 status 属性是否为 "okay")。int of_device_is_available(const struct device_node *device);
device:设备树节点指针。返回值:非零表示可用,零表示不可用。示例:if (of_device_is_available(node)) {
pr_info("Node is available\n");
} else {
pr_info("Node is not available\n");
}3.3 of_platform_populate()
递归地为设备树节点创建平台设备。int of_platform_populate(struct device_node *root, const struct of_device_id *matches, const struct of_dev_auxdata *lookup, struct device *parent);
root:设备树根节点指针。matches:设备树匹配表。lookup:设备树辅助数据(可选)。parent:父设备指针(可选)。返回值:0表示成功,负数表示失败。示例:static const struct of_device_id my_custom_device_of_match[] = {
{ .compatible = "my,custom-device", },
{ /* sentinel */ }
};int ret = of_platform_populate(NULL, my_custom_device_of_match, NULL, NULL);
if (ret) {
pr_err("Failed to populate platform devices\n");
}

在设备树中,别名(Aliases)和引用(References)

1. 别名(Aliases)别名是一种方便的方式,用于给设备树中的节点或路径指定一个易于使用的名称。在设备树中,别名通常用于简化设备的访问,使得访问特定设备的代码更直观。定义方式:别名在设备树的顶部通过aliases节点进行声明。例如:aliases {
spi0 = "spi@1";
i2c0 = "i2c@0";
serial0 = "uart@0";
};在这个例子中,您可以使用spi0来引用名为spi@1的设备节点。使用场景:别名通常用于驱动程序中,以便通过别名快速访问设备。例如,在驱动程序中,您可以使用of_alias_get_id函数依据别名获取设备的ID。2. 引用(References)引用用于在设备树中引用其他节点或属性,主要通过节点路径来实现。
引用可以帮助减少冗余,特别是在多个节点需要共享同一设备或配置时。定义方式:设备树中的节点可以通过路径引用。例如:my_device: my_device_node {
compatible = "my,device";
reg = <0x1000 0x100>;
};another_device: another_device_node {
compatible = "another,device";
depends_on = <&my_device>; // 远程引用
};在这个例子中,another_device_node中的depends_on属性用来引用了my_device_node。使用场景:引用可以用来指定设备之间的依赖关系,或者在一个设备中引用其他设备的资源。使用引用可以避免重复定义相同的设备配置,有助于提高设备树结构的可读性和可维护性。

compatible 字段

设备树中一个重要的属性,它用于指定设备的兼容性字符串,帮助操作系统识别硬件和对应的驱动程序。

什么是 compatible 属性?

compatible 属性的作用是告诉操作系统这个设备支持的功能和

它的驱动程序的匹配信息。它包含一个或多个字符串,

描述了不同的兼容设备类或设备名称。

该属性通常是一个字符串数组,包含一个或多个值,

描述设备的不同兼容性,开发者可以在设备树源文件中指定。

示例

一些设备树中的节点可能如下所示:

my_device: my_device@0 {
compatible = "vendor,my_device", "vendor,generic_device";
reg = <0x0 0x10000 0x0 0x1000>; // 寄存器地址
interrupts = <1>; // 中断号
};解释
my_device@0:节点的名称,通常包含设备的名称和地址。
compatible:这个节点指定了它的兼容性为vendor,my_device和vendor,generic_device。这表明这个设备可以被这两个驱动程序支持。
reg 和 interrupts 是与硬件设备相关的其他重要信息。

在驱动程序中的作用

在涉及 Linux 内核驱动的开发时,设备树的 compatible 属性用于驱动程序的识别和绑定。通常在驱动代码中,也对 compatible 属性进行匹配,以确定是否能够处理特定的设备。驱动代码示例
在驱动程序中,可能会看到如下代码片段:static const struct of_device_id my_device_ids[] = {{ .compatible = "vendor,my_device" },{ .compatible = "vendor,generic_device" },{},
};MODULE_DEVICE_TABLE(of, my_device_ids);of_device_id:用于定义与设备树中的 compatible 字符串匹配的结构体数组。
MODULE_DEVICE_TABLE:用于将模块与设备树的兼容性代码进行连结,这样,当设备在启动时,内核可以根据设备树中的信息找到相应的驱动程序进行加载。

ompatible 属性相关的API

    1. of_device_get_match_data功能: 根据设备树节点中的 compatible 属性,返回与之匹配的驱动数据。const void *of_device_get_match_data(struct device *dev);参数:dev:指向设备结构的指针,通常在驱动程序中获取。返回值: 返回指向匹配数据的指针,如果没有匹配,返回 NULL2. of_matchDevice功能: 检查设备树节点与给定的设备 ID 列表的匹配。const struct of_device_id *of_match_device(const struct of_device_id *matches, struct device *dev);参数:matches:设备ID匹配表。dev:指向要匹配的设备结构的指针。返回值: 返回匹配的 of_device_id 结构指针,如果没有匹配,返回 NULL3. of_device_id功能: 定义设备与驱动匹配的结构。通常用于在驱动程序中声明支持哪些设备。struct of_device_id {const char *compatible;     // 设备的兼容性字符串const void *data;           // 可选的私有数据,用于驱动程序};使用示例: 将其用于驱动程序中,定义支持的设备。static const struct of_device_id my_device_ids[] = {{ .compatible = "vendor,my_device" },{ .compatible = "vendor,generic_device" },{},};4. of_register_driver功能: 注册一个与设备树相连接的驱动程序。int of_register_driver(struct of_driver *drv);参数:drv:指向 of_driver 结构体的指针,包含驱动的描述信息。5. of_driver功能: 描述一个设备树驱动的结构。struct of_driver {struct module *owner;           // 驱动模块的所有者struct of_device_id *ids;       // 支持的设备ID列表int (*probe)(struct device *dev); // 驱动的探测函数void (*remove)(struct device *dev); // 驱动的移除函数};6. of_get_child_by_name功能: 根据名称获取设备树节点的子节点。struct device_node *of_get_child_by_name(const struct device_node *np, const char *name);参数:np:父节点。name:要匹配的子节点的名称。返回值: 返回匹配的子节点指针,如果未找到,则返回 NULL7. of_get_parent功能: 获取设备树节点的父节点。struct device_node *of_get_parent(const struct device_node *np);参数:np:设备树节点。返回值: 返回父节点指针,如果没有父节点则返回 NULL

http://www.mrgr.cn/news/29430.html

相关文章:

  • python-简单的数据结构
  • 发布Java项目到Maven中央仓库
  • vimrc nnoremap配置
  • centos bash脚本一键运行安装go环境
  • 智算筑基,九章云极DataCanvas公司闪耀2024年服贸会
  • i++volatile
  • 超详细超实用!!!零基础java开发之云风笔记笔记列表接口条件查询(九)
  • C CS3214
  • 产品经理有必要学习大模型技术吗?
  • 数据治理新时代:掌握关键的数据提取技术
  • ai头像免费软件有哪些?卡哇伊头像用这些
  • 【Springboot】——响应与分层解耦架构
  • 如何利用AI进行有效的科技产品发布
  • idea 中MyBatisX插件没有出现蓝色鸟
  • 突破空间限制:4个远程控制电脑的办法
  • 十大最佳电子商务市场广告工具,助力提升你的业务
  • go-orm接口原生到框架
  • 印度2024年节日季节的数字营销趋势
  • 国产开源大语言模型优劣大盘点
  • GaussDB关键技术原理:高弹性(五)