Linux驱动之platform总线详解

目录1、platform总线简介1.1、Linux驱动的分离和分层思想1.1.1、Linux驱动的分离1.2、platform平台驱动模型2、platform框架2.1、platform总...

platform 总线是 bus_type 类型的常量,之所以说它是常量是因为这个变量已经被 Linux 内核赋值好了,其结构体成员对应的函数也已经在内核里面写好。

定义如下:


/* drivers/base/platform.c */
 
struct bus_type platform_bus_type = {
    .name = "platform",
    .dev_groups = platform_dev_groups,
    .match = platform_match,       /* 匹配函数 */
    .uevent = platform_uevent,
    .pm = &platform_dev_pm_ops,
};

platform_bus_type 中的 platform_match 就是我们前面所说的做驱动和设备匹配的函数,该函数定义如下:


/* drivers/base/platform.c */
 
static int platform_match(struct device *dev, struct device_driver *drv)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct platform_driver *pdrv = to_platform_driver(drv);
 
    /*When driver_override is set,only bind to the matching driver*/
    if (pdev->driver_override)
        return !strcmp(pdev->driver_override, drv->name);
 
    /* 设备树OF类型匹配
       驱动基类的 of_match_table 里的 compatible 匹配表与设备树
       每一个设备节点的 compatible 属性作比较,有相同就表示匹配成功 */
    if (of_driver_match_device(dev, drv))
        return 1;
 
    /* ACPI 匹配 */
    if (acpi_driver_match_device(dev, drv))
        return 1;
 
    /* id_table 匹配
       platform 驱动里的 id_table 数组会保存很多 id 信息  */
    if (pdrv->id_table)
        return platform_match_id(pdrv->id_table, pdev) != NULL;
 
    /* name 匹配
       直接粗暴比较platform 的驱动和设备里面的 name 信息 */
    return (strcmp(pdev->name, drv->name) == 0);
}

这个匹配函数什么时候用,在哪里用,我们不妨先留一个悬念。

2.2、platform 驱动

2.2.1、platform 驱动定义

platform 驱动用结构体 platform_driver 来表示,该结构体内容为:


/* include/linux/platform_device.h */
 
struct platform_driver {
    int (*probe)(struct platform_device *);    /* platform驱动和platform设备匹配后会执行这个probe函数 */
    int (*remove)(struct platform_device *);
    void (*shutdown)(struct platform_device *);
    int (*suspend)(struct platform_device *, pm_message_t state);
    int (*resume)(struct platform_device *);
    struct device_driver driver;                /* 驱动基类 */
    const struct platform_device_id *id_table;  /* id_table表 */
    bool prevent_deferred_probe;
};

platform_driver 中const struct platform_device_id *id_table 是 id_table 表,在 platform 总线匹配驱动和设备时id_table 表匹配法时使用的,这个id_table 表其实是一个数组,里面的每个元素类型都为platform_device_id,platform_device_id 是一个结构体,内容如下:


struct platform_device_id {
    char name[PLATFORM_NAME_SIZE];
    kernel_ulong_t driver_data;
};

platform_driver 中driver 是一个驱动基类,相当于驱动具有的最基础的属性,在不同总线下具有的属性则存放在platform_driver 结构体下。

驱动基类结构体device_driver 内容为:


/* include/linux/device.h */
 
struct device_driver {
    const char *name;                               /* platform 总线来匹配设备与驱动的第四种方        
                                                       法就是直接粗暴匹配两者的 name 字段 */
    struct bus_type *bus;
    struct module *owner;
    const char *mod_name; 
    bool suppress_bind_attrs; 
    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);
    void (*shutdown) (struct device *dev);
    int (*suspend) (struct device *dev, pm_message_t state);
    int (*resume) (struct device *dev);
    const struct attribute_group **groups;
    const struct dev_pm_ops *pm;
    struct driver_private *p;
};

driver 中 of_match_table 也是一个匹配表,这个匹配表是 platform 总线给驱动和设备做匹配时使用设备树匹配时用的,也是一个数组,数组元素都为of_device_id 类型,该类型结构体如下:


/* include/linux/mod_devicetable.h */
 
struct of_device_id {
    char name[32];
    char type[32];
    char compatible[128];   /* 使用设备树匹配时就是把设备节点的 compatible 属性值和 of_match_table 中
                               每个项目的这个 compatible 作比较,如果有相等的就表示设备和驱动匹配成功 */
    const void *data;
};

2.2.2、platform 驱动注册

用platform_driver 结构体定义好 platform 驱动后,用 platform_driver_register 函数向 Linux 内核注册 platform 驱动,函数大致流程如下:


platform_driver_register (drv)
    -> __platform_driver_register
        -> drv->driver.probe = platform_drv_probe;       /* 把 platform_drv_probe 这个函数赋给
                                                            platform 驱动里的驱动基类 drier 的 probe 函数 */
        -> driver_registe (&drv->driver)                 /* 向 Linux 内核注册驱动基类 driver  */
            -> ...... 
                -> drv->driver->probe                    /* 最终执行驱动基类 driver 的 probe 函数,
                                                            其实就是上面给的 platform_drv_probe 函数 */
                    -> platform_drv_probe
                        -> drv->probe                    /* platform_drv_probe 函数又会执行                 
                                                            platform 驱动 drv 的 probe 函数 */

上面的分析中从 driver_register (&drv->driver) 到 drv->driver->probe 这一步我们用省略号代替了,现在来做一下分析:


driver_register(&drv->driver)
    -> bus_add_driver                             /* 向总线添加驱动 */
        -> driver_attach 
            -> bus_for_each_dev                   /* 查找总线下每一个设备,即遍历操作 */ 
                -> __driver_attach                /* 每个设备都调用此函数 */
                    -> driver_match_device        /* 检查是否匹配 */
                        -> 调用bus下的match匹配函数
                    -> driver_probe_device        /* 匹配成功后执行此函数 */
                        -> really_probe 
                            -> drv->probe         /* 执行drv下的probe函数 */

根据driver_register 函数流程,我们就知道了总线的 match 匹配函数会在这里遍历使用,这就回答了我们之前留下的一个问题:总线 match 函数在哪里用,一旦匹配成功就会进入到驱动的 probe 函数。

根据 platform_driver_register 函数流程,我们可以得出一个结论:向 Linux 内核注册 platform driver 过程里面会有一个遍历驱动和设备匹配的过程,匹配成功后最终会执行 platform driver 的 probe 函数,过程中 的驱动基类 driver 的 probe 函数和 platform_drv_probe 函数都是达到这个目的的中转函数而已。

值得注意的是,最终会执行的 platform driver 的 probe 函数是由我们来写的,所以主动权又回到我们手里。

2.3、platform 设备

2.3.1、platform 设备定义

如果我们用的 Linux 版本支持设备树,那就在设备树中去描述设备,如果不支持设备树,就要定义好 platform 设备。这里我们需要考虑的一个点是,总线下的匹配函数 match 在做匹配时是先设备树匹配,然后 id_table 表匹配,然后才是 name 字段匹配。支持设备树时,直接在设备树节点里面改设备信息,内核启动时会自动遍历设备树节点,匹配成功就会自动生成一个 platform_device,给下一步来使用。不是设备树的话,这个 platform_device 就是由开发者来写。

这里我们先不用设备树,自己来定义 platform 设备。platform 设备用 platform_device 结构体来表示,该结构体定义如下:


/* include/linux/platform_device.h */
 
struct platform_device {
    const char *name;                   /* 设备名,得和对应的 platform 驱动的 name 一样,
                                           否则设备就无法匹配到对应驱动 */
    int id; 
    bool id_auto;
    struct device dev;
    u32 num_resources; 
    struct resource *resource;
    const struct platform_device_id *id_entry;
    char *driver_override; /* Driver name to force a match */
    /* MFD cell pointer */
    struct mfd_cell *mfd_cell;
    /* arch specific additions */
    struct pdev_archdata archdata;
};

2.4、platform 匹配过程

platform 总线对驱动和设备的匹配过程其实上面零零碎碎也已经讲的差不多了,现在我们汇总起来在过一遍。

前面也说过,总线下的驱动和设备的匹配是通过总线下的 match 函数来实现的,不同的总线对应的 match 函数肯定不一样,这个我们不用管,内核都会写好。我们所用的 platform 总线对应的 match 函数是 platform_match 函数,分析一下这个函数:


platform_match
    -> of_driver_match_device          /* 设备树匹配 */
    -> acpi_driver_match_device        /* ACPI 匹配 */
    -> platform_match_id               /* platform_driver->id_table 匹配 */
    -> strcmp(pdev->name, drv->name)   /* name 匹配 */

通过对上面匹配函数的一个简单分析,我们知道匹配函数做匹配的顺序是先匹配设备树,然后匹配 id_table 表,然后才是暴力匹配 name 字段。对于支持设备树的 Linux 版本,我们一上来做设备树匹配就完事。不支持设备树时,我们就得定义 platform 设备,再用 id_tabale 表或 name 匹配,一般情况下都是选用 name 匹配。

现在我们来具体看一下设备树条件下的匹配过程:


of_driver_match_device     /* of函数一般是用于设备树,这也算给了我们提示 */
    -> of_match_device (drv->of_match_table, dev)    
        -> of_match_node  
            -> __of_match_node
                -> __of_device_is_compatible
                    -> __of_find_property(device, "compatible", NULL)   /* 取出compatible属性值 */

看上面的分析我们就知道了这个匹配过程最终是驱动基类的 of_match_table 里的 compatible 去设备树节点里面的compatible 属性作比较。这个就是把设备树与 platform 总线串起来的一个机理,从而实现了在设备树对应节点里面写设备信息,驱动另外单独写的目的,也就是我们前面讲的驱动分离。

3、总结

在具体的开发过程中我们并不需要真的去写一个 platform 总线模型,内核中都已经给我们定义好了。我们对 platform 总线模型的分析主要是搞清楚如何将驱动和设备匹配的,即当我们插入设备是如何找到对应驱动或插入驱动如何找到对应设备的,并最终调用 probe 函数。其实不管是先有驱动后有设备、还是先有设备后有驱动,最终匹配成功后第一件事都是执行驱动的 probe 函数,所以我们尽可放心的忽略中间曲折的情感纠葛,直接把注意力放在最终的 probe 函数。

到此这篇关于Linux驱动之platform总线详解的文章就介绍到这了,更多相关Linux驱动platform总线内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

本站部分内容来源互联网,如果有图片或者内容侵犯您的权益请联系我们删除!

相关文档推荐