[PATCH v6 part2 5/8] usb: introduce port status lock

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

 



In general we do not want khubd to act on port status changes that are
the result of in progress resets or USB runtime PM operations.
Specifically port power control testing has been able to trigger an
unintended disconnect in hub_port_connect_change(), paraphrasing:

	if ((portstatus & USB_PORT_STAT_CONNECTION) && udev &&
	    udev->state != USB_STATE_NOTATTACHED) {
		if (portstatus & USB_PORT_STAT_ENABLE) {
			/* Nothing to do */
		} else if (udev->state == USB_STATE_SUSPENDED &&
				udev->persist_enabled) {
			...
		} else {
			/* Don't resuscitate */;
		}
	}

...by falling to the "Don't resuscitate" path or missing
USB_PORT_STAT_CONNECTION because usb_port_resume() was in the middle of
modifying the port status.

So, we want a new lock to hold off khubd for a given port while the
child device is being suspended, resumed, or reset.  The lock ordering
rules are now usb_lock_device() => usb_lock_port().  This is mandated by
the device core which may hold the device_lock on the usb_device before
invoking usb_port_{suspend|resume} which in turn take the status_lock on
the usb_port.  We attempt to hold the status_lock for the duration of a
port_event() run, and drop/re-acquire it when needing to take the
device_lock.  The lock is also dropped/re-acquired during
hub_port_reconnect().

This patch also deletes hub->busy_bits as all use cases are now covered
by port PM runtime synchronization or the port->status_lock and it
pushes down usb_device_lock() into usb_remote_wakeup().

Signed-off-by: Dan Williams <dan.j.williams@xxxxxxxxx>
---
 drivers/usb/core/hcd.c  |    2 -
 drivers/usb/core/hub.c  |   95 ++++++++++++++++++++++++++++++++++-------------
 drivers/usb/core/hub.h  |    4 +-
 drivers/usb/core/port.c |    6 ---
 drivers/usb/core/usb.h  |    6 +++
 5 files changed, 77 insertions(+), 36 deletions(-)

diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index af9f053b9626..63fb256d2f99 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -2246,9 +2246,7 @@ static void hcd_resume_work(struct work_struct *work)
 	struct usb_hcd *hcd = container_of(work, struct usb_hcd, wakeup_work);
 	struct usb_device *udev = hcd->self.root_hub;
 
-	usb_lock_device(udev);
 	usb_remote_wakeup(udev);
-	usb_unlock_device(udev);
 }
 
 /**
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index d1c0d2d2f1af..e39f8fd3d49f 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -2755,6 +2755,20 @@ static int port_is_power_on(struct usb_hub *hub, unsigned portstatus)
 	return ret;
 }
 
+static void usb_lock_port(struct usb_port *port_dev)
+		__acquires(&port_dev->status_lock)
+{
+	mutex_lock(&port_dev->status_lock);
+	__acquire(&port_dev->status_lock);
+}
+
+static void usb_unlock_port(struct usb_port *port_dev)
+		__releases(&port_dev->status_lock)
+{
+	mutex_unlock(&port_dev->status_lock);
+	__release(&port_dev->status_lock);
+}
+
 #ifdef	CONFIG_PM
 
 /* Check if a port is suspended(USB2.0 port) or in U3 state(USB3.0 port) */
@@ -2977,6 +2991,8 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
 	int		status;
 	bool		really_suspend = true;
 
+	usb_lock_port(port_dev);
+
 	/* enable remote wakeup when appropriate; this lets the device
 	 * wake up the upstream hub (including maybe the root hub).
 	 *
@@ -3071,6 +3087,8 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
 	}
 
 	usb_mark_last_busy(hub->hdev);
+
+	usb_unlock_port(port_dev);
 	return status;
 }
 
@@ -3210,13 +3228,13 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
 		}
 	}
 
+	usb_lock_port(port_dev);
+
 	/* Skip the initial Clear-Suspend step for a remote wakeup */
 	status = hub_port_status(hub, port1, &portstatus, &portchange);
 	if (status == 0 && !port_is_suspended(hub, portstatus))
 		goto SuspendCleared;
 
-	set_bit(port1, hub->busy_bits);
-
 	/* see 7.1.7.7; affects power usage, but not budgeting */
 	if (hub_is_superspeed(hub->hdev))
 		status = hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_U0);
@@ -3255,8 +3273,6 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
 		}
 	}
 
-	clear_bit(port1, hub->busy_bits);
-
 	status = check_port_resume_type(udev,
 			hub, port1, status, portchange, portstatus);
 	if (status == 0)
@@ -3274,16 +3290,18 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
 		usb_unlocked_enable_lpm(udev);
 	}
 
+	usb_unlock_port(port_dev);
+
 	return status;
 }
 
 #ifdef	CONFIG_PM_RUNTIME
 
-/* caller has locked udev */
 int usb_remote_wakeup(struct usb_device *udev)
 {
 	int	status = 0;
 
+	usb_lock_device(udev);
 	if (udev->state == USB_STATE_SUSPENDED) {
 		dev_dbg(&udev->dev, "usb %sresume\n", "wakeup-");
 		status = usb_autoresume_device(udev);
@@ -3292,6 +3310,7 @@ int usb_remote_wakeup(struct usb_device *udev)
 			usb_autosuspend_device(udev);
 		}
 	}
+	usb_unlock_device(udev);
 	return status;
 }
 
@@ -3996,9 +4015,10 @@ static int hub_enable_device(struct usb_device *udev)
  * Returns device in USB_STATE_ADDRESS, except on error.
  *
  * If this is called for an already-existing device (as part of
- * usb_reset_and_verify_device), the caller must own the device lock.  For a
- * newly detected device that is not accessible through any global
- * pointers, it's not necessary to lock the device.
+ * usb_reset_and_verify_device), the caller must own the device lock and
+ * the port lock.  For a newly detected device that is not accessible
+ * through any global pointers, it's not necessary to lock the device,
+ * but it is still necessary to lock the port.
  */
 static int
 hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
@@ -4466,7 +4486,9 @@ static void hub_port_reconnect(struct usb_hub *hub, int port1, u16 portstatus,
 		}
 
 		/* reset (non-USB 3.0 devices) and get descriptor */
+		usb_lock_port(port_dev);
 		status = hub_port_init(hub, udev, port1, i);
+		usb_unlock_port(port_dev);
 		if (status < 0)
 			goto loop;
 
@@ -4588,6 +4610,7 @@ done:
  */
 static void hub_port_connect_change(struct usb_hub *hub, int port1,
 					u16 portstatus, u16 portchange)
+		__must_hold(&port_dev->status_lock)
 {
 	struct usb_port *port_dev = hub->ports[port1 - 1];
 	struct usb_device *udev = port_dev->child;
@@ -4620,26 +4643,29 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
 			/* For a suspended device, treat this as a
 			 * remote wakeup event.
 			 */
-			usb_lock_device(udev);
+			usb_unlock_port(port_dev);
 			status = usb_remote_wakeup(udev);
-			usb_unlock_device(udev);
+			usb_lock_port(port_dev);
 #endif
 		} else {
 			/* Don't resuscitate */;
 		}
-
 	}
 	clear_bit(port1, hub->change_bits);
 
+	/* successfully revalidated the connection */
 	if (status == 0)
 		return;
 
+	usb_unlock_port(port_dev);
 	hub_port_reconnect(hub, port1, portstatus, portchange);
+	usb_lock_port(port_dev);
 }
 
 /* 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)
+		__must_hold(&port_dev->status_lock)
 {
 	struct usb_port *port_dev = hub->ports[port - 1];
 	struct usb_device *hdev;
@@ -4664,9 +4690,9 @@ static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port,
 		/* TRSMRCY = 10 msec */
 		msleep(10);
 
-		usb_lock_device(udev);
+		usb_unlock_port(port_dev);
 		ret = usb_remote_wakeup(udev);
-		usb_unlock_device(udev);
+		usb_lock_port(port_dev);
 		if (ret < 0)
 			connect_change = 1;
 	} else {
@@ -4678,6 +4704,7 @@ static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port,
 }
 
 static void port_event(struct usb_hub *hub, int port1)
+		__must_hold(&port_dev->status_lock)
 {
 	struct usb_port *port_dev = hub->ports[port1 - 1];
 	struct usb_device *udev = port_dev->child;
@@ -4770,9 +4797,11 @@ static void port_event(struct usb_hub *hub, int port1)
 				if (status < 0)
 					hub_port_disable(hub, port1, 1);
 			} else {
+				usb_unlock_port(port_dev);
 				usb_lock_device(udev);
 				status = usb_reset_device(udev);
 				usb_unlock_device(udev);
+				usb_lock_port(port_dev);
 				connect_change = 0;
 			}
 		}
@@ -4869,13 +4898,14 @@ static void hub_events(void)
 		for (i = 1; i <= hdev->maxchild; i++) {
 			struct usb_port *port_dev = hub->ports[i - 1];
 
-			if (!test_bit(i, hub->busy_bits)
-					&& (test_and_clear_bit(i, hub->event_bits)
+			if (test_and_clear_bit(i, hub->event_bits)
 					|| test_bit(i, hub->change_bits)
-					|| test_bit(i, hub->wakeup_bits))) {
+					|| test_bit(i, hub->wakeup_bits)) {
 				pm_runtime_get_noresume(&port_dev->dev);
 				pm_runtime_barrier(&port_dev->dev);
+				usb_lock_port(port_dev);
 				port_event(hub, i);
+				usb_unlock_port(port_dev);
 				pm_runtime_put_sync(&port_dev->dev);
 			}
 		}
@@ -5114,15 +5144,18 @@ static int descriptors_changed(struct usb_device *udev,
  * if the reset wasn't even attempted.
  *
  * Note:
- * The caller must own the device lock.  For example, it's safe to use
- * this from a driver probe() routine after downloading new firmware.
- * For calls that might not occur during probe(), drivers should lock
- * the device using usb_lock_device_for_reset().
+ * The caller must own the device lock and the port lock, the latter is
+ * taken by usb_reset_device().  For example, it's safe to use
+ * usb_reset_device() from a driver probe() routine after downloading
+ * new firmware.  For calls that might not occur during probe(), drivers
+ * should lock the device using usb_lock_device_for_reset().
  *
  * Locking exception: This routine may also be called from within an
  * autoresume handler.  Such usage won't conflict with other tasks
  * holding the device lock because these tasks should always call
- * usb_autopm_resume_device(), thereby preventing any unwanted autoresume.
+ * usb_autopm_resume_device(), thereby preventing any unwanted
+ * autoresume.  The autoresume handler is expected to have already
+ * acquired the port lock before calling this routine.
  */
 static int usb_reset_and_verify_device(struct usb_device *udev)
 {
@@ -5141,11 +5174,9 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
 		return -EINVAL;
 	}
 
-	if (!parent_hdev) {
-		/* this requires hcd-specific logic; see ohci_restart() */
-		dev_dbg(&udev->dev, "%s for root hub!\n", __func__);
+	if (!parent_hdev)
 		return -EISDIR;
-	}
+
 	parent_hub = usb_hub_to_struct_hub(parent_hdev);
 
 	/* Disable USB2 hardware LPM.
@@ -5174,7 +5205,6 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
 		goto re_enumerate;
 	}
 
-	set_bit(port1, parent_hub->busy_bits);
 	for (i = 0; i < SET_CONFIG_TRIES; ++i) {
 
 		/* ep0 maxpacket size may change; let the HCD know about it.
@@ -5184,7 +5214,6 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
 		if (ret >= 0 || ret == -ENOTCONN || ret == -ENODEV)
 			break;
 	}
-	clear_bit(port1, parent_hub->busy_bits);
 
 	if (ret < 0)
 		goto re_enumerate;
@@ -5302,7 +5331,9 @@ int usb_reset_device(struct usb_device *udev)
 	int ret;
 	int i;
 	unsigned int noio_flag;
+	struct usb_port *port_dev;
 	struct usb_host_config *config = udev->actconfig;
+	struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent);
 
 	if (udev->state == USB_STATE_NOTATTACHED ||
 			udev->state == USB_STATE_SUSPENDED) {
@@ -5311,6 +5342,14 @@ int usb_reset_device(struct usb_device *udev)
 		return -EINVAL;
 	}
 
+	if (!udev->parent) {
+		/* this requires hcd-specific logic; see ohci_restart() */
+		dev_dbg(&udev->dev, "%s for root hub!\n", __func__);
+		return -EISDIR;
+	}
+
+	port_dev = hub->ports[udev->portnum - 1];
+
 	/*
 	 * Don't allocate memory with GFP_KERNEL in current
 	 * context to avoid possible deadlock if usb mass
@@ -5344,7 +5383,9 @@ int usb_reset_device(struct usb_device *udev)
 		}
 	}
 
+	usb_lock_port(port_dev);
 	ret = usb_reset_and_verify_device(udev);
+	usb_unlock_port(port_dev);
 
 	if (config) {
 		for (i = config->desc.bNumInterfaces - 1; i >= 0; --i) {
diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
index 2537bfbf7aca..1189cf7045e4 100644
--- a/drivers/usb/core/hub.h
+++ b/drivers/usb/core/hub.h
@@ -45,8 +45,6 @@ struct usb_hub {
 	unsigned long		event_bits[1];	/* status change bitmask */
 	unsigned long		change_bits[1];	/* ports with logical connect
 							status change */
-	unsigned long		busy_bits[1];	/* ports being reset or
-							resumed */
 	unsigned long		removed_bits[1]; /* ports with a "removed"
 							device present */
 	unsigned long		wakeup_bits[1];	/* ports that have signaled
@@ -84,6 +82,7 @@ struct usb_hub {
  * @peer: related usb2 and usb3 ports (share the same connector)
  * @connect_type: port's connect type
  * @location: opaque representation of platform connector location
+ * @status_lock: synchronize port_event() vs usb_port_{suspend|resume}
  * @portnum: port index num based one
  * @power_is_on: port's power state
  * @did_runtime_put: port has done pm_runtime_put().
@@ -95,6 +94,7 @@ struct usb_port {
 	struct usb_port *peer;
 	enum usb_port_connect_type connect_type;
 	usb_port_location_t location;
+	struct mutex status_lock;
 	u8 portnum;
 	unsigned power_is_on:1;
 	unsigned did_runtime_put:1;
diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
index 0ff5854c9581..f7b6f0542ab4 100644
--- a/drivers/usb/core/port.c
+++ b/drivers/usb/core/port.c
@@ -91,8 +91,6 @@ static int usb_port_runtime_resume(struct device *dev)
 		pm_runtime_get_sync(&peer->dev);
 
 	usb_autopm_get_interface(intf);
-	set_bit(port1, hub->busy_bits);
-
 	retval = usb_hub_set_port_power(hdev, hub, port1, true);
 	msleep(hub_power_on_good_delay(hub));
 	if (port_dev->child && !retval) {
@@ -109,7 +107,6 @@ static int usb_port_runtime_resume(struct device *dev)
 		retval = 0;
 	}
 
-	clear_bit(port1, hub->busy_bits);
 	usb_autopm_put_interface(intf);
 
 	return retval;
@@ -133,12 +130,10 @@ static int usb_port_runtime_suspend(struct device *dev)
 		return -EAGAIN;
 
 	usb_autopm_get_interface(intf);
-	set_bit(port1, hub->busy_bits);
 	retval = usb_hub_set_port_power(hdev, hub, port1, false);
 	usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_CONNECTION);
 	if (!hub_is_superspeed(hdev))
 		usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE);
-	clear_bit(port1, hub->busy_bits);
 	usb_autopm_put_interface(intf);
 
 	/*
@@ -399,6 +394,7 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1)
 	port_dev->dev.driver = &usb_port_driver;
 	dev_set_name(&port_dev->dev, "%s-port%d", dev_name(&hub->hdev->dev),
 			port1);
+	mutex_init(&port_dev->status_lock);
 	retval = device_register(&port_dev->dev);
 	if (retval)
 		goto error_register;
diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h
index 7ddcecac12e7..6e632c2d8a9f 100644
--- a/drivers/usb/core/usb.h
+++ b/drivers/usb/core/usb.h
@@ -113,6 +113,12 @@ static inline int usb_autoresume_device(struct usb_device *udev)
 
 static inline int usb_remote_wakeup(struct usb_device *udev)
 {
+	/*
+	 * In the PM_RUNTIME=n case we still bounce the lock to keep
+	 * usb_remote_wakeup() as a flush for locked device operations
+	 */
+	usb_lock_device(udev);
+	usb_unlock_device(udev);
 	return 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