platform总线--驱动发现设备的过程

来源:互联网 发布:微信视频强制分享源码 编辑:程序博客网 时间:2024/05/21 17:50

  总线时物理存在,Linux系统提供了一种简单的总线flatform。

  platform  不是物理存在的总线,而是逻辑概念。现代PC机提供了一条根总线(PCI 总线)管理设备,但是有些设备没有挂载在PCI总线上,不能由PCI总线管理,于是Linux内核虚拟了platform总线来统一管理这种设备。


1、从驱动发现设备的过程

  platform总线虽然简单,但有总线的通用功能。我们选的例子是 q40kbd, 在driver/input/serio目录里,这是一个键盘驱动,使用了platform总线。

1.1、驱动的初始化

  设备驱动一般从初始化函数进行分析,q40kbd_init作用是把驱动程序注册到系统,代码如下

static int __init q40kbd_init(void){int error;if (!MACH_IS_Q40)return -ENODEV;//驱动作为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总线的用法,PCI总线可以自动扫描设备,而platform总线时虚拟的总线,物理上并不存在,没有扫描设备的功能,所以platform需要直接注册设备


1.2、注册驱动

  驱动注册调用函数是 platform_driver_register,代码如下:

/** * platform_driver_register - register a driver for platform-level devices * @drv: platform driver structure */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);}EXPORT_SYMBOL_GPL(platform_driver_register);
   platform_driver_register  函数把驱动总线设置为platform总线,然后依次设置 驱动的各个指针,最后调用driver_register函数注册驱动。driver_register 函数很简单,初始化之后就调用 bus_add_driver

1.3、为总线增加一个驱动

  bus_add_driver 的作用是为总线增加一个驱动,bus_add_driver代码如下【微差别】:

/** * bus_add_driver - Add a driver to the bus. * @drv: driver. */int bus_add_driver(struct device_driver *drv){struct bus_type *bus;struct driver_private *priv;int error = 0;bus = bus_get(drv->bus);if (!bus)return -EINVAL;pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);priv = kzalloc(sizeof(*priv), GFP_KERNEL);if (!priv) {error = -ENOMEM;goto out_put_bus;}klist_init(&priv->klist_devices, NULL, NULL);priv->driver = drv;drv->p = priv;priv->kobj.kset = bus->p->drivers_kset;error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,     "%s", drv->name);if (error)goto out_unregister;if (drv->bus->p->drivers_autoprobe) {error = driver_attach(drv);if (error)goto out_unregister;}klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);module_add_driver(drv->owner, drv);error = driver_create_file(drv, &driver_attr_uevent);if (error) {printk(KERN_ERR "%s: uevent attr (%s) failed\n",__func__, drv->name);}error = driver_add_attrs(bus, drv);if (error) {/* How the hell do we get out of this pickle? Give up */printk(KERN_ERR "%s: driver_add_attrs(%s) failed\n",__func__, drv->name);}if (!drv->suppress_bind_attrs) {error = add_bind_files(drv);if (error) {/* Ditto */printk(KERN_ERR "%s: add_bind_files(%s) failed\n",__func__, drv->name);}}kobject_uevent(&priv->kobj, KOBJ_ADD);return 0;out_unregister:kobject_put(&priv->kobj);kfree(drv->p);drv->p = NULL;out_put_bus:bus_put(bus);return error;}

  bus_add_driver函数使用了kobject_register 和 driver_add_attrs等函数为sysfs文件系统创建设备驱动相关的 目录 和 文件。 前面介绍过

1.4、驱动加载

  真正执行驱动加载的是 driver_attach 函数,代码如下:

/** * driver_attach - try to bind driver to devices. * @drv: driver. * * Walk the list of devices that the bus has on it and try to * match the driver with each one.  If driver_probe_device() * returns 0 and the @dev->driver is set, we've found a * compatible pair. */int driver_attach(struct device_driver * drv){return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);}EXPORT_SYMBOL_GPL(driver_attach);


/** * bus_for_each_dev - device iterator. * @bus: bus type. * @start: device to start iterating from. * @data: data for the callback. * @fn: function to be called for each device. * * Iterate over @bus's list of devices, and call @fn for each, * passing it @data. If @start is not NULL, we use that device to * begin iterating from. * * We check the return of @fn each time. If it returns anything * other than 0, we break out and return that value. * * NOTE: The device that returns a non-zero value is not retained * in any way, nor is its refcount incremented. If the caller needs * to retain this data, it should do so, and increment the reference * count in the supplied callback. */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,从设备start开始klist_iter_init_node(&bus->p->klist_devices, &i, (start ? &start->p->knode_bus : NULL));while ((dev = next_device(&i)) && !error)error = fn(dev, data);klist_iter_exit(&i);return error;}EXPORT_SYMBOL_GPL(bus_for_each_dev);


初始化一个kList,从设备start开始:



int bus_for_each_dev (struct bus_type * bus, struct device * start, void *data, int (*fn) (struct device *, void *))

{

  struct klist_liter i;

  struct device * dev;

  int error;


  if (!bus)

    return -EINVAL;

  //初始化一个klist,

  klist_iter_init_node( &bus->klist_device, &i, (start ? &start->knode : NULL)) ;

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

    error = fn(dev, data);

  klist_iter_exit( &i );

  return error;

}

初始化一个kList,从设备start开始:

/lib/Klist.c

/** * klist_iter_init_node - Initialize a klist_iter structure. * @k: klist we're iterating. * @i: klist_iter we're filling. * @n: node to start with. * * Similar to klist_iter_init(), but starts the action off with @n, * instead of with the list head. */void klist_iter_init_node(struct klist *k, struct klist_iter *i,  struct klist_node *n){i->i_klist = k;i->i_cur = n;if (n)kref_get(&n->n_ref);}//----include/linux/Klist.hstruct klist_iter {struct klist*i_klist;struct klist_node*i_cur;};//----include/linux/Klist.hstruct klist_node;struct klist {spinlock_tk_lock;struct list_headk_list;void(*get)(struct klist_node *);void(*put)(struct klist_node *);} __attribute__ ((aligned (sizeof(void *))));struct klist_node {void*n_klist;/* never access directly */struct list_headn_node;struct krefn_ref;};

总线类型的结构体定义在 include/linux/Device.h 文件中:

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 subsys_private *p;};


/** * struct subsys_private - structure to hold the private to the driver core portions of the bus_type/class structure. * * @subsys - the struct kset that defines this subsystem * @devices_kset - the list of devices associated * * @drivers_kset - the list of drivers associated * @klist_devices - the klist to iterate over the @devices_kset * @klist_drivers - the klist to iterate over the @drivers_kset * @bus_notifier - the bus notifier list for anything that cares about things *                 on this bus. * @bus - pointer back to the struct bus_type that this structure is associated *        with. * * @class_interfaces - list of class_interfaces associated * @glue_dirs - "glue" directory to put in-between the parent device to *              avoid namespace conflicts * @class_mutex - mutex to protect the children, devices, and interfaces lists. * @class - pointer back to the struct class that this structure is associated *          with. * * This structure is the one that is the actual kobject allowing struct * bus_type/class to be statically allocated safely.  Nothing outside of the * driver core should ever touch these fields. */struct subsys_private {struct kset subsys;struct kset *devices_kset;struct kset *drivers_kset;struct klist klist_devices;struct klist klist_drivers;struct blocking_notifier_head bus_notifier;unsigned int drivers_autoprobe:1;struct bus_type *bus;struct list_head class_interfaces;struct kset glue_dirs;struct mutex class_mutex;struct class *class;};

struct klist_node;struct klist {spinlock_tk_lock;struct list_headk_list;void(*get)(struct klist_node *);void(*put)(struct klist_node *);} __attribute__ ((aligned (sizeof(void *))));



1.5、遍历总线上已挂载的设备

  遍历总线上已挂载的设备,起始位置是初始化klist_iter 结构设置的 start 设备,只遍历这个设备之后挂载的设备。当前场景设置的start设备为空,所以要遍历所有的platform总线设备。 对每个设备调用 fn 函数指针, fn就是传入的函数指针 __driver_attach,它的代码如下:

__driver_attach() ---/drivers/base/Dd.c

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 (!driver_match_device(drv, dev))return 0;if (dev->parent)/* Needed for USB */device_lock(dev->parent);device_lock(dev);if (!dev->driver)driver_probe_device(drv, dev);device_unlock(dev);if (dev->parent)device_unlock(dev->parent);return 0;}

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

{

  struct device_driver * drv = data;

if ( dev->parent )/* need for usb */

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

down(&dev->sem);

if (!dev->driver)

driver_probe_device(drv, dev);

return 0;

}

  __driver_attach获取设备的锁之后,调用driver_probe_device函数,代码如下:

int driver_proe_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;

//总线的match函数通过后,继续调用总线的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);

driver_probe_device函数分为两个步骤:

--第一步 调用总线提供的match函数; 如果检测通过,说明设备和驱动匹配,设备所指向的驱动指针要赋予为当前驱动

--第二步 探测probe。首先调用总线提供的probe函数,如果驱动有自己的probe函数,还要调用驱动的probe函数

   Probe 目的是 总线或设备的进一步探测。比如硬盘控制器,本身是一个PCI设备,同时又提供硬盘接入的功能。那么它的驱动probe函数就要扫描scsi总线,把所有接入的硬盘都扫描出来。


1.5.1 match函数

  platform总线的match函数就是platform_match


  platform_match 函数很简单,比较驱动的名字和设备的名字是否相同,相同就可以匹配。

1.5.2 probe函数

  platform_drv_probe 是封装的函数,简单调用了 驱动的probe函数,驱动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;}

  q40kbd_probe函数设置了一个serio结构变量,然后注册到系统。