• 平台总线 —— 设备驱动模型 —bus-dev-drv


    引入:

      在之前的基础上,我们已经可以写出一个功能比较完备的字符设备驱动,但是还是存在一些问题:

      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
    ");
     9         return 1;
    10     }else{
    11         printk("match failed
    ");
    12         return 0;
    13     }
    14     return 0;
    15 }
    mybus_match

      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-----------
    ",__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-----------
    ",__FUNCTION__);
    22     int ret;
    23     //将device注册到总线中去
    24     ret = device_register(&mydev);
    25     if(ret < 0)
    26     {
    27         printk("device_register failed
    ");
    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");
    mydev.c
     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-------------
    ",__FUNCTION__);
    10     return 0;
    11 }
    12 
    13 int mydrv_remove (struct device *dev)
    14 {
    15     printk("--------------%s-------------
    ",__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-------------
    ",__FUNCTION__);
    36 
    37     //将驱动注册到总线中
    38     int ret;
    39     ret = driver_register(&mydrv);
    40     if(ret < 0)
    41     {
    42         printk("driver register failed
    ");
    43         return ret;
    44     }
    45 
    46     return 0;
    47 }
    48 
    49 static void __exit mydrv_exit(void)
    50 {
    51     printk("-------------%s------------
    ",__FUNCTION__);
    52     driver_unregister(&mydrv);
    53 }
    54 
    55 
    56 
    57 module_init(mydrv_init);
    58 module_exit(mydrv_exit);
    59 
    60 MODULE_LICENSE("GPL");
    mydrv.c
     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
    ");
    14         return 1;
    15     }else{
    16         printk("match failed
    ");
    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------------
    ",__FUNCTION__);
    35     //构建总线      /sys/bus/mybus
    36     int ret = bus_register(&mybus);
    37     if(ret != 0)
    38     {
    39         printk("bus_register error
    ");
    40         return ret;
    41     }
    42     return 0;
    43 }
    44 
    45 static void __exit mybus_exit(void)
    46 {
    47     printk("------------%s------------
    ",__FUNCTION__);
    48     bus_unregister(&mybus);
    49 }
    50 
    51 
    52 module_init(mybus_init);
    53 module_exit(mybus_exit);
    54 
    55 MODULE_LICENSE("GPL");
    mybus.c

    测试:

    当有设备文件和驱动算法匹配(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-----------
    ",__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-----------
    ",__FUNCTION__);
    32     int ret;
    33     //将device注册到总线中去
    34     ret = device_register(&mydev);
    35     if(ret < 0)
    36     {
    37         printk("device_register failed
    ");
    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");
    mydev.c
     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-------------
    ",__FUNCTION__);
    13 
    14     pdesc = (struct mydev_desc *)dev->platform_data;
    15 
    16     printk("name  = %s
    ", pdesc->name);
    17     printk("irqno = %d
    ", 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-------------
    ",__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-------------
    ",__FUNCTION__);
    48 
    49     //将驱动注册到总线中
    50     int ret;
    51     ret = driver_register(&mydrv);
    52     if(ret < 0)
    53     {
    54         printk("driver register failed
    ");
    55         return ret;
    56     }
    57 
    58     return 0;
    59 }
    60 
    61 static void __exit mydrv_exit(void)
    62 {
    63     printk("-------------%s------------
    ",__FUNCTION__);
    64     driver_unregister(&mydrv);
    65 }
    66 
    67 
    68 
    69 module_init(mydrv_init);
    70 module_exit(mydrv_exit);
    71 
    72 MODULE_LICENSE("GPL");
    mydrv.c
     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
    ");
    14         return 1;
    15     }else{
    16         printk("match failed
    ");
    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------------
    ",__FUNCTION__);
    35     //构建总线      /sys/bus/mybus
    36     int ret = bus_register(&mybus);
    37     if(ret != 0)
    38     {
    39         printk("bus_register error
    ");
    40         return ret;
    41     }
    42     return 0;
    43 }
    44 
    45 static void __exit mybus_exit(void)
    46 {
    47     printk("------------%s------------
    ",__FUNCTION__);
    48     bus_unregister(&mybus);
    49 }
    50 
    51 
    52 module_init(mybus_init);
    53 module_exit(mybus_exit);
    54 
    55 MODULE_LICENSE("GPL");
    mybus.c
     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
    dev_info.h

      测试结果:

      

    小结:

      主要学习了设备驱动模型的概念,了解了驱动设备模型中的分离与合并的实现。分离,是指将具有差异性的硬件信息与稳定的算法与控制逻辑分离开,体现在文件的分离。那么二者之间的桥梁是什么?就是虚拟的bus总线,体现在/sys/bus下,bus可以使用系统自带的,也可以自定义。在二者详总线注册之后,可以通过总线的match方法进行匹配,完成了第一次的合并,match之后系统会自动调用probe探测函数,探测什么呢?探测硬件状态是否正常,因为match匹配的是软件上的信息。除了探测,probe方法还会提供操作的接口fops,使驱动能对硬件进行控制,等,具体实现在平台设备驱动中学习。  

      对于dev文件,设备相关,代码量不多,但是需要经常改动。对于drv文件,内部实现的功能多,代码量大,但是改动少。

  • 相关阅读:
    常用知识点集合
    LeetCode 66 Plus One
    LeetCode 88 Merge Sorted Array
    LeetCode 27 Remove Element
    LeetCode 26 Remove Duplicates from Sorted Array
    LeetCode 448 Find All Numbers Disappeared in an Array
    LeetCode 219 Contains Duplicate II
    LeetCode 118 Pascal's Triangle
    LeetCode 119 Pascal's Triangle II
    LeetCode 1 Two Sum
  • 原文地址:https://www.cnblogs.com/y4247464/p/12399228.html
Copyright © 2020-2023  润新知