LINUX设备驱动之platform总线

来源:互联网 发布:python怎么引入丌 编辑:程序博客网 时间:2024/05/13 00:49

转自:http://blog.chinaunix.net/uid-7859076-id-2552433.html

Eric Fang  2010-01-19

--------------------------------------------------------------

本站分析linux内核源码,版本号为2.6.32.3

转载请注明出处:http://ericfang.cublog.cn/

--------------------------------------------------------------

 

阅读本文之前,如果你对设备驱动模型还不了解,请先阅读本站设备驱动模型相关文章。

Platform总线是kernel中的一种虚拟总线,2.6版本很多驱动都用它来实现。

一.Platform初始化

系统启动时初始化时创建了platform_bus设备和platform_bus_type总线:

内核初始化函数kernel_init()中调用了do_basic_setup(),该函数中调用driver_init(),该函数中调用platform_bus_init(),我们看看platform_bus_init()函数:

int __init platform_bus_init(void)

{

       int error;

 

       early_platform_cleanup();

 

       error = device_register(&platform_bus);

       if (error)

              return error;

       error =  bus_register(&platform_bus_type);

       if (error)

              device_unregister(&platform_bus);

       return error;

}

device_register(&platform_bus)中的platform_bus如下:

struct device platform_bus = {

       .init_name       = "platform",

};

改函数把设备名为platform的设备platform_bus注册到系统中,其他的platform的设备都会以它为parent。它在sysfs中目录下. /sys/devices/platform

接着bus_register(&platform_bus_type)注册了platform_bus_type总线,看一下改总线的定义:

struct bus_type platform_bus_type = {

       .name             = "platform",

       .dev_attrs       = platform_dev_attrs,

       .match           = platform_match,

       .uevent           = platform_uevent,

       .pm         = &platform_dev_pm_ops,

};

默认platform_bus_type中没有定义probe函数。

我们分析一下其中platform_matchplatform_uevent函数。在分析设备驱动模型是已经知道总线类型match函数是在设备匹配驱动时调用,uevent函数在产生事件时调用。

platform_match()代码如下:

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);

 

       /* match against the id table first */

       if (pdrv->id_table)

              return platform_match_id(pdrv->id_table, pdev) != NULL;

 

       /* fall-back to driver name match */

       return (strcmp(pdev->name, drv->name) == 0);

}

static const struct platform_device_id *platform_match_id(

                     struct platform_device_id *id,

                     struct platform_device *pdev)

{

       while (id->name[0]) {

              if (strcmp(pdev->name, id->name) == 0) {

                     pdev->id_entry = id;

                     return id;

              }

              id++;

       }

       return NULL;

}

不难看出,如果pdrvid_table数组中包含了pdev->name,或者drv->namepdev->name名字相同,都会认为是匹配成功。id_table数组是为了应对那些对应设备和驱动的drv->namepdev->name名字不同的情况。

再看看platform_uevent()函数:

static int platform_uevent(struct device *dev, struct kobj_uevent_env *env)

{

       struct platform_device   *pdev = to_platform_device(dev);

 

       add_uevent_var(env, "MODALIAS=%s%s", PLATFORM_MODULE_PREFIX,

              (pdev->id_entry) ? pdev->id_entry->name : pdev->name);

       return 0;

}

添加了MODALIAS环境变量,我们回顾一下:platform_bus. parent->kobj->kset->uevent_opsdevice_uevent_opsbus_uevent_ops的定义如下:

static struct kset_uevent_ops device_uevent_ops = {

       .filter =    dev_uevent_filter,

       .name =          dev_uevent_name,

       .uevent = dev_uevent,

};

当调用device_add()时会调用kobject_uevent(&dev->kobj, KOBJ_ADD)产生一个事件,这个函数中会调用相应的kset_uevent_opsuevent函数,这里即为dev_uevent(),我们看一下这个函数的代码片段:

static int dev_uevent(struct kset *kset, struct kobject *kobj,

                    struct kobj_uevent_env *env)

{

       .

       .

       .

       /* have the bus specific function add its stuff */

       if (dev->bus && dev->bus->uevent) {

              retval = dev->bus->uevent(dev, env);

              if (retval)

                     pr_debug("device: '%s': %s: bus uevent() returned %d\n",

                             dev_name(dev), __func__, retval);

       }

       .

       .

       .

}

从这里看到如果bus->uevent()函数存在则会调用它。

到这里我们清楚了platform_uevent会在哪里调用了。

 

二.Platform设备的注册

我们在设备模型的分析中知道了把设备添加到系统要调用device_initialize()platform_device_add(pdev)函数。

对于platform设备的初始化,内核源码也提供了platform_device_alloc()函数。

对于platform设备的初注册,内核源码提供了platform_device_add()函数,它是进行一系列的操作后调用device_add()将设备注册到相应的总线上,内核代码中platform设备的其他注册函数都是基于这个函数,如platform_device_register()platform_device_register_simple()platform_device_register_data()等。

我们对这些函数逐个分析,首先看看初始化函数platform_device_alloc()

struct platform_device * platform_device_alloc(const char *name, int id)

{

       struct platform_object *pa;

 

       pa = kzalloc(sizeof(struct platform_object) + strlen(name), GFP_KERNEL);

       if (pa) {

              strcpy(pa->name, name);

              pa->pdev.name = pa->name;

              pa->pdev.id = id;

              device_initialize(&pa->pdev.dev);

              pa->pdev.dev.release = platform_device_release;

       }

 

       return pa ? &pa->pdev : NULL;

}

该函数首先为platform设备分配内存空间,这里的struct platform_object结构是struct platform _device结构的封装,其定义如下:

struct platform_object {

       struct platform_device pdev;

       char name[1];

};

其中第二个字段name的地址用于存放第一个字段pdevname指针上的内容,函数中的代码说明了这点:

              strcpy(pa->name, name);

              pa->pdev.name = pa->name;

接着用输入参数id初始化platform_deviceid字段,这个id是在设置代表它的kobject时会用到的,我们将在后面分析到,如果不用它,则设为-1

接着调用device_initialize()初始化platform_device内嵌的device,并设置其release函数指针。

platform_device_alloc()函数分析完了。

接着我们看看platform_device_add()函数:

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;

 

       pdev->dev.bus = &platform_bus_type;

设置父节点和总线,这里的platform_busplatform_bus_type在上面的初始化部分已经分析。

       if (pdev->id != -1)

              dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id);

       else

              dev_set_name(&pdev->dev, "%s", pdev->name);

设置pdev->dev内嵌的kobjname字段,它是pdev->name指向的内容加上id,如果id-1则忽略它,关于dev_set_name()函数已经在分析设备驱动模型时分析过,这里不再累赘。

       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;

              }

       }

初始化资源并将资源分配给它,每个资源的它的parent不存在则根据flags域设置parentflagsIORESOURCE_MEM,则所表示的资源为I/O映射内存,flagsIORESOURCE_IO,则所表示的资源为I/O端口。

       pr_debug("Registering platform device '%s'. Parent at %s\n",

               dev_name(&pdev->dev), dev_name(pdev->dev.parent));

 

       ret = device_add(&pdev->dev);

就在这里把设备注册到总线上,如果你对device_add()函数不熟悉,请参考本站的设别模型分析部分内容。

       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;

}

platform_device_add()函数分析完了,我们看下platform_device_register()函数:

int platform_device_register(struct platform_device *pdev)

{

       device_initialize(&pdev->dev);

       return platform_device_add(pdev);

}

没错它就是初始化pdev->dev后调用platform_device_add()把它注册到platform_bus_type上。

在看看platform_device_register_simple()函数:

struct platform_device *platform_device_register_simple(const char *name,

                                                 int id,

                                                 struct resource *res,

                                                 unsigned int num)

{

       struct platform_device *pdev;

       int retval;

 

       pdev = platform_device_alloc(name, id);

       if (!pdev) {

              retval = -ENOMEM;

              goto error;

       }

 

       if (num) {

              retval = platform_device_add_resources(pdev, res, num);

              if (retval)

                     goto error;

       }

 

       retval = platform_device_add(pdev);

       if (retval)

              goto error;

 

       return pdev;

 

error:

       platform_device_put(pdev);

       return ERR_PTR(retval);

}

该函数就是调用了platform_device_alloc()platform_device_add()函数来创建的注册platform device,函数也根据res参数分配资源,看看platform_device_add_resources()函数:

int platform_device_add_resources(struct platform_device *pdev,

                              struct resource *res, unsigned int num)

{

       struct resource *r;

 

       r = kmalloc(sizeof(struct resource) * num, GFP_KERNEL);

       if (r) {

              memcpy(r, res, sizeof(struct resource) * num);

              pdev->resource = r;

              pdev-> num_resources = num;

       }

       return r ? 0 : -ENOMEM;

}

很简单,为资源分配内存空间,并拷贝参数res中的内容,链接到device并设置其num_resources

 

三.Platform设备的注册

我们在设备驱动模型的分析中已经知道驱动在注册要调用driver_register()platform driver的注册函数platform_driver_register()同样也是进行其它的一些初始化后调用driver_register()将驱动注册到platform_bus_type总线上,看一下这个函数:

int platform_driver_register(struct platform_driver *drv)

{

       drv->driver.bus = &platform_bus_type;

       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);

}

这里我们要先看看struct platform_driver结构:

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;

       struct platform_device_id *id_table;

};

上面的函数指定了内嵌的driverbus字段为platform_bus_type,即为它将要注册到的总线。

然后设定了platform_driver内嵌的driverproberemoveshutdown函数。

看下相应的这三个函数:

static int platform_drv_probe(struct device *_dev)

{

       struct platform_driver *drv = to_platform_driver(_dev->driver);

       struct platform_device *dev = to_platform_device(_dev);

 

       return drv->probe(dev);

}

static int platform_drv_remove(struct device *_dev)

{

       struct platform_driver *drv = to_platform_driver(_dev->driver);

       struct platform_device *dev = to_platform_device(_dev);

 

       return drv->remove(dev);

}

 

static void platform_drv_shutdown(struct device *_dev)

{

       struct platform_driver *drv = to_platform_driver(_dev->driver);

       struct platform_device *dev = to_platform_device(_dev);

 

       drv->shutdown(dev);

}

从这三个函数的代码可以看到,又找到了相应的platform_driverplatform_device,然后调用platform_driverproberemoveshutdown函数。这是一种高明的做法:在不针对某个驱动具体的proberemoveshutdown指向的函数,而通过上三个过度函数来找到platform_driver,然后调用proberemoveshutdown接口。

 

如果设备和驱动都注册了,就可以通过bus ->match bus->probedriver->probe进行设备驱动匹配了,这部分内容将留到具体的设备中再做分析。

 

2.6.32.3版本的代码中,还针对某些不需要产生hotplug事件的设备提供设备驱动的匹配函数platform_driver_probe(),调用这个函数前首先要注册设备,看一下这个函数:

int __init_or_module platform_driver_probe(struct platform_driver *drv,

              int (*probe)(struct platform_device *))

{

       int retval, code;

 

       /* make sure driver won't have bind/unbind attributes */

       drv->driver.suppress_bind_attrs = true;

 

       /* temporary section violation during probe() */

       drv-> probe = probe;

       retval = code = platform_driver_register(drv);

 

       /*

        * Fixup that section violation, being paranoid about code scanning

        * the list of drivers in order to probe new devices.  Check to see

        * if the probe was successful, and make sure any forced probes of

        * new devices fail.

        */

       spin_lock(&platform_bus_type.p->klist_drivers.k_lock);

       drv->probe = NULL;

       if (code == 0 && list_empty(&drv->driver.p->klist_devices.k_list))

              retval = -ENODEV;

       drv->driver.probe = platform_drv_probe_fail;

       spin_unlock(&platform_bus_type.p->klist_drivers.k_lock);

 

       if (code != retval)

              platform_driver_unregister(drv);

       return retval;

}

该函数先设置drvprobe为输入函数,然后将drv注册到总线,这个过程回去匹配设备,这时会找到调用这个函数前注册的设备,然后将其挂钩,接着设置drv->probeNULL,设置drv->driver.probe platform_drv_probe_fail,这样后面如果产生匹配事件都会是匹配失败,也即platform_drv_probe_fail()匹配不成功,其代码如下:

static int platform_drv_probe_fail(struct device *_dev)

{

       return -ENXIO;

}

正如我们分析的一样。

 

 

到此,Platform总线分析完了,后面其他模块的分析中将会有platform的例子,有了上面的基础,到时我们就可以轻松的分析了^_^!
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 小孩鼻子里有鼻屎呼吸不通怎么办 鼻子不通通气鼻屎粘在鼻子怎么办 鼻子里面干燥长鼻屎怎么办 鼻孔里干的难受怎么办 每天有很多鼻屎怎么办 鼻子里面干的疼怎么办 婴儿鼻屎堵住了怎么办 婴儿有很多鼻屎怎么办 隆鼻7天好多鼻屎怎么办 隆鼻第五天好多鼻屎怎么办 小孩鼻屎堵住了怎么办 风寒感冒流清鼻涕怎么办 流清鼻涕吐黄痰不发烧怎么办 宝宝流黄鼻涕发烧怎么办 感冒了浓鼻涕多怎么办 感冒流浓鼻涕怎么办速效办法 孩子一直流清水鼻涕怎么办 宝宝鼻子呼噜呼噜响怎么办 鼻涕往嗓子里流怎么办 咳嗽痰多鼻涕多怎么办 没感冒嗓子痰多鼻涕怎么办 孩子感冒后鼻涕特别多怎么办 经常有鼻涕怎么办才好 怀孕后鼻涕痰多怎么办 鼻炎有鼻涕痰多怎么办 宝宝咳嗽痰多鼻涕多怎么办 宝宝两岁清鼻涕咳嗽痰多怎么办 喉咙咸咸的有痰怎么办 宝宝咳嗽鼻塞喉咙有痰怎么办 绝地求生刺激战场射击键误触怎么办 在皮卡堂卡的游泳了怎么办 假如遇到老赖没能力还钱怎么办 服刑人拒不执行伤害赔偿怎么办? 面对当前严峻形势作为军人怎么办 想起诉不知道对方地址怎么办 遇见家里来嫌疑人员怎么办 老滚5老婆死了怎么办 美化包安装之后闪退怎么办 蕉下的伞坏了怎么办 苹果7通话音质特别差怎么办 雨伞的伞骨坏了怎么办