summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorRakesh Bodla <rbodla@nvidia.com>2011-06-22 10:58:40 +0530
committerVarun Colbert <vcolbert@nvidia.com>2011-07-08 22:50:43 -0700
commit8be6f0a64aa65c70648982a541d97c066d07ff96 (patch)
tree31dd7d2b1677b7842b1a00a585587bdcd68d3d49 /drivers
parenta290e4bcf8a798cf0acb1ee8828f1828779089f5 (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. Signed-off-by: Roger Quadros <roger.quadros@nokia.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> (cherry picked from commit 1b9ba000177ee47bcc5b44c7c34e48e735f5f9b1 kernel.org, linux-2.6 tree) Change-Id: I9d7082042f33c10110070febf36c95d61a292f82 Reviewed-on: http://git-master/r/37788 Reviewed-by: Varun Colbert <vcolbert@nvidia.com> Tested-by: Varun Colbert <vcolbert@nvidia.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/usb/gadget/composite.c63
1 files changed, 61 insertions, 2 deletions
diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
index 331eb847e138..fba2fc5f00f5 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,40 @@ 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);
+}
+