自学内容网 自学内容网

【Linux】总线-设备-驱动模型

背景

前面,我们介绍了写驱动代码的一些常规步骤,并且也写了最基本的驱动代码,但是那些代码存在着问题,我们将硬件的信息都写进了驱动里了,如果我们在杂项设备驱动中控制led,那么会在硬件操作接口中包含硬件信息,如果引脚有变化,这个驱动代码就得重新修改,虽然修改也很简单,但是从框架的角度来看,这是不合理的,相当于驱动代码写死了。

于是,Linux引入了设备驱动模型分层的概念,将我们编写的驱动代码分为两块:设备和驱动。设备负责提供硬件资源,驱动代码负责去使用这些设备提供的硬件资源。并由总线将他们联系起来。

bus
device
driver

设备(device):挂载在某个总线的物理设备。

驱动(driver):与特定设备相关的软件,负责初始化该设备以及提供一些操作该设备的操作方式。

总线(bus):负责管理挂载对应总线的设备以及驱动。

类(class):对于具有相同功能的设备,归结到一种类别,进行分类管理。

重要目录

/sys/bus目录下的每个子目录都是注册好了的总线类型,每个总线类型下包含两个子目录(/sys/bus/devicessys/bus/drivers)。其中/sys/bus/devices下是该总线下的所有设备,而这些设备都是符号链接,他们分别指向真正的设备(/sys/devices目录下)。sys/bus/drivers目录下是所有注册在这个总线上的驱动,每个子目录下是一些可以观察和修改的driver参数。

/sys/devices目录下是全局设备结构体系,包含所有被发现的注册在各种总线上的各种物理设备。

/sys/dev目录下是所有的设备节点,但实际上都是些链接文件,同样指向了/sys/devices目录下的文件。

/sys/class目录下则是包含所有注册在kernel里面的设备类型,这是按照设备功能分类的设备模型。
在这里插入图片描述

框架原理

  • 总线管理着两个链表:设备链表和驱动链表。
  • 当我们向内核注册一个驱动时,便插入到总线的驱动链表。
  • 当我们向内核注册一个设备时,便插入到总线的设备链表。
  • 在插入的同时,总线会执行一个bus_type结构体中的match方法对新插入的设备或驱动进行匹配。
  • 匹配成功后,会调用驱动device_driver结构体中的probe方法。通常在probe方法中获取设备资源,具体的功能可自定义。
  • 移除设备或驱动时,会调用device_driver结构体中remove方法。

设备驱动模型

总线

总线是连接处理器和设备之间的桥梁,总线代表着同类设备需要共同遵守的工作时序。

在这里插入图片描述

bus_type结构体(内核源码/include/linux/device.h)

struct bus_type {
    const char              *name;
    const struct attribute_group **bus_groups;
    const struct attribute_group **dev_groups;
    const struct attribute_group **drv_groups;

    int (*match)(struct device *dev, struct device_driver *drv);
    int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
    int (*probe)(struct device *dev);
    int (*remove)(struct device *dev);

    int (*suspend)(struct device *dev, pm_message_t state);
    int (*resume)(struct device *dev);

    const struct dev_pm_ops *pm;

    struct subsys_private *p;

};
  • name :指定总线的名称,当新注册一种总线类型时,会在/sys/bus目录创建一个新的目录,目录名就是该参数的值;
  • drv_groups、dev_groups、bus_groups :分别表示驱动、设备以及总线的属性。这些属性可以是内部变量、字符串等等。通常会在对应的/sys目录下在以文件的形式存在,对于驱动而言,在目录/sys/bus//driver/存放了设备的默认属性;设备则在目录/sys/bus//devices/中。这些文件一般是可读写的,用户可以通过读写操作来获取和设置这些attribute的值。
  • match :当向总线注册一个新的设备或者是新的驱动时,会调用该回调函数。该回调函数主要负责判断是否有注册了的驱动适合新的设备,或者新的驱动能否驱动总线上已注册但没有驱动匹配的设备;
  • uevent :总线上的设备发生添加、移除或者其它动作时,就会调用该函数,来通知驱动做出相应的对策。
  • probe :当总线将设备以及驱动相匹配之后,执行该回调函数,最终会调用驱动提供的probe函数。
  • remove :当设备从总线移除时,调用该回调函数;
  • suspend、resume :电源管理的相关函数,当总线进入睡眠模式时,会调用suspend回调函数;而resume回调函数则是在唤醒总线的状态下执行;
  • pm :电源管理的结构体,存放了一系列跟总线电源管理有关的函数,与device_driver结构体中的pm_ops有关;
  • p :该结构体用于存放特定的私有数据,其成员klist_devices和klist_drivers记录了挂载在该总线的设备和驱动;

在实际编写linux驱动模块时,Linux内核已经为我们写好了大部分总线驱动,正常情况下我们一般不会去注册一个新的总线, 内核中提供了bus_register函数来注册总线,以及bus_unregister函数来注销总线。

注册/注销总线API(内核源码/drivers/base/bus.c)
    
int bus_register(struct bus_type *bus);
void bus_unregister(struct bus_type *bus);

设备

device结构体(内核源码/include/linux/device.h)

struct device {
const char *init_name;
        struct device           *parent;
        struct bus_type *bus;
        struct device_driver *driver;
        void            *platform_data;
        void            *driver_data;
        struct device_node      *of_node;
        dev_t                   devt;
        struct class            *class;
void (*release)(struct device *dev);
        const struct attribute_group **groups;  /* optional groups */
struct device_private   *p;
........
};
  • init_name :指定该设备的名称,总线匹配时,一般会根据比较名字,来进行配对;
  • parent :表示该设备的父对象,前面提到过,旧版本的设备之间没有任何关联,引入Linux设备模型之后,设备之间呈树状结构,便于管理各种设备;
  • bus :表示该设备依赖于哪个总线,当我们注册设备时,内核便会将该设备注册到对应的总线。
  • of_node :存放设备树中匹配的设备节点。当内核使能设备树,总线负责将驱动的of_match_table以及设备树的compatible属性进行比较之后,将匹配的节点保存到该变量。
  • platform_data :一个指针,用于保存具体的平台相关的数据。具体的driver模块,可以将一些私有的数据,暂存在这里,需要使用的时候,再拿出来,因此设备模型并不关心该指针得实际含义。
  • driver_data :同上,驱动层可通过dev_set/get_drvdata函数来获取该成员;
  • class :指向了该设备对应类,开篇我们提到的触摸,鼠标以及键盘等设备,对于计算机而言,他们都具有相同的功能,都归属于输入设备。我们可以在/sys/class目录下对应的类找到该设备,如input、leds、pwm等目录;
  • dev :dev_t类型变量,字符设备章节提及过,它是用于标识设备的设备号,该变量主要用于向/sys目录中导出对应的设备。
  • release :回调函数,当设备被注销时,会调用该函数。如果我们没定义该函数时,移除设备时,会提示“Device ‘xxxx’ does not have a release() function, it is broken and must be fixed”的错误。
  • group :指向struct attribute_group类型的指针,指定该设备的属性;
  • *p:是私有数据结构指针,该指针中会保存子设备链表、用于添加到bus/driver/prent等设备中的链表头等等。
内核也提供相关的API来注册和注销设备
    
int device_register(struct device *dev);
void device_unregister(struct device *dev);

在讲解总线的时候,我们说过,当成功注册总线时,会在/sys/bus目录下创建对应总线的目录,该目录下有两个子目录,分别是drivers和devices, 我们使用device_register注册的设备从属于某个总线时,该总线的devices目录下便会存在该设备文件。

驱动

device_driver结构体(内核源码/include/linux/device.h)
    
struct device_driver {
        const char              *name;
        struct bus_type         *bus;

        struct module           *owner;
        const char              *mod_name;      /* used for built-in modules */

        bool suppress_bind_attrs;       /* disables bind/unbind via sysfs */

        const struct of_device_id       *of_match_table;
        const struct acpi_device_id     *acpi_match_table;

        int (*probe) (struct device *dev);
        int (*remove) (struct device *dev);

        const struct attribute_group **groups;
        struct driver_private *p;

};
  • name :指定驱动名称,总线进行匹配时,利用该成员与设备名进行比较;
  • bus :表示该驱动依赖于哪个总线,内核需要保证在驱动执行之前,对应的总线能够正常工作;
  • suppress_bind_attrs :布尔量,用于指定是否通过sysfs导出bind与unbind文件,bind与unbind文件是驱动用于绑定/解绑关联的设备。
  • owner :表示该驱动的拥有者,一般设置为THIS_MODULE;
  • of_match_table :指定该驱动支持的设备类型。当内核使能设备树时,会利用该成员与设备树中的compatible属性进行比较。
  • remove :当设备从操作系统中拔出或者是系统重启时,会调用该回调函数;
  • probe :当驱动以及设备匹配后,会执行该回调函数,对设备进行初始化。通常的代码,都是以main函数开始执行的,但是在内核的驱动代码,都是从probe函数开始的。
  • group :指向struct attribute_group类型的指针,指定该驱动的属性;

内核提供了driver_register函数以及driver_unregister函数来注册/注销驱动,成功注册的驱动会记录在/sys/bus//drivers目录

device_driver结构体(内核源码/include/linux/device.h)
    

int driver_register(struct device_driver *drv);
void driver_unregister(struct device_driver *drv);

示例

暂无。一般情况我们都会在系统已注册的总线上写驱动和设备。这篇文章主要了解这种架构的原理。


原文地址:https://blog.csdn.net/zhudaokuan/article/details/142987692

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!