zoukankan      html  css  js  c++  java
  • 平台总线 —— 设备驱动模型 —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文件,内部实现的功能多,代码量大,但是改动少。

  • 相关阅读:
    bzoj1600 [Usaco2008 Oct]建造栅栏(递推)
    bzoj1607 / P2926 [USACO08DEC]拍头Patting Heads
    bzoj2733 / P3224 [HNOI2012]永无乡(并查集+线段树合并)
    loj2163 / bzoj2212 / P3521 [POI2011]ROT-Tree Rotations(线段树合并)
    UVA11090 Going in Cycle!!(二分判负环)
    【C++】位应用(2)-设置某位的值
    【C++】位应用(2)-设置某位的值
    【C++】位操作的应用
    【C++】位操作的应用
    C++ 强转注意问题
  • 原文地址:https://www.cnblogs.com/y4247464/p/12399228.html
Copyright © 2011-2022 走看看