[PATCH 4/6] usb/gadget: push disconnect into a workqueue

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

 



->disconnect() is usually called in two places:
- on device / gadget removal
- on reset interrupt aka device disconnect.

The udc usually calls this an then cleans up the hardware. On the gadget
side, the disconnect callbacks disables every function which leads to
endpoint disabling and killing all requests.
If we perform this async (and later) then there is not much harm:
- the requests are most likely gone, the gadget isn't canceling them. If
  not, we still do it.
- the endpoint is disabled, disabling it again shouldn't cause any harm.
- freeing the memory "later". The memory is allocated again in "SET
  CONFIG" which is performed by the same work queue so it is
  synchronized.

In theory we could get into the following scenario:
- disconnect, -> schedule wq for config cleanup
- connect again
- SET ADDRESS
- GET CONFIG || STATUS || INTERFACE before the wq finished running.
  To avoid this unlikely event, disc_pend has been introduced. In that
  case we behave act like we don't have any config set and return with
  an error like the spec wants us to.

Signed-off-by: Sebastian Andrzej Siewior <bigeasy@xxxxxxxxxxxxx>
---
 drivers/usb/gadget/composite.c |   43 +++++++++++++++++++++++++++++----------
 include/linux/usb/composite.h  |    2 +
 2 files changed, 34 insertions(+), 11 deletions(-)

diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
index 051db6f..f5c5bd8 100644
--- a/drivers/usb/gadget/composite.c
+++ b/drivers/usb/gadget/composite.c
@@ -1176,7 +1176,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
 	case USB_REQ_GET_CONFIGURATION:
 		if (ctrl->bRequestType != USB_DIR_IN)
 			goto unknown;
-		if (cdev->config)
+		if (cdev->config && !cdev->disc_pend)
 			*(u8 *)req->buf = cdev->config->bConfigurationValue;
 		else
 			*(u8 *)req->buf = 0;
@@ -1189,7 +1189,9 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
 	case USB_REQ_SET_INTERFACE:
 		if (ctrl->bRequestType != USB_RECIP_INTERFACE)
 			goto unknown;
-		if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
+		if (!cdev->config || cdev->disc_pend)
+			break;
+		if (intf >= MAX_CONFIG_INTERFACES)
 			break;
 		f = cdev->config->interface[intf];
 		if (!f)
@@ -1202,7 +1204,9 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
 	case USB_REQ_GET_INTERFACE:
 		if (ctrl->bRequestType != (USB_DIR_IN|USB_RECIP_INTERFACE))
 			goto unknown;
-		if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
+		if (!cdev->config || cdev->disc_pend)
+			break;
+		if (intf >= MAX_CONFIG_INTERFACES)
 			break;
 		f = cdev->config->interface[intf];
 		if (!f)
@@ -1229,7 +1233,9 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
 			goto unknown;
 		value = 2;	/* This is the length of the get_status reply */
 		put_unaligned_le16(0, req->buf);
-		if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
+		if (!cdev->config || cdev->disc_pend)
+			break;
+		if (intf >= MAX_CONFIG_INTERFACES)
 			break;
 		f = cdev->config->interface[intf];
 		if (!f)
@@ -1252,7 +1258,9 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
 			goto unknown;
 		switch (w_value) {
 		case USB_INTRF_FUNC_SUSPEND:
-			if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
+			if (!cdev->config || cdev->disc_pend)
+				break;
+			if (intf >= MAX_CONFIG_INTERFACES)
 				break;
 			f = cdev->config->interface[intf];
 			if (!f)
@@ -1286,7 +1294,9 @@ unknown:
 		 */
 		switch (ctrl->bRequestType & USB_RECIP_MASK) {
 		case USB_RECIP_INTERFACE:
-			if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
+			if (!cdev->config || cdev->disc_pend)
+				break;
+			if (intf >= MAX_CONFIG_INTERFACES)
 				break;
 			f = cdev->config->interface[intf];
 			break;
@@ -1336,18 +1346,27 @@ done:
 	return value;
 }
 
-static void composite_disconnect(struct usb_gadget *gadget)
+static void composite_disconnect_wq(struct work_struct *wq)
 {
-	struct usb_composite_dev	*cdev = get_gadget_data(gadget);
-	unsigned long			flags;
+	struct usb_composite_dev *cdev = container_of(wq,
+			struct usb_composite_dev, disc_work);
 
 	/* REVISIT:  should we have config and device level
 	 * disconnect callbacks?
 	 */
-	spin_lock_irqsave(&cdev->lock, flags);
+	spin_lock_irq(&cdev->lock);
 	if (cdev->config)
 		reset_config(cdev);
-	spin_unlock_irqrestore(&cdev->lock, flags);
+	spin_unlock_irq(&cdev->lock);
+	cdev->disc_pend = 0;
+}
+
+static void composite_disconnect(struct usb_gadget *gadget)
+{
+	struct usb_composite_dev	*cdev = get_gadget_data(gadget);
+
+	cdev->disc_pend = 1;
+	queue_work(system_nrt_wq, &cdev->disc_work);
 }
 
 /*-------------------------------------------------------------------------*/
@@ -1374,6 +1393,7 @@ composite_unbind(struct usb_gadget *gadget)
 	 * so there's no i/o concurrency that could affect the
 	 * state protected by cdev->lock.
 	 */
+	flush_work(&cdev->disc_work);
 	flush_work(&cdev->wq);
 	WARN_ON(cdev->config);
 
@@ -1440,6 +1460,7 @@ static int composite_bind(struct usb_gadget *gadget)
 	spin_lock_init(&cdev->lock);
 	spin_lock_init(&cdev->deact_lock);
 	INIT_WORK(&cdev->wq, composite_delayed_setup);
+	INIT_WORK(&cdev->disc_work, composite_disconnect_wq);
 	cdev->gadget = gadget;
 	set_gadget_data(gadget, cdev);
 	INIT_LIST_HEAD(&cdev->configs);
diff --git a/include/linux/usb/composite.h b/include/linux/usb/composite.h
index a332cc2..576432d 100644
--- a/include/linux/usb/composite.h
+++ b/include/linux/usb/composite.h
@@ -339,6 +339,7 @@ struct usb_composite_dev {
 	/* private: */
 	/* internals */
 	unsigned int			suspended:1;
+	unsigned int			disc_pend:1;
 	struct usb_device_descriptor	desc;
 	struct list_head		configs;
 	struct usb_composite_driver	*driver;
@@ -347,6 +348,7 @@ struct usb_composite_dev {
 	u8				product_override;
 	u8				serial_override;
 
+	struct work_struct		disc_work;
 	/* the gadget driver won't enable the data pullup
 	 * while the deactivation count is nonzero.
 	 */
-- 
1.7.8.3

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