在Linux内核中,编写驱动一般都要经历:申请注册设备号、注册操作方法集、硬件初始化、创建设备节点,虽然设备不同,但是每个设备驱动的编写都要经历这几步,在这些流程步骤中,只有硬件初始化随着设备不同,会存在很大差异,但是其他步骤都是一模一样的,为了提高代码重用,降低驱动开发的复杂度,引入了总线概念:在编写代码时,将设备硬件信息和操作逻辑剥离,硬件信息独立在device中,操作逻辑在driver中,这样在设备硬件信息变动时,只需要修改device就可以了
总线框架将整个驱动分成3个部分,每个部分都定义了响应的结构体:device、bus、driver:
/*
bus_type类型中关于总线的属性成员不止下列这些,但是这里只以常用的作说明
*/
struct bus_type {
const char *name; //总线名称,最后driver和device是不是属于同一总线,就全看他相不相同
int (*match)(struct device *dev, struct device_driver *drv);//需要我们在自定义总线的时候去实现的匹配规则函数,也就是说driver和device互相匹配规则的实现
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);//生成/sys/bus下的结构,不需要我们关心,由内核提供
int (*probe)(struct device *dev); //当match匹配成功返回1的时候,才会调用该函数
int (*remove)(struct device *dev);//当总线卸载时调用
};
驱动结构体:
/*
总线中对驱动的常见描述
*/
struct device_driver {
const char *name; //驱动和设备用来匹配的名称
struct bus_type *bus; //该驱动所属的总线名称
int (*probe) (struct device *dev); //当驱动注册的时候,和设备匹配之后调用的探测函数,一般用来完成对设备的硬件初始化、方法注册
int (*remove) (struct device *dev);//卸载驱动的时候执行,和probe执行内容相反
};
设备结构体:
/*
描述设备对象信息
*/
struct device {
const char *init_name; /* 设备匹配名称 */
struct bus_type *bus; /* 设备所属总线名称 */
void (*release)(struct device *dev);/*设备注销时调用的回收数据函数*/
void *platform_data; /* 描述设备信息的自定义结构体指针 */
};
注意对描述总线结构体的变量进行符号导出
#include
#include
#include
#include
#include //自己定义的dbgprintk调试打印接口,这里要注意,如果在Ubuntu里面使用内核打印的时候,Ubuntu12.04默认将内核输出对应的超级终端为1(切换时直接ctrl+alt+f1-f6,终端1就是f1)
int mybus_match(struct device *dev, struct device_driver *drv)
{
dbgprintk("in mybus_match");
return 1;
}
struct bus_type mybus = {
.name = "mybus",
.match = mybus_match,
};
static int __init mybus_init(void)
{
int ret = 0;
//1、注册总线
ret = bus_register(&mybus);
if(ret){
dbgprintk("bus register err!");
}
dbgprintk("mybus init !");
return ret;
}
static void __exit mybus_exit(void)
{
dbgprintk("mybus exit !");
bus_unregister(&mybus);
}
EXPORT_SYMBOL(mybus); //一定要导出,driver和device需要使用
module_init(mybus_init);
module_exit(mybus_exit);
MODULE_LICENSE("GPL");
注意外部声明描述总线结构体的变量,在编译模块的时候和总线定义模块在一个Makefile中编译(使用到总线模块导出符号)
#include
#include
#include
#include
extern struct bus_type mybus; //声明,而这时候需要用到总线模块中的符号导出,这需要将二者放在一个Makefile中编译
int mydrv_probe (struct device *dev)
{
dbgprintk("driver matched device successed!");
return 0;
}
int mydrv_remove (struct device *dev)
{
dbgprintk("remove device !");
return 0;
}
static struct device_driver mydrv = {
.name = "mybus",
.bus = &mybus,
.probe = mydrv_probe,
.remove = mydrv_remove,
};
static int __init mybus_drv_init(void)
{
int ret = 0;
dbgprintk("mybus drv init !");
//1、注册driver
ret = driver_register(&mydrv);
if(ret){
dbgprintk("driver register error !");
return ret;
}
return ret;
}
static void __exit mybus_drv_exit(void)
{
dbgprintk("mybus is exit!");
driver_unregister(&mydrv);
}
module_init(mybus_drv_init);
module_exit(mybus_drv_exit);
MODULE_LICENSE("GPL");
注意外部声明描述总线结构体的变量,同时自定义设备数据,在编译模块的时候和总线定义模块在一个Makefile中编译(使用到总线模块导出符号)
#include
#include
#include
#include
#include
extern struct bus_type mybus;
struct mydev_desc{
char *name;
int irqno;
unsigned long addr;
};
struct mydev_desc devinfo = {
.name = "mybus",
.irqno = 999,
.addr = 0x40008000,
};
void mydev_release(struct device *dev)
{
dbgprintk("device release !");
}
struct device mydev = {
.init_name = "my_device",
.bus = &mybus,
.release = mydev_release,
.platform_data = &devinfo,
};
static int __init mybus_init(void)
{
int ret = 0;
//1、注册总线
ret = device_register(&mydev);
if(ret<0){
dbgprintk("device register err!");
}
dbgprintk("mydev init !");
return ret;
}
static void __exit mybus_exit(void)
{
dbgprintk("mydev exit !");
device_unregister(&mydev);
}
EXPORT_SYMBOL(mybus);
module_init(mybus_init);
module_exit(mybus_exit);
MODULE_LICENSE("GPL");