USB: Disable hub-initiated LPM for comms devices.
[sfrench/cifs-2.6.git] / drivers / usb / class / cdc-wdm.c
index c6f6560d436c804bba1f539a2b6cd1c12be8633d..ea8b304f0e853af63651e0d70c5f6d1b69938fe9 100644 (file)
@@ -157,8 +157,9 @@ static void wdm_out_callback(struct urb *urb)
        spin_lock(&desc->iuspin);
        desc->werr = urb->status;
        spin_unlock(&desc->iuspin);
-       clear_bit(WDM_IN_USE, &desc->flags);
        kfree(desc->outbuf);
+       desc->outbuf = NULL;
+       clear_bit(WDM_IN_USE, &desc->flags);
        wake_up(&desc->wait);
 }
 
@@ -308,9 +309,6 @@ static void free_urbs(struct wdm_device *desc)
 
 static void cleanup(struct wdm_device *desc)
 {
-       spin_lock(&wdm_device_list_lock);
-       list_del(&desc->device_list);
-       spin_unlock(&wdm_device_list_lock);
        kfree(desc->sbuf);
        kfree(desc->inbuf);
        kfree(desc->orq);
@@ -338,7 +336,7 @@ static ssize_t wdm_write
        if (we < 0)
                return -EIO;
 
-       desc->outbuf = buf = kmalloc(count, GFP_KERNEL);
+       buf = kmalloc(count, GFP_KERNEL);
        if (!buf) {
                rv = -ENOMEM;
                goto outnl;
@@ -368,6 +366,7 @@ static ssize_t wdm_write
        r = usb_autopm_get_interface(desc->intf);
        if (r < 0) {
                kfree(buf);
+               rv = usb_translate_errors(r);
                goto outnp;
        }
 
@@ -383,6 +382,7 @@ static ssize_t wdm_write
 
        if (r < 0) {
                kfree(buf);
+               rv = r;
                goto out;
        }
 
@@ -406,12 +406,15 @@ static ssize_t wdm_write
        req->wIndex = desc->inum;
        req->wLength = cpu_to_le16(count);
        set_bit(WDM_IN_USE, &desc->flags);
+       desc->outbuf = buf;
 
        rv = usb_submit_urb(desc->command, GFP_KERNEL);
        if (rv < 0) {
                kfree(buf);
+               desc->outbuf = NULL;
                clear_bit(WDM_IN_USE, &desc->flags);
                dev_err(&desc->intf->dev, "Tx URB error: %d\n", rv);
+               rv = usb_translate_errors(rv);
        } else {
                dev_dbg(&desc->intf->dev, "Tx URB has been submitted index=%d",
                        req->wIndex);
@@ -527,11 +530,13 @@ static int wdm_flush(struct file *file, fl_owner_t id)
        struct wdm_device *desc = file->private_data;
 
        wait_event(desc->wait, !test_bit(WDM_IN_USE, &desc->flags));
-       if (desc->werr < 0)
+
+       /* cannot dereference desc->intf if WDM_DISCONNECTING */
+       if (desc->werr < 0 && !test_bit(WDM_DISCONNECTING, &desc->flags))
                dev_err(&desc->intf->dev, "Error in flush path: %d\n",
                        desc->werr);
 
-       return desc->werr;
+       return usb_translate_errors(desc->werr);
 }
 
 static unsigned int wdm_poll(struct file *file, struct poll_table_struct *wait)
@@ -542,7 +547,7 @@ static unsigned int wdm_poll(struct file *file, struct poll_table_struct *wait)
 
        spin_lock_irqsave(&desc->iuspin, flags);
        if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
-               mask = POLLERR;
+               mask = POLLHUP | POLLERR;
                spin_unlock_irqrestore(&desc->iuspin, flags);
                goto desc_out;
        }
@@ -593,6 +598,7 @@ static int wdm_open(struct inode *inode, struct file *file)
                        desc->count--;
                        dev_err(&desc->intf->dev,
                                "Error submitting int urb - %d\n", rv);
+                       rv = usb_translate_errors(rv);
                }
        } else {
                rv = 0;
@@ -618,10 +624,15 @@ static int wdm_release(struct inode *inode, struct file *file)
        mutex_unlock(&desc->wlock);
 
        if (!desc->count) {
-               dev_dbg(&desc->intf->dev, "wdm_release: cleanup");
-               kill_urbs(desc);
-               if (!test_bit(WDM_DISCONNECTING, &desc->flags))
+               if (!test_bit(WDM_DISCONNECTING, &desc->flags)) {
+                       dev_dbg(&desc->intf->dev, "wdm_release: cleanup");
+                       kill_urbs(desc);
                        desc->manage_power(desc->intf, 0);
+               } else {
+                       /* must avoid dev_printk here as desc->intf is invalid */
+                       pr_debug(KBUILD_MODNAME " %s: device gone - cleaning up\n", __func__);
+                       cleanup(desc);
+               }
        }
        mutex_unlock(&wdm_mutex);
        return 0;
@@ -768,6 +779,9 @@ static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor
 out:
        return rv;
 err:
+       spin_lock(&wdm_device_list_lock);
+       list_del(&desc->device_list);
+       spin_unlock(&wdm_device_list_lock);
        cleanup(desc);
        return rv;
 }
@@ -893,8 +907,16 @@ static void wdm_disconnect(struct usb_interface *intf)
        cancel_work_sync(&desc->rxwork);
        mutex_unlock(&desc->wlock);
        mutex_unlock(&desc->rlock);
+
+       /* the desc->intf pointer used as list key is now invalid */
+       spin_lock(&wdm_device_list_lock);
+       list_del(&desc->device_list);
+       spin_unlock(&wdm_device_list_lock);
+
        if (!desc->count)
                cleanup(desc);
+       else
+               dev_dbg(&intf->dev, "%s: %d open files - postponing cleanup\n", __func__, desc->count);
        mutex_unlock(&wdm_mutex);
 }
 
@@ -1012,6 +1034,7 @@ static struct usb_driver wdm_driver = {
        .post_reset =   wdm_post_reset,
        .id_table =     wdm_ids,
        .supports_autosuspend = 1,
+       .disable_hub_initiated_lpm = 1,
 };
 
 module_usb_driver(wdm_driver);