总线平台驱动程序总浅析:只为自己肤浅的理解,针对2.6.32.2内核
一、Linux的驱动程序,模型一般在总线、设备、驱动3个模块其结构为:
1、三者的结构以及工作流程
1.1、总线:struct bus_type--核心成员如下
struct bus_type {
const char *name; //总线名字
struct bus_attribute *bus_attrs; //总线属性
struct device_attribute *dev_attrs; //每一条总线也是一个设备,所以会有设备属性
struct driver_attribute *drv_attrs; //驱动属性
int (*match)(struct device *dev, struct device_driver *drv);
/*这个函数相当的重要,主要是匹配驱动程序和和对应设备是否匹配的函数,在2.6.32以前,
*会匹配device_driver(驱动程序)的name成员和device(设备)的id_table,2.6.32不是匹配这个了
*(主要是因为device里面已经没有这个成员了),在device成员里面有个init_name,最终匹配这个。具体的
*注册过程你可以追踪device_register()这个函数,下面会分析
*/
int (*probe)(struct device *dev);
struct bus_type_private *p;
}; /* match函数必须要有 */
1.2、设备:struct device--核心成员如下
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj; /* 与驱动匹配要使用的东西(在上面结构体-总线的match函数使用)*/
const char *init_name;
/*
* 注册设备(device_register(struct device *dev))的时候,会把这个东西填入到
* kobj.name里面,初始化设备的时候要用到这个。是最终匹配驱动的关键,这个成员在注册设备
* 的时候,必须要初始化
*/
......
struct bus_type *bus;
/*
* 设备所在的总线,非常重要的一个成员,这个成员就决定当这个设备注册是时候,是哪个总线驱动
* 来负责匹配,同样在注册设备之前必须要被初始化。
*/
struct device_driver *driver;
/*
* 这个成员,会在总线match函数匹配成功后,会调用驱动的connect函数,该函数会把匹配成功
* 的驱动程序赋值给这个成员
*/
void *platform_data; /* 私有数据,具体使用试具体情况而定 */
......
};
1.3、驱动:struct device_driver--其核心成员如下
struct device_driver {
const char *name;
/* 驱动程序的名字,当insmod后可以在sys/bus/bus<驱动所在的总线>/drivers下面看到这个名字 */
struct bus_type *bus; /* 驱动程序所在的总线 */
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
int (*probe) (struct device *dev);
/*当驱动程序和设备匹配(总线驱动的match函数来完成匹配工作)成功的时候,由总线驱动来调用驱动程序的probe函数,
* probe函数也是整个驱动程序的入口,在这个入口里面你可以注册很多东西,如字符设备驱动,混杂设备驱动,以及块设
* 备驱动,网络设备驱动等等。
*/
int (*remove) (struct device *dev);
/*善后工作,针对probe函数的善后工作,probe里面如果注册什么,这个函数就将会注销什么,相应的释放probe里面分配
* 的内存等等工作
*/
.....
struct driver_private *p; /*这个成员也比较重要,针对具体的驱动程序的设计的时候会用到。*/
};
后面介绍的平台驱动--struct platform_driver 留心的朋友你会发现,这个结构体只是上面那个结构体的一个封装。里面增加了一点东西。
1.4、总线驱动模型的工作流程
在设备和驱动的注册中,会将设备和驱动程序链接到其所在的总线上去,这样才能使得总线能够遍历其加载的设备(或者驱动)是有对应的驱动(设备)链表来匹配。
在这个模型中其实我们需要三个驱动程序,也就是需要三个.ko模块。即bus.ko、device.ko、driver.ko稍后会把这个简单的代码给贴出来这样设计驱动程序的好处有驱动程序只能通过匹配成功设备后才能得到相应的资源,具体体现在platform平台驱动设计中。另外就是能够达到一个驱动程序可以满足多种设备的公用(只要名字相同)。由于驱动的设计和具体的硬件联系不大,保证了驱动程序的移植性。还有诸多好处,我就不在述说了。假如你是按照这种思想设计驱动程序的话,要加载你的驱动程序,首先你得保证你的驱动程序所在的总线驱动要在内核中,也就是说你得写加载bus.ko模块,然后才能加载device.ko、driver.ko这两个模块,这两个模块只要其中一个模块被加载,总线驱动(bus.ko)就会干这样一件事情:eg:如果你是加载的device.ko(设备,现实中如你给pc机插上一个U盘等等)这个模块,内核会调用你这个设备的总线驱动程序,然后控制权交给总线驱动,总线驱动会在遍历自己的驱动程序链表中每一个驱动程序,主要体现在总线驱动调用自己的match函数来检查自己的驱动程序链表里面是否有驱动程序支持这个新加入的设备,如果支持的话,就调用支持这个新设备的驱动程序的probe函数(具体要把这个新加入的设备看做是字符设备还是块设备以及其他设备,要看这个probe函数里面怎么来注册的了),看括号的注释你应该清楚,probe函数就是驱动程序的核心部分。然后后续的工作就和一般的书写字符设备,块设备步骤一样了,在驱动程序的remove函数里面一般会做在与probe函数里面对应的善后工作(必须注销驱动程序,释放中断啊等等-这样才能让系统资源的有效利用)。
2、简单的总线驱动模型例子:针对2.6.32的内核
2.1、总线部分
#include <linux/device.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/stat.h>
static char *Version = "$Rversion:1.0 $";
/*
* 总线的匹配函数,dev_name(dev)就是获取dev->kobject.name这个成员会在注册设备的时候,会用设备的
* init_name成员的内容来进行初始化也就是说,实际上匹配设备和驱动的是device.init_name和device_driver.name
*两个成员,所以在设备和驱动模块中,必须被初始化一样
*/
static int my_match(struct device *dev,struct device_driver *driver)
{
return !strncmp(dev_name(dev), driver->name, strlen(driver->name));
}
static void my_bus_release(struct device *dev)
{
printk(KERN_DEBUG"my bus realse\n");
}
struct device my_bus = {
.init_name = "my_bus0",
.release = my_bus_release,
/*
*总线一般也会被注册成一个设备,主要是为了方便管理才主要设计的。release函数在设备拔出后
* 会被调用,如果是驱动卸载后不会被调用,只有在驱动先卸载,在拔出设备的时候才会被调用
*/
};
struct bus_type my_bus_type= {
.name = "my_bus",
.match = my_match,
};
static ssize_t show_bus_version(struct bus_type *bus,char *buf)
{
return snprintf(buf,PAGE_SIZE,"%s\n",Version);
}
/* 定义总线属性,体现在/proc/bus/my_bus/下面的文件中,可以用命令cat查看其信息 */
static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL);
static __init int my_bus_init(void)
{
int ret;
ret = bus_register(&my_bus_type); /* 注册总线。注册成功返回0 */
if(ret)
return ret;
if(bus_create_file(&my_bus_type,&bus_attr_version)) /* 添加总线属性 */
printk(KERN_NOTICE"Fail to create version attribute!\n");
ret = device_register(&my_bus); /* 注册总线设备 */
if(ret)
return ret;
/* 添加总线设备属性 */
device_create_file(&my_bus, &dev_attr_mybus);
return ret;
}
static void my_bus_exit(void)
{
bus_unregister(&my_bus_type);
}
EXPORT_SYMBOL(my_bus); /*导出变量:my_bus和my_bus_type,这两个因为/*
EXPORT_SYMBOL(my_bus_type); /*设备和驱动部分模块要引用,所有得导出*/
module_init(my_bus_init);
module_exit(my_bus_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("TboyARM Inc.");
2.2、设备部分
#include <linux/device.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/stat.h>
/* 声明前面总线导出的两个变量 */
extern struct device my_bus;
extern struct bus_type my_bus_type;
static void my_dev_release(struct device *dev)
{
printk("my_dev_release!\n");
}
struct device my_dev = {
.init_name = "my_dev", /* 最终总线将调用匹配函数匹配这个 */
.bus = &my_bus_type, /* 设备所在的总线 */
.parent = &my_bus, /* 父设备 */
/* parent设备就是总线设备
*总线也是一个设备。注意:.parent成员如果要添加的话,其对应的父设备必须要注册,否则在这个设备注册的地方会出错
*/
.release = my_dev_release,
};
static ssize_t mydev_show(struct device *dev,char *buf)
{
return sprintf(buf,"%s\n","This is my device!");
}
static DEVICE_ATTR(dev, S_IRUGO, mydev_show, NULL);
/*属性格式
* DEVICE_ATTR(name, mode, show, store);
*/
static int __init my_bus_dev_init(void)
{
int ret;
/* 初始化设备 */
//dev_set_name(&my_dev, "my_dev");由于前面已经给init_name赋值了,所以这里没有必要再初始化了。
/* 设备注册 */
ret = device_register(&my_dev);
if(ret != 0){
printk(KERN_NOTICE"can not register device!\n");
return ret;
}
/* 创建属性文件 */
device_create_file(&my_dev, &dev_attr_dev);
return ret;
}
static void my_bus_dev_exit(void)
{
device_unregister(&my_dev);
}
module_init(my_bus_dev_init);
module_exit(my_bus_dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("TboyARM Inc.");
2.3、驱动程序部分:
#include <linux/device.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/stat.h>
extern struct bus_type my_bus_type;
static int my_probe(struct device *dev )
{
printk("Driver found device which my driver can handle!\n");
return 0;
}
static int my_remove(struct device *dev)
{
printk("Driver found device unpluged!\n");
return 0;
}
struct device_driver my_driver = {
.name = "my_dev", //与设备中的init_name必须要一致
.bus = &my_bus_type,
.probe = my_probe, //驱动程序的主入口函数
.remove = my_remove,
};
/* 显示属性的函数 */
static ssize_t mydriver_show(struct device_driver *driver,char *buf)
{
return sprintf(buf,"%s\n","This is my driver!");
}
/* 定义驱动程序的属性 */
static DRIVER_ATTR(drv, S_IRUGO, mydriver_show, NULL);
static int __init my_driver_init(void)
{
int ret;
ret = driver_register(&my_driver); /* 注册驱动程序 */
if(ret != 0){
printk(KERN_NOTICE"can not register driver!\n");
return ret;
}
driver_create_file(&my_driver, &driver_attr_drv);
return ret;
}
static void my_driver_exit(void)
{
driver_unregister(&my_driver);
}
module_init(my_driver_init);
module_exit(my_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("TboyARM Inc.");
以上是简单的总线、驱动、设备模型的实例代码。都是经过验证了的。