浏览代码

USB: keep count of unsuspended children

This patch (as818b) simplifies autosuspend processing by keeping track
of the number of unsuspended children of each USB hub.  This will
permit us to avoid a good deal of unnecessary work all the time; we
will no longer have to create a bunch of workqueue entries to carry
out autosuspend requests, only to have them fail because one of the
hub's children isn't suspended.

The basic idea is simple.  There already is a usage counter in the
usb_device structure for preventing autosuspends.  The patch just
increments that counter for every unsuspended child.  There's only one
tricky part: When a device disconnects we need to remember whether it
was suspended at the time (leave the counter alone) or not (decrement
the counter).

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Alan Stern 18 年之前
父节点
当前提交
ee49fb5dc8
共有 3 个文件被更改,包括 38 次插入11 次删除
  1. 23 11
      drivers/usb/core/driver.c
  2. 14 0
      drivers/usb/core/hub.c
  3. 1 0
      include/linux/usb.h

+ 23 - 11
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];

+ 14 - 0
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;

+ 1 - 0
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 */