linux module_platform_driver宏的使用
module_platform_drivers使用方式
module_platform_driver 是一个宏,用于简化在 Linux 内核模块编程中注册平台设备驱动的过程。
具体使用方法如下:
- 包含相关头文件:
在代码文件的顶部,包含所需的头文件,如:
#include <linux/module.h>
#include <linux/platform_device.h>
- 定义平台设备驱动结构体:
创建一个结构体来描述平台设备驱动,包含驱动的名称、设备探测函数、设备注销函数等。例如:
static int my_probe(struct platform_device *pdev)
{
// 设备探测逻辑
return 0; // 返回0表示成功
}
static int my_remove(struct platform_device *pdev)
{
// 设备注销逻辑
return 0; // 返回0表示成功
}
static struct platform_driver my_platform_driver = {
.driver = {
.name = "my_device",
.owner = THIS_MODULE,
},
.probe = my_probe,
.remove = my_remove,
};
- 使用
module_platform_driver
宏注册平台设备驱动模块:
使用module_platform_driver
宏来注册平台设备驱动模块,将上述定义的结构体作为参数传递给宏。例如:
module_platform_driver(my_platform_driver);
通过使用 module_platform_driver 宏,内核会在模块加载时自动注册平台设备驱动,并根据驱动的名称与设备树中的设备节点进行匹配。当匹配成功时,内核将调用驱动结构体中指定的设备探测函数进行设备的初始化。当模块卸载时,内核会自动调用驱动结构体中指定的设备注销函数进行设备的注销。
请注意,上述示例中的函数和结构体名称仅供参考,实际使用时需要根据具体情况进行适配和命名。另外,还需确保内核配置中已启用平台设备支持(CONFIG_OF 和 CONFIG_PLATFORM_DRIVERS)以及相关的依赖项。
先说结论,再看细节
- module_platform_driver对module_init/module_exit进行了封装,简化了模板代码
- module_platform_driver注册的driver在匹配到设备树节点、或者匹配到对应的平台设备时才会执行probe函数
问题:
怎么匹配设备树节点或者设备?
再来看看细节
module_platform_driver
的宏定义在include/linux/platform_device.h
文件
#define module_platform_driver(__platform_driver) \
module_driver(__platform_driver, platform_driver_register, \
platform_driver_unregister)
module_driver
定义在include/linux/device/driver.h
文件
/**
* module_driver() - Helper macro for drivers that don't do anything
* special in module init/exit. This eliminates a lot of boilerplate.
* Each module may only use this macro once, and calling it replaces
* module_init() and module_exit().
*
* @__driver: driver name
* @__register: register function for this driver type
* @__unregister: unregister function for this driver type
* @...: Additional arguments to be passed to __register and __unregister.
*
* Use this macro to construct bus specific macros for registering
* drivers, and do not use it on its own.
*/
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
追根溯源,从代码中看到,module_platform_driver最终还是调用了module_init,但是,又不仅仅是调用了module_init,还调用了platform_driver_register
和platform_driver_unregister
,这两个函数的作用就是注册和卸载平台驱动。
再来看看platform_driver_register
/* include/linux/platform_device.h */
#define platform_driver_register(drv) \
__platform_driver_register(drv, THIS_MODULE)
https://www.cnblogs.com/hueyxu/p/13659262.html
查看platform设备device和驱动driver
// 里面包含了设备树节点和platform_device_register_data两种方式注册的设备
# /sys/bus/platform/devices
// 包含了module_platform_driver注册的驱动
# /sys/bus/platform/drivers
设备驱动模型
Linux内核在2.6版本中引入设备驱动模型,简化了驱动程序的编写。Linux设备驱动模型包含设备(device)、总线(bus)、类(class)和驱动(driver),它们之间相互关联。其中设备(device)和驱动(driver)通过总线(bus)绑定在一起。
Linux内核中,分别用 bus_type、 device_driver和 device结构来描述总线、驱动和设备,结构体定义详见 linux/device.h。设备和对应的驱动必须依附于同一种总线,因此 device_driver和 device结构中都包含 struct bus_type指针。
对于早期的Linux内核(2.6版本以前)来说,通常在驱动代码中xxx_driver 注册过程中调用 probe() 函数来对设备进行初始化。
引入Linux设备驱动模型下,设备和驱动可以分开注册,依赖总线完成相互绑定。系统每注册一个设备的时候,会寻找与之匹配的驱动;相反,系统每注册一个驱动的时候,会寻找与之匹配的设备。这个过程中,设备和驱动的匹配工作由总线完成。
platform总线的注册
platform总线是一种虚拟的总线,与之相对应的是PCI、I2C、SPI等实体总线。引入虚拟platform总线是为了解决某些设备无法直接依附在现有实体总线上的问题。
可以看到platform_bus_init
在do_initcalls
前面完成了初始化。
start_kernel
-> rest_init
-> kernel_init
-> do_basic_setup //这个阶段之前完成了CPU相关初始化,设备还没工作
->driver_init
-> platform_bus_init //注册platform总线
-> do_initcalls // 开始pure_initcall、core_initcall、device_initcall等不同阶段的初始化
platform device和platform driver如何绑定
注册driver时绑定device
如下函数依次递进直接到platform_match
函数 | 解释 |
---|---|
__platform_driver_register | 该函数会对 struct device_driver 的bus、probe、remove等回调函数进行初始化,紧接着调用 driver_register(&globalfifo_driver->driver) |
driver_register | 对bus、probe、remove等回调函数初始化进行判断,保证总线和驱动上相应的函数只能存在一个。bus_add_driver(&(globalfifo_driver.driver)) ,将驱动注册到总线上 |
bus_add_driver | 如果总线使能 drivers_autoprobe ,将调用 driver_attach() 尝试匹配设备 |
driver_attach | driver_attach() 函数找到驱动依附的总线信息,遍历总线上链表 klist_devices 得到当前总线上存在的设备,然后调用 __driver_attach(dev, drv) 函数,尝试将驱动和设备绑定。 |
driver_match_device | driver_match_device(drv, dev) 回调 drv->bus->match() 函数,对于platform_bus为 platform_match() 。 |
注册device时绑定driver
函数 | 解释 |
---|---|
platform_device_register | 主要对 struct device 中基本成员进行初始化,包括 kobject 、 struct device_private 、 struct mutex 等 |
platform_device_add | device_add 添加设备 |
device_add | bus_add_device注册到总线系统中。并建立sysfs的相关目录:总线系统中建立到设备的链接,同时也在设备目录下建立到总线的subsystem链接 |
bus_probe_device | 尝试在总线上寻找可以绑定的驱动。经过层层调用,最终又调用到 driver_match_device() 和 driver_probe_device() 函数,查找总线上能和当前设备匹配的驱动,并将驱动和设备绑定在了一起。 |
device_initial_probe | |
__device_attach | 后面流程和上面一样了 |
platform_match
driver_override 有效时,尝试将驱动名字和 driver_override 匹配
- 基于设备树风格的匹配
- 基于 ACPI 风格的匹配
- 匹配 ID 表
- 匹配 platform_device 设备名和驱动的名字
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}