Linux/Android总线设备驱动和平台设备驱动程序总结
总线平台驱动程序总结:只为自己肤浅的理解
修改部分主要为:my_bus.c和my_bus_device.c中的代码,也修改了一点见解部分都在这两块里面
以platform平台为例子的理解
Linux的驱动程序,模型一般在总线、设备、驱动3个模块
其结构为:
总线:bus_type
struct
const
struct
struct
struct
int
在2.6.32以前(具体多少我也不知道)会匹配device_driver(驱动程序)的name成员和
device(设备)的id_table,2.6.32的时候就不是匹配这个了,在device成员里面有个
init_name,最终将会和这个匹配。具体的注册过程你可以追踪device_register()这个函数,这个函数里面
有两个成员:如下
int
{
device_initialize(dev);
return
}不知道为什么在2.6.32里面用这个函数注册要失败,可能是某些地方没有初始化。后面就用
device_add(dev);这个函数来注册,结果就成功了。具体为什么同样你可以跟踪内核代码
int
struct
};
设备:核心成员如下
struct
struct
struct
struct
const
的关键
struct
struct
struct
void
....
}
驱动:struct
struct
const
struct
struct
const
bool
int
probe函数也是整个驱动程序的入口,在这个入口里面你可以注册很多东西,如字符设备驱动,混杂设备驱动
块设备驱动,网络设备驱动等等。
int
.....
struct
};
后面介绍的平台驱动--struct
工作流程:
这个模型:其实需要三个.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
static
{
return
}
static
{
printk(KERN_DEBUG"my
}
struct
.init_name
.release
};
struct
.name
.match
};
static
{
return
}
static
static
{
int
ret
if(ret)
return
ret
if(ret)
return
device_create_file(&my_bus,
if(bus_create_file(&my_bus_type,&bus_attr_version))
printk(KERN_NOTICE"Fail
return
}
static
{
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
2、设备部分:
#include
#include
#include
#include
#include
#include
extern
extern
//static
static
{
printk("my_dev_release!\n");
}
struct
.init_name
.bus
.parent
.release
};
static
{
return
}
static
static
{
int
//dev_set_name(&my_dev,
device_register(&my_dev);
device_create_file(&my_dev,
return
}
static
{
device_unregister(&my_dev);
}
module_init(my_bus_dev_init);
module_exit(my_bus_dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("TboyARM
3、驱动程序部分:
#include
#include
#include
#include
#include
#include
extern
static
{
printk("Driver
return
}
static
{
printk("Driver
return
}
struct
.name
.bus
.probe
.remove
};
static
{
return
}
static
static
{
int
driver_register(&my_driver);
driver_create_file(&my_driver,
return
}
static
{
driver_unregister(&my_driver);
}
module_init(my_driver_init);
module_exit(my_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("TboyARM
以上是简单的总线、驱动、设备模型的实例代码。接下来就来分析platform平台驱动。
platform平台驱动浅析
一、两个重要成员:
platform_device
platform_driver
根据前面一天的学习总线、驱动、设备模型应该还有一个platform_bus成员!查看内核源码发现
struct
.init_name
};这个东西内核已经给我们弄好了,这个也体现出来了总线也是一个设备,所以我们不需要写总线部分的驱动
struct
.name
.dev_attrs
.match
.uevent
.pm
};
二、工作流程:
1、定义platform_device
2、注册platform_device
3、定义platform_driver
4、注册platform_driver
三、平台设备的描述
1、结构体定义
struct
const
int
struct
u32
struct
struct
struct
};
2、platform_device的分配和使用
struct
name:
id:
3、注册平台设备
platform_device_register这个函数注册不成功。我也不知道为什么?就用下面一个函数
int
bus体现在哪里?
pdev->dev.bus这个成员,在调用这个函数之前,要先把这个成员给填充
4、设备资源的讲解:
struct
resource_size_t
resource_size_t
const
unsigned
struct
};
eg:
static
.start
.end
.flags
};
static
.start
.end
.flags
};
5、有资源,肯定就要能够获取资源。获取资源的函数
struct
dev:
type:
num:
eg:
platform_get_resource(pdev,IORESOURCE_IRQ,0)
四、平台驱动的描述:platform_driver
1、platform_driver结构体定义
struct
int
int
void
int
int
struct
driver成员里面的bus成员就是platform_bus_type;与前面的平台设备部分对应
struct
};
2、平台驱动的注册
int
五、驱动分析
platform_driver_register(struct
drv->driver.bus
return
ret
driver_attach(drv);
return
while
error
分析__driver_attach函数
static
if
return
if
driver_probe_device(drv,
如果总线没有的话,就调用驱动程序的probe函数,平台总线(platform_bus_type)没有probe函数,所以
将调用驱动程序的函数。
*************************
这个就是为什么说驱动程序的probe函数是整个驱动程序的精华,在这里我们可以在probe函数注册各种字符驱动、
块设备驱动程序、等等
平台总线的match函数
static
{
struct
struct
if
return
return
}
六、关于__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
看过前面的内容人都知道怎么用这个函数了,我要讲解的是后面的num成员,别小看这个成员哦,我可被纠结了半天,后面是参考dm9000的驱动才知道原因的。还是以例子来说明吧
static
[0]
.start
.end
.flags
.name
},
[1]
.start
.end
.flags
.name
},
[2]
.start
.end
.flags
.name
},
[3]
.start
.end
.flags
.name
},
};以这个例子来说明。如果你要获取前两个中断资源你可以资源调用这个函数
platform_get_resource(dev,IORESOURCE_IRQ,0)获取第一个
platform_get_resource(dev,IORESOURCE_IRQ,1)获取第二个
但是如果你要获取后面两个的资源你就必须要这样条用
platform_get_resource(dev,IORESOURCE_MEM,0)获取第一个
platform_get_resource(dev,IORESOURCE_MEM,1)获取第二个
看出点什么没有?我一开始以为后面的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开始编号的,其排列顺序应该是按照你是资源数组来排列的。