[PATCH v7 6/6] usb: gadget: uvc: allow ioctl to send response in status stage

From: Paul Elder
Date: Wed Jan 23 2019 - 22:02:59 EST


We now have a mechanism to signal the UDC driver to reply to a control
OUT request with STALL or ACK, and we have packaged the setup stage data
and the data stage data of a control OUT request into a single
UVC_EVENT_DATA for userspace to consume. After telling the UDC to delay
the status stage, the ioctl UVCIOC_SEND_RESPONSE should be called to
notify the UDC driver to reply with STALL or ACK, for control OUT
requests. In the case of a control IN request, the ioctl sends the UVC
data as before.

This means that the completion handler will also be called for the
status stage, so make the UVC gadget driver aware of if the
completion handler is called for the status stage, and do nothing (as
opposed to giving userspace the UVC data again).

Signed-off-by: Paul Elder <paul.elder@xxxxxxxxxxxxxxxx>
---
No change from v6

Changes from v5:

- add event_status flag and use to keep track of whether or not the
gadget is in the status stage or not
- do nothing if the completion handler is called during the status stage

No change from v4
No change from v3

Changes from v2:
- calling usb_ep_set_halt in uvc_send_response if data->length < 0 is
now common for both IN and OUT transfers so make that check common
- remove now unnecessary field setting for the usb_request to be queued
for the status stage

Changes from v1:
- remove usb_ep_delay_status call from the old proposed API
- changed portions of uvc_send_response to match v2 API
- remove UDC warning that send_response is not implemented

drivers/usb/gadget/function/f_uvc.c | 11 +++++++++--
drivers/usb/gadget/function/uvc.h | 1 +
drivers/usb/gadget/function/uvc_v4l2.c | 24 ++++++++++++++++++------
3 files changed, 28 insertions(+), 8 deletions(-)

diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
index 6303ed346af9..dd3a06e28435 100644
--- a/drivers/usb/gadget/function/f_uvc.c
+++ b/drivers/usb/gadget/function/f_uvc.c
@@ -208,15 +208,19 @@ uvc_function_ep0_complete(struct usb_ep *ep, struct usb_request *req)
struct v4l2_event v4l2_event;
struct uvc_event *uvc_event = (void *)&v4l2_event.u.data;

- if (uvc->event_setup_out) {
- uvc->event_setup_out = 0;
+ if (uvc->event_status) {
+ uvc->event_status = 0;
+ return;
+ }

+ if (uvc->event_setup_out) {
memset(&v4l2_event, 0, sizeof(v4l2_event));
v4l2_event.type = UVC_EVENT_DATA;
uvc_event->data.length = req->actual;
memcpy(&uvc_event->data.data, req->buf, req->actual);
memcpy(&uvc_event->data.setup, &uvc->control_setup,
sizeof(uvc_event->data.setup));
+
v4l2_event_queue(&uvc->vdev, &v4l2_event);
}
}
@@ -242,6 +246,8 @@ uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
uvc->event_length = le16_to_cpu(ctrl->wLength);
memcpy(&uvc->control_setup, ctrl, sizeof(uvc->control_setup));

+ uvc->event_status = 0;
+
if (uvc->event_setup_out) {
struct usb_request *req = uvc->control_req;

@@ -251,6 +257,7 @@ uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
*/
req->length = uvc->event_length;
req->zero = 0;
+ req->explicit_status = 1;
usb_ep_queue(f->config->cdev->gadget->ep0, req, GFP_KERNEL);
} else {
struct v4l2_event v4l2_event;
diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 1d89b1df4ba0..5754548d94c5 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -171,6 +171,7 @@ struct uvc_device {
/* Events */
unsigned int event_length;
unsigned int event_setup_out : 1;
+ unsigned int event_status : 1;
};

static inline struct uvc_device *to_uvc(struct usb_function *f)
diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index ac48f49d9f10..338811c612c5 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -35,15 +35,27 @@ uvc_send_response(struct uvc_device *uvc, struct uvc_request_data *data)
struct usb_composite_dev *cdev = uvc->func.config->cdev;
struct usb_request *req = uvc->control_req;

+ if (data->length < 0)
+ return usb_ep_set_halt(cdev->gadget->ep0);
+
/*
* For control OUT transfers the request has been enqueued synchronously
- * by the setup handler, there's nothing to be done here.
+ * by the setup handler, we just need to tell the UDC whether to ACK or
+ * STALL the control transfer.
*/
- if (uvc->event_setup_out)
- return 0;
-
- if (data->length < 0)
- return usb_ep_set_halt(cdev->gadget->ep0);
+ if (uvc->event_setup_out) {
+ /*
+ * The length field carries the control request status.
+ * Negative values signal a STALL and zero values an ACK.
+ * Positive values are not valid as there is no data to send
+ * back in the status stage.
+ */
+ if (data->length > 0)
+ return -EINVAL;
+
+ uvc->event_status = 1;
+ return usb_ep_queue(cdev->gadget->ep0, req, GFP_KERNEL);
+ }

req->length = min_t(unsigned int, uvc->event_length, data->length);
req->zero = data->length < uvc->event_length;
--
2.20.1