Thread (13 messages) 13 messages, 5 authors, 2021-06-25

Re: [PATCH 3/3] usb: gadget: uvc: add format/frame handling code

From: paul.elder@ideasonboard.com
Date: 2021-06-14 10:08:04

Hi Michael,

Thank you for the patch.

On Mon, May 31, 2021 at 12:22:39AM +0200, Michael Grzeschik wrote:
The hostside format selection is currently only done in userspace, as
the events for SET_CUR and GET_CUR is allways moved to the application
s/is allways/are always/
layer. Since the v4l2 devices parses the configfs data, the format
s/devices/device/
negotiation can be done in the kernel. This patch adds the functions to
set the curent configuration and still forwards all unknown events to
s/curent/current/

Reading just from this sentence, it sounds like only unknown events are
forwarded to userspace, and all known events are not. I see from the
code that this is indeed the case.

s/and still fowards/while continuing to forward/
the userspace level.
Ah, I see a lot of this is lifted from our uvc-gadget :)

Which is fine. It's nice to see this processing moved to the kernel,
especially since configfs already has the necessary information.
quoted hunk ↗ jump to hunk
Signed-off-by: Michael Grzeschik <redacted>
---
 drivers/usb/gadget/function/f_uvc.c     | 270 ++++++++++++++++++++++--
 drivers/usb/gadget/function/uvc.h       |  14 ++
 drivers/usb/gadget/function/uvc_v4l2.c  |  16 +-
 drivers/usb/gadget/function/uvc_video.c |  15 +-
 4 files changed, 297 insertions(+), 18 deletions(-)
diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
index 7945ea93a775a..cd40a063751ae 100644
--- a/drivers/usb/gadget/function/f_uvc.c
+++ b/drivers/usb/gadget/function/f_uvc.c
@@ -201,21 +201,253 @@ static const struct usb_descriptor_header * const uvc_ss_streaming[] = {
  * Control requests
  */
 
+void uvc_fill_streaming_control(struct uvc_device *uvc,
+			   struct uvc_streaming_control *ctrl,
+			   int iframe, int iformat, unsigned int ival)
Indentation.
+{
+	struct uvcg_format *ufmt;
+	struct uvcg_frame *ufrm;
+	unsigned int i;
+
+	/* Restrict the iformat, iframe and ival to valid values. Negative
+	 * values for ifrmat and iframe will result in the maximum valid value
+	 * being selected
+	 */
+	iformat = clamp((unsigned int)iformat, 1U, (unsigned int)uvc->nformats);
+	ufmt = uvc->fmt[iformat-1];
+
+	iframe = clamp((unsigned int)iframe, 1U, (unsigned int)uvc->nframes);
+	ufrm = find_frame_by_index(uvc, ufmt, iframe);
+	if (!ufrm)
+		return;
+
+	for (i = 0; i < ufrm->frame.b_frame_interval_type; i++) {
+		if (ival <= ufrm->dw_frame_interval[i]) {
+			ival = ufrm->dw_frame_interval[i];
+			break;
+		}
+	}
+
+	if (i == ufrm->frame.b_frame_interval_type)
+		ival = ufrm->dw_frame_interval[ufrm->frame.b_frame_interval_type-1];
+
+	memset(ctrl, 0, sizeof(*ctrl));
+
+	ctrl->bmHint = 1;
+	ctrl->bFormatIndex = iformat;
+	ctrl->bFrameIndex = iframe;
+	ctrl->dwFrameInterval = ival;
+	ctrl->dwMaxVideoFrameSize = ufrm->frame.dw_max_video_frame_buffer_size;
+
+	switch (uvc->func.config->cdev->gadget->speed) {
+	case USB_SPEED_SUPER:
+		ctrl->dwMaxPayloadTransferSize = uvc_ss_streaming_ep.wMaxPacketSize;
+		break;
+	case USB_SPEED_HIGH:
+		ctrl->dwMaxPayloadTransferSize = uvc_hs_streaming_ep.wMaxPacketSize;
+		break;
+	case USB_SPEED_FULL:
+	default:
+		ctrl->dwMaxPayloadTransferSize = uvc_fs_streaming_ep.wMaxPacketSize;
+		break;
+	}
+	ctrl->bmFramingInfo = 3;
+	ctrl->bPreferedVersion = 1;
+	ctrl->bMaxVersion = 1;
+}
+
+static int uvc_events_process_data(struct uvc_device *uvc, struct usb_request *req)
+{
+	struct uvc_video *video = &uvc->video;
+	struct uvc_streaming_control *target;
+	struct uvc_streaming_control *ctrl;
+	struct uvcg_frame *ufrm;
+	struct uvcg_format *ufmt;
+
+	switch (video->control) {
+	case UVC_VS_PROBE_CONTROL:
+		pr_debug("setting probe control, length = %d\n", req->actual);
+		target = &video->probe;
+		break;
+
+	case UVC_VS_COMMIT_CONTROL:
+		pr_debug("setting commit control, length = %d\n", req->actual);
+		target = &video->commit;
+		break;
+
+	default:
+		pr_debug("setting unknown control, length = %d\n", req->actual);
Should this be error instead of debug?
+		return -EOPNOTSUPP;
+	}
+
+	ctrl = (struct uvc_streaming_control *)req->buf;
+
+	if (video->control == UVC_VS_PROBE_CONTROL)
+		uvc_fill_streaming_control(uvc, target, ctrl->bFormatIndex,
+					   ctrl->bFrameIndex, ctrl->dwFrameInterval);
+
+	ufmt = uvc->fmt[target->bFormatIndex-1];
+	if (!ufmt)
+		return -EINVAL;
+
+	ufrm = find_frame_by_index(uvc, ufmt, ctrl->bFrameIndex);
+	if (!ufrm)
+		return -EINVAL;
+
+	if (video->control == UVC_VS_COMMIT_CONTROL) {
+		spin_lock(&video->frame_lock);
+
+		uvc_fill_streaming_control(uvc, target, ctrl->bFormatIndex,
+				   ctrl->bFrameIndex, ctrl->dwFrameInterval);
+
+		video->cur_frame = ufrm;
+		video->cur_format = ufmt;
+
+		spin_unlock(&video->frame_lock);
+	}
+
+	return 0;
+}
+
+static void
+uvc_events_process_streaming(struct uvc_device *uvc, uint8_t req, uint8_t cs,
+			     struct uvc_request_data *resp)
+{
+	struct uvc_streaming_control *ctrl;
+
+	pr_debug("streaming request (req %02x cs %02x)\n", req, cs);
+
+	if (cs != UVC_VS_PROBE_CONTROL && cs != UVC_VS_COMMIT_CONTROL)
+		return;
+
+	ctrl = (struct uvc_streaming_control *)&resp->data;
+	resp->length = sizeof(*ctrl);
+
+	switch (req) {
+	case UVC_SET_CUR:
+		uvc->video.control = cs;
+		resp->length = 34;
+		break;
+
+	case UVC_GET_CUR:
+		if (cs == UVC_VS_PROBE_CONTROL)
+			memcpy(ctrl, &uvc->video.probe, sizeof(*ctrl));
+		else
+			memcpy(ctrl, &uvc->video.commit, sizeof(*ctrl));
+		break;
+
+	case UVC_GET_MIN:
+	case UVC_GET_MAX:
+	case UVC_GET_DEF:
+		if (req == UVC_GET_MAX)
+			uvc_fill_streaming_control(uvc, ctrl, -1, -1, UINT_MAX);
+		else
+			uvc_fill_streaming_control(uvc, ctrl, 1, 1, 0);
+		break;
+
+	case UVC_GET_RES:
+		memset(ctrl, 0, sizeof(*ctrl));
+		break;
+
+	case UVC_GET_LEN:
+		resp->data[0] = 0x00;
+		resp->data[1] = 0x22;
+		resp->length = 2;
+		break;
+
+	case UVC_GET_INFO:
+		resp->data[0] = 0x03;
+		resp->length = 1;
+		break;
+	}
+}
+
+static int
+uvc_events_process_class(struct uvc_device *uvc, const struct usb_ctrlrequest *ctrl,
+			 struct uvc_request_data *resp)
+{
+	if ((ctrl->bRequestType & USB_RECIP_MASK) != USB_RECIP_INTERFACE)
+		return -EINVAL;
+
+	switch (ctrl->wIndex & 0xff) {
+	case UVC_INTF_VIDEO_CONTROL:
+		return -EOPNOTSUPP;
+
+	case UVC_INTF_VIDEO_STREAMING:
+		uvc_events_process_streaming(uvc, ctrl->bRequest, ctrl->wValue >> 8, resp);
+		break;
+
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int
+uvc_events_process_setup(struct uvc_device *uvc, const struct usb_ctrlrequest *ctrl,
+			 struct uvc_request_data *resp)
+{
+	uvc->video.control = 0;
+
+	pr_debug("bRequestType %02x bRequest %02x wValue %04x wIndex %04x wLength %04x\n",
+		ctrl->bRequestType, ctrl->bRequest, ctrl->wValue,
+		ctrl->wIndex, ctrl->wLength);
+
+	switch (ctrl->bRequestType & USB_TYPE_MASK) {
+	case USB_TYPE_STANDARD:
+		return -EOPNOTSUPP;
+
+	case USB_TYPE_CLASS:
+		return uvc_events_process_class(uvc, ctrl, resp);
+
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+/* --------------------------------------------------------------------------
+ * Requests handling
+ */
+
+static
+int 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);
+
+	req->length = min_t(unsigned int, uvc->event_length, data->length);
+	req->zero = data->length < uvc->event_length;
+
+	memcpy(req->buf, data->data, req->length);
+
+	return usb_ep_queue(cdev->gadget->ep0, req, GFP_KERNEL);
+}
An exact copy of this function already exists in uvc_v4l2.c. Over there
it's used to handle UVCIOC_SEND_RESPONSE, but the content and purpose of
this and the other copy are the same, so I think they can be unified.
+
 static void
 uvc_function_ep0_complete(struct usb_ep *ep, struct usb_request *req)
 {
 	struct uvc_device *uvc = req->context;
-	struct v4l2_event v4l2_event;
-	struct uvc_event *uvc_event = (void *)&v4l2_event.u.data;
+	int ret;
 
 	if (uvc->event_setup_out) {
 		uvc->event_setup_out = 0;
+		ret = uvc_events_process_data(uvc, req);
If !ret, don't you still need to uvc_send_response()? That was the
previous behavior, just that userspace would receive the v4l2 event, and
then respond with UVCIOC_SEND_RESPONSE.
quoted hunk ↗ jump to hunk
+		if (ret == -EOPNOTSUPP) {
+			struct v4l2_event v4l2_event;
+			struct uvc_event *uvc_event = (void *)&v4l2_event.u.data;
 
-		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);
-		v4l2_event_queue(&uvc->vdev, &v4l2_event);
+			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);
+			v4l2_event_queue(&uvc->vdev, &v4l2_event);
+		}
 	}
 }
 
@@ -223,8 +455,8 @@ static int
 uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
 {
 	struct uvc_device *uvc = to_uvc(f);
-	struct v4l2_event v4l2_event;
-	struct uvc_event *uvc_event = (void *)&v4l2_event.u.data;
+	struct uvc_request_data resp;
+	int ret = 0;
 
 	if ((ctrl->bRequestType & USB_TYPE_MASK) != USB_TYPE_CLASS) {
 		uvcg_info(f, "invalid request type\n");
@@ -241,12 +473,22 @@ uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
 	uvc->event_setup_out = !(ctrl->bRequestType & USB_DIR_IN);
 	uvc->event_length = le16_to_cpu(ctrl->wLength);
 
-	memset(&v4l2_event, 0, sizeof(v4l2_event));
-	v4l2_event.type = UVC_EVENT_SETUP;
-	memcpy(&uvc_event->req, ctrl, sizeof(uvc_event->req));
-	v4l2_event_queue(&uvc->vdev, &v4l2_event);
+	ret = uvc_events_process_setup(uvc, ctrl, &resp);
+	if (ret == -EOPNOTSUPP) {
+		struct v4l2_event v4l2_event;
+		struct uvc_event *uvc_event = (void *)&v4l2_event.u.data;
 
-	return 0;
+		memset(&v4l2_event, 0, sizeof(v4l2_event));
+		v4l2_event.type = UVC_EVENT_SETUP;
+		memcpy(&uvc_event->req, ctrl, sizeof(uvc_event->req));
+		v4l2_event_queue(&uvc->vdev, &v4l2_event);
+
+		return 0;
+	} else if (ret) {
+		return ret;
+	}
+
+	return uvc_send_response(uvc, &resp);
 }
 
 void uvc_function_setup_continue(struct uvc_device *uvc)
diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 62d7420a25666..d06b5282b4058 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -13,6 +13,7 @@
 #include <linux/mutex.h>
 #include <linux/spinlock.h>
 #include <linux/usb/composite.h>
+#include <linux/usb/video.h>
 #include <linux/videodev2.h>
 
 #include <media/v4l2-device.h>
@@ -85,6 +86,12 @@ struct uvc_video {
 	struct uvcg_frame *cur_frame;
 
 	struct mutex mutex;	/* protects frame parameters */
+	spinlock_t frame_lock;
+
+	struct uvc_streaming_control probe;
+	struct uvc_streaming_control commit;
+
+	int control;
 
 	/* Requests */
 	unsigned int req_size;
@@ -170,4 +177,11 @@ extern void uvc_function_connect(struct uvc_device *uvc);
 extern void uvc_function_disconnect(struct uvc_device *uvc);
 
 extern int uvc_frame_default(struct uvcg_format *ufmt);
+extern struct uvcg_frame *find_frame_by_index(struct uvc_device *uvc,
+					      struct uvcg_format *ufmt,
+					      int index);
+extern void uvc_fill_streaming_control(struct uvc_device *uvc,
+			   struct uvc_streaming_control *ctrl,
+			   int iframe, int iformat, unsigned int ival);
Indentation.
quoted hunk ↗ jump to hunk
+
 #endif /* _UVC_GADGET_H_ */
diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 83830b8864a6e..a89d3d661d99f 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -205,7 +205,8 @@ uvc_v4l2_get_format(struct file *file, void *fh, struct v4l2_format *fmt)
 }
 
 static int _uvc_v4l2_try_fmt(struct uvc_video *video,
-	struct v4l2_format *fmt, struct uvcg_format **uvc_format, struct uvcg_frame **uvc_frame)
+	struct v4l2_format *fmt, struct uvc_streaming_control *probe,
+	struct uvcg_format **uvc_format, struct uvcg_frame **uvc_frame)
 {
 	struct uvc_device *uvc = video->uvc;
 	struct uvcg_format *ufmt;
@@ -253,6 +254,9 @@ static int _uvc_v4l2_try_fmt(struct uvc_video *video,
 		*uvc_format = ufmt;
 	if (uvc_frame != NULL)
 		*uvc_frame = ufrm;
+	if (probe)
+		uvc_fill_streaming_control(uvc, probe,
+				i + 1, ufrm->frame.b_frame_index, 0);
 
 	return 0;
 }
@@ -264,7 +268,7 @@ uvc_v4l2_try_fmt(struct file *file, void *fh, struct v4l2_format *fmt)
 	struct uvc_device *uvc = video_get_drvdata(vdev);
 	struct uvc_video *video = &uvc->video;
 
-	return _uvc_v4l2_try_fmt(video, fmt, NULL, NULL);
+	return _uvc_v4l2_try_fmt(video, fmt, NULL, NULL, NULL);
 }
 
 static int
@@ -273,17 +277,23 @@ uvc_v4l2_set_format(struct file *file, void *fh, struct v4l2_format *fmt)
 	struct video_device *vdev = video_devdata(file);
 	struct uvc_device *uvc = video_get_drvdata(vdev);
 	struct uvc_video *video = &uvc->video;
+	struct uvc_streaming_control probe;
 	struct uvcg_format *ufmt;
 	struct uvcg_frame *ufrm;
 	int ret;
 
-	ret = _uvc_v4l2_try_fmt(video, fmt, &ufmt, &ufrm);
+	ret = _uvc_v4l2_try_fmt(video, fmt, &probe, &ufmt, &ufrm);
 	if (ret)
 		return ret;
 
+	spin_lock(&video->frame_lock);
+
+	video->commit = probe;
 	video->cur_format = ufmt;
 	video->cur_frame = ufrm;
 
+	spin_unlock(&video->frame_lock);
+
 	return ret;
 }
 
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index b14780bddd838..8f549fe608362 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -346,13 +346,26 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
  */
 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
 {
+	int framedef;
+
 	INIT_LIST_HEAD(&video->req_free);
 	spin_lock_init(&video->req_lock);
+	spin_lock_init(&video->frame_lock);
+
+	/* Allocate a stream specific work queue for asynchronous tasks. */
+	video->async_wq = alloc_workqueue("uvcvideo", WQ_UNBOUND | WQ_HIGHPRI | WQ_CPU_INTENSIVE, 0);
Where does async_wq come from?


Thanks,

Paul
+	if (!video->async_wq)
+		return -EINVAL;
+
 	INIT_WORK(&video->pump, uvcg_video_pump);
 
 	video->uvc = uvc;
 	video->def_format = video->cur_format = uvc->fmt[0];
-	video->cur_frame = uvc->frm[uvc_frame_default(video->def_format) - 1];
+	framedef = uvc_frame_default(video->def_format);
+	video->cur_frame = uvc->frm[framedef - 1];
+
+	uvc_fill_streaming_control(uvc, &video->probe, 1, framedef, 0);
+	uvc_fill_streaming_control(uvc, &video->commit, 1, framedef, 0);
 
 	/* Initialize the video buffers queue. */
 	uvcg_queue_init(&video->queue, V4L2_BUF_TYPE_VIDEO_OUTPUT,
-- 
2.29.2
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help