diff options
Diffstat (limited to 'drivers/base/class.c')
| -rw-r--r-- | drivers/base/class.c | 132 |
1 files changed, 96 insertions, 36 deletions
diff --git a/drivers/base/class.c b/drivers/base/class.c index dfef9793871a..7328f3231a65 100644 --- a/drivers/base/class.c +++ b/drivers/base/class.c @@ -10,27 +10,29 @@ static LIST_HEAD(class_list); int devclass_add_driver(struct device_driver * drv) { - if (drv->devclass) { - pr_debug("Registering driver %s:%s with class %s\n", - drv->bus->name,drv->name,drv->devclass->name); - - spin_lock(&device_lock); - list_add_tail(&drv->class_list,&drv->devclass->drivers); - spin_unlock(&device_lock); + struct device_class * cls = get_devclass(drv->devclass); + if (cls) { + down_write(&cls->rwsem); + pr_debug("device class %s: adding driver %s:%s\n", + cls->name,drv->bus->name,drv->name); + list_add_tail(&drv->class_list,&cls->drivers); devclass_drv_link(drv); + up_write(&cls->rwsem); } return 0; } void devclass_remove_driver(struct device_driver * drv) { - if (drv->devclass) { - pr_debug("Removing driver %s:%s:%s\n", - drv->devclass->name,drv->bus->name,drv->name); - spin_lock(&device_lock); + struct device_class * cls = drv->devclass; + if (cls) { + down_write(&cls->rwsem); + pr_debug("device class %s: removing driver %s:%s\n", + cls->name,drv->bus->name,drv->name); list_del_init(&drv->class_list); - spin_unlock(&device_lock); devclass_drv_unlink(drv); + up_write(&cls->rwsem); + put_devclass(cls); } } @@ -38,9 +40,7 @@ void devclass_remove_driver(struct device_driver * drv) static void enum_device(struct device_class * cls, struct device * dev) { u32 val; - spin_lock(&device_lock); val = cls->devnum++; - spin_unlock(&device_lock); dev->class_num = val; devclass_dev_link(cls,dev); } @@ -51,18 +51,44 @@ static void unenum_device(struct device_class * cls, struct device * dev) dev->class_num = 0; } +/** + * devclass_add_device - register device with device class + * @dev: device to be registered + * + * This is called when a device is either registered with the + * core, or after the a driver module is loaded and bound to + * the device. + * The class is determined by looking at @dev's driver, so one + * way or another, it must be bound to something. Once the + * class is determined, it's set to prevent against concurrent + * calls for the same device stomping on each other. + * + * /sbin/hotplug should be called once the device is added to + * class and all the interfaces. + */ int devclass_add_device(struct device * dev) { - struct device_class * cls = dev->driver->devclass; + struct device_class * cls; int error = 0; - if (cls) { - pr_debug("adding device '%s' to class '%s'\n", - dev->name,cls->name); - if (cls->add_device) - error = cls->add_device(dev); - if (!error) { - enum_device(cls,dev); - interface_add(cls,dev); + + if (dev->driver) { + cls = get_devclass(dev->driver->devclass); + if (cls) { + down_write(&cls->rwsem); + pr_debug("device class %s: adding device %s\n", + cls->name,dev->name); + if (cls->add_device) + error = cls->add_device(dev); + if (!error) { + enum_device(cls,dev); + interface_add(cls,dev); + } + + /* notify userspace (call /sbin/hotplug) here */ + + up_write(&cls->rwsem); + if (error) + put_devclass(cls); } } return error; @@ -70,40 +96,74 @@ int devclass_add_device(struct device * dev) void devclass_remove_device(struct device * dev) { - struct device_class * cls = dev->driver->devclass; - if (cls) { - pr_debug("removing device '%s' from class '%s'\n", - dev->name,cls->name); - interface_remove(cls,dev); - unenum_device(cls,dev); - if (cls->remove_device) - cls->remove_device(dev); + struct device_class * cls; + + if (dev->driver) { + cls = dev->driver->devclass; + if (cls) { + down_write(&cls->rwsem); + pr_debug("device class %s: removing device %s\n", + cls->name,dev->name); + interface_remove(cls,dev); + unenum_device(cls,dev); + if (cls->remove_device) + cls->remove_device(dev); + up_write(&cls->rwsem); + put_devclass(cls); + } } } +struct device_class * get_devclass(struct device_class * cls) +{ + struct device_class * ret = cls; + spin_lock(&device_lock); + if (cls && cls->present && atomic_read(&cls->refcount) > 0) + atomic_inc(&cls->refcount); + else + ret = NULL; + spin_unlock(&device_lock); + return ret; +} + +void put_devclass(struct device_class * cls) +{ + if (atomic_dec_and_lock(&cls->refcount,&device_lock)) { + list_del_init(&cls->node); + spin_unlock(&device_lock); + devclass_remove_dir(cls); + } +} + + int devclass_register(struct device_class * cls) { INIT_LIST_HEAD(&cls->drivers); INIT_LIST_HEAD(&cls->intf_list); - - pr_debug("registering device class '%s'\n",cls->name); + init_rwsem(&cls->rwsem); + atomic_set(&cls->refcount,2); + cls->present = 1; + pr_debug("device class '%s': registering\n",cls->name); spin_lock(&device_lock); list_add_tail(&cls->node,&class_list); spin_unlock(&device_lock); devclass_make_dir(cls); + put_devclass(cls); return 0; } void devclass_unregister(struct device_class * cls) { - pr_debug("unregistering device class '%s'\n",cls->name); - devclass_remove_dir(cls); spin_lock(&device_lock); - list_del_init(&class_list); + cls->present = 0; spin_unlock(&device_lock); + pr_debug("device class '%s': unregistering\n",cls->name); + put_devclass(cls); } EXPORT_SYMBOL(devclass_register); EXPORT_SYMBOL(devclass_unregister); +EXPORT_SYMBOL(get_devclass); +EXPORT_SYMBOL(put_devclass); |
