引入:

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

  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节点和设备电源的管理
  • 使得驱动代码,具有更好的扩展性和跨平台性,就不会因为新的平台而再次编写驱动

 

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

 

 

 

3、Sysfs文件系统

  在linux系统中有一个sysfs伪文件系统,挂载与 sys/ 目录下,目录详细描述了所有与设备、驱动和硬件相关的信息。

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

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

 

 

 图中,USB总线下,挂载了USB的drivers和devices,devices隶属于USB总线,会以软连接的形式指向 /sys 下的Devices文件夹(记录了所有的设备信息)里的对应设备usb2。Classes文件下是对设备的分类,例如Mouse1,鼠标不仅属于输入设备,也属于USB设备。通过软链接将设备管理起来,可以通过总线的方式、设备方式或classes类的方式查看设备。

  在bus目录下是系统所有的总线,在系统开机后,这些总线会自动创建,如果想要构建自己的总线,设备与驱动,该如何做?

  4、总线模型编程

  基本实现如下:

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

 

 

   在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)//将设备从总线上注销

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

  

  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)

平台总线 —— 设备驱动模型 —bus-dev-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 }
mybus_match

相关文章:

  • 2022-01-16
  • 2021-04-16
  • 2022-01-31
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-01-23
猜你喜欢
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2021-09-08
  • 2021-09-27
相关资源
相似解决方案