平台总线

来源:互联网 发布:淘宝的女童模特 编辑:程序博客网 时间:2024/04/17 05:43

Linux/Android总线设备驱动和平台设备驱动程序总结

总线平台驱动程序总结:只为自己肤浅的理解 针对2.6.32内核 mini2440 建议用Notepad个软件查看
修改部分主要为:my_bus.c和my_bus_device.c中的代码,也修改了一点见解部分都在这两块里面
以platform平台为例子的理解
Linux的驱动程序,模型一般在总线、设备、驱动3个模块
其结构为:
总线: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成员里面有个
init_name,最终将会和这个匹配。具体的注册过程你可以追踪device_register()这个函数,这个函数里面
有两个成员:如下
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}不知道为什么在2.6.32里面用这个函数注册要失败,可能是某些地方没有初始化。后面就用
device_add(dev);这个函数来注册,结果就成功了。具体为什么同样你可以跟踪内核代码
int (*probe)(struct device *dev); 
struct bus_type_private *p;
}; match函数必须要有
设备:核心成员如下
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj; 与驱动匹配的东西
const char *init_name; 注册设备的时候,会把这个东西填入到kobj.name里面,初始化设备的时候要用到这个。是最终匹配驱动
的关键
struct device_type *type;
struct bus_type *bus; 设备所在的总线,非常重要的一个成员
struct device_driver *driver; 
void *platform_data; 
....
}
驱动: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; 
bool suppress_bind_attrs; 
int (*probe) (struct device *dev); (总线来完成匹配工作的match函数)当驱动程序和设备匹配成功的时候,由总线驱动来调用驱动程序的probe函数
probe函数也是整个驱动程序的入口,在这个入口里面你可以注册很多东西,如字符设备驱动,混杂设备驱动
块设备驱动,网络设备驱动等等。
int (*remove) (struct device *dev); //善后工作,针对probe函数的善后工作
.....
struct driver_private *p; //这个成员也比较重要
};
后面介绍的平台驱动--struct platform_driver 留心的朋友你会发现,这个结构体只是对device_driver结构体的一个封装。里面增加了一点东西。
工作流程:
这个模型:其实需要三个.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.6.32的内核
1、总线部分:
#include 
#include 
#include 
#include 
#include 
#include 
static char *Version "$Rversion:1.0 $";
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,

};
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);
}
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);
if(ret)
return ret;
 

ret device_register(&my_bus);

if(ret)

return ret;


device_create_file(&my_bus, &dev_attr_mybus);

复制代码

if(bus_create_file(&my_bus_type,&bus_attr_version))
printk(KERN_NOTICE"Fail to create version attribute!\n");
return 0;
}
static void my_bus_exit(void)
{
bus_unregister(&my_bus_type);
 

device_unregister(&my_bus);

复制代码

}
EXPORT_SYMBOL(my_bus);
EXPORT_SYMBOL(my_bus_type);
module_init(my_bus_init);
module_exit(my_bus_exit);
MODULE_LICENSE("GPL"); 
MODULE_AUTHOR("TboyARM Inc."); 
2、设备部分:
#include 
#include 
#include 
#include 
#include 
#include 
 
extern struct device my_bus;

extern struct bus_type my_bus_type;
//static char ptr_name "my_dev" ;
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, 

.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);
static int __init my_bus_dev_init(void)
{
int ret 0;

//dev_set_name(&my_dev, "my_dev");

device_register(&my_dev);

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."); 
3、驱动程序部分:
#include 
#include 
#include 
#include 
#include 
#include 
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",
.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 0;
driver_register(&my_driver);
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."); 
以上是简单的总线、驱动、设备模型的实例代码。接下来就来分析platform平台驱动。
platform平台驱动浅析
一、两个重要成员:
platform_device
platform_driver
根据前面一天的学习总线、驱动、设备模型应该还有一个platform_bus成员!查看内核源码发现
struct device platform_bus {
.init_name "platform",
};这个东西内核已经给我们弄好了,这个也体现出来了总线也是一个设备,所以我们不需要写总线部分的驱动

struct bus_type platform_bus_type {
.name "platform",
.dev_attrs platform_dev_attrs, 属性
.match platform_match, 匹配函数
.uevent platform_uevent, 事件函数,相当的重要
.pm &platform_dev_pm_ops,
};
二、工作流程:
1、定义platform_device 定义一个设备
2、注册platform_device 注册一个设备
3、定义platform_driver
4、注册platform_driver
三、平台设备的描述
1、结构体定义
struct platform_device {
const char name; 设备名
int id; 设备编号,配合设备名使用
struct device dev; 前面一天中的device结构,其实platform_device只是对device结构进行了一个封装
u32 num_resources;
struct resource resource; 设备资源 非常重要,什么基地址,中断号等等这些都在这个里面
struct platform_device_id *id_entry;

struct pdev_archdata archdata;
};
2、platform_device的分配和使用
struct platform_device *platform_device_alloc(const char *name, int id)
name: 设备名
id: 设备id,一般为-1
3、注册平台设备
platform_device_register这个函数注册不成功。我也不知道为什么?就用下面一个函数
int platform_device_add(struct platform_device *pdev)在platform_bus总线里面里面去注册一个设备,(用这个函数注册的时候,其撤销函数要用platform_device_del)
bus体现在哪里?
pdev->dev.bus这个成员,在调用这个函数之前,要先把这个成员给填充 填充这个:platform_bus_type(填充的函数就是platform_device_alloc这个里面)
4、设备资源的讲解:
struct resource {
resource_size_t start; 资源的起始物理地址
resource_size_t end; 资源的结束物理地址
const char *name; 资源的名称
unsigned long flags; 资源的类型,必须MEM,IO,IRQ类型等等
struct resource *parent, *sibling, *child; 资源的链表指针
};
eg:
static struct resource s3c_wdt_resource1 {
.start 0x44100000,
.end 0x44200000,
.flags IORESOURCE_MEM, 内存资源
};
static struct resource s3c_wdt_resource2 {
.start 20,
.end 20, 有的设备有多个中断号
.flags IORESOURCE_IRQ, 中断资源
};
5、有资源,肯定就要能够获取资源。获取资源的函数
struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num)
dev: 资源所属的设备
type: 获取资源的类型
num: 获取的资源数
eg:
platform_get_resource(pdev,IORESOURCE_IRQ,0) 获取中断号
四、平台驱动的描述:platform_driver
1、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; 里面照样有一个device_driver成员,也只是前面一天中device_driver的扩展
driver成员里面的bus成员就是platform_bus_type;与前面的平台设备部分对应
struct platform_device_id *id_table; 匹配的时候也会用到
};
2、平台驱动的注册
int platform_driver_register(struct platform_driver *drv) 
五、驱动分析 <**************>相当的经典
platform_driver_register(struct platform_driver *drv)
drv->driver.bus &platform_bus_type; //这个就是这个驱动程序所在的总线,platform_bus_type是一个全局变量

return driver_register(&drv->driver); //前面一天中的注册驱动程序
ret bus_add_driver(drv); //往总线上添加这个驱动程序
driver_attach(drv);
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); //对这条总线上的每一个设备都去调用__driver_attach函数
while ((dev next_device(&i)) && !error)
error fn(dev, data); fn就是__driver_attach函数
分析__driver_attach函数
static int __driver_attach(struct device *dev, void *data)
if (!driver_match_device(drv, dev))
return 0; 匹配失败,返回0
if (!dev->driver)
driver_probe_device(drv, dev); 匹配成功后,会进入这个函数,主要作用:首先看总线有没有probe函数,有就调用总线的probe函数
如果总线没有的话,就调用驱动程序的probe函数,平台总线(platform_bus_type)没有probe函数,所以
将调用驱动程序的函数。
*************************
这个就是为什么说驱动程序的probe函数是整个驱动程序的精华,在这里我们可以在probe函数注册各种字符驱动、
块设备驱动程序、等等
平台总线的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);

if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;

return (strcmp(pdev->name, drv->name) == 0); 平台设备和平台驱动要相同,就匹配成功
}
六、关于__init修饰
每个程序都会有这样一个段,运行时只会调用一次。执行完后就把这段代码的内存释放掉了。 //起到节约内存的作用

platform驱动模型总结:(一定要分开平台驱动程序和平台设备这个概念,这样才好理解下面这段话)
其实platform驱动模型其实就是前面讲解的驱动、设备、总线模型的一个扩充。有心的你一定发现了,平台总线、平台设备、平台驱动的定义都只是前面三者的一个扩展。这里重点是体现了一种面向对象的设计的思想。内核在我们编译的时候就已经让其支持platform总线了。也就是总线驱动被嵌入到内核里面了,我们要在写驱动的时候,这个平台总线驱动不需要我们来编写了,我们只需要书写平台设备和平台驱动的代码,最后生成一个platform_driver.ko和platform_device.ko模块,然后加载就行了。然而这两个模块具体完成什么工作,这个可能是大家最关心的。platform_device.ko设备部分的模块最重要的就是提供这个设备有哪些资源,其硬件接口在哪里等等信息,然后给平台驱动一个统一的接口,这个接口就在resource成员中,如果你忘记了这个成员可以回过头去看platform_device这个结构体的定义。这个里面有中断资源、内存资源以及DMA资源等。如果你觉得要写这个模块(platform_device.ko不是platform_driver.ko)还是比较麻烦的话,还有一个比较简单的方式,就是在内核一个什么文件里面(我搞忘了)把你要注册的设备结构体(platform_device)定义在那个文件里面,然后将这个设备添加在那个数组里面,重新编译一次内核,进而用新内核启动你的开发板,这样的话内核就支持你需要的那个platform平台设备了。这样的话你就只需要写驱动部分的模块了。好吧,现在回到最重要的部分的讲解,也就是驱动模块了。平台驱动platform_driver结构体中有一个name成员,这个成员就是判断该驱动是否支持你的设备的关键,如果与你的设备结构体platform_device.dev.kobj里面的name完全相同的话,就证明是匹配的。而这个匹配的过程是由platform总线来做的。具体的说就是,假如我们的驱动程序已经加载到内核里面了,如果此时你将设备接入到开发板,内核查看你这个设备依赖的总线如果是platform总线,那么就将处理你这个设备的工作交给platform总线驱动来完成,platform总线首先调用它自己的match函数,这个函数会遍历自己的平台驱动程序链表,依次查看驱动程序是否能匹配你这个设备,进而处理你的这个设备(判断的依据就是根据驱动的name和platform_device.dev.kobj->name,应该是这个成员,不太记得清了)。当match函数返回真的时候,总线驱动程序就会调用平台驱动程序的probe函数,也就是说,这个函数才是平台驱动程序的真正的入口。(以上的例子过程其实就是在平台设备或平台驱动注册的时候要干的事情)在平台驱动程序的probe函数里面可以发挥你的所有IQ做很多是事情,比如注册字符驱动、块驱动、网络驱动、注册中断等等,然后实现各种read、write等函数。在这个probe函数里面既然要注册驱动以及中断等,可是这些总是需要一些基本信息吧?必须注册中断,你总得知道中断号吧?然而这个中断号、以及设备的内存起始物理地址这些应该不会在驱动程序中出现,不然你这个驱动程序就不好移植,达不到支持很多设备吧。考虑到这些原因,我们的内核的聪明之处就在于把设备和驱动分开了,设备资源在另外一个模块里面,设备需要的资源也在自己的模块里面。获取资源是平台驱动程序必须要干的事情,函数platform_get_resource就是获取资源的函数,在这里要详细的讲解一下这个函数,这个函数有点意思。之所以要详细的讲解一下,主要由于我用的时候出过问题,也是让大家少走一下弯路(也许你会一下就跳过这个误区,但是不凡有像我这样的人跳不过去,最后调试了半天才调试对)。其函数原型为
struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num)
看过前面的内容人都知道怎么用这个函数了,我要讲解的是后面的num成员,别小看这个成员哦,我可被纠结了半天,后面是参考dm9000的驱动才知道原因的。还是以例子来说明吧
static struct resource button_resource[] {
[0] {
.start IRQ_EINT19,
.end IRQ_EINT14,
.flags IORESOURCE_IRQ,
.name "key1",
},
[1] {
.start IRQ_EINT19,
.end IRQ_EINT14,
.flags IORESOURCE_IRQ,
.name "key2",
},
[2] {
.start 0x56000064,
.end 0x56000067,
.flags IORESOURCE_MEM,
.name "key1_mem",
},
[3] {
.start 0x56000064,
.end 0x56000067,
.flags IORESOURCE_MEM,
.name "key2_mem",
},
};以这个例子来说明。如果你要获取前两个中断资源你可以资源调用这个函数
platform_get_resource(dev,IORESOURCE_IRQ,0)获取第一个
platform_get_resource(dev,IORESOURCE_IRQ,1)获取第二个 ******注意num成员的值
但是如果你要获取后面两个的资源你就必须要这样条用
platform_get_resource(dev,IORESOURCE_MEM,0)获取第一个
platform_get_resource(dev,IORESOURCE_MEM,1)获取第二个 *******注意num成员的值
看出点什么没有?我一开始以为后面的num值就是资源数组的下标,我调试了很久都不行,加各种打印信息,后面知道如果把后面的num值写成了2和3,返回的值是一个NULL值,也就是
空指针,我那个郁闷啊,后面参考dm9000的代码才知道原因。原来num的值是要根据type来计算的,platform_get_resource(dev,IORESOURCE_IRQ,0)这个是获取中断资源的0号资源,
platform_get_resource(dev,IORESOURCE_MEM,0)是获取内存资源的0号资源,每一种资源都是从0开始编号的,其排列顺序应该是按照你是资源数组来排列的。