ChangeSet 1.1587.3.40, 2004/05/05 14:10:42-07:00, stern@rowland.harvard.edu

[PATCH] PATCH: (as268) Import device-reset changes from gadget-2.6 tree

This patch imports the changes that David Brownell has made to the
device-reset functions in his gadget-2.6 tree.  Once these ongoing
troubling questions about locking are settled, I'll add support for the
"descriptors changed" case.


 drivers/usb/core/hub.c |  196 ++++++++++++++++++++++++-------------------------
 1 files changed, 99 insertions(+), 97 deletions(-)


diff -Nru a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
--- a/drivers/usb/core/hub.c	Fri May 14 15:29:31 2004
+++ b/drivers/usb/core/hub.c	Fri May 14 15:29:31 2004
@@ -38,7 +38,6 @@
 
 /* Wakes up khubd */
 static spinlock_t hub_event_lock = SPIN_LOCK_UNLOCKED;
-static DECLARE_MUTEX(usb_address0_sem);
 
 static LIST_HEAD(hub_event_list);	/* List of hubs needing servicing */
 static LIST_HEAD(hub_list);		/* List of all hubs (for cleanup) */
@@ -962,7 +961,7 @@
 	return -1;
 }
 
-int hub_port_disable(struct usb_device *hub, int port)
+static int hub_port_disable(struct usb_device *hub, int port)
 {
 	int ret;
 
@@ -1042,6 +1041,8 @@
 static int
 hub_port_init (struct usb_device *hub, struct usb_device *dev, int port)
 {
+	static DECLARE_MUTEX(usb_address0_sem);
+
 	int			i, j, retval = -ENODEV;
 	unsigned		delay = HUB_SHORT_RESET_TIME;
 	enum usb_device_speed	oldspeed = dev->speed;
@@ -1447,7 +1448,7 @@
 				hub_port_connect_change(hub, i, portstatus, portchange);
 			} else if (portchange & USB_PORT_STAT_C_ENABLE) {
 				dev_dbg (hubdev (dev),
-					"port %d enable change, status %x\n",
+					"port %d enable change, status %08x\n",
 					i + 1, portstatus);
 				clear_port_feature(dev,
 					i + 1, USB_PORT_FEAT_C_ENABLE);
@@ -1559,6 +1560,9 @@
 	.id_table =	hub_id_table,
 };
 
+/*
+ * This should be a separate module.
+ */
 int usb_hub_init(void)
 {
 	pid_t pid;
@@ -1602,25 +1606,68 @@
 	usb_deregister(&hub_driver);
 } /* usb_hub_cleanup() */
 
+
+static int config_descriptors_changed(struct usb_device *dev)
+{
+	unsigned			index;
+	unsigned			len = 0;
+	struct usb_config_descriptor	*buf;
+
+	for (index = 0; index < dev->descriptor.bNumConfigurations; index++) {
+		if (len < dev->config[index].desc.wTotalLength)
+			len = dev->config[index].desc.wTotalLength;
+	}
+	buf = kmalloc (len, SLAB_KERNEL);
+	if (buf == 0) {
+		dev_err(&dev->dev, "no mem to re-read configs after reset\n");
+		/* assume the worst */
+		return 1;
+	}
+	for (index = 0; index < dev->descriptor.bNumConfigurations; index++) {
+		int length;
+		int old_length = dev->config[index].desc.wTotalLength;
+
+		length = usb_get_descriptor(dev, USB_DT_CONFIG, index, buf,
+				old_length);
+		if (length < old_length) {
+			dev_dbg(&dev->dev, "config index %d, error %d\n",
+					index, length);
+			break;
+		}
+		if (memcmp (buf, dev->rawdescriptors[index], old_length)
+				!= 0) {
+			dev_dbg(&dev->dev, "config index %d changed (#%d)\n",
+				index, buf->bConfigurationValue);
+/* FIXME enable this when we can re-enumerate after reset;
+ * until then DFU-ish drivers need this and other workarounds
+ */
+//			break;
+		}
+	}
+	kfree(buf);
+	return index != dev->descriptor.bNumConfigurations;
+}
+
 /*
- * WARNING - If a driver calls usb_reset_device, you should simulate a
- * disconnect() and probe() for other interfaces you doesn't claim. This
- * is left up to the driver writer right now. This insures other drivers
- * have a chance to re-setup their interface.
+ * WARNING - don't reset any device unless drivers for all of its
+ * interfaces are expecting that reset!  Maybe some driver->reset()
+ * method should eventually help ensure sufficient cooperation.
  *
- * Take a look at proc_resetdevice in devio.c for some sample code to
- * do this.
- * Use this only from within your probe function, otherwise use
- * usb_reset_device() below, which ensure proper locking
+ * This is the same as usb_reset_device() except that the caller
+ * already holds dev->serialize.  For example, it's safe to use
+ * this from a driver probe() routine after downloading new firmware.
  */
-int usb_physical_reset_device(struct usb_device *dev)
+int __usb_reset_device(struct usb_device *dev)
 {
 	struct usb_device *parent = dev->parent;
-	struct usb_device_descriptor *descriptor;
+	struct usb_device_descriptor descriptor = dev->descriptor;
 	int i, ret, port = -1;
 
-	if (!parent) {
-		err("attempting to reset root hub!");
+	if (dev->maxchild) {
+		/* this requires hub- or hcd-specific logic;
+		 * see hub_reset() and OHCI hc_restart()
+		 */
+		dev_dbg(&dev->dev, "%s for hub!\n", __FUNCTION__);
 		return -EINVAL;
 	}
 
@@ -1633,114 +1680,69 @@
 	if (port < 0)
 		return -ENOENT;
 
-	descriptor = kmalloc(sizeof *descriptor, GFP_NOIO);
-	if (!descriptor) {
-		return -ENOMEM;
-	}
-
-	down(&usb_address0_sem);
-
-	/* Send a reset to the device */
-	if (hub_port_reset(parent, port, dev, HUB_SHORT_RESET_TIME)) {
-		hub_port_disable(parent, port);
-		up(&usb_address0_sem);
-		kfree(descriptor);
-		return(-ENODEV);
-	}
-
-	/* Reprogram the Address */
-	ret = usb_set_address(dev);
-	if (ret < 0) {
-		err("USB device not accepting new address (error=%d)", ret);
-		hub_port_disable(parent, port);
-		up(&usb_address0_sem);
-		kfree(descriptor);
-		return ret;
-	}
-
-	/* Let the SET_ADDRESS settle */
-	wait_ms(10);
-
-	up(&usb_address0_sem);
-
-	/*
-	 * Now we fetch the configuration descriptors for the device and
-	 * see if anything has changed. If it has, we dump the current
-	 * parsed descriptors and reparse from scratch. Then we leave
-	 * the device alone for the caller to finish setting up.
-	 *
-	 * If nothing changed, we reprogram the configuration and then
-	 * the alternate settings.
-	 */
-
-	ret = usb_get_descriptor(dev, USB_DT_DEVICE, 0, descriptor,
-			sizeof(*descriptor));
-	if (ret < 0) {
-		kfree(descriptor);
-		return ret;
-	}
-
-	le16_to_cpus(&descriptor->bcdUSB);
-	le16_to_cpus(&descriptor->idVendor);
-	le16_to_cpus(&descriptor->idProduct);
-	le16_to_cpus(&descriptor->bcdDevice);
-
-	if (memcmp(&dev->descriptor, descriptor, sizeof(*descriptor))) {
-		kfree(descriptor);
-		usb_destroy_configuration(dev);
-
-		/* FIXME Linux doesn't yet handle these "device morphed"
-		 * paths.  DFU variants need this to work ... and they
-		 * include the "config descriptors changed" case this
-		 * doesn't yet detect!
-		 */
-		dev->state = USB_STATE_NOTATTACHED;
-		dev_err(&dev->dev, "device morphed (DFU?), nyet supported\n");
-
-		return -ENODEV;
-	}
-
-	kfree(descriptor);
+	ret = hub_port_init(parent, dev, port);
+	if (ret < 0)
+		goto re_enumerate;
+ 
+	/* Device might have changed firmware (DFU or similar) */
+	if (memcmp(&dev->descriptor, &descriptor, sizeof descriptor)
+			|| config_descriptors_changed (dev)) {
+		dev_info(&dev->dev, "device firmware changed\n");
+		dev->descriptor = descriptor;	/* for disconnect() calls */
+		goto re_enumerate;
+  	}
+  
+	if (!dev->actconfig)
+		return 0;
 
 	ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
 			USB_REQ_SET_CONFIGURATION, 0,
 			dev->actconfig->desc.bConfigurationValue, 0,
 			NULL, 0, HZ * USB_CTRL_SET_TIMEOUT);
 	if (ret < 0) {
-		err("failed to set dev %s active configuration (error=%d)",
-			dev->devpath, ret);
-		return ret;
-	}
+		dev_err(&dev->dev,
+			"can't restore configuration #%d (error=%d)\n",
+			dev->actconfig->desc.bConfigurationValue, ret);
+		goto re_enumerate;
+  	}
 	dev->state = USB_STATE_CONFIGURED;
 
 	for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) {
 		struct usb_interface *intf = dev->actconfig->interface[i];
 		struct usb_interface_descriptor *desc;
 
+		/* set_interface resets host side toggle and halt status even
+		 * for altsetting zero.  the interface may have no driver.
+		 */
 		desc = &intf->cur_altsetting->desc;
 		ret = usb_set_interface(dev, desc->bInterfaceNumber,
 			desc->bAlternateSetting);
 		if (ret < 0) {
-			err("failed to set active alternate setting "
-				"for dev %s interface %d (error=%d)",
-				dev->devpath, desc->bInterfaceNumber, ret);
-			return ret;
+			dev_err(&dev->dev, "failed to restore interface %d "
+				"altsetting %d (error=%d)\n",
+				desc->bInterfaceNumber,
+				desc->bAlternateSetting,
+				ret);
+			goto re_enumerate;
 		}
 	}
 
 	return 0;
+ 
+re_enumerate:
+	/* FIXME make some task re-enumerate; don't just mark unusable */
+	dev->state = USB_STATE_NOTATTACHED;
+	return -ENODEV;
 }
+EXPORT_SYMBOL(__usb_reset_device);
 
 int usb_reset_device(struct usb_device *udev)
 {
-	struct device *gdev = &udev->dev;
 	int r;
 	
-	down_read(&gdev->bus->subsys.rwsem);
-	r = usb_physical_reset_device(udev);
-	up_read(&gdev->bus->subsys.rwsem);
+	down(&udev->serialize);
+	r = __usb_reset_device(udev);
+	up(&udev->serialize);
 
 	return r;
 }
-
-
