设备树笔记
一,设备树platform_driver示例
1.设备树中设备需要定义一个设备节点,包含设备的相关信息和属性。例如:设一个按键设备 “my_key”
/ { //根节点
compatible = "my_key_demo"; //根节点的兼容性,
my_key{ //设备节点 “my_key”
compatible = "example,my_key";
mykey-gpios = <&gpio0 RK_PB0 GPIO_ACTIVE_LOW>;
reg = <0x00001234 0x0800>;
interrupt-parent = <&gpio0>;
interrupts = <10 IRQ_TYPE_LEVEL_HIGH>;
}
};
在上述示例中,该设备节点具有
compatible: 属性来标识设备的兼容性,作为驱动和设备(设备节点)的匹配依据
reg :属性用于指定设备的地址范围,
interrupt-parent :属性指定中断控制器的父节点,
interrupts :属性指定设备的中断号和触发类型。
2.在驱动程序中定义 platform_driver 结构体,并使用 of_match_ptr() 宏将设备的兼容性标识与驱动程序关联起来
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_device.h>
static int mykey_probe(struct platform_device *pdev)
{
// 在这里执行设备的初始化和操作
gpio_key = devm_gpiod_get(dev, "mykey",GPIOD_OUT_LOW);
if (IS_ERR(gpio_key ))
{
ret = PTR_ERR(gpio_key);
}
return 0;
}
static int mykey_remove(struct platform_device *pdev)
{
// 在这里执行设备的清理和操作
return 0;
}
static const struct of_device_id my_driver_of_match[] = {
{ .compatible = "example,my_key" },
{},
};
MODULE_DEVICE_TABLE(of, my_driver_of_match);
static struct platform_driver my_driver =
{
.probe = mykey_probe,
.remove = mykey_remove,
.driver = {
.name = "my_key",
.of_match_table = of_match_ptr(my_driver_of_match),
},
};
static int mykey_init(void)
{
int retval = 0;
retval = platform_driver_register(&my_driver );
if (retval < 0)
return retval;
return 0;
}
static void mykey_exit(void)
{
platform_driver_unregister(&my_driver );
}
module_init(mykey_init);
module_exit(mykey_exit);
MODULE_AUTHOR("zs");
MODULE_LICENSE("GPL");
注意点:调用devm_gpiod_get时的注意点,使用该方法时,给方法传递的第二个参数一定要特别注意,追踪该方法在内核中的具体实现可知:devm_gpiod_get最终调用的是gpiolib.c中的“__gpiod_get_index”方法,而该方法最终调用的是“of_find_gpio”方法,查看该方法的实现可知,方法在查找GPIO资源时已经自动为我们加了“-gpio”、“-gpios”后缀,所以我们在调用devm_gpiod_get时,要对匹配字符串进行相应的修改。
比如在DTS中有如下的资源:
mykey-gpios = <&gpio0 RK_PB0 GPIO_ACTIVE_LOW>;
那么在调用devm_gpiod_get去获取该资源时,第二个参数直接传"mykey"就可以了,而不是传递“mykey-gpios ”。
二,platform驱动总线的匹配方式
platform_driver、platform_device 和device_driver结构体的定义:
2.1:platform_driver
struct platform_driver 是Linux内核中用于表示平台驱动程序的结构体。它包含了与平台设备驱动相关的属性和操作,用于管理和控制平台设备。
下面是 struct platform_driver 结构体的定义:
struct platform_driver {
int (*probe)(struct platform_device *pdev);
int (*remove)(struct platform_device *pdev);
void (*shutdown)(struct platform_device *pdev);
int (*suspend)(struct platform_device *pdev, pm_message_t state);
int (*resume)(struct platform_device *pdev);
struct device_driver driver; // device_driver结构体**
const struct platform_device_id *id_table; // 匹配规则三
bool prevent_deferred_probe;
};
struct platform_driver 结构体的主要字段如下:
probe: 当匹配到某个平台设备时,调用该函数进行设备的初始化。
remove: 在设备被移除时,调用该函数进行设备的清理和释放资源操作。
shutdown: 可选字段,在系统关机时调用该函数进行设备的关机处理。
suspend: 可选字段,在设备进入挂起状态时调用该函数进行设备的挂起操作。
resume: 可选字段,在设备从挂起状态恢复时调用该函数进行设备的恢复操作。
driver: 包含了和设备驱动相关的属性,如名称、owner等。
id_table: 可选字段,用于指定平台设备的标识表,用于驱动程序的设备匹配。
prevent_deferred_probe: 可选字段,用于控制是否延迟设备的探测。
2.2:platform_device
struct platform_device 是 Linux 内核中用于表示平台设备的结构体。它用于描述与特定平台相关的设备,例如 SoC 上的外设或其他与硬件集成的设备。
下面是 struct platform_device 结构体的定义:
struct platform_device {
const char *name; // 匹配方式一
int id;
struct device dev;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
};
struct platform_device 结构体的主要字段如下:
name: 设备的名称。该名称通常与设备驱动程序关联。
id: 设备的 ID。用于标识同一类型的多个设备。
dev: 表示该平台设备的通用设备结构体。它可以被设备模型用于管理设备对象。
num_resources: 设备资源的数量。
resource: 指向设备资源描述符的指针数组。每个资源描述符包含了设备在系统中所需的 I/O 地址、
中断等信息。
id_entry: 指向平台设备标识表的指针,用于驱动程序的设备匹配
2.3:device_driver
struct device_driver 是 Linux 内核中用于表示设备驱动程序的结构体。它包含了与设备驱动相关的属性和操作,用于管理和控制特定类型的设备。
下面是 struct device_driver 结构体的定义:
struct device_driver {
const char *name; // 匹配规则一
struct bus_type *bus;
struct module *owner;
const char *mod_name;
bool suppress_bind_attrs;
const struct of_device_id *of_match_table; // 匹配规则二
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct attribute_group **groups;
const struct dev_pm_ops *pm;
struct driver_private *p;
};
struct device_driver 结构体的主要字段如下:
name: 设备驱动程序的名称。
bus: 指向设备所属的总线类型的指针。
owner: 指向拥有该设备驱动程序的模块的指针。
mod_name: 拥有该设备驱动程序的模块的名称。
suppress_bind_attrs: 一个布尔值,用于控制是否在 sysfs 中暴露 bind/unbind 相关的属性。
of_match_table: 指向设备树匹配表的指针,用于匹配设备树节点。
probe: 当匹配到设备时,调用该函数来初始化设备。
remove: 在设备被移除时,调用该函数来清理和释放资源。
shutdown: 可选字段,在系统关机时调用该函数进行设备的关机处理。
suspend: 可选字段,在设备进入挂起状态时调用该函数进行设备的挂起操作。
resume: 可选字段,在设备从挂起状态恢复时调用该函数进行设备的恢复操作。
groups: 指向设备驱动程序所属的属性组数组的指针。
pm: 指向设备的电源管理操作的指针。
在 Linux 内核中,platform 驱动总线使用设备树中的设备节点来与驱动程序进行匹配。介绍 platform 驱动总线的三种常见的匹配方式。
基于driver.name属性的匹配方式:通过driver.name和device.name来进行对比匹配
基于 compatible 属性的匹配方式: 设备树中的设备节点通常会包含一个 compatible 属性,用于描述设备的兼容性标识。platform 驱动总线通过比较设备节点的 compatible 属性与驱动程序中定义的 of_device_id 结构体数组中的兼容性标识,来进行匹配。具体示例可以参考前面提到的 of_match_device() 函数的用法。
基于 platform_device_id 的匹配方式: 驱动程序可以定义一个 platform_device_id 结构体数组,并通过 MODULE_DEVICE_TABLE(platform, …) 宏将其注册为可供内核使用的设备表。设备节点在与驱动程序匹配时,会通过比较设备节点的 name 属性与驱动程序中定义的 platform_device_id 结构体数组中的设备名称,来进行匹配。具体示例可以参考前面提到的 platform_device_id 的用法。
需要注意的是,platform 驱动总线的匹配方式是基于设备树的,因此要求设备树中正确地描述了设备的配置信息,包括设备节点的兼容性标识和其他属性。
三,of_match_ptr,of_match_device,platform_get_device_id
**of_match_ptr()**是一个用于将 of_device_id 结构体数组传递给 of_match_table 字段的宏。它将 of_device_id 结构体数组的指针转换为 const struct of_device_id * 类型,以便在注册 platform_driver 时使用。
of_match_table 是 platform_driver 结构体中的一个字段,用于指定设备树匹配表。通过传递 of_match_ptr() 宏转换后的指针,可以将设备树匹配表与驱动程序关联起来,实现设备树节点和驱动程序的匹配。
static struct platform_driver my_driver =
{
.probe = mykey_probe,
.remove = mykey_remove,
.driver = {
.name = "my_key",
.of_match_table = of_match_ptr(my_driver_of_match),
},
};
使用了 of_match_ptr() 宏将 my_driver_of_match 数组的指针传递给了 of_match_table 字段。这样,当 platform_driver 注册到内核时,内核就能根据设备树节点的兼容性标识进行匹配,并调用相应的回调函数。