summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/usb/core/driver.c34
-rw-r--r--drivers/usb/core/hub.c14
-rw-r--r--include/linux/usb.h1
3 files changed, 38 insertions, 11 deletions
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
index 40c1bf0..0fa15bd 100644
--- a/drivers/usb/core/driver.c
+++ b/drivers/usb/core/driver.c
@@ -1048,7 +1048,7 @@ int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
/* If the suspend succeeded, propagate it up the tree */
} else if (parent)
- usb_autosuspend_device(parent, 0);
+ usb_autosuspend_device(parent, 1);
// dev_dbg(&udev->dev, "%s: status %d\n", __FUNCTION__, status);
return status;
@@ -1096,9 +1096,25 @@ int usb_resume_both(struct usb_device *udev)
/* Propagate the resume up the tree, if necessary */
if (udev->state == USB_STATE_SUSPENDED) {
if (parent) {
- usb_pm_lock(parent);
- parent->auto_pm = 1;
- status = usb_resume_both(parent);
+ status = usb_autoresume_device(parent, 1);
+ if (status == 0) {
+ status = usb_resume_device(udev);
+ if (status) {
+ usb_autosuspend_device(parent, 1);
+
+ /* It's possible usb_resume_device()
+ * failed after the port was
+ * unsuspended, causing udev to be
+ * logically disconnected. We don't
+ * want usb_disconnect() to autosuspend
+ * the parent again, so tell it that
+ * udev disconnected while still
+ * suspended. */
+ if (udev->state ==
+ USB_STATE_NOTATTACHED)
+ udev->discon_suspended = 1;
+ }
+ }
} else {
/* We can't progagate beyond the USB subsystem,
@@ -1107,11 +1123,9 @@ int usb_resume_both(struct usb_device *udev)
if (udev->dev.parent->power.power_state.event !=
PM_EVENT_ON)
status = -EHOSTUNREACH;
- }
- if (status == 0)
- status = usb_resume_device(udev);
- if (parent)
- usb_pm_unlock(parent);
+ else
+ status = usb_resume_device(udev);
+ }
} else {
/* Needed only for setting udev->dev.power.power_state.event
@@ -1119,8 +1133,6 @@ int usb_resume_both(struct usb_device *udev)
status = usb_resume_device(udev);
}
- /* Now the parent won't suspend until we are finished */
-
if (status == 0 && udev->actconfig) {
for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) {
intf = udev->actconfig->interface[i];
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 46df5e6..e46d38b 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -1039,6 +1039,8 @@ static void recursively_mark_NOTATTACHED(struct usb_device *udev)
if (udev->children[i])
recursively_mark_NOTATTACHED(udev->children[i]);
}
+ if (udev->state == USB_STATE_SUSPENDED)
+ udev->discon_suspended = 1;
udev->state = USB_STATE_NOTATTACHED;
}
@@ -1228,6 +1230,14 @@ void usb_disconnect(struct usb_device **pdev)
*pdev = NULL;
spin_unlock_irq(&device_state_lock);
+ /* Decrement the parent's count of unsuspended children */
+ if (udev->parent) {
+ usb_pm_lock(udev);
+ if (!udev->discon_suspended)
+ usb_autosuspend_device(udev->parent, 1);
+ usb_pm_unlock(udev);
+ }
+
put_device(&udev->dev);
}
@@ -1356,6 +1366,10 @@ static int __usb_new_device(void *void_data)
goto fail;
}
+ /* Increment the parent's count of unsuspended children */
+ if (udev->parent)
+ usb_autoresume_device(udev->parent, 1);
+
exit:
module_put(THIS_MODULE);
return err;
diff --git a/include/linux/usb.h b/include/linux/usb.h
index 5634a2d..0cd73ed 100644
--- a/include/linux/usb.h
+++ b/include/linux/usb.h
@@ -362,6 +362,7 @@ struct usb_device {
u8 portnum; /* Parent port number (origin 1) */
u8 level; /* Number of USB hub ancestors */
+ unsigned discon_suspended:1; /* Disconnected while suspended */
unsigned have_langid:1; /* whether string_langid is valid */
int string_langid; /* language ID for strings */