summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--drivers/usb/core/hub.c114
1 files changed, 80 insertions, 34 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 087e3bb70e09..df68e2562582 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -644,6 +644,81 @@ static void hub_stop(struct usb_hub *hub)
 	hub_quiesce(hub);
 }
 
+#define HUB_RESET		1
+#define HUB_RESUME		2
+#define HUB_RESET_RESUME	3
+
+#ifdef CONFIG_PM
+
+static void hub_restart(struct usb_hub *hub, int type)
+{
+	struct usb_device *hdev = hub->hdev;
+	int port1;
+
+	/* Check each of the children to see if they require
+	 * USB-PERSIST handling or disconnection.  Also check
+	 * each unoccupied port to make sure it is still disabled.
+	 */
+	for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
+		struct usb_device *udev = hdev->children[port1-1];
+		int status = 0;
+		u16 portstatus, portchange;
+
+		if (!udev || udev->state == USB_STATE_NOTATTACHED) {
+			if (type != HUB_RESET) {
+				status = hub_port_status(hub, port1,
+						&portstatus, &portchange);
+				if (status == 0 && (portstatus &
+						USB_PORT_STAT_ENABLE))
+					clear_port_feature(hdev, port1,
+							USB_PORT_FEAT_ENABLE);
+			}
+			continue;
+		}
+
+		/* Was the power session lost while we were suspended? */
+		switch (type) {
+		case HUB_RESET_RESUME:
+			portstatus = 0;
+			portchange = USB_PORT_STAT_C_CONNECTION;
+			break;
+
+		case HUB_RESET:
+		case HUB_RESUME:
+			status = hub_port_status(hub, port1,
+					&portstatus, &portchange);
+			break;
+		}
+
+		/* For "USB_PERSIST"-enabled children we must
+		 * mark the child device for reset-resume and
+		 * turn off the various status changes to prevent
+		 * khubd from disconnecting it later.
+		 */
+		if (USB_PERSIST && udev->persist_enabled && status == 0 &&
+				!(portstatus & USB_PORT_STAT_ENABLE)) {
+			if (portchange & USB_PORT_STAT_C_ENABLE)
+				clear_port_feature(hub->hdev, port1,
+						USB_PORT_FEAT_C_ENABLE);
+			if (portchange & USB_PORT_STAT_C_CONNECTION)
+				clear_port_feature(hub->hdev, port1,
+						USB_PORT_FEAT_C_CONNECTION);
+			udev->reset_resume = 1;
+		}
+
+		/* Otherwise for a reset_resume we must disconnect the child,
+		 * but as we may not lock the child device here
+		 * we have to do a "logical" disconnect.
+		 */
+		else if (type == HUB_RESET_RESUME)
+			hub_port_logical_disconnect(hub, port1);
+	}
+
+	hub_activate(hub);
+}
+
+#endif	/* CONFIG_PM */
+
 /* caller has locked the hub device */
 static int hub_pre_reset(struct usb_interface *intf)
 {
@@ -2015,49 +2090,20 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg)
 
 static int hub_resume(struct usb_interface *intf)
 {
-	struct usb_hub		*hub = usb_get_intfdata (intf);
-
-	dev_dbg(&intf->dev, "%s\n", __FUNCTION__);
+	struct usb_hub *hub = usb_get_intfdata(intf);
 
-	/* tell khubd to look for changes on this hub */
-	hub_activate(hub);
+	dev_dbg(&intf->dev, "%s\n", __func__);
+	hub_restart(hub, HUB_RESUME);
 	return 0;
 }
 
 static int hub_reset_resume(struct usb_interface *intf)
 {
 	struct usb_hub *hub = usb_get_intfdata(intf);
-	struct usb_device *hdev = hub->hdev;
-	int port1;
 
+	dev_dbg(&intf->dev, "%s\n", __func__);
 	hub_power_on(hub);
-
-	for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
-		struct usb_device *child = hdev->children[port1-1];
-
-		if (child) {
-
-			/* For "USB_PERSIST"-enabled children we must
-			 * mark the child device for reset-resume and
-			 * turn off the connect-change status to prevent
-			 * khubd from disconnecting it later.
-			 */
-			if (USB_PERSIST && child->persist_enabled) {
-				child->reset_resume = 1;
-				clear_port_feature(hdev, port1,
-						USB_PORT_FEAT_C_CONNECTION);
-
-			/* Otherwise we must disconnect the child,
-			 * but as we may not lock the child device here
-			 * we have to do a "logical" disconnect.
-			 */
-			} else {
-				hub_port_logical_disconnect(hub, port1);
-			}
-		}
-	}
-
-	hub_activate(hub);
+	hub_restart(hub, HUB_RESET_RESUME);
 	return 0;
 }