• Linux驱动开发十三.platform设备驱动——1.linux驱动的分离与分层


    (到目前为止,我们写的驱动都是简单的GPIO接口操作。但是实际环境中,我们会写I2C、SPI等设备的驱动。再回忆一下我们写驱动的流程,除了基础的框架基本上每次都是重复的造轮子。这样复杂的驱动写起来肯定是不能按照这种方法进行的,所以我们需要按照Linux的驱动分离与分层的思路构建我们以后的驱动架构。所以在引入platform设备驱动框架以前,我们要先了解一下Linux下的驱动的分离与分层。

    Linux驱动的分离与分层

    Linux是一个成熟、庞大的操作系统,所以代码的复用性尤其重要,否则就会在内核中存在大量重复的代码。特别是驱动程序,因为驱动程序占用了Linux内核代码量的大部分。这时候就需要我们在编写驱动的时候考虑到代码的复用特性。

    比方我们现在有3个平台,每个平台都要和一个I2C设备进行通讯(比如我们在写裸机I2C的MPU6050),那么按照我们先前写的驱动就是这样的

     对于每个平台来说,我们都需要写一套驱动来匹配外设,因为每个平台的I2C控制器是不一样的,但是右边的IC是一样的,所以写一个驱动就可以了。可是如果我们有多个I2C设备,比如触摸屏什么的,又要再写另外的驱动程序,并且要对应每个平台写I2C控制器的程序,这样就没法提现出代码的复用性。我们需要对就是在每个平台的I2C控制器写一个接口(也叫主机驱动),每个设备只要通过I2C的驱动接口来访问,就能把驱动文件简化

    这种情况我们只需要考虑统一接口就可以了。因为半导体厂商一般会直接提供现成的I2C控制器驱动让我们使用。

    前面说了,实际情况中我们肯定不会只用一种I2C设备,那么实际的驱动架构就是这样的

    上面这种状态就是驱动的分隔(或者说分离),一般说来I2C、SPI等设备都会用这种方式来简化驱动的开发过程。在实际的驱动开发中,一般I2C主机驱动是由半导体厂商写好了,我们只要提供好设备信息就可以了,比如设备接到了哪个I2C接口上,速率是多少等等。相当于将设备信息从设备驱动中剥离出来,驱动使用标准方法从设备树获取设备信息,然后根据这些信息来对设备进行初始化。这样就是驱动只负责驱动,设备只负责设备,中间有个中间件来做之间的匹配就行了。这个就是Linux中的总线(bus)、驱动(driver)和设备(device)模型,也就是驱动分离。总线就是那个中间件,负责驱动和设备之间的匹配。

     我们在向系统注册一个驱动的时候,总线会在右边的设备中查找,看看有没有与之匹配的设备,有的话就将两者联系起来,反而言之,在向系统中注册一个设备的时候,总线就会在左边的驱动中查找看看有没有和该设备匹配的驱动,这个过程就跟我们在电脑上插了个U盘的过程是一样的。后面我们要讲到platform驱动架构就是在这个思路下产生的。

    驱动分层

    在上面我们通过总线将驱动和设备分隔开来,下面要考虑的就是驱动的分层了。这个分层没有向分隔那么有明显的界限,可以参考网络的7层模型,主要是通过分层来实现不同层级处理不同工作的内容。比如我们经常说的input子系统,gpio子系统等等。针对input子系统来说,它负责了所有跟输入有关的驱动,包括键盘、鼠标、触摸屏等等,其中最底层的就是设备的原始驱动,负责获取输入设备的原始值,并且将获取到的输入事件上报给input的核心层;核心层会根据需求处理各种IO模型,然后提供给file_operations操作集合。我们在编写输入设备驱动的时候只要处理好输入事件上报的过程就可以了,也就是处理上面图中的核心层。至于这些事件的处理是由上层考虑的,就不用我们操心了。这样就能简化驱动编写的过程。驱动的分层是一个很抽象的过程,只能通过慢慢的积累找到概念。

    驱动——总线——设备

    前面根据驱动的分离和分层的概念延伸出了驱动(driver)——总线(bus)——设备(device)的驱动框架。由于分层的概念,总线用来索引设备和驱动,代码不用我们编写。需要我们编写的驱动和设备,不管驱动或设备在向总线注册的时候,总线都会向另一端匹配对应的内容。

    总线

    总线在内核里由一个bus_type结构体来描述(路径在include/linux/device.h下)

     1 struct bus_type {
     2     const char        *name;
     3     const char        *dev_name;
     4     struct device        *dev_root;
     5     struct device_attribute    *dev_attrs;    /* use dev_groups instead */
     6     const struct attribute_group **bus_groups;
     7     const struct attribute_group **dev_groups;
     8     const struct attribute_group **drv_groups;
     9 
    10     int (*match)(struct device *dev, struct device_driver *drv);
    11     int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
    12     int (*probe)(struct device *dev);
    13     int (*remove)(struct device *dev);
    14     void (*shutdown)(struct device *dev);
    15 
    16     int (*online)(struct device *dev);
    17     int (*offline)(struct device *dev);
    18 
    19     int (*suspend)(struct device *dev, pm_message_t state);
    20     int (*resume)(struct device *dev);
    21 
    22     const struct dev_pm_ops *pm;
    23 
    24     const struct iommu_ops *iommu_ops;
    25 
    26     struct subsys_private *p;
    27     struct lock_class_key lock_key;
    28 };

    其中第10行加粗的match函数就是总线用来根据注册的设备来找对应驱动的函数。所以match函数在每条总线上是必须都要有点。函数有两个参数

    int (*match)(struct device *dev, struct device_driver *drv);

    参数dev就是device,即设备,参数drv就是driver,即驱动。我们启动内核,挂载完成根目录系统以后可以看一下

     在/sys/bus目录下有各种总线设备,我打印了一下i2c总线下的内容,里面有两个文件夹:devices和drivers,分别代表了i2c总线下面有的设备和驱动。i2c控制器通过总线在设备和驱动中有没有可以匹配的。

    在要对linux内核注册一个新的总线,就要用到下面的函数进行挂载和卸载:

    extern int __must_check bus_register(struct bus_type *bus);
    extern void bus_unregister(struct bus_type *bus);

    可以在内核里面搜一下,一般进行总线注册的一般都是核心层完成的任务(文件名一般都是**-core.c),用户是不用考虑这个过程的。

    驱动

    驱动在内核里是由结构体device_driver来描述的(路径跟bus_type一样都是在device.h里)

     1 /**
     2  * struct device_driver - The basic device driver structure
     3  * @name:    Name of the device driver.
     4  * @bus:    The bus which the device of this driver belongs to.
     5  * @owner:    The module owner.
     6  * @mod_name:    Used for built-in modules.
     7  * @suppress_bind_attrs: Disables bind/unbind via sysfs.
     8  * @of_match_table: The open firmware table.
     9  * @acpi_match_table: The ACPI match table.
    10  * @probe:    Called to query the existence of a specific device,
    11  *        whether this driver can work with it, and bind the driver
    12  *        to a specific device.
    13  * @remove:    Called when the device is removed from the system to
    14  *        unbind a device from this driver.
    15  * @shutdown:    Called at shut-down time to quiesce the device.
    16  * @suspend:    Called to put the device to sleep mode. Usually to a
    17  *        low power state.
    18  * @resume:    Called to bring a device from sleep mode.
    19  * @groups:    Default attributes that get created by the driver core
    20  *        automatically.
    21  * @pm:        Power management operations of the device which matched
    22  *        this driver.
    23  * @p:        Driver core's private data, no one other than the driver
    24  *        core can touch this.
    25  *
    26  * The device driver-model tracks all of the drivers known to the system.
    27  * The main reason for this tracking is to enable the driver core to match
    28  * up drivers with new devices. Once drivers are known objects within the
    29  * system, however, a number of other things become possible. Device drivers
    30  * can export information and configuration variables that are independent
    31  * of any specific device.
    32  */
    33 struct device_driver {
    34     const char        *name;
    35     struct bus_type        *bus;
    36 
    37     struct module        *owner;
    38     const char        *mod_name;    /* used for built-in modules */
    39 
    40     bool suppress_bind_attrs;    /* disables bind/unbind via sysfs */
    41 
    42     const struct of_device_id    *of_match_table;
    43     const struct acpi_device_id    *acpi_match_table;
    44 
    45     int (*probe) (struct device *dev);
    46     int (*remove) (struct device *dev);
    47     void (*shutdown) (struct device *dev);
    48     int (*suspend) (struct device *dev, pm_message_t state);
    49     int (*resume) (struct device *dev);
    50     const struct attribute_group **groups;
    51 
    52     const struct dev_pm_ops *pm;
    53 
    54     struct driver_private *p;
    55 };

    结构体内容比较复杂,不过还好,内核里把重要的元素都给了注释。

    第35行的bus指定了该驱动属于哪个总线。我们要关注的就是第45行的probe函数,在驱动和设备匹配以后,这个probe函数就会执行。

    驱动的加载和释放是通过下面函数执行的

    1 extern int __must_check driver_register(struct device_driver *drv);
    2 extern void driver_unregister(struct device_driver *drv);

    在内核执行了drever_register函数注册驱动以后,bus会根据driver里的name去找对应的设备,大概的流程设这样的

     总之,就是在向总线总线注册新的驱动的时候,会检查当前总线下单所有设备,如果有点话就执行驱动里的probe函数。所以最后我们要做的就是这个probe函数。

    设备

    设备的描述也在device.h里的一个结构体(struct device),这个结构体成员变量太多了这里就不再陈列每个成员的注释了。

     1 struct device {
     2     struct device        *parent;
     3 
     4     struct device_private    *p;
     5 
     6     struct kobject kobj;
     7     const char        *init_name; /* initial name of the device */
     8     const struct device_type *type;
     9 
    10     struct mutex        mutex;    /* mutex to synchronize calls to
    11                      * its driver.
    12                      */
    13 
    14     struct bus_type    *bus;        /* type of bus device is on */
    15     struct device_driver *driver;    /* which driver has allocated this
    16                        device */
    17     void        *platform_data;    /* Platform specific data, device
    18                        core doesn't touch it */
    19     void        *driver_data;    /* Driver data, set and get with
    20                        dev_set/get_drvdata */
    21     struct dev_pm_info    power;
    22     struct dev_pm_domain    *pm_domain;
    23 
    24 #ifdef CONFIG_PINCTRL
    25     struct dev_pin_info    *pins;
    26 #endif
    27 
    28 #ifdef CONFIG_NUMA
    29     int        numa_node;    /* NUMA node this device is close to */
    30 #endif
    31     u64        *dma_mask;    /* dma mask (if dma'able device) */
    32     u64        coherent_dma_mask;/* Like dma_mask, but for
    33                          alloc_coherent mappings as
    34                          not all hardware supports
    35                          64 bit addresses for consistent
    36                          allocations such descriptors. */
    37     unsigned long    dma_pfn_offset;
    38 
    39     struct device_dma_parameters *dma_parms;
    40 
    41     struct list_head    dma_pools;    /* dma pools (if dma'ble) */
    42 
    43     struct dma_coherent_mem    *dma_mem; /* internal for coherent mem
    44                          override */
    45 #ifdef CONFIG_DMA_CMA
    46     struct cma *cma_area;        /* contiguous memory area for dma
    47                        allocations */
    48 #endif
    49     /* arch specific additions */
    50     struct dev_archdata    archdata;
    51 
    52     struct device_node    *of_node; /* associated device tree node */
    53     struct fwnode_handle    *fwnode; /* firmware device node */
    54 
    55     dev_t            devt;    /* dev_t, creates the sysfs "dev" */
    56     u32            id;    /* device instance */
    57 
    58     spinlock_t        devres_lock;
    59     struct list_head    devres_head;
    60 
    61     struct klist_node    knode_class;
    62     struct class        *class;
    63     const struct attribute_group **groups;    /* optional groups */
    64 
    65     void    (*release)(struct device *dev);
    66     struct iommu_group    *iommu_group;
    67 
    68     bool            offline_disabled:1;
    69     bool            offline:1;
    70 };

    第14行成员bus,就是指定总线,第15行成员driver,就是指定的驱动。

    在对总线注册新设备的时候,是通过下面的函数进行的

    1 extern int __must_check device_register(struct device *dev);
    2 extern void device_unregister(struct device *dev);

    整个注册的过程跟前面那个差不多

     中间有一部分跟前面的流程是一样的我就省略了。反正最后执行的都是驱动下的probe函数。总之就是驱动和设备匹配以后都会执行probe函数。所以我们后面所有的驱动开发基本上都是围绕着这个probe函数来进行的。

    platform平台驱动

    在前面我们提到了设备驱动的分离,然后引出了总线、驱动和设备的模型,并且我们在前面已经看到了在sys/bus路径下有各种各样的总线供我们使用了(类似I2C,SPI,USB等等),但是有些SOC外设(比如GPT、RTC、LCD等外设)是不好归结到哪个总线里的,为此Linux就提出了一种虚拟的总线——platform总线。那么为了使用方便,platform总线对应还要有platform设备和platform驱动。

    platform总线

    platform总线是bus_type的一个具体的实例化对象(drivers/base/platform.c)

    1 struct bus_type platform_bus_type = {
    2     .name        = "platform",
    3     .dev_groups    = platform_dev_groups,
    4     .match        = platform_match,
    5     .uevent        = platform_uevent,
    6     .pm        = &platform_dev_pm_ops,
    7 };

    对于这个platform而言,主要的还是match函数,负责驱动和设备的匹配。

    platform驱动

    描述platform驱动的结构体为platform_driver

     1 struct platform_driver {
     2     int (*probe)(struct platform_device *);
     3     int (*remove)(struct platform_device *);
     4     void (*shutdown)(struct platform_device *);
     5     int (*suspend)(struct platform_device *, pm_message_t state);
     6     int (*resume)(struct platform_device *);
     7     struct device_driver driver;
     8     const struct platform_device_id *id_table;
     9     bool prevent_deferred_probe;
    10 };

    需要我们关注的就是那个probe函数。platform驱动的注册是通过下面的函数执行的

    1 #define platform_driver_register(drv) \
    2     __platform_driver_register(drv, THIS_MODULE)
    3 extern int __platform_driver_register(struct platform_driver *,
    4                     struct module *);
    5 extern void platform_driver_unregister(struct platform_driver *);

    第一行是个宏,引用了第3行的函数,整个驱动加载的流程跟driver加载的流程基本一致,这里就不再说了,反正最后在匹配成功以后还是执行的那个probe函数

    platform设备

    platform设备通过结构体platform_device描述

     1 struct platform_device {
     2     const char    *name;
     3     int        id;
     4     bool        id_auto;
     5     struct device    dev;
     6     u32        num_resources;
     7     struct resource    *resource;
     8 
     9     const struct platform_device_id    *id_entry;
    10     char *driver_override; /* Driver name to force a match */
    11 
    12     /* MFD cell pointer */
    13     struct mfd_cell *mfd_cell;
    14 
    15     /* arch specific additions */
    16     struct pdev_archdata    archdata;
    17 };

    设备的使用分两种情况:没有设备树信息和有设备树节点信息。如果没有设备树,需要驱动开发人员编写设备注册文件,使用platform_device_register函数进行注册;有设备树的时候只需按需求修改设备树节点就可以了。在和platform_driver匹配完成后会执行platform_driver下单probe函数。

    platform的匹配过程

    platform的匹配是由下面的函数进行的

     1 static int platform_match(struct device *dev, struct device_driver *drv)
     2 {
     3     struct platform_device *pdev = to_platform_device(dev);
     4     struct platform_driver *pdrv = to_platform_driver(drv);
     5 
     6     /* When driver_override is set, only bind to the matching driver */
     7     if (pdev->driver_override)
     8         return !strcmp(pdev->driver_override, drv->name);
     9 
    10     /* Attempt an OF style match first */
    11     if (of_driver_match_device(dev, drv))
    12         return 1;
    13 
    14     /* Then try ACPI style match */
    15     if (acpi_driver_match_device(dev, drv))
    16         return 1;
    17 
    18     /* Then try to match against the id table */
    19     if (pdrv->id_table)
    20         return platform_match_id(pdrv->id_table, pdev) != NULL;
    21 
    22     /* fall-back to driver name match */
    23     return (strcmp(pdev->name, drv->name) == 0);
    24 }

    在第11行的函数就是通过设备树匹配信息,其他各个行的内容根据函数名称都可以名称了解其意义。整个函数最终步骤是通过name来进行匹配的,通过strcmp比较两个字符串,如果两个name相通就返回0,最后和0比较就返回1,否则返回0。这个就是在没有设备树的条件下使用的。

    在有设备树信息的时候,会通过下面的函数和设备树里compatible节点下的字符串列表去匹配。这个过程我们在后面的案例中会用到

    1 static inline int of_driver_match_device(struct device *dev,
    2                      const struct device_driver *drv)
    3 {
    4     return of_match_device(drv->of_match_table, dev) != NULL;
    5 }

    然后下面就是这样的流程

  • 相关阅读:
    Spring Boot基础
    MyBatis开启二级缓存
    MyBatis逆向工程
    html实现“加入收藏”代码
    vue-router 基本使用
    vue 脚手架安装
    webpack入门 webpack4常见出错之处
    $.ajax()方法详解
    防止网页被嵌套
    H5字符实体参考
  • 原文地址:https://www.cnblogs.com/yinsedeyinse/p/16586620.html
Copyright © 2020-2023  润新知