Greg and Pat:

This patch (as536) simplifies the driver-model core by replacing the klist 
used to store the set of devices bound to a driver with a regular list 
protected by a mutex.  It turns out that even with a klist, there are too 
many opportunities for races for the list to be used safely by more than 
one thread at a time.  And given that only one thread uses the list at any 
moment, there's no need to add all the extra overhead of making it a 
klist.

This version of the patch addresses the concerns raised earlier by Pat:  
the list and mutex have been given more succinct names, and the obscure
special-case code in device_attach has been replaced with a FIXME comment.  
Note that the new iterators in driver.c could easily be changed to use
list_for_each_entry_safe and list_for_each_entry, if the functions didn't
offer the feature of starting in the middle of the list.  If no callers
use that feature, it should be removed.

I still think the APIs for device_bind_driver and device_release_driver
need to be changed, because they each rely on dev->drv not changing at a
time when no protecting locks are held.  They will have to be fixed up in
a later patch.

Alan Stern



Signed-off-by: Alan Stern <[EMAIL PROTECTED]>

Index: usb-2.6/drivers/base/dd.c
===================================================================
--- usb-2.6.orig/drivers/base/dd.c
+++ usb-2.6/drivers/base/dd.c
@@ -24,28 +24,37 @@
 #define to_drv(node) container_of(node, struct device_driver, kobj.entry)
 
 
+/*
+ *     The caller must hold both @dev->drv->devlist_mutex and
+ *     @dev->sem (acquired in that order).
+ */
+static void __device_bind_driver(struct device * dev)
+{
+       pr_debug("bound device '%s' to driver '%s'\n",
+                dev->bus_id, dev->driver->name);
+       list_add_tail(&dev->node_driver, &dev->driver->devlist);
+       sysfs_create_link(&dev->driver->kobj, &dev->kobj,
+                         kobject_name(&dev->kobj));
+       sysfs_create_link(&dev->kobj, &dev->driver->kobj, "driver");
+}
+
 /**
  *     device_bind_driver - bind a driver to one device.
  *     @dev:   device.
  *
  *     Allow manual attachment of a driver to a device.
  *     Caller must have already set @dev->driver.
- *
- *     Note that this does not modify the bus reference count
- *     nor take the bus's rwsem. Please verify those are accounted
- *     for before calling this. (It is ok to call with no other effort
- *     from a driver's probe() method.)
- *
- *     This function must be called with @dev->sem held.
  */
 void device_bind_driver(struct device * dev)
 {
-       pr_debug("bound device '%s' to driver '%s'\n",
-                dev->bus_id, dev->driver->name);
-       klist_add_tail(&dev->driver->klist_devices, &dev->knode_driver);
-       sysfs_create_link(&dev->driver->kobj, &dev->kobj,
-                         kobject_name(&dev->kobj));
-       sysfs_create_link(&dev->kobj, &dev->driver->kobj, "driver");
+       struct device_driver *drv = dev->driver;
+
+       down(&drv->devlist_mutex);
+       down(&dev->sem);
+       if (dev->driver == drv)
+               __device_bind_driver(dev);
+       up(&dev->sem);
+       up(&drv->devlist_mutex);
 }
 
 /**
@@ -59,16 +68,21 @@ void device_bind_driver(struct device * 
  *     because we don't know the format of the ID structures, nor what
  *     is to be considered a match and what is not.
  *
- *
  *     This function returns 1 if a match is found, an error if one
  *     occurs (that is not -ENODEV or -ENXIO), and 0 otherwise.
  *
- *     This function must be called with @dev->sem held.
+ *     The caller must hold @drv->devlist_mutex.
  */
 int driver_probe_device(struct device_driver * drv, struct device * dev)
 {
        int ret = 0;
 
+       down(&dev->sem);
+       if (dev->driver) {
+               ret = -EBUSY;
+               goto Done;
+       }
+
        if (drv->bus->match && !drv->bus->match(dev, drv))
                goto Done;
 
@@ -82,7 +96,7 @@ int driver_probe_device(struct device_dr
                        goto ProbeFailed;
                }
        }
-       device_bind_driver(dev);
+       __device_bind_driver(dev);
        ret = 1;
        pr_debug("%s: Bound Device %s to Driver %s\n",
                 drv->bus->name, dev->bus_id, drv->name);
@@ -102,13 +116,20 @@ int driver_probe_device(struct device_dr
                       drv->name, dev->bus_id, ret);
        }
  Done:
+       up(&dev->sem);
        return ret;
 }
 
 static int __device_attach(struct device_driver * drv, void * data)
 {
        struct device * dev = data;
-       return driver_probe_device(drv, dev);
+       int ret;
+
+       down(&drv->devlist_mutex);
+       ret = driver_probe_device(drv, dev);
+       up(&drv->devlist_mutex);
+
+       return ret;
 }
 
 /**
@@ -124,15 +145,21 @@ static int __device_attach(struct device
  */
 int device_attach(struct device * dev)
 {
-       int ret = 0;
+       int ret = 1;
+       struct device_driver * drv;
 
-       down(&dev->sem);
-       if (dev->driver) {
-               device_bind_driver(dev);
-               ret = 1;
+       drv = dev->driver;
+       if (drv) {
+
+               /* FIXME: What if drv is rmmod'ed right now? */
+
+               down(&drv->devlist_mutex);
+               down(&dev->sem);
+               __device_bind_driver(dev);
+               up(&dev->sem);
+               up(&drv->devlist_mutex);
        } else
                ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
-       up(&dev->sem);
        return ret;
 }
 
@@ -141,7 +168,7 @@ static int __driver_attach(struct device
        struct device_driver * drv = data;
 
        /*
-        * Lock device and try to bind to it. We drop the error
+        * Try to bind drv to dev. 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.
@@ -150,11 +177,8 @@ static int __driver_attach(struct device
         * is an error.
         */
 
-       down(&dev->sem);
        if (!dev->driver)
                driver_probe_device(drv, dev);
-       up(&dev->sem);
-
 
        return 0;
 }
@@ -170,46 +194,55 @@ static int __driver_attach(struct device
  */
 void driver_attach(struct device_driver * drv)
 {
+       down(&drv->devlist_mutex);
        bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
+       up(&drv->devlist_mutex);
 }
 
-/**
- *     device_release_driver - manually detach device from driver.
- *     @dev:   device.
- *
- *     Manually detach device from driver.
- *
- *     __device_release_driver() must be called with @dev->sem held.
- */
 
-static void __device_release_driver(struct device * dev)
+/*
+ *     The caller must hold @driver->devlist_mutex.
+ */
+static int __device_release_driver(struct device * dev, void * driver)
 {
-       struct device_driver * drv;
+       struct device_driver * drv = driver;
 
-       drv = dev->driver;
-       if (drv) {
-               get_driver(drv);
+       down(&dev->sem);
+       if (dev->driver == drv) {
                sysfs_remove_link(&drv->kobj, kobject_name(&dev->kobj));
                sysfs_remove_link(&dev->kobj, "driver");
-               klist_remove(&dev->knode_driver);
+               list_del(&dev->node_driver);
 
                if (drv->remove)
                        drv->remove(dev);
                dev->driver = NULL;
-               put_driver(drv);
        }
+       up(&dev->sem);
+       return 0;
 }
 
+/**
+ *     device_release_driver - manually detach device from driver.
+ *     @dev:   device.
+ *
+ *     Manually detach device from driver.
+ */
 void device_release_driver(struct device * dev)
 {
+       struct device_driver * drv;
+
+       drv = dev->driver;
+       if (!drv)
+               return;
+
        /*
         * If anyone calls device_release_driver() recursively from
         * within their ->remove callback for the same device, they
         * will deadlock right here.
         */
-       down(&dev->sem);
-       __device_release_driver(dev);
-       up(&dev->sem);
+       down(&drv->devlist_mutex);
+       __device_release_driver(dev, drv);
+       up(&drv->devlist_mutex);
 }
 
 
@@ -219,25 +252,7 @@ void device_release_driver(struct device
  */
 void driver_detach(struct device_driver * drv)
 {
-       struct device * dev;
-
-       for (;;) {
-               spin_lock_irq(&drv->klist_devices.k_lock);
-               if (list_empty(&drv->klist_devices.k_list)) {
-                       spin_unlock_irq(&drv->klist_devices.k_lock);
-                       break;
-               }
-               dev = list_entry(drv->klist_devices.k_list.prev,
-                               struct device, knode_driver.n_node);
-               get_device(dev);
-               spin_unlock_irq(&drv->klist_devices.k_lock);
-
-               down(&dev->sem);
-               if (dev->driver == drv)
-                       __device_release_driver(dev);
-               up(&dev->sem);
-               put_device(dev);
-       }
+       driver_for_each_device(drv, NULL, drv, __device_release_driver);
 }
 
 
Index: usb-2.6/drivers/base/driver.c
===================================================================
--- usb-2.6.orig/drivers/base/driver.c
+++ usb-2.6/drivers/base/driver.c
@@ -15,16 +15,10 @@
 #include <linux/string.h>
 #include "base.h"
 
-#define to_dev(node) container_of(node, struct device, driver_list)
+#define to_dev(node) container_of(node, struct device, node_driver)
 #define to_drv(obj) container_of(obj, struct device_driver, kobj)
 
 
-static struct device * next_device(struct klist_iter * i)
-{
-       struct klist_node * n = klist_next(i);
-       return n ? container_of(n, struct device, knode_driver) : NULL;
-}
-
 /**
  *     driver_for_each_device - Iterator for devices bound to a driver.
  *     @drv:   Driver we're iterating.
@@ -37,18 +31,22 @@ static struct device * next_device(struc
 int driver_for_each_device(struct device_driver * drv, struct device * start, 
                           void * data, int (*fn)(struct device *, void *))
 {
-       struct klist_iter i;
-       struct device * dev;
+       struct list_head * ptr, * tmp;
        int error = 0;
 
        if (!drv)
                return -EINVAL;
 
-       klist_iter_init_node(&drv->klist_devices, &i,
-                            start ? &start->knode_driver : NULL);
-       while ((dev = next_device(&i)) && !error)
-               error = fn(dev, data);
-       klist_iter_exit(&i);
+       down(&drv->devlist_mutex);
+       ptr = (start ? &start->node_driver : drv->devlist.next);
+       while (ptr != &drv->devlist) {
+               tmp = ptr->next;
+               error = fn(to_dev(ptr), data);
+               if (error)
+                       break;
+               ptr = tmp;
+       }
+       up(&drv->devlist_mutex);
        return error;
 }
 
@@ -74,18 +72,22 @@ struct device * driver_find_device(struc
                                   struct device * start, void * data,
                                   int (*match)(struct device *, void *))
 {
-       struct klist_iter i;
-       struct device *dev;
+       struct list_head * ptr;
+       struct device *dev = NULL;
 
        if (!drv)
                return NULL;
 
-       klist_iter_init_node(&drv->klist_devices, &i,
-                            (start ? &start->knode_driver : NULL));
-       while ((dev = next_device(&i)))
+       down(&drv->devlist_mutex);
+       ptr = (start ? &start->node_driver : drv->devlist.next);
+       for (; ptr != &drv->devlist; ptr = ptr->next) {
+               dev = to_dev(ptr);
                if (match(dev, data) && get_device(dev))
                        break;
-       klist_iter_exit(&i);
+       }
+       if (ptr == &drv->devlist)
+               dev = NULL;
+       up(&drv->devlist_mutex);
        return dev;
 }
 EXPORT_SYMBOL_GPL(driver_find_device);
@@ -157,7 +159,8 @@ void put_driver(struct device_driver * d
  */
 int driver_register(struct device_driver * drv)
 {
-       klist_init(&drv->klist_devices);
+       INIT_LIST_HEAD(&drv->devlist);
+       init_MUTEX(&drv->devlist_mutex);
        init_completion(&drv->unloaded);
        return bus_add_driver(drv);
 }
Index: usb-2.6/include/linux/device.h
===================================================================
--- usb-2.6.orig/include/linux/device.h
+++ usb-2.6/include/linux/device.h
@@ -107,7 +107,8 @@ struct device_driver {
 
        struct completion       unloaded;
        struct kobject          kobj;
-       struct klist            klist_devices;
+       struct list_head        devlist;
+       struct semaphore        devlist_mutex;
        struct klist_node       knode_bus;
 
        struct module           * owner;
@@ -270,7 +271,7 @@ extern void class_device_destroy(struct 
 struct device {
        struct klist            klist_children;
        struct klist_node       knode_parent;           /* node in sibling list 
*/
-       struct klist_node       knode_driver;
+       struct list_head        node_driver;
        struct klist_node       knode_bus;
        struct device   * parent;
 
Index: usb-2.6/drivers/base/bus.c
===================================================================
--- usb-2.6.orig/drivers/base/bus.c
+++ usb-2.6/drivers/base/bus.c
@@ -133,7 +133,7 @@ static struct kobj_type ktype_bus = {
 decl_subsys(bus, &ktype_bus, NULL);
 
 
-/* Manually detach a device from it's associated driver. */
+/* Manually detach a device from its associated driver. */
 static int driver_helper(struct device *dev, void *data)
 {
        const char *name = data;
@@ -175,9 +175,9 @@ static ssize_t driver_bind(struct device
        dev = bus_find_device(bus, NULL, (void *)buf, driver_helper);
        if ((dev) &&
            (dev->driver == NULL)) {
-               down(&dev->sem);
+               down(&drv->devlist_mutex);
                err = driver_probe_device(drv, dev);
-               up(&dev->sem);
+               up(&drv->devlist_mutex);
                put_device(dev);
        }
        return err;

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to