第12章 Linux设备驱动的软件架构思想之platform设备驱动

12.2 platform设备驱动

12.2.1 platform总线、设备与驱动

    在Linux 2.6以后的设备驱动模型中,需关心总线、设备和驱动这3个实体,总线将设备和驱动绑定。在系统每注册一个设备时,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动时,会寻找与之匹配的设备,而匹配由总线完成。

    一个现实的Linux设备和驱动通常都需要挂接在一种总线上,对于本身依附于PCI、USB、I2C、SPI等的设备来说,这自然不是问题,但是在嵌入式系统里面,在SoC系统中集成的独立外设控制器、挂接在SoC内存空间的外设等却不依附于此类总线。基于这一背景,Linux发明了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动成为platform_driver。

注意:所谓的platform_device并不是与字符设备、块设备和网络设备并列的概念,而是Linux系统提供的一种附加手段,通常把在SoC内部集成的I2C、RTC、LCD、看门狗等控制器都归纳为platform_device,而它们本身就是字符设备。

platform_device结构体的定义如代码清单12.1所示。

#include <linux/platform_device.h>

struct platform_device {
        const char      *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;
};

platform_driver结构体的定义如代码清单12.2所示。

#include <linux/platform_device.h>

struct platform_driver {
        int (*probe)(struct platform_device *);
        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;
        bool prevent_deferred_probe;

};

device_driver结构体的定义如代码清单12.3所示。

#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 */
        enum probe_type probe_type;

        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;

};

    与platform_driver地位对等的i2c_driver、spi_driver、usb_driver、pci_driver中都包含device_driver结构体实例成员。其实描述了各种xxx_driver(xxx是总线名)在驱动意义上的一些共性。

    系统为platform总线定义了一个bus_type的实例platform_bus_type,定义位于drivers/base/platform.c,如代码清单12.4所示。

struct bus_type platform_bus_type = {
        .name           = "platform",
        .dev_groups     = platform_dev_groups,
        .match          = platform_match,
        .uevent         = platform_uevent,
        .pm             = &platform_dev_pm_ops,
};

EXPORT_SYMBOL_GPL(platform_bus_type);

重点关注其match()成员函数,这个成员函数确定platform_device和platform_driver之间是如何进行匹配的,如代码清单12.5所示。

代码清单12.5 platform_bus_type的match()成员函数

drivers/base/platform.c

/**
 * platform_match - bind platform device to platform driver.
 * @dev: device.
 * @drv: driver.
 *
 * Platform device IDs are assumed to be encoded like this:
 * "<name><instance>", where <name> is a short description of the type of
 * device, like "pci" or "floppy", and <instance> is the enumerated
 * instance of the device, like '0' or '42'.  Driver IDs are simply
 * "<name>".  So, extract the <name> from the platform_device structure,
 * and compare it against the name of the driver. Return whether they match
 * or not.
 */
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);

        /* Attempt an OF style match first */
        if (of_driver_match_device(dev, drv)) // 基于设备树风格的匹配
                return 1;

        /* Then try ACPI style match */
        if (acpi_driver_match_device(dev, drv)) // 基于ACPI风格的匹配
                return 1;

        /* Then try to match against the id table */
        if (pdrv->id_table)//匹配ID表(即platform_device设备名是否出现在platform_driver的ID表内)
                return platform_match_id(pdrv->id_table, pdev) != NULL; 

        /* fall-back to driver name match */
        return (strcmp(pdev->name, drv->name) == 0);//匹配platform_device设备名和驱动的名字
}

从代码清单12.5可以看出,匹配platform_device和platform_driver有4种可能性:

1)基于设备树风格的匹配。

2) 基于ACPI风格的匹配。

3)匹配ID表(即platform_device设备名是否出现在platform_driver的ID表内)。

4)匹配platform_device设备名和驱动的名字。

    对于Linux 2.6ARM平台而言,对platform_device的定义通常在BSP的板文件中实现,在板文件中,将

platform_device归纳为一个数组,最终通过platform_add_devices()函数统一注册。

platform_add_devices()函数可以将平台设备添加到系统中,这个函数的原型为:

linux/platform_device.h

extern int platform_add_devices(struct platform_device **devs, int num);

该函数的第一个参数为平台设备的指针数组,第二个参数为平台设备的数量,函数内部调用
platform_device_register()函数以注册单个的平台设备。

函数定义:

drivers/base/platform.c

/**
 * platform_add_devices - add a numbers of platform devices
 * @devs: array of platform devices to add
 * @num: number of platform devices in array
 */
int platform_add_devices(struct platform_device **devs, int num)
{
        int i, ret = 0;

        for (i = 0; i < num; i++) {
                ret = platform_device_register(devs[i]); // 注册平台设备
                if (ret) { // 出错处理
                        while (--i >= 0)
                                platform_device_unregister(devs[i]); // 注销平台设备
                        break;
                }
        }

        return ret;
}
EXPORT_SYMBOL_GPL(platform_add_devices);

Linux 3.x之后,ARM Linux不太喜欢以编码的形式去填写platform_device和注册,而倾向于根据设备树中的内容自动展开platform_device。


猜你喜欢

转载自blog.csdn.net/xiezhi123456/article/details/80460431
今日推荐