引入:
在之前的基础上,我们已经可以写出一个功能比较完备的字符设备驱动,但是还是存在一些问题:
1)设备和驱动没有分离;
2)没有类似于WINS的设备管理器,不可以方便的查看设备和驱动信息;
3)不能自动创建设备节点
4)不能自动加载驱动;
.......
以上问题的解决都依托Linux设备驱动模型,后面的内容会围绕以上问题展开。
1、Linux设备驱动模型的由来
回顾字符设备驱动框架实现步骤:
1)实现入口函数 xxx_init()和卸载函数 xxx_exit()
2)申请设备号 register_chrdev (与内核相关)
3)利用udev/mdev机制创建设备文件(节点) class_create, device_create (与内核相关)
4)硬件部分初始化
io资源映射 ioremap,内核提供gpio库函数 (与硬件相关)
注册中断(与硬件相关)
5)构建 file_operation结构 (与内核相关)
6)实现操作硬件方法 xxx_open,xxx_read,xxxx_write
对于硬件的操作无非就是硬件的地址与中断,地址就是提供操作硬件的途径,中断的作用就是异步地去通知SOC数据来了,你可以来处理我了。体现为IO资源映射与中断注册。
假设现在有5个video设备,那么要实现他们的设备驱动的话,每次都得从步骤1-6逐一编写。类似的设备的不同主要体现在硬件部分,在实现逻辑上都是相同的。
由此我们可以将设备驱动层中,硬件相关的易变的数据与稳定算法(改动小)的两部分分离开来,实现代码重用。那我们如何实现设备驱动的分离?接下来就介绍分离的概念:
2.分离的概念
分离就是在驱动层中使用总线把硬件相关的代码(固定的,如板子的网卡、中断地址)和驱动(会根据程序作变动,如点哪一个灯)分离开来,
即要编写两个文件:dev.c和drv.c(设备和驱动)
- 把硬件相关的东西抽出来,即可变的数据,具体体现在设备的差异。
- 把相对稳定的东西也抽出来,即稳定的算法,控制逻辑,可以理解成总线协议(如I2C)
优点:
- 将所有设备挂接到一个虚拟的总线上,方便sysfs节点和设备电源的管理
- 使得驱动代码,具有更好的扩展性和跨平台性,就不会因为新的平台而再次编写驱动
3、Sysfs文件系统
在linux系统中有一个sysfs伪文件系统,挂载与 sys/ 目录下,目录详细描述了所有与设备、驱动和硬件相关的信息。
图中,USB总线下,挂载了USB的drivers和devices,devices隶属于USB总线,会以软连接的形式指向 /sys 下的Devices文件夹(记录了所有的设备信息)里的对应设备usb2。Classes文件下是对设备的分类,例如Mouse1,鼠标不仅属于输入设备,也属于USB设备。通过软链接将设备管理起来,可以通过总线的方式、设备方式或classes类的方式查看设备。
在bus目录下是系统所有的总线,在系统开机后,这些总线会自动创建,如果想要构建自己的总线,设备与驱动,该如何做?
4、总线模型编程
基本实现如下:
在linux中,设备模型定义了各自的类:
struct bus_type — 代表总线 struct device — 代表设备 struct device_driver —代表驱动
1)总线对象:struct bus_type
描述一个总线,管理device和driver,完成匹配
struct bus_type { const char *name; //配对函数 int (*match)(struct device *dev, struct device_driver *drv); }
当总线上添加了新设备或者新驱动函数的时候,内核会调用一次或者多次这个函数。
如果现在添加了一个新的驱动(driver),内核就会调用所属总线(bus)的match函数,
配对总线上所有的设备(device),如果驱动能够对应处理其中一个设备,函数返回1,
告诉内核配对成功。一般的,match函数是判断设备的结构体成员device->bus_id
和驱动函数的结构体成员device_driver->name是否一致,如果一致,
那就表明配对成功。
2)注册和注销
int bus_register(struct bus_type *bus) void bus_unregister(struct bus_type *bus)
5、设备对象:device对象
1)描述设备信息:地址、中断号、及自定义的数据
1 struct device { 2 struct kobject kobj; //所有对象的父类 3 const char *init_name; // 在总线中会有一个名字,用于做匹配,在/sys/bus/mybus/devices/中的名字 4 struct bus_type *bus; //指向该device对象依附于总线的对象(指向哪个总线) 5 void *platform_data; // 自定义的数据,指向任何类型数据 6 } 7 /* kobject 是linux设备模型的根类,通过sys的API接口可以将两 8 * kobject对象关联起来,形成软链接。存在父子关系的kobject在 9 * /sys目录下体现为父子目录的关系。 10 *struct bus_type 、 struct device 、struct device_driver都 11 * 内嵌了struct kobject ,于是会生成对应的总线、设备、驱动的 12 * 目录 13 */
2)注册与注销
1 int device_register(struct device *dev) //将device注册到总线 2 void device_unregister(struct device *dev)//将设备从总线上注销
6、设备驱动对象:driver对象
1)描述设备驱动的方法(代码逻辑)
1 struct device_driver { 2 const char *name; 3 // 在总线中会有一个名字,用于做匹配,在/sys/bus/mybus/drivers/中的名字 4 struct bus_type *bus;//指向该driver对象依附于总线的对象 5 int (*probe) (struct device *dev); // 如果device和driver匹配之后,driver要做的事情 6 int (*remove) (struct device *dev); // 如果device和driver从总线移除之后,driver要做的事情 7 }
int (*probe)(struct device *dev);---- 探测函数 // 当配对(match)成功后,内核就会调用指定驱动中的probe函数来查 // 询设备能否被该驱动操作,如果可以,驱动就会对该设备进行相应的 //操作,如初始化。所以说,真正的驱动函数入口是在probe函数中。 int (*remove) (struct device *dev); —卸载函数 //当设备从总线中移除时,内核会调用驱动函数中的remove函数用, //进行一些设备卸载相应的操作
2)注册和注销
1 int driver_register(struct device_driver *drv) 2 void driver_unregister(struct device_driver *drv)
在mydev和mydrv中向bus总线注册的名字并不一致,故并不会调用probe方法,如果想要实现probe调用,就需要在bus中实现匹配的规则。
7、总线匹配的实现 -- match
要实现总线的匹配,首先要实现总线接口 match,匹配成功之后会自动调用driver的probe方法
1)实现bus中的match方法
int (*match)(struct device *dev, struct device_driver *drv); //如何获取 dev 与 drv ? //device和driver注册到bus后·,bus·会遍历device链表与driver链表 //逐个取出来匹配。这两个参数就是总线中的device与driver。
1 int mybus_match(struct device *dev, struct device_driver *drv) 2 { 3 //匹配成功返回1,失败返回0 4 //先取出dev与drv的name 5 //不能直接使用dev->init_name,因为会把init_name赋给父类kobject,然后置空 6 if(strncmp(drv->name, dev->kobj.name, strlen(drv->name))) 7 { 8 printk("match ok\n"); 9 return 1; 10 }else{ 11 printk("match failed\n"); 12 return 0; 13 } 14 return 0; 15 }
2)保证driver和device中的名字一样
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/device.h> 4 5 extern struct bus_type mybus; 6 7 void mydev_release(struct device *dev) 8 { 9 printk("------------%s-----------\n",__FUNCTION__); 10 } 11 12 //构建一个device对象 13 struct device mydev = { 14 .init_name = "fsdev_drv", /* initial name of the device */ 15 .bus = &mybus, 16 .release = mydev_release, 17 }; 18 19 static int __init mydev_init(void) 20 { 21 printk("------------%s-----------\n",__FUNCTION__); 22 int ret; 23 //将device注册到总线中去 24 ret = device_register(&mydev); 25 if(ret < 0) 26 { 27 printk("device_register failed\n"); 28 return ret; 29 } 30 31 return 0; 32 } 33 34 static int __exit mydev_exit(void) 35 { 36 device_unregister(&mydev); 37 38 } 39 40 module_init(mydev_init); 41 module_exit(mydev_exit); 42 43 MODULE_LICENSE("GPL");
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/device.h> 4 5 6 7 int mydrv_probe (struct device *dev) 8 { 9 printk("--------------%s-------------\n",__FUNCTION__); 10 return 0; 11 } 12 13 int mydrv_remove (struct device *dev) 14 { 15 printk("--------------%s-------------\n",__FUNCTION__); 16 return 0; 17 } 18 19 extern struct bus_type mybus; 20 21 22 struct device_driver mydrv = { 23 24 .name = "fsdev_drv", 25 .bus = &mybus, 26 .probe = mydrv_probe, 27 .remove= mydrv_remove, 28 29 }; 30 31 32 33 static int __init mydrv_init(void) 34 { 35 printk("--------------%s-------------\n",__FUNCTION__); 36 37 //将驱动注册到总线中 38 int ret; 39 ret = driver_register(&mydrv); 40 if(ret < 0) 41 { 42 printk("driver register failed\n"); 43 return ret; 44 } 45 46 return 0; 47 } 48 49 static void __exit mydrv_exit(void) 50 { 51 printk("-------------%s------------\n",__FUNCTION__); 52 driver_unregister(&mydrv); 53 } 54 55 56 57 module_init(mydrv_init); 58 module_exit(mydrv_exit); 59 60 MODULE_LICENSE("GPL");
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/device.h> 4 5 6 int mybus_match(struct device *dev, struct device_driver *drv) 7 { 8 //匹配成功返回1,失败返回0 9 //先取出dev与drv的name 10 //不能直接使用dev->init_name,因为会把init_name赋给父类kobject,然后置空 11 if(!strncmp(drv->name, dev->kobj.name, strlen(drv->name))) 12 { 13 printk("match ok\n"); 14 return 1; 15 }else{ 16 printk("match failed\n"); 17 return 0; 18 } 19 return 0; 20 } 21 22 23 //实例化一个bus对象 24 struct bus_type mybus = { 25 .name = "mybus", 26 .match = mybus_match, 27 28 }; 29 30 EXPORT_SYMBOL(mybus); //在mydev.c中会用到 31 32 static int __init mybus_init(void) 33 { 34 printk("------------%s------------\n",__FUNCTION__); 35 //构建总线 /sys/bus/mybus 36 int ret = bus_register(&mybus); 37 if(ret != 0) 38 { 39 printk("bus_register error\n"); 40 return ret; 41 } 42 return 0; 43 } 44 45 static void __exit mybus_exit(void) 46 { 47 printk("------------%s------------\n",__FUNCTION__); 48 bus_unregister(&mybus); 49 } 50 51 52 module_init(mybus_init); 53 module_exit(mybus_exit); 54 55 MODULE_LICENSE("GPL");
测试:
当有设备文件和驱动算法匹配(match)的时候自动执行probe。
总线在匹配设备和驱动之后驱动要考虑一个这样的问题,设备对应的软件数据结构代表着静态的信息,真实的物理设备此时是否正常还不一定,因此驱动需要探测这个设备是否正常。我们称这个行为为probe,至于如何探测,那是驱动才知道干的事情,
probe :
一般用来获取资源文件信息等,注册驱动,ioremap
等,可以理解为执行驱动的第一个程序
8、device与driver分离与合并的实现 -- probe
在上面我们通过bus实现了device与driver的分离,将硬件的差异性与稳定的控制逻辑以文件的形式分离开来,但是最后驱动还是要控制设备,获取硬件的数据,那么现在就要实现逻辑上的合并,如何实现:通过probe
在dev的device的结构体中,有一个platform_data成员,用来保存自定义数据,故可以另外构造一个描述设备信息的结构体,将其指针赋给platform_data,当probe获得了dev的device结构体,也就间接获取了设备信息
probe(struct device -> platfrom_data --->dev_info)
测试代码:
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/device.h> 4 #include "dev_info.h" 5 6 extern struct bus_type mybus; 7 8 9 10 struct mydev_desc dev_infos = { 11 .name = "dev_test", 12 .irqno = 666, 13 .addr = 0x20033000, 14 }; 15 16 void mydev_release(struct device *dev) 17 { 18 printk("------------%s-----------\n",__FUNCTION__); 19 } 20 21 //构建一个device对象 22 struct device mydev = { 23 .init_name = "fsdev_drv", /* initial name of the device */ 24 .bus = &mybus, 25 .release = mydev_release, 26 .platform_data = &dev_infos, //自定义数据 27 }; 28 29 static int __init mydev_init(void) 30 { 31 printk("------------%s-----------\n",__FUNCTION__); 32 int ret; 33 //将device注册到总线中去 34 ret = device_register(&mydev); 35 if(ret < 0) 36 { 37 printk("device_register failed\n"); 38 return ret; 39 } 40 41 return 0; 42 } 43 44 static int __exit mydev_exit(void) 45 { 46 device_unregister(&mydev); 47 48 } 49 50 51 module_init(mydev_init); 52 module_exit(mydev_exit); 53 54 MODULE_LICENSE("GPL");
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/device.h> 4 #include <linux/io.h> 5 #include "dev_info.h" 6 7 struct mydev_desc *pdesc; 8 9 //probe中设备相关数据来自struct device *dev 10 int mydrv_probe (struct device *dev) 11 { 12 printk("--------------%s-------------\n",__FUNCTION__); 13 14 pdesc = (struct mydev_desc *)dev->platform_data; 15 16 printk("name = %s\n", pdesc->name); 17 printk("irqno = %d\n", pdesc->irqno); 18 19 //假设要执行硬件相关操作 20 unsigned long *paddr = ioremap(pdesc->addr,8); 21 22 return 0; 23 } 24 25 int mydrv_remove (struct device *dev) 26 { 27 printk("--------------%s-------------\n",__FUNCTION__); 28 return 0; 29 } 30 31 extern struct bus_type mybus; 32 33 34 struct device_driver mydrv = { 35 36 .name = "fsdev_drv", 37 .bus = &mybus, 38 .probe = mydrv_probe, 39 .remove= mydrv_remove, 40 41 }; 42 43 44 45 static int __init mydrv_init(void) 46 { 47 printk("--------------%s-------------\n",__FUNCTION__); 48 49 //将驱动注册到总线中 50 int ret; 51 ret = driver_register(&mydrv); 52 if(ret < 0) 53 { 54 printk("driver register failed\n"); 55 return ret; 56 } 57 58 return 0; 59 } 60 61 static void __exit mydrv_exit(void) 62 { 63 printk("-------------%s------------\n",__FUNCTION__); 64 driver_unregister(&mydrv); 65 } 66 67 68 69 module_init(mydrv_init); 70 module_exit(mydrv_exit); 71 72 MODULE_LICENSE("GPL");
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/device.h> 4 5 6 int mybus_match(struct device *dev, struct device_driver *drv) 7 { 8 //匹配成功返回1,失败返回0 9 //先取出dev与drv的name 10 //不能直接使用dev->init_name,因为会把init_name赋给父类kobject,然后置空 11 if(!strncmp(drv->name, dev->kobj.name, strlen(drv->name))) 12 { 13 printk("match ok\n"); 14 return 1; 15 }else{ 16 printk("match failed\n"); 17 return 0; 18 } 19 return 0; 20 } 21 22 23 //实例化一个bus对象 24 struct bus_type mybus = { 25 .name = "mybus", 26 .match = mybus_match, 27 28 }; 29 30 EXPORT_SYMBOL(mybus); //在mydev.c中会用到 31 32 static int __init mybus_init(void) 33 { 34 printk("------------%s------------\n",__FUNCTION__); 35 //构建总线 /sys/bus/mybus 36 int ret = bus_register(&mybus); 37 if(ret != 0) 38 { 39 printk("bus_register error\n"); 40 return ret; 41 } 42 return 0; 43 } 44 45 static void __exit mybus_exit(void) 46 { 47 printk("------------%s------------\n",__FUNCTION__); 48 bus_unregister(&mybus); 49 } 50 51 52 module_init(mybus_init); 53 module_exit(mybus_exit); 54 55 MODULE_LICENSE("GPL");
1 #ifndef __DEV_INFO_H__ 2 3 #define _DEV_INFO_H__ 4 5 6 //单独设置一个自定义数据,描述设备的特性 7 struct mydev_desc{ 8 char *name; 9 int irqno; 10 unsigned long addr; 11 }; 12 13 #endif
测试结果:
小结:
主要学习了设备驱动模型的概念,了解了驱动设备模型中的分离与合并的实现。分离,是指将具有差异性的硬件信息与稳定的算法与控制逻辑分离开,体现在文件的分离。那么二者之间的桥梁是什么?就是虚拟的bus总线,体现在/sys/bus下,bus可以使用系统自带的,也可以自定义。在二者详总线注册之后,可以通过总线的match方法进行匹配,完成了第一次的合并,match之后系统会自动调用probe探测函数,探测什么呢?探测硬件状态是否正常,因为match匹配的是软件上的信息。除了探测,probe方法还会提供操作的接口fops,使驱动能对硬件进行控制,等,具体实现在平台设备驱动中学习。
对于dev文件,设备相关,代码量不多,但是需要经常改动。对于drv文件,内部实现的功能多,代码量大,但是改动少。
来源:https://www.cnblogs.com/y4247464/p/12399228.html