[PATCH v7 14/16] usb: resume (wakeup) child device when port is powered on

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Unconditionally wake up the child device when the power session is
recovered.

This address the following scenarios:

1/ The device may need a reset on power-session loss, without this
   change port power-on recovery exposes khubd to scenarios that
   usb_port_resume() is set to handle.  Prior to port power control the
   only time a power session would be lost is during dpm_suspend of the
   hub.  In that scenario usb_port_resume() is guaranteed to be called
   prior to khubd running for that port.  With this change we wakeup the
   child device as soon as possible (prior to khubd running again for this
   port).

   Although khubd has facilities to wake a child device it will only do
   so if the portstatus / portchange indicates a suspend state.  In the
   case of port power control we are not coming from a hub-port-suspend
   state.  This implemenation extends usb_wake_notification() for the
   port rpm_resume case.

2/ This mechanism rate limits port power toggling.  The minimum port
   power on/off period is now gated by the child device suspend/resume
   latency.  Empirically this mitigates devices downgrading their connection
   on perceived instability of the host connection.  This ratelimiting is
   really only relevant to port power control testing, but it is a nice
   side effect of closing the above race.  Namely, the race of khubd for
   the given port running while a usb_port_resume() event is pending.

3/ Going forward we are finding that power-session recovery requires
   warm-resets (http://marc.info/?t=138659232900003&r=1&w=2).  This
   mechanism allows for warm-resets to be requested at the same point in
   the resume path for hub dpm_suspend power session losses, or port
   rpm_suspend power session losses.

4/ If the device *was* disconnected the only time we'll know for sure is
   after a failed resume, so it's necessary for usb_port_runtime_resume()
   to expedite a usb_port_resume() to clean up the removed device.  The
   reasoning for this is "least surprise" for the user. Turning on a port
   means that hotplug detection is again enabled for the port, it is
   surprising that devices that were removed while the port was off are not
   disconnected until they are attempted to be used.  As a user "why would
   I try to use a device I removed from the system?"

1, 2, and 4 are not a problem in the system dpm_resume case because,
although the power-session is lost, khubd is frozen until after device
resume.  For the port rpm_resume use runtime-pm-synchronization to
guarantee the same sequence of events.  When a usb_wakeup_notification()
is invoked the port device is in the RPM_RESUMING state.  khubd in turn
performs a pm_runtime_barrier() on the port device to flush the port
recovery, holds the port active while it resumes the child, and
completes child device resume before acting on the current portstatus.

Signed-off-by: Dan Williams <dan.j.williams@xxxxxxxxx>
---
 drivers/usb/core/hub.c  |   40 +++++++++++++++++++++++++++++-----------
 drivers/usb/core/port.c |    2 ++
 2 files changed, 31 insertions(+), 11 deletions(-)

diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 194ff3303ee9..6c666796fd21 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -606,9 +606,11 @@ void usb_kick_khubd(struct usb_device *hdev)
  * USB 3.0 hubs do not report the port link state change from U3 to U0 when the
  * device initiates resume, so the USB core will not receive notice of the
  * resume through the normal hub interrupt URB.
+ *
+ * This is also called by usb_port_runtime_resume() to arrange for the child
+ * device to be woken up as part of the power session recovery for the port.
  */
-void usb_wakeup_notification(struct usb_device *hdev,
-		unsigned int portnum)
+void usb_wakeup_notification(struct usb_device *hdev, unsigned int port1)
 {
 	struct usb_hub *hub;
 
@@ -617,7 +619,11 @@ void usb_wakeup_notification(struct usb_device *hdev,
 
 	hub = usb_hub_to_struct_hub(hdev);
 	if (hub) {
-		set_bit(portnum, hub->wakeup_bits);
+		struct usb_port *port_dev = hub->ports[port1 - 1];
+
+		dev_dbg(&port_dev->dev, "%s\n", __func__);
+		if (!test_and_set_bit(port1, hub->wakeup_bits))
+			pm_runtime_get(&port_dev->dev);
 		kick_khubd(hub);
 	}
 }
@@ -4677,28 +4683,33 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
 
 /* Returns 1 if there was a remote wakeup and a connect status change. */
 static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port,
-		u16 portstatus, u16 portchange)
+		u16 portstatus, u16 portchange, int wakeup_change)
 		__must_hold(&port_dev->status_lock)
 {
 	struct usb_port *port_dev = hub->ports[port - 1];
+	int connect_change = 0, do_wakeup = 1;
 	struct usb_device *hdev;
 	struct usb_device *udev;
-	int connect_change = 0;
 	int ret;
 
 	hdev = hub->hdev;
 	udev = port_dev->child;
 	if (!hub_is_superspeed(hdev)) {
 		if (!(portchange & USB_PORT_STAT_C_SUSPEND))
-			return 0;
+			do_wakeup = 0;
 		usb_clear_port_feature(hdev, port, USB_PORT_FEAT_C_SUSPEND);
 	} else {
 		if (!udev || udev->state != USB_STATE_SUSPENDED ||
 				 (portstatus & USB_PORT_STAT_LINK_STATE) !=
 				 USB_SS_PORT_LS_U0)
-			return 0;
+			do_wakeup = 0;
 	}
 
+	if (wakeup_change || do_wakeup)
+		/* pass */;
+	else
+		return 0;
+
 	if (udev) {
 		/* TRSMRCY = 10 msec */
 		msleep(10);
@@ -4726,7 +4737,11 @@ static void port_event(struct usb_hub *hub, int port1)
 	u16 portstatus, portchange;
 
 	connect_change = test_bit(port1, hub->change_bits);
-	wakeup_change = test_and_clear_bit(port1, hub->wakeup_bits);
+	if (test_and_clear_bit(port1, hub->wakeup_bits)) {
+		/* balance usb_wakeup_notification(), we won't suspend here */
+		pm_runtime_put(&port_dev->dev);
+		wakeup_change = 1;
+	}
 
 	if (hub_port_status(hub, port1, &portstatus, &portchange) < 0)
 		return;
@@ -4790,9 +4805,12 @@ static void port_event(struct usb_hub *hub, int port1)
 
 	/* take port actions that require the port to be powered on */
 	if (pm_runtime_active(&port_dev->dev)) {
-		if (hub_handle_remote_wakeup(hub, port1,
-				portstatus, portchange))
-			connect_change = 1;
+		/*
+		 * There will be a wakeup pending if the device signalled it,
+		 * or if we are re-powering a port
+		 */
+		connect_change |= hub_handle_remote_wakeup(hub, port1,
+				portstatus, portchange, wakeup_change);
 
 		/*
 		 * Warm reset a USB3 protocol port if it's in
diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
index db32315a8cd8..d01499a33bd7 100644
--- a/drivers/usb/core/port.c
+++ b/drivers/usb/core/port.c
@@ -104,6 +104,8 @@ static int usb_port_runtime_resume(struct device *dev)
 		if (retval < 0)
 			dev_dbg(&port_dev->dev, "can't get reconnection after setting port  power on, status %d\n",
 					retval);
+
+		usb_wakeup_notification(hdev, port1);
 		retval = 0;
 	}
 

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux