platform driver模型简介
在Linux 的设备驱动模型中,关心总线,设备和驱动这三个实体,总线将设备和驱动绑定,在系统中每注册一个设备的时候,会寻找与之对应的驱动;相反的,在系统中每注册一个驱动的时候,会找到相类似的设备,而匹配由总线完成
一个现实的Linux 设备和驱动通常都需要挂接在一种总线上,对于本身依附于PCI,USB,I2C和SPI等设备而言,这自然不是问题,但是在嵌入式系统中,Soc系统中集成的独立外设控制器,挂接在Soc 内存空间的外设等确不依赖于此类总线,基于这个背景,Linux 发明了一种模拟的总线,称为Platform总线,相应的设备称为Platform device,驱动则为Platform Driver;但是所谓的Platform_device 不是和字符设备、块设备和网络设备并列的概念,而是Linux系统提供的一种附件手段
platform driver基本原理
platform driver 的基本架构如下图所示:
platform_driver 可将cdev有关的一系列操作(前提是字符设备的驱动开发)放到platform_driver的probe函数中去实现,这样就把cdev挂到platform bus上去了
主要原理:
注册platform device
系统初始化时,调用platform_add_devices函数,把所有放置在板级platform_device数组中的platform_device注册到系统中去,此函数循环调用platform_device_register函数,来注册每个platform_device platform_device_register中会调用platform_device_add函数platform_device全部注册到系统之后,便可以通过platform的操作接口,来获取platform_device中的resource资源. 比如地址、中断号等,以进行request_memregion、ioremap(将resource分配的物理地址映射到kernel的虚拟空间来)和request_irq操作platform的操作接口包括platform_get_irq、platform_get_irq_byname、platform_get_resource、platform_get_resource_byname等
加载platform driver
在驱动模块insmod到系统时,驱动代码可以通过上面函数来获取对应platform_device的resource资源.比如在module_init中,会调用plarform_driver_register,这个会引用到platform_driver中的probe函数. probe函数可以通过get_resource来获取寄存器物理基地址,然后ioremap到kernel的虚拟空间来,这样驱动就可以正式操纵和修改设备的寄存器
进行cdev的初始化及cdev_add的操作
驱动注册的时候,platform_driver_register()—》driver_register()—》bus_add_driver()—》driver_attach()—》bus_for_each_dev()函数对每个挂在虚拟的platform bus的设备作__driver_attach()—》driver_probe_device()—》drv->bus—》match()==platform_match()-& gt;比较strncmp(pdev->name, drv->name, BUS_ID_SIZE),如果相符就调用platform_drv_probe()->driver->probe(),如果probe成功则绑定该设备到该驱动
platform driver数据结构
platform_device 结构
代码路径:linux-5.4.6/include/linux/platform_device.h
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u64 dma_mask;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
- name:设备的名称
- dev:真正有用的设备,通过contain_of,能找到整个platform_device
- num_resources, resource: 系统使用的资源。Linux系统资源包括IO,寄存器,DMA,Bus,Memory等。
platform_driver 结构
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
- probe:将driver绑定到device上调用该函数
- remove:系统卸载设备的时候,将driver和device解绑
- shutdown:关机时使设备静默
- suspend:使设备进入睡眠模式
- resume: 唤醒设备
platform_bus_type结构
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.dma_configure = platform_dma_configure,
.pm = &platform_dev_pm_ops,
};
kernel在进入C语言阶段,会进入start_kernel函数(init/main.c),进行一些内存管理,调度。最终调用到driver_init函数中,执行的函数platform_bus_init就是platform总线的注册函数
platform_bus_init 最关键是下面的 platform_match函数:
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);
/* 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);
}
platform driver实例
platform_driver 注册流程:
- 定义module_init 函数
- 定义platform_driver 结构体
- 注册platform_driver到platform bus 中
static struct platform_driver s3c_tvscaler_driver = {
.probe = s3c_tvscaler_probe,
.remove = s3c_tvscaler_remove,
.suspend = s3c_tvscaler_suspend,
.resume = s3c_tvscaler_resume,
.driver = {
.owner = THIS_MODULE,
.name = "s3c-tvscaler",
},
};
static char banner[] __initdata = KERN_INFO "S3C6410 TV scaler Driver, (c) 2008 Samsung Electronics\n";
int __init s3c_tvscaler_pre_init(void)
{
printk(banner);
if(platform_driver_register(&s3c_tvscaler_driver) != 0)
{
printk("platform device register Failed \n");
return -1;
}
printk(" S3C6410 TV scaler Driver module init OK. \n");
return 0;
}
void s3c_tvscaler_exit(void)
{
platform_driver_unregister(&s3c_tvscaler_driver);
printk("S3C: tvscaler module exit\n");
}
module_init(s3c_tvscaler_pre_init);
module_exit(s3c_tvscaler_exit);
也可以使用系统预定宏来注册Platform_driver
module_platform_driver(komeda_platform_driver);
代码路径:linux-5.4.6/include/linux
/* module_platform_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()
*/
#define module_platform_driver(__platform_driver) \
module_driver(__platform_driver, platform_driver_register, \
platform_driver_unregister)
module_driver在/include/linux/device.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);
展开后同样可以调用到 plarform_driver_register
Platform Driver 加载成功后,对应的设备节点如下:
/sys/bus/platform/devices
/sys/device/platform/