总线设备驱动模型主要包含总线、设备、驱动三个部分,总线可以是一条真实存在的总线,例如USB、I2C等典型的设备。但是对于一些设备(内部的设备)可能没有现成的总线。Linux 2.6内核中引入了总线设备驱动模型。总线设备驱动模型与之前的三类驱动(字符、块设备、网络设备)没有必然的联系。设备只是搭载到了总线中。在 linux内核中假设存在一条虚拟总线,称之为platform总线。
platform总线相比与常规的总线模型其优势主要是platform总线是由内核实现的,而不用自己定义总线类型,总线设备来加载总线。platform总线是内核已经实现好的。只需要添加相应的platform device和platform driver。具体的实现过程主要包括如下的过程:
整体而言只需要完成两个步骤,也就是设备的实现和驱动的实现,每一个实现都包括相关结构体的定义和注册。
platform_device注册
需要注意的是platform_device 实质上是经过处理过的设备,在platform_device结构体中存在一个设备结构体,与之前的设备存在差别的是引入了设备资源。这些设备资源就能实现对设备寄存器,中断等资源的访问。平台设备的基本结构体如下:
struct platform_device {
/*设备名*/
const char * name;
/*设备ID号*/
int id;
/*结构体包含一个具体的device结构体*/
struct device dev;
/*资源的数量*/
u32 num_resources;
/*资源结构体,用来保存硬件的资源*/
struct resource * resource;
/*平台设备的ID*/
struct platform_device_id *id_entry;
};
其中struct device 和 struct resource 是重要的结构体。struct device 在总线设备驱动模型中已经提到了。这次讨论一下struct resource。
struct resource {
/*资源的起始值,如果是地址,那么是物理地址,不是虚拟地址*/
resource_size_t start;
/*资源的结束值,如果是地址,那么是物理地址,不是虚拟地址*/
resource_size_t end;
/*资源名*/
const char *name;
/*资源的标示,用来识别不同的资源*/
unsigned long flags;
/*资源指针,可以构成链表*/
struct resource *parent, *sibling, *child;
};
platform_device 的注册很简单,只需要在设备的初始化函数中首先定义相应的设备,通常采用函数platform_device *platform_device_alloc(const char *name, int id)动态申请,通常name就是需要申请的设备名,而id为-1。然后采用
int platform_device_add(struct platform_device *pdev)
或者
int platform_device_register(struct platform_device *pdev)
注册定义好的设备即可。
同样在退出函数中释放注册好的设备即可,可以采用函数:
void platform_device_unregister(struct platform_device *pdev)。
然后一个平台设备就完成了,不需要像自己实现模型时定义相关的文件属性等。
设备资源可以通过相关函数得到:
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num)
中断资源也可以通过:
int platform_get_irq(struct platform_device *dev, unsigned int num)
资源的使用主要是驱动实现过程中需要使用到的,但是后期的使用一般需要在驱动的probe函数中实现申请中断或者IO内存才能使用,而不能直接使用。特别是资源中的地址通常是物理地址,需要通过申请IO内存和映射完成物理到虚拟地址的转换,便于进程的访问。
platform_driver注册
平台驱动结构体platform_driver实现如下:
struct platform_driver {
/*平台驱动需要实现的相关函数操作,
其中的前4个函数与最后一个函数与device_driver中的函数是相同的
本质是实现对device_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 (*suspend_late)(struct platform_device *, pm_message_t state);
int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *);
/*内嵌了一个设备驱动结构体*/
struct device_driver driver;
/*平台设备ID,这与platform_device中的struct platform_device_id *id_entry是相同的
主要是完成总线的匹配操作,platform总线的匹配操作第一匹配要素就是该元素。而不再是简单的name选项。
*/
struct platform_device_id *id_table;
};
通常驱动的入口函数:
int (*probe)(struct platform_device *);
当总线完成了设备的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);
/* match against the id table first */
/*从定义上分析,id_table是首先匹配的对象,然后才是name的匹配,当ID匹配完成时就说明匹配好了*/
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总线的匹配函数手下是比较id_table是匹配的首选项。
probe 函数称之为探针函数,用于检测总线上有该驱动能够处理的设备,而remove函数则是为了说明总线上该驱动能够处理的设备被移除。
因此这两个函数是在平台设备中一定要被实现的函数。
其他的函数则不一样要求实现。
平台驱动的设计主要是完成平台驱动结构体的填充和注册。
通常的平台驱动结构体实现如下:
static struct platform_driver my_driver =
{
/*平台驱动的probe函数实现*/
.probe = my_probe,
/*平台驱动的remove函数实现*/
.remove = my_remove,
/*实现设备驱动的name和owner变量*/
.driver =
{
/*该参数主要实现总线中的匹配函数调用*/
.name = "my_dev",
/*该函数表示模块的拥有者*/
.owner = THIS_MODULE,
},
};
其中的my_probe和my_remove是自己定义的probe和remove函数。
最主要的是内嵌设备驱动结构体的填充,主要的填充包括name和owner两个,当然也可以包括其他的。由于没有填充id_table,那么 name就是总线匹配操作的第一选择。因此如果没有填充好id_table,那么name元素是一定要实现的,不然不能完成相应的设备驱动匹配操作。
完成platform_driver结构体的填充过后就是完成驱动的在初始化阶段的注册以及退出阶段的释放操作,基本的实现函数为:
注册函数,通常在驱动初始化函数中调用:
int platform_driver_register(struct platform_driver *drv)
释放函数,通常在驱动退出函数调用:
void platform_driver_unregister(struct platform_driver *drv)
完成相关的注册以后总线、设备、驱动的大概框架就完成啦。
但是这只是常用的框架,还不能在应用程序中使用。
基于平台驱动的设备驱动都是基于总线架构的,基本的实现过程与之前的简单字符设备存在较大的差别,主要的区别在驱动的初始化不在是平台设备驱动的初始化函数中实现,而是在probe函数中实现。而驱动的卸载函数则是在remove函数中实现。probe函数是平台总线实现匹配以后首先被调用的函数,因此在其中实现字符设备、块设备、网络设备驱动的初始化是有意义的,这样的设备驱动就是基于平台总线的设备驱动,便于维护。
平台总线驱动的注册过程分析:
int platform_driver_register(struct platform_driver *drv)
{
/*第一步,仍然是完成结构体的填充操作*/
/*驱动的总线类型*/
drv->driver.bus = &platform_bus_type;
/*将自己定义的probe函数赋值给平台驱动中设备驱动的probe函数,其他函数类似*/
if (drv->probe)
drv->driver.probe = platform_drv_probe;
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
if (drv->suspend)
drv->driver.suspend = platform_drv_suspend;
if (drv->resume)
drv->driver.resume = platform_drv_resume;
/*第二步,仍然是完成一般设备驱动的注册操作*/
/*然手就是一般驱动的注册,这样就完成了设备的注册*/
return driver_register(&drv->driver);
}
/*设备驱动的probe函数的赋值过程*/
static int platform_drv_probe(struct device *_dev)
{
/*得到设备对应的平台驱动*/
struct platform_driver *drv = to_platform_driver(_dev->driver);
/*得到设备的平台设备*/
struct platform_device *dev = to_platform_device(_dev);
/*下面的probe是自己实现的probe函数。具体的实现思路:
根据一般设备找对应的平台设备,同时根据设备的驱动找到平台驱动。
然后返回平台驱动的probe函数(自己实现通常是初始化操作)地址。
*/
return drv->probe(dev);
}
实现的总线平台驱动模型的最简单源码:
平台设备的实现:device.c
#include<linux/device.h>
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/string.h>
#include<linux/platform_device.h>
/*平台模型驱动的平台设备对象*/
static struct platform_device *my_device;
/*初始化函数*/
static int __init my_device_init(void)
{
int ret = 0;
/*采用platform_device_alloc分配一个platform_device对象
参数分别为platform_device的name,和id。
*/
my_device = platform_device_alloc("my_dev",-1);
/*注册设备,注意不是platform_device_register,将平台设备注册到内核中*/
ret = platform_device_add(my_device);
/*如果出错释放相关的内存单元*/
if(ret)
{
platform_device_put(my_device);
}
return ret;
}
/*卸载处理函数*/
static void __exit my_device_exit(void)
{
platform_device_unregister(my_device);
}
module_init(my_device_init);
module_exit(my_device_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("GP-<gp19861112@yahoo.com.cn>");
平台驱动的实现:driver.c
#include<linux/module.h>
#include<linux/init.h>
#include<linux/kernel.h>
#include<linux/platform_device.h>
#include<linux/string.h>
/*平台驱动中的probe和remove函数是必须实现的函数*/
/*设备驱动的探测函数,主要实现检测总线上是否有该驱动对应的设备*/
static my_probe(struct device *dev)
{
/*
如果添加实际的设备到该平台总线设备驱动模型中,则可以在该函数
中实现具体的设备驱动函数的初始化操作,包括设备号的申请,设备
的初始化,添加。自动设备文件创建函数的添加等操作。
或者是混杂字符设备的相关初始化操作。当然结构体的相关处理仍
然采取全局变量的形式。
*/
printk("Driver found devices which this driver can be handle\n");
return 0;
}
/*设备驱动的移除函数,主要检测该驱动支持设备的移除活动检测*/
static my_remove(struct device *dev)
{
/*
如果添加实际的设备到该平台总线设备驱动模型中,则可以在该函数
中实现具体的设备的释放,包括设备的删除,设备号的注销等操作。
*/
printk("Driver found device unpluded\n");
return 0;
}
static struct platform_driver my_driver =
{
/*平台驱动的probe函数实现*/
.probe = my_probe,
/*平台驱动的remove函数实现*/
.remove = my_remove,
/*实现设备驱动的name和owner变量*/
.driver =
{
/*该参数主要实现总线中的匹配函数调用*/
.name = "my_dev",
/*该函数表示模块的拥有者*/
.owner = THIS_MODULE,
},
};
/*初始化函数*/
static int __init my_driver_init(void)
{
/*注册平台驱动*/
return platform_driver_register(&my_driver);
}
/*退出函数*/
static void __exit my_driver_exit(void)
{
/*注销平台驱动*/
return platform_driver_unregister(&my_driver);
}
/*加载和卸载*/
module_init(my_driver_init);
module_exit(my_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("GP-<gp19861112@yahoo.com.cn>");
将一般设备驱动加入到总线设备模型中的相关操作是后期总结和学习的内容。设备驱动实现的实现原理还是之前的那些操作,但是初始化和推出函数发生了改变。