USB: fix bug of device descriptor got from superspeed device
[linux-flexiantxendom0.git] / drivers / usb / core / hub.c
index bc06a8f..ada678e 100644 (file)
@@ -24,6 +24,7 @@
 #include <linux/kthread.h>
 #include <linux/mutex.h>
 #include <linux/freezer.h>
+#include <linux/rcupdate.h>
 
 #include <asm/uaccess.h>
 #include <asm/byteorder.h>
@@ -1936,6 +1937,10 @@ fail:
  */
 int usb_deauthorize_device(struct usb_device *usb_dev)
 {
+       char *product = NULL;
+       char *manufacturer = NULL;
+       char *serial = NULL;
+
        usb_lock_device(usb_dev);
        if (usb_dev->authorized == 0)
                goto out_unauthorized;
@@ -1943,11 +1948,12 @@ int usb_deauthorize_device(struct usb_device *usb_dev)
        usb_dev->authorized = 0;
        usb_set_configuration(usb_dev, -1);
 
-       kfree(usb_dev->product);
+       product = usb_dev->product;
+       manufacturer = usb_dev->manufacturer;
+       serial = usb_dev->serial;
+
        usb_dev->product = kstrdup("n/a (unauthorized)", GFP_KERNEL);
-       kfree(usb_dev->manufacturer);
        usb_dev->manufacturer = kstrdup("n/a (unauthorized)", GFP_KERNEL);
-       kfree(usb_dev->serial);
        usb_dev->serial = kstrdup("n/a (unauthorized)", GFP_KERNEL);
 
        usb_destroy_configuration(usb_dev);
@@ -1955,6 +1961,12 @@ int usb_deauthorize_device(struct usb_device *usb_dev)
 
 out_unauthorized:
        usb_unlock_device(usb_dev);
+       if (product || manufacturer || serial) {
+               synchronize_rcu();
+               kfree(product);
+               kfree(manufacturer);
+               kfree(serial);
+       }
        return 0;
 }
 
@@ -1962,6 +1974,9 @@ out_unauthorized:
 int usb_authorize_device(struct usb_device *usb_dev)
 {
        int result = 0, c;
+       char *product = NULL;
+       char *manufacturer = NULL;
+       char *serial = NULL;
 
        usb_lock_device(usb_dev);
        if (usb_dev->authorized == 1)
@@ -1980,11 +1995,12 @@ int usb_authorize_device(struct usb_device *usb_dev)
                goto error_device_descriptor;
        }
 
-       kfree(usb_dev->product);
+       product = usb_dev->product;
+       manufacturer = usb_dev->manufacturer;
+       serial = usb_dev->serial;
+       
        usb_dev->product = NULL;
-       kfree(usb_dev->manufacturer);
        usb_dev->manufacturer = NULL;
-       kfree(usb_dev->serial);
        usb_dev->serial = NULL;
 
        usb_dev->authorized = 1;
@@ -2012,6 +2028,12 @@ error_device_descriptor:
 error_autoresume:
 out_authorized:
        usb_unlock_device(usb_dev);     // complements locktree
+       if (product || manufacturer || serial) {
+               synchronize_rcu();
+               kfree(product);
+               kfree(manufacturer);
+               kfree(serial);
+       }
        return result;
 }
 
@@ -3072,6 +3094,22 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
        if (retval)
                goto fail;
 
+       /*
+        * Some superspeed devices have finished the link training process
+        * and attached to a superspeed hub port, but the device descriptor
+        * got from those devices show they aren't superspeed devices. Warm
+        * reset the port attached by the devices can fix them.
+        */
+       if ((udev->speed == USB_SPEED_SUPER) &&
+                       (le16_to_cpu(udev->descriptor.bcdUSB) < 0x0300)) {
+               dev_err(&udev->dev, "got a wrong device descriptor, "
+                               "warm reset device\n");
+               hub_port_reset(hub, port1, udev,
+                               HUB_BH_RESET_TIME, true);
+               retval = -EINVAL;
+               goto fail;
+       }
+
        if (udev->descriptor.bMaxPacketSize0 == 0xff ||
                        udev->speed == USB_SPEED_SUPER)
                i = 512;