总线设备驱动模型学习
一、总线概述
随着技术的不断进步,系统的拓扑结构也越来越复杂,对热插拔,跨平台移植性的要求也越来越高,2.4内核已经难以满足这些需求。为适应这种形势的需要,从Linux 2.6内核开始提供了全新的设备模型。
总线:创建一条总线,跟按键一样,首先是描述总线结构,接着是注册总线,注销总线。总线设备,例如usb总线,上面会有很多类型的usb的驱动,例如鼠标、键盘.....等,当我们把之一的usb插上的时候,usb总线会把每个驱动遍历一遍,找到相应的驱动程序执行。
1.1总线描述
在 Linux 内核中, 总线由 bus_type 结构表示, 定义在 <linux/device.h>
1 struct bus_type{ 2 const char *name; /*总线名称*/ 3 int (*match) (struct device *dev, struct 4 device_driver *drv); /*驱动与设备的匹配函数*/ 5 … 6 }
int (*match)(struct device * dev, struct device_driver * drv)当一个新设备或者新驱动被添加到这个总线时,该函数被调用。用于判断指定的驱动程序是否能处理指定的设备。若可以,则返回非零。
1.2总线注册于注销
总线的注册使用:
bus_register(struct bus_type *bus)
若成功,新的总线将被添加进系统,并可在/sys/bus 下看到相应的目录。
总线的注销使用:
void bus_unregister(struct bus_type *bus)
创建一条总线:
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/kernel.h> 4 #include <linux/device.h> 5 MODULE_LICENSE("GPL"); 6 int my_match(struct device *dev,struct device_driver *drv) 7 { 8 return 0; 9 } 10 11 struct bus_type my_bus_type = { 12 .name = "my_bus", 13 .match = my_match, 14 }; 15 16 EXPORT_SYMBOL(my_bus_type);//变量输出 17 18 int my_bus_init(void) 19 { 20 int ret; 21 ret = bus_register(&my_bus_type); 22 return ret; 23 } 24 25 void my_bus_exit(void) 26 { 27 bus_unregister(&my_bus_type); 28 } 29 30 module_init(my_bus_init); 31 module_exit(my_bus_exit);
控制台运行:
二、总线驱动
2.1总线设备驱动描述结构
在 Linux内核中, 驱动由 device_driver结构表示。
1 struct device_driver{ 2 { 3 const char *name; /*驱动名称*/ 4 struct bus_type *bus; /*驱动程序所在的总线*/ 5 int (*probe) (struct device *dev); 6 … 7 }
prode函数,当我们有设备加到总线的时候,当设备与总线的某个借口相匹配的时候,系统就会调用prode函数。对我的设备进行相应的初始化。
2.2驱动的注册于注销
驱动的注册:
int driver_register(struct device_driver *drv)
驱动的注销使用:
void driver_unregister(struct device_driver *drv)
总线设备驱动程序:
#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/device.h> MODULE_LICENSE("GPL"); extern struct bus_type my_bus_type; int my_probe (struct device *dev) { printk(KERN_EMERG"driver found the device it can handle!\n"); //如果是实际应用的驱动,这里会做很多的硬件初始化操作 return 0; } struct device_driver my_driver = { .name = "my_dev", .bus = &my_bus_type, /*驱动是属于哪一条总线的。这里来自总线驱动程序,所以总线的代码 要有EXPORT_SYMBOL符号导出标志*/ .probe = my_probe, }; int my_driver_init(void) { int ret; ret = driver_register(&my_driver); return 0; } void my_driver_exit(void) { driver_unregister(&my_driver); } module_init(my_driver_init); module_exit(my_driver_exit);
上面的目录/sys/bus/存的是系统总线的各类接口,可以看到了创建的mybus总线,进去,打开驱动drivers的目录,里面有创建的驱动my_dev,驱动挂载成功了。
三、设备
3.1设备描述结构
在 Linux内核中, 设备由struct device结构表示。
1 struct device{ 2 { 3 const char*init_name; /*设备的名字*/ 4 struct bus_type *bus; /*设备所在的总线*/ 5 … 6 }
3.2设备注销与注册
设备的注册使用如下函数
int device_register(struct device *dev)
设备的注销使用:
void device_unregister(struct device *dev)
设备程序如下:
1 #include <linux/device.h> 2 #include <linux/module.h> 3 #include <linux/kernel.h> 4 #include <linux/init.h> 5 6 MODULE_LICENSE("GPL"); 7 8 extern struct bus_type my_bus_type; 9 10 struct device my_dev= 11 { 12 .init_name = "my_dev", 13 .bus = &my_bus_type, 14 }; 15 16 int my_device_init(void) 17 { 18 int ret; 19 ret = device_register(&my_dev); 20 return 0; 21 } 22 23 void my_device_exit(void) 24 { 25 device_unregister(&my_dev); 26 } 27 28 module_init(my_device_init); 29 module_exit(my_device_exit);
四、编译运行
Makefile文件如下:
obj-m := driver.o bus.o device.o KDIR :=/home/kernel/kernel/linux-ok6410 all: make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm clean: rm -f *.ko *.o *.order *.symvers *.mod.c *~
将我们的总线程序修改为:
1 int my_match(struct device *dev,struct device_driver *drv) 2 { 3 return !strncmp(dev->init_name,drv->name,strlen(drv->name)); //名字匹配 4 5 }
运行结果如下:
分析错误提醒:
出现了空指针:是在strncmp里出现了空指针,这个空指针是init_name;但是我们在我的device.c里已经.init_name="my_dev",为什么还是空指针呢?接下来看内核代码:
首先是找device_register:
进入上面的device_add函数:
上面的代码就是把不为空的init_name,赋值给dev_set_name,然后自身的值变为NULL。所以,我们的程序出现空指针的原因。这个值被赋值到了成员kobj.name:
因此我们将bus程序修改为:
1 int my_match(struct device *dev,struct device_driver *drv) 2 { 3 // return !strncmp(dev->init_name,drv->name,strlen(drv->name)); //名字匹配 4 return !strncmp(dev->kobj.name,drv->name,strlen(drv->name)); 5 }
编译运行:
当然也可以先执行设备程序在执行驱动程序,但是总线是必须先创建的