summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRakesh Bodla <rbodla@nvidia.com>2011-06-13 17:19:34 +0530
committerYuhao Ding <yding@nvidia.com>2011-06-16 18:26:02 -0700
commitccb7b4793e54d6367430b2c42bcfadbe18ff365d (patch)
treeee05dfc2138ed159da05f7de9bf299138bc2ddbc
parent6fbd5ae6491399581c4c719789c7e9259d40c504 (diff)
usb: gadget: composite: Allow function drivers to pause control transfers
Some USB function drivers (e.g. f_mass_storage.c) need to delay or defer the data/status stages of standard control requests like SET_CONFIGURATION or SET_INTERFACE till they are done with their bookkeeping and are actually ready for accepting new commands to their interface. They can now achieve this functionality by returning USB_GADGET_DELAYED_STATUS in their setup handlers (e.g. set_alt()). The composite framework will then defer completion of the control transfer by not completing the data/status stages. This ensures that the host does not send new packets to the interface till the function driver is ready to take them. When the function driver that requested for USB_GADGET_DELAYED_STATUS is done with its bookkeeping, it should signal the composite framework to continue with the data/status stages of the control transfer. It can do so by invoking the new API usb_composite_setup_continue(). This is where the control transfer's data/status stages are completed and host can initiate new transfers. The DELAYED_STATUS mechanism is currently only supported if the expected data phase is 0 bytes (i.e. w_length == 0). Since SET_CONFIGURATION and SET_INTERFACE are the only cases that will use this mechanism, this is not a limitation. (cherry picked from commit 1b9ba000177ee47bcc5b44c7c34e48e735f5f9b1) Change-Id: I4aaf702e03898573f92e58223a29eafbc9612219 Reviewed-on: http://git-master/r/36273 Tested-by: Rakesh Bodla <rbodla@nvidia.com> Reviewed-by: Hanumanth Venkateswa Moganty <vmoganty@nvidia.com>
-rw-r--r--drivers/usb/gadget/composite.c62
-rw-r--r--include/linux/usb/composite.h17
2 files changed, 75 insertions, 4 deletions
diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
index 331eb847e138..803a06ea256a 100644
--- a/drivers/usb/gadget/composite.c
+++ b/drivers/usb/gadget/composite.c
@@ -558,13 +558,23 @@ static int set_config(struct usb_composite_dev *cdev,
reset_config(cdev);
goto done;
}
+
+ if (result == USB_GADGET_DELAYED_STATUS) {
+ DBG(cdev,
+ "%s: interface %d (%s) requested delayed status\n",
+ __func__, tmp, f->name);
+ cdev->delayed_status++;
+ DBG(cdev, "delayed_status count %d\n",
+ cdev->delayed_status);
+ }
}
/* when we return, be sure our power usage is valid */
power = c->bMaxPower ? (2 * c->bMaxPower) : CONFIG_USB_GADGET_VBUS_DRAW;
done:
usb_gadget_vbus_draw(gadget, power);
-
+ if (result >= 0 && cdev->delayed_status)
+ result = USB_GADGET_DELAYED_STATUS;
schedule_work(&cdev->switch_work);
return result;
}
@@ -994,6 +1004,14 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
if (w_value && !f->set_alt)
break;
value = f->set_alt(f, w_index, w_value);
+ if (value == USB_GADGET_DELAYED_STATUS) {
+ DBG(cdev,
+ "%s: interface %d (%s) requested delayed status\n",
+ __func__, intf, f->name);
+ cdev->delayed_status++;
+ DBG(cdev, "delayed_status count %d\n",
+ cdev->delayed_status);
+ }
break;
case USB_REQ_GET_INTERFACE:
if (ctrl->bRequestType != (USB_DIR_IN|USB_RECIP_INTERFACE))
@@ -1075,7 +1093,7 @@ unknown:
}
/* respond with data transfer before status phase? */
- if (value >= 0) {
+ if (value >= 0 && value != USB_GADGET_DELAYED_STATUS) {
req->length = value;
req->zero = value < w_length;
value = usb_ep_queue(gadget->ep0, req, GFP_ATOMIC);
@@ -1084,6 +1102,10 @@ unknown:
req->status = 0;
composite_setup_complete(gadget->ep0, req);
}
+ } else if (value == USB_GADGET_DELAYED_STATUS && w_length != 0) {
+ WARN(cdev,
+ "%s: Delayed status not supported for w_length != 0",
+ __func__);
}
done:
@@ -1442,3 +1464,39 @@ void usb_composite_unregister(struct usb_composite_driver *driver)
return;
usb_gadget_unregister_driver(&composite_driver);
}
+
+/**
+ * usb_composite_setup_continue() - Continue with the control transfer
+ * @cdev: the composite device who's control transfer was kept waiting
+ *
+ * This function must be called by the USB function driver to continue
+ * with the control transfer's data/status stage in case it had requested to
+ * delay the data/status stages. A USB function's setup handler (e.g. set_alt())
+ * can request the composite framework to delay the setup request's data/status
+ * stages by returning USB_GADGET_DELAYED_STATUS.
+ */
+void usb_composite_setup_continue(struct usb_composite_dev *cdev)
+{
+ int value;
+ struct usb_request *req = cdev->req;
+ unsigned long flags;
+
+ DBG(cdev, "%s\n", __func__);
+ spin_lock_irqsave(&cdev->lock, flags);
+
+ if (cdev->delayed_status == 0) {
+ WARN(cdev, "%s: Unexpected call\n", __func__);
+
+ } else if (--cdev->delayed_status == 0) {
+ DBG(cdev, "%s: Completing delayed status\n", __func__);
+ req->length = 0;
+ value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+ if (value < 0) {
+ DBG(cdev, "ep_queue --> %d\n", value);
+ req->status = 0;
+ composite_setup_complete(cdev->gadget->ep0, req);
+ }
+ }
+
+ spin_unlock_irqrestore(&cdev->lock, flags);
+}
diff --git a/include/linux/usb/composite.h b/include/linux/usb/composite.h
index cb28d4302781..26b30db9770c 100644
--- a/include/linux/usb/composite.h
+++ b/include/linux/usb/composite.h
@@ -38,6 +38,14 @@
#include <linux/usb/gadget.h>
#include <linux/switch.h>
+/*
+ * USB function drivers should return USB_GADGET_DELAYED_STATUS if they
+ * wish to delay the data/status stages of the control transfer till they
+ * are ready. The control transfer will then be kept from completing till
+ * all the function drivers that requested for USB_GADGET_DELAYED_STAUS
+ * invoke usb_composite_setup_continue().
+ */
+#define USB_GADGET_DELAYED_STATUS 0x7fff /* Impossibly large value */
struct usb_composite_dev;
struct usb_configuration;
@@ -300,7 +308,7 @@ struct usb_composite_driver {
extern int usb_composite_register(struct usb_composite_driver *);
extern void usb_composite_unregister(struct usb_composite_driver *);
-
+extern void usb_composite_setup_continue(struct usb_composite_dev *cdev);
/**
* struct usb_composite_device - represents one composite usb gadget
@@ -354,7 +362,12 @@ struct usb_composite_dev {
*/
unsigned deactivations;
- /* protects at least deactivation count */
+ /* the composite driver won't complete the control transfer's
+ * data/status stages till delayed_status is zero.
+ */
+ int delayed_status;
+
+ /* protects deactivations and delayed_status counts*/
spinlock_t lock;
/* switch indicating connected/disconnected state */