深入浅出linux之总线和设备(platform总线)

来源:互联网 发布:c语言关键字有哪些 编辑:程序博客网 时间:2024/06/08 15:18
 

  在设备基本模型一节,我们了解到总线发现设备,管理设备,配置设备的功能。一般来说,总线都是物理存在的,但是linux系统提供了一种简单的总线platform。Platform并不是一种物理存在的总线,而是个逻辑概念。PC机系统提供了一条根总线(PCI总线)来管理设备,但是有些设备,并不由PCI总线管理,于是linux内核虚拟了一条platform总线,来统一管理这些设备。

Platform总线虽然是最简单的总线,但是一样具有总线的通用功能。通过分析这种简单的总线,可以帮助我们理解总线的概念和总线对设备和驱动的管理,方便后面对复杂总线的分析,比如说PCI总线。

 

   我们选的例子是q40kbd,在目录drivers/input/serio里面。实际上,这就是一个键盘控制器驱动(不是键盘驱动)。而它使用了platform总线。

 

一  从驱动发现设备的过程

 

===========================q40kbd_init==================================

static int __init q40kbd_init(void)

{

       int error;

 

       if (!MACH_IS_Q40)

              return -EIO;

    /*驱动作为platform总线驱动注册*/

       error = platform_driver_register(&q40kbd_driver);

       if (error)

              return error;

 

    /*分配一个platform设备*/

       q40kbd_device = platform_device_alloc("q40kbd", -1);

       if (!q40kbd_device)

              goto err_unregister_driver;

 

    /*platform设备注册*/

       error = platform_device_add(q40kbd_device);

       if (error)

              goto err_free_device;

 

       return 0;

 

 err_free_device:

       platform_device_put(q40kbd_device);

 err_unregister_driver:

       platform_driver_unregister(&q40kbd_driver);

       return error;

}

  这段代码很简单,就是先注册platform驱动,再注册platform设备。先分析驱动的注册。

 

================================platform_driver_register====================

int platform_driver_register(struct platform_driver *drv)

{  

    /*驱动的总线赋值为platform总线*/

       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;

       if (drv->suspend)

              drv->driver.suspend = platform_drv_suspend;

       if (drv->resume)

              drv->driver.resume = platform_drv_resume;

       return driver_register(&drv->driver);

}

 

============================driver_register===============================

int driver_register(struct device_driver * drv)

{

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

       }

       klist_init(&drv->klist_devices, klist_devices_get, klist_devices_put);

       init_completion(&drv->unloaded);

       return bus_add_driver(drv);

}

  这个函数做参数检查和初始化后,实际调用了bus_add_driver

 

==============================bus_add_driver=============================

int bus_add_driver(struct device_driver * drv)

{

       struct bus_type * bus = get_bus(drv->bus);

       int error = 0;

 

       if (bus) {

              pr_debug("bus %s: add driver %s\n", bus->name, drv->name);

        /*这段代码使用了kobject,实际是在sysfs里面创建文件*/

              error = kobject_set_name(&drv->kobj, "%s", drv->name);

              if (error) {

                     put_bus(bus);

                     return error;

              }

              drv->kobj.kset = &bus->drivers;

              if ((error = kobject_register(&drv->kobj))) {

                     put_bus(bus);

                     return error;

              }

 

        /**/

              driver_attach(drv);

        /*驱动加入总线的驱动列表*/

              klist_add_tail(&drv->knode_bus, &bus->klist_drivers);

        /*这行和下面一行也是为了在sysfs创建驱动的属性文件和模块*/

              module_add_driver(drv->owner, drv);

 

              driver_add_attrs(bus, drv);

        add_bind_files(drv);

       }

       return error;

}

  这里有kobject_register函数调用和driver_add_attrs。从前文的sysfs文件系统和koject分析,这些函数都是为了在根目录的sys目录下面创建目录和文件,每个设备都有自己的一些特性文件,就不一一分析了。

  driver_attach真正执行了驱动的加载。继续看这个函数。

 

======================================================================

void driver_attach(struct device_driver * drv)

{

       bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);

}

 

=======================bus_for_each_dev==================================

 

int bus_for_each_dev(struct bus_type * bus, struct device * start,

                   void * data, int (*fn)(struct device *, void *))

{

       struct klist_iter i;

       struct device * dev;

       int error = 0;

 

       if (!bus)

              return -EINVAL;

 

       klist_iter_init_node(&bus->klist_devices, &i,

                          (start ? &start->knode_bus : NULL));

       while ((dev = next_device(&i)) && !error)

              error = fn(dev, data);

       klist_iter_exit(&i);

       return error;

}

  这段代码就是遍历总线上已经挂载的设备,然后调用fn函数。Fn就是前面传入的函数指针__driver_attach。所以继续分析__driver_attach

 

================================================================

static int __driver_attach(struct device * dev, void * data)

{

       struct device_driver * drv = data;

 

       /*

        * Lock device and try to bind to it. We drop the error

        * here and always return 0, because we need to keep trying

        * to bind to devices and some drivers will return an error

        * simply if it didn't support the device.

        *

        * driver_probe_device() will spit a warning if there

        * is an error.

        */

 

       if (dev->parent)     /* Needed for USB */

              down(&dev->parent->sem);

       down(&dev->sem);

    if (!dev->driver)

              driver_probe_device(drv, dev);

       up(&dev->sem);

       if (dev->parent)

              up(&dev->parent->sem);

 

       return 0;

}

  这段代码主要是执行driver_probe_device。

======================================================================

int driver_probe_device(struct device_driver * drv, struct device * dev)

{

       int ret = 0;

    /*先调用总线配置的match函数*/

       if (drv->bus->match && !drv->bus->match(dev, drv))

              goto Done;

 

       pr_debug("%s: Matched Device %s with Driver %s\n",

               drv->bus->name, dev->bus_id, drv->name);

       dev->driver = drv;

    /*bus的match函数通过后,继续调用bus的probe函数*/

       if (dev->bus->probe) {

              ret = dev->bus->probe(dev);

              if (ret) {

                     dev->driver = NULL;

                     goto ProbeFailed;

              }

       } else if (drv->probe) {

        /*如果驱动提供了probe函数,则调用驱动的probe函数*/

              ret = drv->probe(dev);

              if (ret) {

                     dev->driver = NULL;

                     goto ProbeFailed;

              }

       }

    /*设备发现了驱动,通过sysfs创建一些文件。和设备做符号链接*/

       device_bind_driver(dev);

       ret = 1;

       pr_debug("%s: Bound Device %s to Driver %s\n",

               drv->bus->name, dev->bus_id, drv->name);

       goto Done;

 

 ProbeFailed:

       if (ret == -ENODEV || ret == -ENXIO) {

              /* Driver matched, but didn't support device

               * or device not found.

               * Not an error; keep going.

               */

              ret = 0;

       } else {

              /* driver matched but the probe failed */

              printk(KERN_WARNING

                     "%s: probe of %s failed with error %d\n",

                     drv->name, dev->bus_id, ret);

       }

 Done:

       return ret;

}

  简要概述这段代码,就是先match,然后probe。如果驱动有自己的probe函数,还要调用驱动的probe。从前面的分析知道,match就是platform_match,而platform没有提供probe函数,调用的probe就是platform_drv_probe。这样继续分析platform_match和platform_drv_probe函数。

 

======================================================================

static int platform_match(struct device * dev, struct device_driver * drv)

{

       struct platform_device *pdev = container_of(dev, struct platform_device, dev);

 

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

}

  很简单,就是比较驱动的名字和设备的名字是否相同,相同局可以匹配。而注册的Q40kbd驱动的名字是“q40kbd”,也就是说要找到这个名字的设备,两者就匹配了。

 

======================================================================

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

}

  这里又调用了驱动本身的probe函数。这个就是q40kbd_probe。

 

======================================================================

static int __devinit q40kbd_probe(struct platform_device *dev)

{

       q40kbd_port = kzalloc(sizeof(struct serio), GFP_KERNEL);

       if (!q40kbd_port)

              return -ENOMEM;

 

       q40kbd_port->id.type    = SERIO_8042;

       q40kbd_port->open      = q40kbd_open;

       q40kbd_port->close      = q40kbd_close;

       q40kbd_port->dev.parent     = &dev->dev;

       strlcpy(q40kbd_port->name, "Q40 Kbd Port", sizeof(q40kbd_port->name));

       strlcpy(q40kbd_port->phys, "Q40", sizeof(q40kbd_port->phys));

 

       serio_register_port(q40kbd_port);

       printk(KERN_INFO "serio: Q40 kbd registered\n");

 

       return 0;

}

  代码很简单,引出的问题可不小。关键是这一步serio_register_port是做什么?在platform总线里面又多出来个serio,这又是为了什么?这个问题放到下一节分析。

 

二  从设备找到驱动的过程

  从platform驱动的注册过程,我们发现和input的过程很相像,都是一个个遍历设备,看是否和某个驱动匹配。那么下面的加载设备的过程,应该也是遍历驱动,看是否匹配。继续分析是否如此。

 

======================================================================

int platform_device_add(struct platform_device *pdev)

{

       int i, ret = 0;

 

       if (!pdev)

              return -EINVAL;

    /*如果没父设备,就设置platform_bus为父设备*/

       if (!pdev->dev.parent)

              pdev->dev.parent = &platform_bus;

    /*设置设备的bus为platform_bus_type */

       pdev->dev.bus = &platform_bus_type;

 

       if (pdev->id != -1)

              snprintf(pdev->dev.bus_id, BUS_ID_SIZE, "%s.%u", pdev->name, pdev->id);

       else

              strlcpy(pdev->dev.bus_id, pdev->name, BUS_ID_SIZE);

 

    /*把设备io端口和io内存注册到系统*/

       for (i = 0; i < pdev->num_resources; i++) {

              struct resource *p, *r = &pdev->resource[i];

 

              if (r->name == NULL)

                     r->name = pdev->dev.bus_id;

 

              p = r->parent;

              if (!p) {

                     if (r->flags & IORESOURCE_MEM)

                            p = &iomem_resource;

                     else if (r->flags & IORESOURCE_IO)

                            p = &ioport_resource;

              }

 

              if (p && insert_resource(p, r)) {

                     printk(KERN_ERR

                            "%s: failed to claim resource %d\n",

                            pdev->dev.bus_id, i);

                     ret = -EBUSY;

                     goto failed;

              }

       }

 

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

               pdev->dev.bus_id, pdev->dev.parent->bus_id);

 

       ret = device_add(&pdev->dev);

       if (ret == 0)

              return ret;

 

 failed:

       while (--i >= 0)

              if (pdev->resource[i].flags & (IORESOURCE_MEM|IORESOURCE_IO))

                     release_resource(&pdev->resource[i]);

       return ret;

}

  注意io端口(设备控制寄存器)和io内存注册到系统的那部分代码。回顾设备基本概念一节,PCI总线是扫描设备的配置文件,确定设备的io端口和io内存,而platform不是物理上存在的,那么自然不能自动扫描设备,那么如何配置设备的io端口和io内存哪?

   从内核驱动中,可以发现platform设备很多使用了探测的方式。一般是先往一个io端口写数据,看回应得到设备的io端口。至于我们分析的q40kbd设备,它甚至没有注册自己的io端口和内存,而是直接使用了缺省值。说明这是一种很古老的设备了。

  继续分析device_add:

======================================================================

int device_add(struct device *dev)

{

       struct device *parent = NULL;

       char *class_name = NULL;

       int error = -EINVAL;

 

       dev = get_device(dev);

       if (!dev || !strlen(dev->bus_id))

              goto Error;

 

       parent = get_device(dev->parent);

 

       pr_debug("DEV: registering device: ID = '%s'\n", dev->bus_id);

 

       /* first, register with generic layer. */

       kobject_set_name(&dev->kobj, "%s", dev->bus_id);

       if (parent)

              dev->kobj.parent = &parent->kobj;

    /*在sys目录生成设备目录*/

       if ((error = kobject_add(&dev->kobj)))

              goto Error;

    /**/

       dev->uevent_attr.attr.name = "uevent";

       dev->uevent_attr.attr.mode = S_IWUSR;

       if (dev->driver)

              dev->uevent_attr.attr.owner = dev->driver->owner;

       dev->uevent_attr.store = store_uevent;

       device_create_file(dev, &dev->uevent_attr);

    /*设备的属性文件*/

       if (MAJOR(dev->devt)) {

              struct device_attribute *attr;

              attr = kzalloc(sizeof(*attr), GFP_KERNEL);

              if (!attr) {

                     error = -ENOMEM;

                     goto PMError;

              }

              attr->attr.name = "dev";

              attr->attr.mode = S_IRUGO;

              if (dev->driver)

                     attr->attr.owner = dev->driver->owner;

              attr->show = show_dev;

              error = device_create_file(dev, attr);

              if (error) {

                     kfree(attr);

                     goto attrError;

              }

 

              dev->devt_attr = attr;

       }

    /*创建设备类的符号链接*

       if (dev->class) {

              sysfs_create_link(&dev->kobj, &dev->class->subsys.kset.kobj,

                              "subsystem");

              sysfs_create_link(&dev->class->subsys.kset.kobj, &dev->kobj,

                              dev->bus_id);

 

              sysfs_create_link(&dev->kobj, &dev->parent->kobj, "device");

              class_name = make_class_name(dev->class->name, &dev->kobj);

              sysfs_create_link(&dev->parent->kobj, &dev->kobj, class_name);

       }

    /*设备的能源管理*/

       if ((error = device_pm_add(dev)))

              goto PMError;

       if ((error = bus_add_device(dev)))

              goto BusError;

       kobject_uevent(&dev->kobj, KOBJ_ADD);

       bus_attach_device(dev);

       if (parent)

              klist_add_tail(&dev->knode_parent, &parent->klist_children);

 

       if (dev->class) {

              /* tie the class to the device */

              down(&dev->class->sem);

              list_add_tail(&dev->node, &dev->class->devices);

              up(&dev->class->sem);

       }

 

       /* notify platform of device entry */

       if (platform_notify)

              platform_notify(dev);

 Done:

      kfree(class_name);

       put_device(dev);

       return error;

 BusError:

       device_pm_remove(dev);

 PMError:

       if (dev->devt_attr) {

              device_remove_file(dev, dev->devt_attr);

              kfree(dev->devt_attr);

       }

 attrError:

       kobject_uevent(&dev->kobj, KOBJ_REMOVE);

       kobject_del(&dev->kobj);

 Error:

       if (parent)

              put_device(parent);

       goto Done;

}

  在device_add里面,要为设备在sys目录创建很多目录和文件。还要为别的相关子系统创建链接。这些内容已经多次出现了,这里就不一一分析了。

  kobject_uevent是个比较复杂的函数,这里面出现了uevent的概念,这个和linux的hotplug有极大关系。

  真正的处理函数是bus_attach_device,继续分析:

======================================================================

void bus_attach_device(struct device * dev)

{

       struct bus_type * bus = dev->bus;

 

       if (bus) {

              device_attach(dev);

              klist_add_tail(&dev->knode_bus, &bus->klist_devices);

       }

}

 

int device_attach(struct device * dev)

{

       int ret = 0;

 

       down(&dev->sem);

       if (dev->driver) {

              device_bind_driver(dev);

              ret = 1;

       } else

              ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);

       up(&dev->sem);

       return ret;

}

  回顾一下,上文添加驱动的处理函数是bus_for_each_dev,是遍历设备。而这里是bus_for_each_drv,是遍历驱动。这两个函数几乎一样,读者可以自行分析一下。

 

三  总结和引申

  分析了platform这个简单总线,我们发现和input设备的管理方式类似。总线下面也有两个列表,一个是设备列表,一个是驱动列表。驱动注册时,要加入总线的驱动列表,同时在对应的设备列表里面寻找匹配的设备。

   Platform总线虽然简单,但是引入了总线的概念。在内核驱动中,总线的使用是很多的。驱动目录里面的scsi,usb,ieee1394,pcmcia都是总线类型,它们也都有统一的设备和驱动管理。读者可以分析一下这些总线是怎么处理设备注册和驱动注册的。

 

习题:读者可以尝试注册一个假总线,然后注册一个设备和驱动,看看sysfs目录是否正常,设备是否正常被驱动。