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