Skip to content

Commit

Permalink
HID: usbhid: fix error paths in suspend
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Alan Stern authored and Jiri Kosina committed Jul 20, 2012
1 parent d4150c8 commit eb055fd
Showing 1 changed file with 44 additions and 27 deletions.
71 changes: 44 additions & 27 deletions drivers/hid/usbhid/hid-core.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 */
Expand All @@ -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);
Expand All @@ -1496,26 +1525,30 @@ 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);
hid_cease_io(usbhid);

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)
Expand All @@ -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;
}
Expand Down

0 comments on commit eb055fd

Please sign in to comment.