HID: usbhid: fix error paths in suspend
authorAlan Stern <stern@rowland.harvard.edu>
Thu, 19 Jul 2012 20:09:01 +0000 (16:09 -0400)
committerJiri Kosina <jkosina@suse.cz>
Fri, 20 Jul 2012 09:24:25 +0000 (11:24 +0200)
This patch (as1597) fixes some of the error paths in usbhid's suspend
routine.  The driver was not careful to restart everything that might
have been stopped, in cases where a suspend failed.

For example, once the HID_SUSPENDED flag is set, an output report
submission would not restart the corresponding URB queue.  If a
suspend fails, it's therefore necessary to check whether the queues
need to be restarted.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
CC: Oliver Neukum <oliver@neukum.org>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
drivers/hid/usbhid/hid-core.c

index 4309c03..dedd8e4 100644 (file)
@@ -993,9 +993,10 @@ static int usbhid_output_raw_report(struct hid_device *hid, __u8 *buf, size_t co
 
 static void usbhid_restart_queues(struct usbhid_device *usbhid)
 {
-       if (usbhid->urbout)
+       if (usbhid->urbout && !test_bit(HID_OUT_RUNNING, &usbhid->iofl))
                usbhid_restart_out_queue(usbhid);
-       usbhid_restart_ctrl_queue(usbhid);
+       if (!test_bit(HID_CTRL_RUNNING, &usbhid->iofl))
+               usbhid_restart_ctrl_queue(usbhid);
 }
 
 static void hid_free_buffers(struct usb_device *dev, struct hid_device *hid)
@@ -1462,11 +1463,38 @@ void usbhid_put_power(struct hid_device *hid)
 
 
 #ifdef CONFIG_PM
+static int hid_resume_common(struct hid_device *hid, bool driver_suspended)
+{
+       struct usbhid_device *usbhid = hid->driver_data;
+       int status;
+
+       spin_lock_irq(&usbhid->lock);
+       clear_bit(HID_SUSPENDED, &usbhid->iofl);
+       usbhid_mark_busy(usbhid);
+
+       if (test_bit(HID_CLEAR_HALT, &usbhid->iofl) ||
+                       test_bit(HID_RESET_PENDING, &usbhid->iofl))
+               schedule_work(&usbhid->reset_work);
+       usbhid->retry_delay = 0;
+
+       usbhid_restart_queues(usbhid);
+       spin_unlock_irq(&usbhid->lock);
+
+       status = hid_start_in(hid);
+       if (status < 0)
+               hid_io_error(hid);
+
+       if (driver_suspended && hid->driver && hid->driver->resume)
+               status = hid->driver->resume(hid);
+       return status;
+}
+
 static int hid_suspend(struct usb_interface *intf, pm_message_t message)
 {
        struct hid_device *hid = usb_get_intfdata(intf);
        struct usbhid_device *usbhid = hid->driver_data;
        int status;
+       bool driver_suspended = false;
 
        if (PMSG_IS_AUTO(message)) {
                spin_lock_irq(&usbhid->lock);   /* Sync with error handler */
@@ -1482,8 +1510,9 @@ static int hid_suspend(struct usb_interface *intf, pm_message_t message)
                        if (hid->driver && hid->driver->suspend) {
                                status = hid->driver->suspend(hid, message);
                                if (status < 0)
-                                       return status;
+                                       goto failed;
                        }
+                       driver_suspended = true;
                } else {
                        usbhid_mark_busy(usbhid);
                        spin_unlock_irq(&usbhid->lock);
@@ -1496,11 +1525,14 @@ static int hid_suspend(struct usb_interface *intf, pm_message_t message)
                        if (status < 0)
                                return status;
                }
+               driver_suspended = true;
                spin_lock_irq(&usbhid->lock);
                set_bit(HID_SUSPENDED, &usbhid->iofl);
                spin_unlock_irq(&usbhid->lock);
-               if (usbhid_wait_io(hid) < 0)
-                       return -EIO;
+               if (usbhid_wait_io(hid) < 0) {
+                       status = -EIO;
+                       goto failed;
+               }
        }
 
        hid_cancel_delayed_stuff(usbhid);
@@ -1508,14 +1540,15 @@ static int hid_suspend(struct usb_interface *intf, pm_message_t message)
 
        if (PMSG_IS_AUTO(message) && test_bit(HID_KEYS_PRESSED, &usbhid->iofl)) {
                /* lost race against keypresses */
-               status = hid_start_in(hid);
-               if (status < 0)
-                       hid_io_error(hid);
-               usbhid_mark_busy(usbhid);
-               return -EBUSY;
+               status = -EBUSY;
+               goto failed;
        }
        dev_dbg(&intf->dev, "suspend\n");
        return 0;
+
+ failed:
+       hid_resume_common(hid, driver_suspended);
+       return status;
 }
 
 static int hid_resume(struct usb_interface *intf)
@@ -1527,23 +1560,7 @@ static int hid_resume(struct usb_interface *intf)
        if (!test_bit(HID_STARTED, &usbhid->iofl))
                return 0;
 
-       clear_bit(HID_SUSPENDED, &usbhid->iofl);
-       usbhid_mark_busy(usbhid);
-
-       if (test_bit(HID_CLEAR_HALT, &usbhid->iofl) ||
-           test_bit(HID_RESET_PENDING, &usbhid->iofl))
-               schedule_work(&usbhid->reset_work);
-       usbhid->retry_delay = 0;
-       status = hid_start_in(hid);
-       if (status < 0)
-               hid_io_error(hid);
-       usbhid_restart_queues(usbhid);
-
-       if (status >= 0 && hid->driver && hid->driver->resume) {
-               int ret = hid->driver->resume(hid);
-               if (ret < 0)
-                       status = ret;
-       }
+       status = hid_resume_common(hid, true);
        dev_dbg(&intf->dev, "resume status %d\n", status);
        return 0;
 }