Linux驱动之platform模型

1、什么是platform(平台)总线?

      相对于USB、PCI、I2C、SPI等物理总线来说,platform总线是一种虚拟、抽象出来的总线,实际中并不存在这样的总线。

      那为什么需要platform总线呢?其实是Linux设备驱动模型为了保持设备驱动的统一性而虚拟出来的总线。因为对于usb设备、i2c设备、pci设备、spi设备等等,他们与cpu的通信都是直接挂在相应的总线下面与我们的cpu进行数据交互的。

      但是在我们的嵌入式系统当中,并不是所有的设备都能够归属于这些常见的总线,在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设却不依附与此类总线。所以Linux驱动模型为了保持完整性,将这些设备挂在一条虚拟的总线上(platform总线),而不至于使得有些设备挂在总线上,另一些设备没有挂在总线上。

platform总线相关代码:driver\base\platform.c
platform相关结构体定义:include\linux\platform_device.h

2、platform总线管理下的2员大将

(1)两个结构体platform_device和platform_driver

      对于任何一种Linux设备驱动模型下的总线都由两个部分组成:描述设备相关的结构体和描述驱动相关的结构体在platform总线下就是platform_device和platform_driver,下面是对两个结构体的各个元素进行分析:

platform_device结构体:(include\linux\platform_device.h)

struct platform_device {           //  platform总线设备
    const char    * name;          //  平台设备的名字
    int        id;                 //   ID 是用来区分如果设备名字相同的时候(通过在后面添加一个数字来代表不同的设备,因为有时候有这种需求)
    struct device    dev;          //   内置的device结构体
    u32        num_resources;      //   资源结构体数量
    struct resource    * resource; //   指向一个资源结构体数组
    const struct platform_device_id    *id_entry; //  用来进行与设备驱动匹配用的id_table表
    
    /* arch specific additions */
    struct pdev_archdata    archdata;             //  自留地添加自己的东西
};

platform_device结构体中的struct resource结构体分析:

struct resource {      // 资源结构体
    resource_size_t start;      // 资源的起始值,如果是地址,那么是物理地址,不是虚拟地址
    resource_size_t end;        // 资源的结束值,如果是地址,那么是物理地址,不是虚拟地址
    const char *name;           // 资源名
    unsigned long flags;        // 资源的标示,用来识别不同的资源
    struct resource *parent, *sibling, *child;   // 资源指针,可以构成链表
};

platform_driver结构体:(include\linux\platform_device.h)

struct platform_driver {
    int (*probe)(struct platform_device *);     //  这个probe函数其实和device_driver中的是一样的功能,但是一般是使用device_driver中的那个
    int (*remove)(struct platform_device *);    //  卸载平台设备驱动的时候会调用这个函数,但是device_driver下面也有,具体调用的是谁这个就得分析了
    void (*shutdown)(struct platform_device *);
    int (*suspend)(struct platform_device *, pm_message_t state);
    int (*resume)(struct platform_device *);
    struct device_driver driver;                //   内置的device_driver 结构体
    const struct platform_device_id *id_table;  //  该设备驱动支持的设备的列表他是通过这个指针去指向platform_device_id 类型的数组
};

(2)两组接口函数(driver\base\platform.c)

int platform_driver_register(struct platform_driver *); // 用来注册我们的设备驱动

void platform_driver_unregister(struct platform_driver *); // 用来卸载我们的设备驱动

int platform_device_register(struct platform_device *); // 用来注册我们的设备

void platform_device_unregister(struct platform_device *); // 用来卸载我们的设备

3、platform平台总线的初始化

(1)platform平台总线的注册初始化: platform_bus_init

platform_bus_init

early_platform_cleanup               //  进行一些早期的平台清理   
device_register                      //  注册设备 (在/sys/devices/目录下建立platform目录对应的设备对象象/sys/devices/platform/) 
bus_register                         //  总线注册

(2)相关结构体

struct bus_type {
    const char        *name;                     //  总线名字
    struct bus_attribute    *bus_attrs;          //  该总线的属性
    struct device_attribute    *dev_attrs;       //  该总线下设备的属性
    struct driver_attribute    *drv_attrs;       //  该总线下设备驱动的属性

    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);
    void (*shutdown)(struct device *dev);

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

    const struct dev_pm_ops *pm;  //  电源管理相关的

    struct bus_type_private *p;   //  总线的私有数据 p->subsys.kobj 表示该总线在驱动模型中对应的对象
};
struct bus_type_private {
    struct kset subsys;                //  这个是bus主要的kset
    struct kset *drivers_kset;         //  这个kset指针用来指向该总线的drivers目录的
    struct kset *devices_kset;         //  这个kse指针用来指向该总线的devices目录的
    struct klist klist_devices;        //  用来挂接该总线下的设备的一个链表头
    struct klist klist_drivers;        //  用来挂接该总线下的设备驱动的一个链表头
    struct blocking_notifier_head bus_notifier;
    unsigned int drivers_autoprobe:1;  //  是否需要在设备驱动注册时候子自动匹配设备
    struct bus_type *bus;              //  指向本bus结构体
};

(3)函数详解

bus_register:

int bus_register(struct bus_type *bus)
{
    int retval;
    struct bus_type_private *priv;                               //  定义一个bus_type_private 结构体指针

    priv = kzalloc(sizeof(struct bus_type_private), GFP_KERNEL); //   申请分配内存
    if (!priv)
        return -ENOMEM;

    priv->bus = bus;            //   使用 priv->bus 指向我们传进来的bus
    bus->p = priv;              //   通过  bus->p  指向priv    这里其实就是将bus与priv建立关系,这个跟之前的device、class的设计是一样的

    BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);

    retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);  //   给我们的bus在设备驱动模型中的对象设置名字 bus->p->subsys.kobj
    if (retval)
        goto out;

//   这里就是对bus的私有数据进行一些填充
    priv->subsys.kobj.kset = bus_kset;      //  设置bus对象的父对象,也就是 /sys/bus这目录作为他的上层目录,所有的具体的总线类型对象都是在这个目录下
    priv->subsys.kobj.ktype = &bus_ktype;   //  设置bus对象的  对象类型为 bus_ktype
    priv->drivers_autoprobe = 1;            //  配置为在注册设备或者是注册设备驱动时自动进行配置,这个就决定了为什么我们在注册设备或者是设备驱动能够进行自动匹配

    retval = kset_register(&priv->subsys);  //  注册kset结构体(内部会调用kobject_add_internal函数,也就是将bus对象添加到 /sys/bus/目录下, /sys/bus/xxx_busType  对应具体的总线)
    if (retval)
        goto out;

    retval = bus_create_file(bus, &bus_attr_uevent);  //  在该bus下建立属性文件(对应的就是bus下的uevent属性)
    if (retval)
        goto bus_uevent_fail;

    priv->devices_kset = kset_create_and_add("devices", NULL, //  在具体总线的目录下创建kset容器对象象/sys/bus/xxx_busType/devices
                         &priv->subsys.kobj);                 //  通过priv->devices_kset指针去指向 这个目录对应的对象
    if (!priv->devices_kset) {
        retval = -ENOMEM;
        goto bus_devices_fail;
    }

    priv->drivers_kset = kset_create_and_add("drivers", NULL,   //   /sys/bus/xxx_busType/drivers
                         &priv->subsys.kobj);                   //   通过 priv->drivers_kset 指针去指向  这个目录对应的对象
    if (!priv->drivers_kset) {
        retval = -ENOMEM;
        goto bus_drivers_fail;
    }

    klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);  //  初始化链表  klist
    klist_init(&priv->klist_drivers, NULL, NULL);                            //  初始化链表  klist

    retval = add_probe_files(bus);    //  添加探针文件,其实内部做的还是添加属性文件   /sys/bus/xxx_busType/drivers_probe    /sys/bus/xxx_busType/drivers_autoprobe
    if (retval)
        goto bus_probe_files_fail;

    retval = bus_add_attrs(bus);      //  根据 bus->bus_attrs 中的属性设置来添加属性文件
    if (retval)
        goto bus_attrs_fail;

    pr_debug("bus: '%s': registered\n", bus->name);
    return 0;

bus_attrs_fail:
    remove_probe_files(bus);
bus_probe_files_fail:
    kset_unregister(bus->p->drivers_kset);
bus_drivers_fail:
    kset_unregister(bus->p->devices_kset);
bus_devices_fail:
    bus_remove_file(bus, &bus_attr_uevent);
bus_uevent_fail:
    kset_unregister(&bus->p->subsys);
    kfree(bus->p);
out:
    bus->p = NULL;
    return retval;
}

4、platform平台设备注册

(1)platform平台总线注册函数: platform_device_register

platform_device_register

device_initialize

platform_device_add

(2)函数分析

int platform_device_add(struct platform_device *pdev)
{
    int i, ret = 0;

    if (!pdev)
        return -EINVAL;

    if (!pdev->dev.parent)
        pdev->dev.parent = &platform_bus;       //  将平台设备的父设备设置为 platform_bus (对应的就是  /sys/devices/platform 这个目录)

    pdev->dev.bus = &platform_bus_type;         //  设置平台设备挂接在 platform总线下 platform_bus_type

    if (pdev->id != -1)
        dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id); //  给平台设备对应的对象设置名字  name.id  (如果我们的 pdev->id 设置不等于-1时)
    else
        dev_set_name(&pdev->dev, "%s", pdev->name);

//   下面的for 循环是对平台设备资源的一些处理
    for (i = 0; i < pdev->num_resources; i++) {
        struct resource *p, *r = &pdev->resource[i];

        if (r->name == NULL)
            r->name = dev_name(&pdev->dev);

        p = r->parent;
        if (!p) {
            if (resource_type(r) == IORESOURCE_MEM)
                p = &iomem_resource;
            else if (resource_type(r) == IORESOURCE_IO)
                p = &ioport_resource;
        }

        if (p && insert_resource(p, r)) {
            printk(KERN_ERR
                   "%s: failed to claim resource %d\n",
                   dev_name(&pdev->dev), i);
            ret = -EBUSY;
            goto failed;
        }
    }
//
    pr_debug("Registering platform device '%s'. Parent at %s\n",
         dev_name(&pdev->dev), dev_name(pdev->dev.parent));

    ret = device_add(&pdev->dev);    //   将平台设备添加到系统中去  /sys/devices/platform/xxx
    if (ret == 0)
        return ret;

 failed:
    while (--i >= 0) {
        struct resource *r = &pdev->resource[i];
        unsigned long type = resource_type(r);

        if (type == IORESOURCE_MEM || type == IORESOURCE_IO)
            release_resource(r);
    }

    return ret;
}

5、platform平台设备驱动注册

(1)platform平台设备驱动注册函数: platform_driver_register

platform_driver_register

driver_register

driver_find

bus_add_driver

kobject_init_and_add

driver_attach

klist_add_tail

module_add_driver

driver_create_file

driver_add_attrs

driver_add_groups

int platform_driver_register(struct platform_driver *drv)
{
    drv->driver.bus = &platform_bus_type;            //  设置设备驱动 挂接在 platform平台总线下

//  下面做的就是对 drv 中的函数指针进行填充
    if (drv->probe)
        drv->driver.probe = platform_drv_probe;
    if (drv->remove)
        drv->driver.remove = platform_drv_remove;
    if (drv->shutdown)
        drv->driver.shutdown = platform_drv_shutdown;

    return driver_register(&drv->driver);             //  注册设备驱动
}
int driver_register(struct device_driver *drv)
{
    int ret;
    struct device_driver *other;           //    定义一个设备驱动指针  other

    BUG_ON(!drv->bus->p);

    if ((drv->bus->probe && drv->probe) ||
        (drv->bus->remove && drv->remove) ||
        (drv->bus->shutdown && drv->shutdown))
        printk(KERN_WARNING "Driver '%s' needs updating - please use "
            "bus_type methods\n", drv->name);

    other = driver_find(drv->name, drv->bus);  //   这个函数其实进行了一个校验  比对当前的 总线下是否存在名字和现在需要注册的设备驱动的名字相同的设备驱动
    if (other) {
        put_driver(other);                     //   如果名字相同 直接打印错误  并退出
        printk(KERN_ERR "Error: Driver '%s' is already registered, "
            "aborting...\n", drv->name);
        return -EBUSY;
    }

    ret = bus_add_driver(drv);                  //   在总线挂接设备驱动  就是将设备驱动对应的kobj对象与组织建立关系
    if (ret)
        return ret;
    ret = driver_add_groups(drv, drv->groups);   //
    if (ret)
        bus_remove_driver(drv);
    return ret;
}

6、platform总线下的匹配函数

(1)platform_match函数

static int platform_match(struct device *dev, struct device_driver *drv) // 总线下的设备与设备驱动的匹配函数
{
    struct platform_device *pdev = to_platform_device(dev);      //  通过device  变量获取到 platform_device
    struct platform_driver *pdrv = to_platform_driver(drv);      //   通过 driver 获取  platform_driver

    /* match against the id table first */
    if (pdrv->id_table)    //   如果pdrv中的id_table 表存在
        return platform_match_id(pdrv->id_table, pdev) != NULL;  //  匹配id_table

    /* fall-back to driver name match */   //  第二个就是指直接匹配 pdev->name     drv->name  名字是否形同
    return (strcmp(pdev->name, drv->name) == 0);
}




static const struct platform_device_id *platform_match_id(
            const struct platform_device_id *id,
            struct platform_device *pdev)
{
    while (id->name[0]) {  //  循环去比较id_table数组中的各个id名字是否与pdev->name 相同
        if (strcmp(pdev->name, id->name) == 0) {
            pdev->id_entry = id;       // 将id_table数组中的名字匹配上的 这个数组项 指针赋值给 pdev->id_entry
            return id;         //  返回这个指针
        }
        id++;
    }
    return NULL;
}

      总结: 由上面可知platform总线下设备与设备驱动的匹配原理就是通过名字进行匹配的,先去匹配platform_driver中的id_table表中的各个名字与platform_device->name名字是否相同,如果相同表示匹配成功直接返回,否则直接匹配platform_driver->name与platform_driver->name是否相同,相同则匹配成功,否则失败。

你可能感兴趣的:(Linux驱动之platform模型)