usbcore: resume device resume recursion

This patch (as717b) removes the existing recursion in hub resume code:
Resuming a hub will no longer automatically resume the devices attached
to the hub.

At the same time, it adds one level of recursion: Suspending a USB
device will automatically suspend all the device's interfaces.  Failure
at an intermediate stage will cause all the already-suspended interfaces
to be resumed. Attempts to suspend or resume an interface by itself will
do nothing, although they won't return an error.  Thus the regular
system-suspend and system-resume procedures should continue to work as
before; only runtime PM will be affected.

The patch also removes the code that tests state of the interfaces
before suspending a device.  It's no longer needed, since everything
gets suspended together.


Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
index b0db158..eefc985 100644
--- a/drivers/usb/core/driver.c
+++ b/drivers/usb/core/driver.c
@@ -783,7 +783,7 @@
 	return udriver->resume(udev);
 }
 
-/* Caller has locked intf */
+/* Caller has locked intf's usb_device */
 static int suspend_interface(struct usb_interface *intf, pm_message_t msg)
 {
 	struct usb_driver	*driver;
@@ -815,7 +815,7 @@
 	return status;
 }
 
-/* Caller has locked intf */
+/* Caller has locked intf's usb_device */
 static int resume_interface(struct usb_interface *intf)
 {
 	struct usb_driver	*driver;
@@ -856,14 +856,59 @@
 	return 0;
 }
 
+/* Caller has locked udev */
+int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
+{
+	int			status = 0;
+	int			i = 0;
+	struct usb_interface	*intf;
+
+	if (udev->actconfig) {
+		for (; i < udev->actconfig->desc.bNumInterfaces; i++) {
+			intf = udev->actconfig->interface[i];
+			status = suspend_interface(intf, msg);
+			if (status != 0)
+				break;
+		}
+	}
+	if (status == 0)
+		status = suspend_device(udev, msg);
+
+	/* If the suspend failed, resume interfaces that did get suspended */
+	if (status != 0) {
+		while (--i >= 0) {
+			intf = udev->actconfig->interface[i];
+			resume_interface(intf);
+		}
+	}
+	return status;
+}
+
+/* Caller has locked udev */
+int usb_resume_both(struct usb_device *udev)
+{
+	int			status;
+	int			i;
+	struct usb_interface	*intf;
+
+	status = resume_device(udev);
+	if (status == 0 && udev->actconfig) {
+		for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) {
+			intf = udev->actconfig->interface[i];
+			resume_interface(intf);
+		}
+	}
+	return status;
+}
+
 static int usb_suspend(struct device *dev, pm_message_t message)
 {
 	int	status;
 
 	if (is_usb_device(dev))
-		status = suspend_device(to_usb_device(dev), message);
+		status = usb_suspend_both(to_usb_device(dev), message);
 	else
-		status = suspend_interface(to_usb_interface(dev), message);
+		status = 0;
 	return status;
 }
 
@@ -871,10 +916,12 @@
 {
 	int	status;
 
-	if (is_usb_device(dev))
-		status = resume_device(to_usb_device(dev));
-	else
-		status = resume_interface(to_usb_interface(dev));
+	if (is_usb_device(dev)) {
+		status = usb_resume_both(to_usb_device(dev));
+
+		/* Rebind drivers that had no suspend method? */
+	} else
+		status = 0;
 	return status;
 }