[PATCH v2 01/14] USB: use ->reset_resume for port power recovery

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

 



The reset_resume implementation is in place to recover lost power
sessions.  Testing has already shown that simply waiting for
USB_PORT_STAT_C_CONNECTION is not sufficient for recovering a powered
down port.  Replace recovery actions in usb_port_runtime_resume() with a
deferral to recovery via usb_port_resume().

>From the point the port is powered down to the point that the port is
recovered we do not want khubd or hub_activate() to take action on the
disabled port.  Introduce hub->poweroff_bits to communicate that the
port is not to be serviced until it has been through recovery.

The existing ->power_is_on flag gates hub_activate() from setting change
bits.  Placing the evalaution of the power state there is insufficient
for guaranteeing that khubd does not receive a connection change event.
Also, hub_activate() in this case is only running on hub_resume() we
want to manage change events as individual ports resume.  ->power_is_on
will be removed in a subsequent patch.

Signed-off-by: Dan Williams <dan.j.williams@xxxxxxxxx>
---
 drivers/usb/core/hub.c  |   22 +++++++++++++++++-----
 drivers/usb/core/hub.h  |   16 +---------------
 drivers/usb/core/port.c |   29 +++++++++--------------------
 3 files changed, 27 insertions(+), 40 deletions(-)

diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 06cec635e703..6f2cf4672942 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -1092,6 +1092,9 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
 		struct usb_device *udev = hub->ports[port1 - 1]->child;
 		u16 portstatus, portchange;
 
+		if (test_bit(port1, hub->poweroff_bits))
+			continue;
+
 		portstatus = portchange = 0;
 		status = hub_port_status(hub, port1, &portstatus, &portchange);
 		if (udev || (portstatus & USB_PORT_STAT_CONNECTION))
@@ -2783,8 +2786,14 @@ static int check_port_resume_type(struct usb_device *udev,
 		struct usb_hub *hub, int port1,
 		int status, unsigned portchange, unsigned portstatus)
 {
+	/* port power recovery, proceed to resetting the device */
+	if (test_bit(port1, hub->poweroff_bits)) {
+		status = 0;
+		udev->reset_resume = 1;
+	}
+
 	/* Is the device still present? */
-	if (status || port_is_suspended(hub, portstatus) ||
+	else if (status || port_is_suspended(hub, portstatus) ||
 			!port_is_power_on(hub, portstatus) ||
 			!(portstatus & USB_PORT_STAT_CONNECTION)) {
 		if (status >= 0)
@@ -3871,7 +3880,7 @@ EXPORT_SYMBOL_GPL(usb_enable_ltm);
  * every 25ms for transient disconnects.  When the port status has been
  * unchanged for 100ms it returns the port status.
  */
-int hub_port_debounce(struct usb_hub *hub, int port1, bool must_be_connected)
+int hub_port_debounce(struct usb_hub *hub, int port1)
 {
 	int ret;
 	int total_time, stable_time = 0;
@@ -3885,8 +3894,7 @@ int hub_port_debounce(struct usb_hub *hub, int port1, bool must_be_connected)
 
 		if (!(portchange & USB_PORT_STAT_C_CONNECTION) &&
 		     (portstatus & USB_PORT_STAT_CONNECTION) == connection) {
-			if (!must_be_connected ||
-			     (connection == USB_PORT_STAT_CONNECTION))
+			if (connection == USB_PORT_STAT_CONNECTION)
 				stable_time += HUB_DEBOUNCE_STEP;
 			if (stable_time >= HUB_DEBOUNCE_STABLE)
 				break;
@@ -4437,7 +4445,7 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
 
 	if (portchange & (USB_PORT_STAT_C_CONNECTION |
 				USB_PORT_STAT_C_ENABLE)) {
-		status = hub_port_debounce_be_stable(hub, port1);
+		status = hub_port_debounce(hub, port1);
 		if (status < 0) {
 			if (status != -ENODEV && printk_ratelimit())
 				dev_err(hub_dev, "connect-debounce failed, "
@@ -4749,6 +4757,9 @@ static void hub_events(void)
 				connect_change = 1;
 			}
 
+			if (test_bit(i, hub->poweroff_bits))
+				continue;
+
 			if (portchange & USB_PORT_STAT_C_ENABLE) {
 				if (!connect_change)
 					dev_dbg (hub_dev,
@@ -5155,6 +5166,7 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
 		if (ret >= 0 || ret == -ENOTCONN || ret == -ENODEV)
 			break;
 	}
+	clear_bit(port1, parent_hub->poweroff_bits);
 	clear_bit(port1, parent_hub->busy_bits);
 
 	if (ret < 0)
diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
index 4e4790dea343..0b9bde67ff8b 100644
--- a/drivers/usb/core/hub.h
+++ b/drivers/usb/core/hub.h
@@ -47,6 +47,7 @@ struct usb_hub {
 							status change */
 	unsigned long		busy_bits[1];	/* ports being reset or
 							resumed */
+	unsigned long		poweroff_bits[1]; /* ports logically off */
 	unsigned long		removed_bits[1]; /* ports with a "removed"
 							device present */
 	unsigned long		wakeup_bits[1];	/* ports that have signaled
@@ -106,20 +107,5 @@ extern void usb_hub_remove_port_device(struct usb_hub *hub,
 extern int usb_hub_set_port_power(struct usb_device *hdev, struct usb_hub *hub,
 		int port1, bool set);
 extern struct usb_hub *usb_hub_to_struct_hub(struct usb_device *hdev);
-extern int hub_port_debounce(struct usb_hub *hub, int port1,
-		bool must_be_connected);
 extern int usb_clear_port_feature(struct usb_device *hdev,
 		int port1, int feature);
-
-static inline int hub_port_debounce_be_connected(struct usb_hub *hub,
-		int port1)
-{
-	return hub_port_debounce(hub, port1, true);
-}
-
-static inline int hub_port_debounce_be_stable(struct usb_hub *hub,
-		int port1)
-{
-	return hub_port_debounce(hub, port1, false);
-}
-
diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
index 51542f852393..0ecff7d85739 100644
--- a/drivers/usb/core/port.c
+++ b/drivers/usb/core/port.c
@@ -83,26 +83,13 @@ static int usb_port_runtime_resume(struct device *dev)
 		return -EINVAL;
 
 	usb_autopm_get_interface(intf);
-	set_bit(port1, hub->busy_bits);
-
 	retval = usb_hub_set_port_power(hdev, hub, port1, true);
-	if (port_dev->child && !retval) {
-		/*
-		 * Attempt to wait for usb hub port to be reconnected in order
-		 * to make the resume procedure successful.  The device may have
-		 * disconnected while the port was powered off, so ignore the
-		 * return status.
-		 */
-		retval = hub_port_debounce_be_connected(hub, port1);
-		if (retval < 0)
-			dev_dbg(&port_dev->dev, "can't get reconnection after setting port  power on, status %d\n",
-					retval);
-		usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE);
-		retval = 0;
-	}
-
-	clear_bit(port1, hub->busy_bits);
 	usb_autopm_put_interface(intf);
+
+	/* no child? we're done recovering this port */
+	if (!port_dev->child)
+		clear_bit(port1, hub->poweroff_bits);
+
 	return retval;
 }
 
@@ -122,13 +109,15 @@ static int usb_port_runtime_suspend(struct device *dev)
 			== PM_QOS_FLAGS_ALL)
 		return -EAGAIN;
 
+	set_bit(port1, hub->poweroff_bits);
 	usb_autopm_get_interface(intf);
-	set_bit(port1, hub->busy_bits);
 	retval = usb_hub_set_port_power(hdev, hub, port1, false);
+	if (retval)
+		clear_bit(port1, hub->poweroff_bits);
 	usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_CONNECTION);
 	usb_clear_port_feature(hdev, port1,	USB_PORT_FEAT_C_ENABLE);
-	clear_bit(port1, hub->busy_bits);
 	usb_autopm_put_interface(intf);
+
 	return retval;
 }
 #endif

--
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