Greg:

This patch creates a special locking routine intended specifically for use 
when calling usb_reset_device().  A special routine like this is needed to 
avoid deadlocks with disconnect().  Symbolically:

        Khubd                           Driver
        -----                           ------
                                        Wants to reset a device, so calls
                                        its own do_reset() function
        Receives connect-change
        notification and calls
        usb_disconnect()

        Locks the device and calls
        driver->disconnect()
                                        do_reset() tries to lock the 
                                        device for the reset, and blocks
        disconnect() can't return
        until do_reset() is finished

One simple-minded way out would be for the driver's do_reset routine to 
use usb_trylock_device().  I don't like this because it will fail if the 
device happens to be locked for some innocuous reason, like somebody 
reading its entry in /proc/bus/usb/devices.

Instead, usb_lock_device_for_reset() calls usb_trylock_device() 
repeatedly, in a polling loop.  It is careful to check that the device 
state isn't NOTATTACHED and that the driver's interface isn't being 
unbound; this will allow the lock-and-reset sequence to fail in scenarios 
like the one above.

For the new routine to be able to tell when an interface is being unbound, 
I had to add an additional field to struct usb_interface.  It's a simple 
enumeration with four possible values:

        USB_INTERFACE_UNBOUND, USB_INTERFACE_BINDING,
        USB_INTERFACE_BOUND, USB_INTERFACE_UNBINDING

The extra overhead of the new field is not large enough to matter.

A quick summary:

        Rename __usb_reset_device to usb_reset_device and get rid of
        the alternate entry point.

        Notify the HCD that endpoint-0's maxpacket size may change
        during a device reset (should have been written earlier).

        Add the new usb_interface_condition enumeration field to
        struct usb_interface.

        Set the new field appropriately as interfaces are bound,
        unbound, claimed, and released.

        Add usb_lock_device_for_reset().

        Change the usb-storage, microtek, and stir4200 IRDA drivers
        to make them use the new locking routine.

With this patch applied, and using an altered gadget driver that forces 
usb-storage to request a device reset, I've been able to go through a 
large number of different tests successfully.  Reset during probe(), 
reset after probe(), disconnect during reset(), reset with device 
descriptor changing...  They all work beautifully.

Please apply.

Alan Stern



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

diff -Nru a/drivers/net/irda/stir4200.c b/drivers/net/irda/stir4200.c
--- a/drivers/net/irda/stir4200.c       Thu Jun 24 14:09:11 2004
+++ b/drivers/net/irda/stir4200.c       Thu Jun 24 14:09:11 2004
@@ -168,6 +168,7 @@
 
 struct stir_cb {
         struct usb_device *usbdev;      /* init: probe_irda */
+       struct usb_interface *usbintf;
         struct net_device *netdev;      /* network layer */
         struct irlap_cb   *irlap;       /* The link layer we are binded to */
         struct net_device_stats stats; /* network statistics */
@@ -508,6 +509,7 @@
 {
        int i, err;
        __u8 mode;
+       int rc;
 
        for (i = 0; i < ARRAY_SIZE(stir_modes); ++i) {
                if (speed == stir_modes[i].speed)
@@ -521,7 +523,14 @@
        pr_debug("speed change from %d to %d\n", stir->speed, speed);
 
        /* sometimes needed to get chip out of stuck state */
+       rc = usb_lock_device_for_reset(stir->usbdev, stir->usbintf);
+       if (rc < 0) {
+               err = rc;
+               goto out;
+       }
        err = usb_reset_device(stir->usbdev);
+       if (rc)
+               usb_unlock_device(stir->usbdev);
        if (err)
                goto out;
 
@@ -1066,6 +1075,7 @@
        stir = net->priv;
        stir->netdev = net;
        stir->usbdev = dev;
+       stir->usbintf = intf;
 
        ret = usb_reset_configuration(dev);
        if (ret != 0) {
diff -Nru a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c
--- a/drivers/usb/core/devio.c  Thu Jun 24 14:09:11 2004
+++ b/drivers/usb/core/devio.c  Thu Jun 24 14:09:11 2004
@@ -722,7 +722,7 @@
 
 static int proc_resetdevice(struct dev_state *ps)
 {
-       return __usb_reset_device(ps->dev);
+       return usb_reset_device(ps->dev);
 
 }
 
diff -Nru a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
--- a/drivers/usb/core/hub.c    Thu Jun 24 14:09:11 2004
+++ b/drivers/usb/core/hub.c    Thu Jun 24 14:09:11 2004
@@ -2014,8 +2014,10 @@
  *
  * The caller must own the device lock.  For example, it's safe to use
  * this from a driver probe() routine after downloading new firmware.
+ * For calls that might not occur during probe(), drivers should lock
+ * the device using usb_lock_device_for_reset().
  */
-int __usb_reset_device(struct usb_device *udev)
+int usb_reset_device(struct usb_device *udev)
 {
        struct usb_device *parent = udev->parent;
        struct usb_device_descriptor descriptor = udev->descriptor;
@@ -2051,6 +2053,13 @@
                return -ENOENT;
        }
 
+       /* ep0 maxpacket size may change; let the HCD know about it.
+        * Other endpoints will be handled by re-enumeration. */
+       usb_disable_endpoint(udev, 0);
+       usb_disable_endpoint(udev, 0 + USB_DIR_IN);
+       usb_endpoint_running(udev, 0, 0);
+       usb_endpoint_running(udev, 0, 1);
+
        ret = hub_port_init(parent, udev, port);
        if (ret < 0)
                goto re_enumerate;
@@ -2114,16 +2123,4 @@
        spin_unlock_irq(&hub_event_lock);
 
        return -ENODEV;
-}
-EXPORT_SYMBOL(__usb_reset_device);
-
-int usb_reset_device(struct usb_device *udev)
-{
-       int r;
-       
-       down(&udev->serialize);
-       r = __usb_reset_device(udev);
-       up(&udev->serialize);
-
-       return r;
 }
diff -Nru a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
--- a/drivers/usb/core/usb.c    Thu Jun 24 14:09:11 2004
+++ b/drivers/usb/core/usb.c    Thu Jun 24 14:09:11 2004
@@ -100,7 +100,10 @@
        id = usb_match_id (intf, driver->id_table);
        if (id) {
                dev_dbg (dev, "%s - got id\n", __FUNCTION__);
+               intf->condition = USB_INTERFACE_BINDING;
                error = driver->probe (intf, id);
+               intf->condition = error ? USB_INTERFACE_UNBOUND :
+                               USB_INTERFACE_BOUND;
        }
 
        return error;
@@ -112,6 +115,8 @@
        struct usb_interface *intf = to_usb_interface(dev);
        struct usb_driver *driver = to_usb_driver(intf->dev.driver);
 
+       intf->condition = USB_INTERFACE_UNBINDING;
+
        /* release all urbs for this interface */
        usb_disable_interface(interface_to_usbdev(intf), intf);
 
@@ -123,6 +128,7 @@
                        intf->altsetting[0].desc.bInterfaceNumber,
                        0);
        usb_set_intfdata(intf, NULL);
+       intf->condition = USB_INTERFACE_UNBOUND;
 
        return 0;
 }
@@ -321,6 +327,7 @@
 
        dev->driver = &driver->driver;
        usb_set_intfdata(iface, priv);
+       iface->condition = USB_INTERFACE_BOUND;
 
        /* if interface was already added, bind now; else let
         * the future device_add() bind it, bypassing probe()
@@ -360,6 +367,7 @@
 
        dev->driver = NULL;
        usb_set_intfdata(iface, NULL);
+       iface->condition = USB_INTERFACE_UNBOUND;
 }
 
 /**
@@ -869,6 +877,50 @@
 }
 
 /**
+ * usb_lock_device_for_reset - cautiously acquire the lock for a
+ *     usb device structure
+ * @udev: device that's being locked
+ * @iface: interface bound to the driver making the request (optional)
+ *
+ * Attempts to acquire the device lock, but fails if the device is
+ * NOTATTACHED or SUSPENDED, or if iface is specified and the interface
+ * is neither BINDING nor BOUND.  Rather than sleeping to wait for the
+ * lock, the routine polls repeatedly.  This is to prevent deadlock with
+ * disconnect; in some drivers (such as usb-storage) the disconnect()
+ * callback will block waiting for a device reset to complete.
+ *
+ * Returns a negative error code for failure, otherwise 1 or 0 to indicate
+ * that the device will or will not have to be unlocked.  (0 can be
+ * returned when an interface is given and is BINDING, because in that
+ * case the driver already owns the device lock.)
+ */
+int usb_lock_device_for_reset(struct usb_device *udev,
+               struct usb_interface *iface)
+{
+       if (udev->state == USB_STATE_NOTATTACHED)
+               return -ENODEV;
+       if (iface) {
+               switch (iface->condition) {
+                 case USB_INTERFACE_BINDING:
+                       return 0;
+                 case USB_INTERFACE_BOUND:
+                       break;
+                 default:
+                       return -EINTR;
+               }
+       }
+
+       while (!usb_trylock_device(udev)) {
+               msleep(15);
+               if (udev->state == USB_STATE_NOTATTACHED)
+                       return -ENODEV;
+               if (iface && iface->condition != USB_INTERFACE_BOUND)
+                       return -EINTR;
+       }
+       return 1;
+}
+
+/**
  * usb_unlock_device - release the lock for a usb device structure
  * @udev: device that's being unlocked
  *
@@ -1442,6 +1494,7 @@
 
 EXPORT_SYMBOL(usb_lock_device);
 EXPORT_SYMBOL(usb_trylock_device);
+EXPORT_SYMBOL(usb_lock_device_for_reset);
 EXPORT_SYMBOL(usb_unlock_device);
 
 EXPORT_SYMBOL(usb_driver_claim_interface);
diff -Nru a/drivers/usb/image/microtek.c b/drivers/usb/image/microtek.c
--- a/drivers/usb/image/microtek.c      Thu Jun 24 14:09:11 2004
+++ b/drivers/usb/image/microtek.c      Thu Jun 24 14:09:11 2004
@@ -341,12 +341,18 @@
 static int mts_scsi_host_reset (Scsi_Cmnd *srb)
 {
        struct mts_desc* desc = (struct mts_desc*)(srb->device->host->hostdata[0]);
+       int result, rc;
 
        MTS_DEBUG_GOT_HERE();
        mts_debug_dump(desc);
 
-       usb_reset_device(desc->usb_dev); /*FIXME: untested on new reset code */
-       return 0;  /* RANT why here 0 and not SUCCESS */
+       rc = usb_lock_device_for_reset(desc->usb_dev, desc->usb_intf);
+       if (rc < 0)
+               return FAILED;
+       result = usb_reset_device(desc->usb_dev);;
+       if (rc)
+               usb_unlock_device(desc->usb_dev);
+       return result ? FAILED : SUCCESS;
 }
 
 static
@@ -777,6 +783,7 @@
                goto out_kfree;
 
        new_desc->usb_dev = dev;
+       new_desc->usb_intf = intf;
        init_MUTEX(&new_desc->lock);
 
        /* endpoints */
diff -Nru a/drivers/usb/image/microtek.h b/drivers/usb/image/microtek.h
--- a/drivers/usb/image/microtek.h      Thu Jun 24 14:09:11 2004
+++ b/drivers/usb/image/microtek.h      Thu Jun 24 14:09:11 2004
@@ -31,6 +31,7 @@
        struct mts_desc *prev;
 
        struct usb_device *usb_dev;
+       struct usb_interface *usb_intf;
 
        /* Endpoint addresses */
        u8 ep_out;
diff -Nru a/drivers/usb/storage/scsiglue.c b/drivers/usb/storage/scsiglue.c
--- a/drivers/usb/storage/scsiglue.c    Thu Jun 24 14:09:11 2004
+++ b/drivers/usb/storage/scsiglue.c    Thu Jun 24 14:09:11 2004
@@ -261,7 +261,7 @@
 static int bus_reset( Scsi_Cmnd *srb )
 {
        struct us_data *us = (struct us_data *)srb->device->host->hostdata[0];
-       int result;
+       int result, rc;
 
        US_DEBUGP("%s called\n", __FUNCTION__);
        if (us->sm_state != US_STATE_IDLE) {
@@ -286,8 +286,16 @@
                result = -EBUSY;
                US_DEBUGP("Refusing to reset a multi-interface device\n");
        } else {
-               result = usb_reset_device(us->pusb_dev);
-               US_DEBUGP("usb_reset_device returns %d\n", result);
+               rc = usb_lock_device_for_reset(us->pusb_dev, us->pusb_intf);
+               if (rc < 0) {
+                       US_DEBUGP("unable to lock device for reset: %d\n", rc);
+                       result = rc;
+               } else {
+                       result = usb_reset_device(us->pusb_dev);
+                       if (rc)
+                               usb_unlock_device(us->pusb_dev);
+                       US_DEBUGP("usb_reset_device returns %d\n", result);
+               }
        }
        up(&(us->dev_semaphore));
 
diff -Nru a/include/linux/usb.h b/include/linux/usb.h
--- a/include/linux/usb.h       Thu Jun 24 14:09:11 2004
+++ b/include/linux/usb.h       Thu Jun 24 14:09:11 2004
@@ -61,6 +61,13 @@
        int extralen;
 };
 
+enum usb_interface_condition {
+       USB_INTERFACE_UNBOUND = 0,
+       USB_INTERFACE_BINDING,
+       USB_INTERFACE_BOUND,
+       USB_INTERFACE_UNBINDING,
+};
+
 /**
  * struct usb_interface - what usb device drivers talk to
  * @altsetting: array of interface structures, one for each alternate
@@ -75,6 +82,8 @@
  *     be unused.  The driver should set this value in the probe()
  *     function of the driver, after it has been assigned a minor
  *     number from the USB core by calling usb_register_dev().
+ * @condition: binding state of the interface: not bound, binding
+ *     (in probe()), bound to a driver, or unbinding (in disconnect())
  * @dev: driver model's view of this device
  * @class_dev: driver model's class view of this device.
  *
@@ -113,6 +122,7 @@
        unsigned num_altsetting;        /* number of alternate settings */
 
        int minor;                      /* minor number this interface is bound to */
+       enum usb_interface_condition condition;         /* state of binding */
        struct device dev;              /* interface specific device info */
        struct class_device *class_dev;
 };
@@ -346,11 +356,12 @@
 /* for device locking -- don't manipulate usbdev->serialize directly! */
 extern void usb_lock_device(struct usb_device *udev);
 extern int usb_trylock_device(struct usb_device *udev);
+extern int usb_lock_device_for_reset(struct usb_device *udev,
+               struct usb_interface *iface);
 extern void usb_unlock_device(struct usb_device *udev);
 
-/* mostly for devices emulating SCSI over USB */
+/* USB port reset for device reinitialization */
 extern int usb_reset_device(struct usb_device *dev);
-extern int __usb_reset_device(struct usb_device *dev);
 
 extern struct usb_device *usb_find_device(u16 vendor_id, u16 product_id);
 



-------------------------------------------------------
This SF.Net email sponsored by Black Hat Briefings & Training.
Attend Black Hat Briefings & Training, Las Vegas July 24-29 - 
digital self defense, top technical experts, no vendor pitches, 
unmatched networking opportunities. Visit www.blackhat.com
_______________________________________________
[EMAIL PROTECTED]
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel

Reply via email to