From patchwork Thu Sep 8 19:47:45 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Michael Grzeschik X-Patchwork-Id: 604160 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 728F4C6FA8A for ; Thu, 8 Sep 2022 19:48:05 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229905AbiIHTsD (ORCPT ); Thu, 8 Sep 2022 15:48:03 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55206 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229806AbiIHTsB (ORCPT ); Thu, 8 Sep 2022 15:48:01 -0400 Received: from metis.ext.pengutronix.de (metis.ext.pengutronix.de [IPv6:2001:67c:670:201:290:27ff:fe1d:cc33]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 3AF86F342A for ; Thu, 8 Sep 2022 12:47:57 -0700 (PDT) Received: from drehscheibe.grey.stw.pengutronix.de ([2a0a:edc0:0:c01:1d::a2]) by metis.ext.pengutronix.de with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1oWNVE-0001MI-0r; Thu, 08 Sep 2022 21:47:56 +0200 Received: from [2a0a:edc0:0:1101:1d::ac] (helo=dude04.red.stw.pengutronix.de) by drehscheibe.grey.stw.pengutronix.de with esmtp (Exim 4.94.2) (envelope-from ) id 1oWNVB-004gY5-6o; Thu, 08 Sep 2022 21:47:55 +0200 Received: from mgr by dude04.red.stw.pengutronix.de with local (Exim 4.94.2) (envelope-from ) id 1oWNVC-00Fjow-7c; Thu, 08 Sep 2022 21:47:54 +0200 From: Michael Grzeschik To: linux-usb@vger.kernel.org Cc: linux-media@vger.kernel.org, balbi@kernel.org, laurent.pinchart@ideasonboard.com, paul.elder@ideasonboard.com, kernel@pengutronix.de, nicolas@ndufresne.ca, kieran.bingham@ideasonboard.com Subject: [PATCH v13 1/6] media: v4l: move helper functions for fractions from uvc to v4l2-common Date: Thu, 8 Sep 2022 21:47:45 +0200 Message-Id: <20220908194750.3750310-2-m.grzeschik@pengutronix.de> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20220908194750.3750310-1-m.grzeschik@pengutronix.de> References: <20220908194750.3750310-1-m.grzeschik@pengutronix.de> MIME-Version: 1.0 X-SA-Exim-Connect-IP: 2a0a:edc0:0:c01:1d::a2 X-SA-Exim-Mail-From: mgr@pengutronix.de X-SA-Exim-Scanned: No (on metis.ext.pengutronix.de); SAEximRunCond expanded to false X-PTX-Original-Recipient: linux-usb@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org The functions uvc_simplify_fraction and uvc_fraction_to_interval are generic helpers which are also useful for other v4l2 drivers. This patch moves them to v4l2-common. Signed-off-by: Michael Grzeschik --- v1 -> v7: - v7 -> v8: - ported all style fixes and broken links from latest version on rebase v8 -> v13: - drivers/media/usb/uvc/uvc_driver.c | 84 -------------------------- drivers/media/usb/uvc/uvc_v4l2.c | 14 ++--- drivers/media/usb/uvc/uvcvideo.h | 3 - drivers/media/v4l2-core/v4l2-common.c | 86 +++++++++++++++++++++++++++ include/media/v4l2-common.h | 4 ++ 5 files changed, 97 insertions(+), 94 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index 9c05776f11d1f0..0f14dee4b6d794 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -329,90 +329,6 @@ static enum v4l2_ycbcr_encoding uvc_ycbcr_enc(const u8 matrix_coefficients) return V4L2_YCBCR_ENC_DEFAULT; /* Reserved */ } -/* - * Simplify a fraction using a simple continued fraction decomposition. The - * idea here is to convert fractions such as 333333/10000000 to 1/30 using - * 32 bit arithmetic only. The algorithm is not perfect and relies upon two - * arbitrary parameters to remove non-significative terms from the simple - * continued fraction decomposition. Using 8 and 333 for n_terms and threshold - * respectively seems to give nice results. - */ -void uvc_simplify_fraction(u32 *numerator, u32 *denominator, - unsigned int n_terms, unsigned int threshold) -{ - u32 *an; - u32 x, y, r; - unsigned int i, n; - - an = kmalloc_array(n_terms, sizeof(*an), GFP_KERNEL); - if (an == NULL) - return; - - /* - * Convert the fraction to a simple continued fraction. See - * https://en.wikipedia.org/wiki/Continued_fraction - * Stop if the current term is bigger than or equal to the given - * threshold. - */ - x = *numerator; - y = *denominator; - - for (n = 0; n < n_terms && y != 0; ++n) { - an[n] = x / y; - if (an[n] >= threshold) { - if (n < 2) - n++; - break; - } - - r = x - an[n] * y; - x = y; - y = r; - } - - /* Expand the simple continued fraction back to an integer fraction. */ - x = 0; - y = 1; - - for (i = n; i > 0; --i) { - r = y; - y = an[i-1] * y + x; - x = r; - } - - *numerator = y; - *denominator = x; - kfree(an); -} - -/* - * Convert a fraction to a frame interval in 100ns multiples. The idea here is - * to compute numerator / denominator * 10000000 using 32 bit fixed point - * arithmetic only. - */ -u32 uvc_fraction_to_interval(u32 numerator, u32 denominator) -{ - u32 multiplier; - - /* Saturate the result if the operation would overflow. */ - if (denominator == 0 || - numerator/denominator >= ((u32)-1)/10000000) - return (u32)-1; - - /* - * Divide both the denominator and the multiplier by two until - * numerator * multiplier doesn't overflow. If anyone knows a better - * algorithm please let me know. - */ - multiplier = 10000000; - while (numerator > ((u32)-1)/multiplier) { - multiplier /= 2; - denominator /= 2; - } - - return denominator ? numerator * multiplier / denominator : 0; -} - /* ------------------------------------------------------------------------ * Terminal and unit management */ diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c index 4cc3fa6b8c9812..f4d4c33b6dfbd7 100644 --- a/drivers/media/usb/uvc/uvc_v4l2.c +++ b/drivers/media/usb/uvc/uvc_v4l2.c @@ -386,7 +386,7 @@ static int uvc_v4l2_get_streamparm(struct uvc_streaming *stream, mutex_unlock(&stream->mutex); denominator = 10000000; - uvc_simplify_fraction(&numerator, &denominator, 8, 333); + v4l2_simplify_fraction(&numerator, &denominator, 8, 333); memset(parm, 0, sizeof(*parm)); parm->type = stream->type; @@ -427,7 +427,7 @@ static int uvc_v4l2_set_streamparm(struct uvc_streaming *stream, else timeperframe = parm->parm.output.timeperframe; - interval = uvc_fraction_to_interval(timeperframe.numerator, + interval = v4l2_fraction_to_interval(timeperframe.numerator, timeperframe.denominator); uvc_dbg(stream->dev, FORMAT, "Setting frame interval to %u/%u (%u)\n", timeperframe.numerator, timeperframe.denominator, interval); @@ -481,7 +481,7 @@ static int uvc_v4l2_set_streamparm(struct uvc_streaming *stream, /* Return the actual frame period. */ timeperframe.numerator = probe.dwFrameInterval; timeperframe.denominator = 10000000; - uvc_simplify_fraction(&timeperframe.numerator, + v4l2_simplify_fraction(&timeperframe.numerator, &timeperframe.denominator, 8, 333); if (parm->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { @@ -1275,7 +1275,7 @@ static int uvc_ioctl_enum_frameintervals(struct file *file, void *fh, fival->discrete.numerator = frame->dwFrameInterval[index]; fival->discrete.denominator = 10000000; - uvc_simplify_fraction(&fival->discrete.numerator, + v4l2_simplify_fraction(&fival->discrete.numerator, &fival->discrete.denominator, 8, 333); } else { fival->type = V4L2_FRMIVAL_TYPE_STEPWISE; @@ -1285,11 +1285,11 @@ static int uvc_ioctl_enum_frameintervals(struct file *file, void *fh, fival->stepwise.max.denominator = 10000000; fival->stepwise.step.numerator = frame->dwFrameInterval[2]; fival->stepwise.step.denominator = 10000000; - uvc_simplify_fraction(&fival->stepwise.min.numerator, + v4l2_simplify_fraction(&fival->stepwise.min.numerator, &fival->stepwise.min.denominator, 8, 333); - uvc_simplify_fraction(&fival->stepwise.max.numerator, + v4l2_simplify_fraction(&fival->stepwise.max.numerator, &fival->stepwise.max.denominator, 8, 333); - uvc_simplify_fraction(&fival->stepwise.step.numerator, + v4l2_simplify_fraction(&fival->stepwise.step.numerator, &fival->stepwise.step.denominator, 8, 333); } diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index 24c911aeebce56..ff710bdd38b3fd 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -911,9 +911,6 @@ int uvc_xu_ctrl_query(struct uvc_video_chain *chain, struct uvc_xu_control_query *xqry); /* Utility functions */ -void uvc_simplify_fraction(u32 *numerator, u32 *denominator, - unsigned int n_terms, unsigned int threshold); -u32 uvc_fraction_to_interval(u32 numerator, u32 denominator); struct usb_host_endpoint *uvc_find_endpoint(struct usb_host_interface *alts, u8 epaddr); u16 uvc_endpoint_max_bpi(struct usb_device *dev, struct usb_host_endpoint *ep); diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c index e0fbe6ba4b6c49..40f56e044640d7 100644 --- a/drivers/media/v4l2-core/v4l2-common.c +++ b/drivers/media/v4l2-core/v4l2-common.c @@ -484,3 +484,89 @@ s64 v4l2_get_link_freq(struct v4l2_ctrl_handler *handler, unsigned int mul, return freq > 0 ? freq : -EINVAL; } EXPORT_SYMBOL_GPL(v4l2_get_link_freq); + +/* + * Simplify a fraction using a simple continued fraction decomposition. The + * idea here is to convert fractions such as 333333/10000000 to 1/30 using + * 32 bit arithmetic only. The algorithm is not perfect and relies upon two + * arbitrary parameters to remove non-significative terms from the simple + * continued fraction decomposition. Using 8 and 333 for n_terms and threshold + * respectively seems to give nice results. + */ +void v4l2_simplify_fraction(u32 *numerator, u32 *denominator, + unsigned int n_terms, unsigned int threshold) +{ + u32 *an; + u32 x, y, r; + unsigned int i, n; + + an = kmalloc_array(n_terms, sizeof(*an), GFP_KERNEL); + if (an == NULL) + return; + + /* + * Convert the fraction to a simple continued fraction. See + * https://en.wikipedia.org/wiki/Continued_fraction + * Stop if the current term is bigger than or equal to the given + * threshold. + */ + x = *numerator; + y = *denominator; + + for (n = 0; n < n_terms && y != 0; ++n) { + an[n] = x / y; + if (an[n] >= threshold) { + if (n < 2) + n++; + break; + } + + r = x - an[n] * y; + x = y; + y = r; + } + + /* Expand the simple continued fraction back to an integer fraction. */ + x = 0; + y = 1; + + for (i = n; i > 0; --i) { + r = y; + y = an[i-1] * y + x; + x = r; + } + + *numerator = y; + *denominator = x; + kfree(an); +} +EXPORT_SYMBOL_GPL(v4l2_simplify_fraction); + +/* + * Convert a fraction to a frame interval in 100ns multiples. The idea here is + * to compute numerator / denominator * 10000000 using 32 bit fixed point + * arithmetic only. + */ +u32 v4l2_fraction_to_interval(u32 numerator, u32 denominator) +{ + u32 multiplier; + + /* Saturate the result if the operation would overflow. */ + if (denominator == 0 || + numerator/denominator >= ((u32)-1)/10000000) + return (u32)-1; + + /* + * Divide both the denominator and the multiplier by two until + * numerator * multiplier doesn't overflow. If anyone knows a better + * algorithm please let me know. + */ + multiplier = 10000000; + while (numerator > ((u32)-1)/multiplier) { + multiplier /= 2; + denominator /= 2; + } + + return denominator ? numerator * multiplier / denominator : 0; +} +EXPORT_SYMBOL_GPL(v4l2_fraction_to_interval); diff --git a/include/media/v4l2-common.h b/include/media/v4l2-common.h index b708d63995f458..725ff91b26e063 100644 --- a/include/media/v4l2-common.h +++ b/include/media/v4l2-common.h @@ -540,6 +540,10 @@ int v4l2_fill_pixfmt_mp(struct v4l2_pix_format_mplane *pixfmt, u32 pixelformat, s64 v4l2_get_link_freq(struct v4l2_ctrl_handler *handler, unsigned int mul, unsigned int div); +void v4l2_simplify_fraction(u32 *numerator, u32 *denominator, + unsigned int n_terms, unsigned int threshold); +u32 v4l2_fraction_to_interval(u32 numerator, u32 denominator); + static inline u64 v4l2_buffer_get_timestamp(const struct v4l2_buffer *buf) { /* From patchwork Thu Sep 8 19:47:47 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Michael Grzeschik X-Patchwork-Id: 604159 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id BE5BAC6FA89 for ; Thu, 8 Sep 2022 19:48:07 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229807AbiIHTsG (ORCPT ); Thu, 8 Sep 2022 15:48:06 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55204 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229833AbiIHTsB (ORCPT ); Thu, 8 Sep 2022 15:48:01 -0400 Received: from metis.ext.pengutronix.de (metis.ext.pengutronix.de [IPv6:2001:67c:670:201:290:27ff:fe1d:cc33]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 8A782F3BDA for ; Thu, 8 Sep 2022 12:47:58 -0700 (PDT) Received: from drehscheibe.grey.stw.pengutronix.de ([2a0a:edc0:0:c01:1d::a2]) by metis.ext.pengutronix.de with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1oWNVE-0001Mf-PG; Thu, 08 Sep 2022 21:47:56 +0200 Received: from [2a0a:edc0:0:1101:1d::ac] (helo=dude04.red.stw.pengutronix.de) by drehscheibe.grey.stw.pengutronix.de with esmtp (Exim 4.94.2) (envelope-from ) id 1oWNVC-004gYH-65; Thu, 08 Sep 2022 21:47:56 +0200 Received: from mgr by dude04.red.stw.pengutronix.de with local (Exim 4.94.2) (envelope-from ) id 1oWNVC-00Fjp2-8t; Thu, 08 Sep 2022 21:47:54 +0200 From: Michael Grzeschik To: linux-usb@vger.kernel.org Cc: linux-media@vger.kernel.org, balbi@kernel.org, laurent.pinchart@ideasonboard.com, paul.elder@ideasonboard.com, kernel@pengutronix.de, nicolas@ndufresne.ca, kieran.bingham@ideasonboard.com Subject: [PATCH v13 3/6] usb: gadget: uvc: add v4l2 enumeration api calls Date: Thu, 8 Sep 2022 21:47:47 +0200 Message-Id: <20220908194750.3750310-4-m.grzeschik@pengutronix.de> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20220908194750.3750310-1-m.grzeschik@pengutronix.de> References: <20220908194750.3750310-1-m.grzeschik@pengutronix.de> MIME-Version: 1.0 X-SA-Exim-Connect-IP: 2a0a:edc0:0:c01:1d::a2 X-SA-Exim-Mail-From: mgr@pengutronix.de X-SA-Exim-Scanned: No (on metis.ext.pengutronix.de); SAEximRunCond expanded to false X-PTX-Original-Recipient: linux-usb@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org This patch adds support to the v4l2 VIDIOCs for enum_format, enum_framesizes and enum_frameintervals. This way, the userspace application can use these VIDIOCS to query the via configfs exported frame capabilities. With thes callbacks the userspace doesn't have to bring its own configfs parser. Signed-off-by: Michael Grzeschik --- v1 -> v13: - refactored the enum_ callbacks to this separate new patch - renamed +uvc_v4l2_enum_fmt to uvc_v4l2_enum_format - improved coding style - removed unused leftover variable uvc_video in enum functions drivers/usb/gadget/function/f_uvc.c | 32 +++++ drivers/usb/gadget/function/uvc.h | 2 + drivers/usb/gadget/function/uvc_v4l2.c | 176 +++++++++++++++++++++++++ 3 files changed, 210 insertions(+) diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index f4f6cf75930beb..7c416170b499e0 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -888,6 +888,7 @@ static void uvc_free(struct usb_function *f) struct uvc_device *uvc = to_uvc(f); struct f_uvc_opts *opts = container_of(f->fi, struct f_uvc_opts, func_inst); + config_item_put(&uvc->header->item); --opts->refcnt; kfree(uvc); } @@ -941,6 +942,7 @@ static struct usb_function *uvc_alloc(struct usb_function_instance *fi) struct uvc_device *uvc; struct f_uvc_opts *opts; struct uvc_descriptor_header **strm_cls; + struct config_item *streaming, *header, *h; uvc = kzalloc(sizeof(*uvc), GFP_KERNEL); if (uvc == NULL) @@ -973,6 +975,36 @@ static struct usb_function *uvc_alloc(struct usb_function_instance *fi) uvc->desc.fs_streaming = opts->fs_streaming; uvc->desc.hs_streaming = opts->hs_streaming; uvc->desc.ss_streaming = opts->ss_streaming; + + streaming = config_group_find_item(&opts->func_inst.group, "streaming"); + if (!streaming) { + config_item_put(streaming); + mutex_unlock(&opts->lock); + return ERR_PTR(-ENOMEM); + } + + header = config_group_find_item(to_config_group(streaming), "header"); + config_item_put(streaming); + if (!header) { + config_item_put(header); + mutex_unlock(&opts->lock); + return ERR_PTR(-ENOMEM); + } + + h = config_group_find_item(to_config_group(header), "h"); + config_item_put(header); + if (!h) { + config_item_put(h); + mutex_unlock(&opts->lock); + return ERR_PTR(-ENOMEM); + } + + uvc->header = to_uvcg_streaming_header(h); + if (!uvc->header->linked) { + mutex_unlock(&opts->lock); + return ERR_PTR(-EBUSY); + } + ++opts->refcnt; mutex_unlock(&opts->lock); diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h index 58e383afdd4406..641cf2e7afaf6e 100644 --- a/drivers/usb/gadget/function/uvc.h +++ b/drivers/usb/gadget/function/uvc.h @@ -133,6 +133,8 @@ struct uvc_device { bool func_connected; wait_queue_head_t func_connected_queue; + struct uvcg_streaming_header *header; + /* Descriptors */ struct { const struct uvc_descriptor_header * const *fs_control; diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c index 511f106f984375..63cb5a40306c75 100644 --- a/drivers/usb/gadget/function/uvc_v4l2.c +++ b/drivers/usb/gadget/function/uvc_v4l2.c @@ -18,12 +18,92 @@ #include #include #include +#include #include "f_uvc.h" #include "uvc.h" #include "uvc_queue.h" #include "uvc_video.h" #include "uvc_v4l2.h" +#include "uvc_configfs.h" + +static struct uvc_format_desc *to_uvc_format(struct uvcg_format *uformat) +{ + char guid[16] = UVC_GUID_FORMAT_MJPEG; + struct uvc_format_desc *format; + struct uvcg_uncompressed *unc; + + if (uformat->type == UVCG_UNCOMPRESSED) { + unc = to_uvcg_uncompressed(&uformat->group.cg_item); + if (!unc) + return ERR_PTR(-EINVAL); + + memcpy(guid, unc->desc.guidFormat, sizeof(guid)); + } + + format = uvc_format_by_guid(guid); + if (!format) + return ERR_PTR(-EINVAL); + + return format; +} + +struct uvcg_format *find_format_by_index(struct uvc_device *uvc, int index) +{ + struct uvcg_format_ptr *format; + struct uvcg_format *uformat = NULL; + int i = 1; + + list_for_each_entry(format, &uvc->header->formats, entry) { + if (index == i) { + uformat = format->fmt; + break; + } + i++; + } + + return uformat; +} + +struct uvcg_frame *find_frame_by_index(struct uvc_device *uvc, + struct uvcg_format *uformat, + int index) +{ + struct uvcg_format_ptr *format; + struct uvcg_frame_ptr *frame; + struct uvcg_frame *uframe = NULL; + + list_for_each_entry(format, &uvc->header->formats, entry) { + if (format->fmt->type != uformat->type) + continue; + list_for_each_entry(frame, &format->fmt->frames, entry) { + if (index == frame->frm->frame.b_frame_index) { + uframe = frame->frm; + break; + } + } + } + + return uframe; +} + +static struct uvcg_format *find_format_by_pix(struct uvc_device *uvc, + u32 pixelformat) +{ + struct uvcg_format_ptr *format; + struct uvcg_format *uformat = NULL; + + list_for_each_entry(format, &uvc->header->formats, entry) { + struct uvc_format_desc *fmtdesc = to_uvc_format(format->fmt); + + if (fmtdesc->fcc == pixelformat) { + uformat = format->fmt; + break; + } + } + + return uformat; +} /* -------------------------------------------------------------------------- * Requests handling @@ -134,6 +214,99 @@ uvc_v4l2_set_format(struct file *file, void *fh, struct v4l2_format *fmt) return 0; } +static int +uvc_v4l2_enum_frameintervals(struct file *file, void *fh, + struct v4l2_frmivalenum *fival) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + struct uvcg_format *uformat = NULL; + struct uvcg_frame *uframe = NULL; + struct uvcg_frame_ptr *frame; + + uformat = find_format_by_pix(uvc, fival->pixel_format); + if (!uformat) + return -EINVAL; + + list_for_each_entry(frame, &uformat->frames, entry) { + if (frame->frm->frame.w_width == fival->width && + frame->frm->frame.w_height == fival->height) { + uframe = frame->frm; + break; + } + } + if (!uframe) + return -EINVAL; + + if (fival->index >= uframe->frame.b_frame_interval_type) + return -EINVAL; + + fival->discrete.numerator = + uframe->dw_frame_interval[fival->index]; + + /* TODO: handle V4L2_FRMIVAL_TYPE_STEPWISE */ + fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + fival->discrete.denominator = 10000000; + v4l2_simplify_fraction(&fival->discrete.numerator, + &fival->discrete.denominator, 8, 333); + + return 0; +} + +static int +uvc_v4l2_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + struct uvcg_format *uformat = NULL; + struct uvcg_frame *uframe = NULL; + + uformat = find_format_by_pix(uvc, fsize->pixel_format); + if (!uformat) + return -EINVAL; + + if (fsize->index >= uformat->num_frames) + return -EINVAL; + + uframe = find_frame_by_index(uvc, uformat, fsize->index + 1); + if (!uframe) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = uframe->frame.w_width; + fsize->discrete.height = uframe->frame.w_height; + + return 0; +} + +static int +uvc_v4l2_enum_format(struct file *file, void *fh, struct v4l2_fmtdesc *f) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + struct uvc_format_desc *fmtdesc; + struct uvcg_format *uformat; + + if (f->index >= uvc->header->num_fmt) + return -EINVAL; + + uformat = find_format_by_index(uvc, f->index + 1); + if (!uformat) + return -EINVAL; + + if (uformat->type != UVCG_UNCOMPRESSED) + f->flags |= V4L2_FMT_FLAG_COMPRESSED; + + fmtdesc = to_uvc_format(uformat); + f->pixelformat = fmtdesc->fcc; + + strscpy(f->description, fmtdesc->name, sizeof(f->description)); + f->description[strlen(fmtdesc->name) - 1] = 0; + + return 0; +} + static int uvc_v4l2_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *b) { @@ -300,6 +473,9 @@ const struct v4l2_ioctl_ops uvc_v4l2_ioctl_ops = { .vidioc_querycap = uvc_v4l2_querycap, .vidioc_g_fmt_vid_out = uvc_v4l2_get_format, .vidioc_s_fmt_vid_out = uvc_v4l2_set_format, + .vidioc_enum_frameintervals = uvc_v4l2_enum_frameintervals, + .vidioc_enum_framesizes = uvc_v4l2_enum_framesizes, + .vidioc_enum_fmt_vid_out = uvc_v4l2_enum_format, .vidioc_reqbufs = uvc_v4l2_reqbufs, .vidioc_querybuf = uvc_v4l2_querybuf, .vidioc_qbuf = uvc_v4l2_qbuf, From patchwork Thu Sep 8 19:47:50 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Michael Grzeschik X-Patchwork-Id: 604158 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 1C3C2C38145 for ; Thu, 8 Sep 2022 19:48:12 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229950AbiIHTsK (ORCPT ); Thu, 8 Sep 2022 15:48:10 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55210 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229800AbiIHTsD (ORCPT ); Thu, 8 Sep 2022 15:48:03 -0400 Received: from metis.ext.pengutronix.de (metis.ext.pengutronix.de [IPv6:2001:67c:670:201:290:27ff:fe1d:cc33]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id D1F30F4103 for ; Thu, 8 Sep 2022 12:47:58 -0700 (PDT) Received: from drehscheibe.grey.stw.pengutronix.de ([2a0a:edc0:0:c01:1d::a2]) by metis.ext.pengutronix.de with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1oWNVE-0001Ml-Ut; Thu, 08 Sep 2022 21:47:57 +0200 Received: from [2a0a:edc0:0:1101:1d::ac] (helo=dude04.red.stw.pengutronix.de) by drehscheibe.grey.stw.pengutronix.de with esmtp (Exim 4.94.2) (envelope-from ) id 1oWNVC-004gYM-C4; Thu, 08 Sep 2022 21:47:56 +0200 Received: from mgr by dude04.red.stw.pengutronix.de with local (Exim 4.94.2) (envelope-from ) id 1oWNVC-00FjpC-AL; Thu, 08 Sep 2022 21:47:54 +0200 From: Michael Grzeschik To: linux-usb@vger.kernel.org Cc: linux-media@vger.kernel.org, balbi@kernel.org, laurent.pinchart@ideasonboard.com, paul.elder@ideasonboard.com, kernel@pengutronix.de, nicolas@ndufresne.ca, kieran.bingham@ideasonboard.com Subject: [PATCH v13 6/6] usb: gadget: uvc: add format/frame handling code Date: Thu, 8 Sep 2022 21:47:50 +0200 Message-Id: <20220908194750.3750310-7-m.grzeschik@pengutronix.de> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20220908194750.3750310-1-m.grzeschik@pengutronix.de> References: <20220908194750.3750310-1-m.grzeschik@pengutronix.de> MIME-Version: 1.0 X-SA-Exim-Connect-IP: 2a0a:edc0:0:c01:1d::a2 X-SA-Exim-Mail-From: mgr@pengutronix.de X-SA-Exim-Scanned: No (on metis.ext.pengutronix.de); SAEximRunCond expanded to false X-PTX-Original-Recipient: linux-usb@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org The Hostside format selection is currently only done in userspace, as the events for SET_CUR and GET_CUR are always moved to the application layer. Since the v4l2 device parses the configfs data, the format negotiation can be done in the kernel. This patch adds the functions to set the current configuration while continuing to forward all unknown events to the userspace level. The userspace application only needs to be extended with subscription for streamon, streamoff, connect and disconnect for stream handling and become able to serve the uvc device. If the userspace still wants to subscribe for the SETUP and DATA events, it still can. This way the gadget is not braking current applications and stays extendable. Signed-off-by: Michael Grzeschik --- v1 -> v2: - fixed the commit message - changed pr_debug to pr_err in events_process_data - aligned many indentations - simplified uvc_events_process_data - fixed uvc_fill_streaming_control calls in uvcg_video_init - added setup_subscribed to decide if userspace takes over on EOPNOTSUPP - added data_subscribed to decide if userspace takes over on EOPNOTSUPP - removed duplicate send_response - wrting fmt and frm in full v2 -> v3: - added find_format_index to set the right probe v3 -> v4: - add function find_ival_index and use for cur_ival - fix swapped frame and format in uvc_events_process_data on uvc_fill_streaming_control - set proper resp.length on ep0 complete - dropped setting cur_probe on set_format since function was removed - added locking around getting correspondent cur_{frame,format,ival} v4 -> v5: - fixed sparse errors reported by kernel test robot v5 -> v6: - fixed the handling in uvc_function_ep0_complete after events_process_data v6 -> v7: - set dwMaxPayloadTransferSize unconditionally from streaming_maxpacket - fixed check for interface with masking for 0xff v7 -> v13: - drivers/usb/gadget/function/f_uvc.c | 237 +++++++++++++++++++++++- drivers/usb/gadget/function/uvc.h | 19 ++ drivers/usb/gadget/function/uvc_v4l2.c | 66 ++++++- drivers/usb/gadget/function/uvc_video.c | 12 +- 4 files changed, 322 insertions(+), 12 deletions(-) diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index 6f1138e819bdbf..f7d538cc1beb5e 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -204,21 +203,228 @@ 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) +{ + struct f_uvc_opts *opts; + struct uvcg_format *uformat; + struct uvcg_frame *uframe; + + /* 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->header->num_fmt); + uformat = find_format_by_index(uvc, iformat); + if (!uformat) + return; + + iframe = clamp((unsigned int)iframe, 1U, + (unsigned int)uformat->num_frames); + uframe = find_frame_by_index(uvc, uformat, iframe); + if (!uframe) + return; + + ival = clamp((unsigned int)ival, 1U, + (unsigned int)uframe->frame.b_frame_interval_type); + if (!uframe->dw_frame_interval[ival - 1]) + return; + + opts = fi_to_f_uvc_opts(uvc->func.fi); + + memset(ctrl, 0, sizeof(*ctrl)); + + ctrl->bmHint = 1; + ctrl->bFormatIndex = iformat; + ctrl->bFrameIndex = iframe; + ctrl->dwFrameInterval = uframe->dw_frame_interval[ival - 1]; + ctrl->dwMaxVideoFrameSize = + uframe->frame.dw_max_video_frame_buffer_size; + + ctrl->dwMaxPayloadTransferSize = opts->streaming_maxpacket; + 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 *uframe; + struct uvcg_format *uformat; + + 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_err("setting unknown control, length = %d\n", req->actual); + return -EOPNOTSUPP; + } + + ctrl = (struct uvc_streaming_control *)req->buf; + + uvc_fill_streaming_control(uvc, target, ctrl->bFrameIndex, + ctrl->bFormatIndex, ctrl->dwFrameInterval); + + if (video->control == UVC_VS_COMMIT_CONTROL) { + uformat = find_format_by_index(uvc, target->bFormatIndex); + if (!uformat) + return -EINVAL; + + uframe = find_frame_by_index(uvc, uformat, ctrl->bFrameIndex); + if (!uframe) + return -EINVAL; + + spin_lock(&video->frame_lock); + + video->cur_frame = uframe; + video->cur_format = uformat; + video->cur_ival = find_ival_index(uframe, ctrl->dwFrameInterval); + + 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) +{ + unsigned int interface = le16_to_cpu(ctrl->wIndex) & 0xff; + + if ((ctrl->bRequestType & USB_RECIP_MASK) != USB_RECIP_INTERFACE) + return -EINVAL; + + if (interface == uvc->control_intf) + return -EOPNOTSUPP; + else if (interface == uvc->streaming_intf) + uvc_events_process_streaming(uvc, ctrl->bRequest, + le16_to_cpu(ctrl->wValue) >> 8, + resp); + + 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; +} + 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; - 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); + ret = uvc_events_process_data(uvc, req); + /* If we have a real error on process */ + if (ret == -EINVAL) { + struct uvc_request_data resp; + + memset(&resp, 0, sizeof(resp)); + resp.length = -EL2HLT; + + uvc_send_response(uvc, &resp); + } else if (ret == -EOPNOTSUPP && uvc->data_subscribed) { + 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); + } } } @@ -228,6 +434,8 @@ 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"); @@ -245,6 +453,23 @@ 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(&resp, 0, sizeof(resp)); + resp.length = -EL2HLT; + + ret = uvc_events_process_setup(uvc, ctrl, &resp); + /* If we have no error on process */ + if (!ret) + return uvc_send_response(uvc, &resp); + + /* If we have a real error on process */ + if (ret != -EOPNOTSUPP) + return ret; + + /* If we have -EOPNOTSUPP */ + if (!uvc->setup_subscribed) + return uvc_send_response(uvc, &resp); + + /* If we have setup subscribed */ memset(&v4l2_event, 0, sizeof(v4l2_event)); v4l2_event.type = UVC_EVENT_SETUP; memcpy(&uvc_event->req, ctrl, sizeof(uvc_event->req)); diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h index d32f34a7dbc423..ad89d969f869c5 100644 --- a/drivers/usb/gadget/function/uvc.h +++ b/drivers/usb/gadget/function/uvc.h @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include @@ -95,6 +97,12 @@ struct uvc_video { unsigned int cur_ival; struct mutex mutex; /* protects frame parameters */ + spinlock_t frame_lock; + + struct uvc_streaming_control probe; + struct uvc_streaming_control commit; + + int control; unsigned int uvc_num_requests; @@ -131,6 +139,8 @@ struct uvc_device { struct uvc_video video; bool func_connected; wait_queue_head_t func_connected_queue; + bool setup_subscribed; + bool data_subscribed; struct uvcg_streaming_header *header; @@ -189,5 +199,14 @@ extern struct uvcg_format *find_format_by_index(struct uvc_device *uvc, extern struct uvcg_frame *find_frame_by_index(struct uvc_device *uvc, struct uvcg_format *uformat, int index); +extern int find_format_index(struct uvc_device *uvc, + struct uvcg_format *uformat); +extern int find_ival_index(struct uvcg_frame *uframe, int dwFrameInterval); +extern void uvc_fill_streaming_control(struct uvc_device *uvc, + struct uvc_streaming_control *ctrl, + int iframe, int iformat, + unsigned int ival); +extern int uvc_send_response(struct uvc_device *uvc, + struct uvc_request_data *data); #endif /* _UVC_GADGET_H_ */ diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c index bc5e2076c6b7e4..d96c7f340839ed 100644 --- a/drivers/usb/gadget/function/uvc_v4l2.c +++ b/drivers/usb/gadget/function/uvc_v4l2.c @@ -90,6 +90,33 @@ struct uvcg_format *find_format_by_index(struct uvc_device *uvc, int index) return uformat; } +int find_format_index(struct uvc_device *uvc, struct uvcg_format *uformat) +{ + struct uvcg_format_ptr *format; + int i = 1; + + list_for_each_entry(format, &uvc->header->formats, entry) { + if (uformat == format->fmt) + return i; + i++; + } + + return 0; +} + +int find_ival_index(struct uvcg_frame *uframe, int dwFrameInterval) +{ + int i; + + for (i = 0; i < uframe->frame.b_frame_interval_type; i++) { + if (dwFrameInterval == uframe->dw_frame_interval[i]) + return i + 1; + } + + /* fallback */ + return 1; +} + struct uvcg_frame *find_frame_by_index(struct uvc_device *uvc, struct uvcg_format *uformat, int index) @@ -178,8 +205,7 @@ static struct uvcg_frame *find_closest_frame_by_size(struct uvc_device *uvc, * Requests handling */ -static int -uvc_send_response(struct uvc_device *uvc, struct uvc_request_data *data) +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; @@ -221,6 +247,8 @@ uvc_v4l2_get_format(struct file *file, void *fh, struct v4l2_format *fmt) struct uvc_video *video = &uvc->video; struct uvc_format_desc *fmtdesc; + spin_lock(&video->frame_lock); + fmtdesc = to_uvc_format(video->cur_format); fmt->fmt.pix.pixelformat = fmtdesc->fcc; @@ -234,6 +262,8 @@ uvc_v4l2_get_format(struct file *file, void *fh, struct v4l2_format *fmt) fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; fmt->fmt.pix.priv = 0; + spin_unlock(&video->frame_lock); + return 0; } @@ -259,6 +289,7 @@ uvc_v4l2_try_format(struct file *file, void *fh, struct v4l2_format *fmt) struct uvc_video *video = &uvc->video; struct uvcg_format *uformat; struct uvcg_frame *uframe; + int iformat; u8 *fcc; if (fmt->type != video->queue.queue.type) @@ -274,6 +305,10 @@ uvc_v4l2_try_format(struct file *file, void *fh, struct v4l2_format *fmt) if (!uformat) return -EINVAL; + iformat = find_format_index(uvc, uformat); + if (!iformat) + return -EINVAL; + uframe = find_closest_frame_by_size(uvc, uformat, fmt->fmt.pix.width, fmt->fmt.pix.height); if (!uframe) @@ -320,8 +355,12 @@ uvc_v4l2_enum_frameintervals(struct file *file, void *fh, if (fival->index >= 1) return -EINVAL; + spin_lock(&video->frame_lock); + fival->discrete.numerator = uframe->dw_frame_interval[video->cur_ival - 1]; + + spin_unlock(&video->frame_lock); } else { if (fival->index >= uframe->frame.b_frame_interval_type) return -EINVAL; @@ -353,8 +392,12 @@ uvc_v4l2_enum_framesizes(struct file *file, void *fh, if (fsize->index >= 1) return -EINVAL; + spin_lock(&video->frame_lock); + uformat = video->cur_format; uframe = video->cur_frame; + + spin_unlock(&video->frame_lock); } else { uformat = find_format_by_pix(uvc, fsize->pixel_format); if (!uformat) @@ -388,7 +431,11 @@ uvc_v4l2_enum_format(struct file *file, void *fh, struct v4l2_fmtdesc *f) if (f->index >= 1) return -EINVAL; + spin_lock(&video->frame_lock); + uformat = video->cur_format; + + spin_unlock(&video->frame_lock); } else { if (f->index >= uvc->header->num_fmt) return -EINVAL; @@ -512,14 +559,20 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh, if (sub->type < UVC_EVENT_FIRST || sub->type > UVC_EVENT_LAST) return -EINVAL; - if (sub->type == UVC_EVENT_SETUP && uvc->func_connected) + if (sub->type == UVC_EVENT_STREAMON && uvc->func_connected) return -EBUSY; ret = v4l2_event_subscribe(fh, sub, 2, NULL); if (ret < 0) return ret; - if (sub->type == UVC_EVENT_SETUP) { + if (sub->type == UVC_EVENT_SETUP) + uvc->setup_subscribed = true; + + if (sub->type == UVC_EVENT_DATA) + uvc->data_subscribed = true; + + if (sub->type == UVC_EVENT_STREAMON) { uvc->func_connected = true; handle->is_uvc_app_handle = true; uvc_function_connect(uvc); @@ -549,7 +602,10 @@ uvc_v4l2_unsubscribe_event(struct v4l2_fh *fh, if (ret < 0) return ret; - if (sub->type == UVC_EVENT_SETUP && handle->is_uvc_app_handle) { + if (sub->type == UVC_EVENT_SETUP) + uvc->setup_subscribed = false; + + if (sub->type == UVC_EVENT_STREAMON && handle->is_uvc_app_handle) { uvc_v4l2_disable(uvc); handle->is_uvc_app_handle = false; } diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c index 37867c93073418..c20d832c4a0b8a 100644 --- a/drivers/usb/gadget/function/uvc_video.c +++ b/drivers/usb/gadget/function/uvc_video.c @@ -533,10 +533,11 @@ static int uvc_default_frame_interval(struct uvc_video *video) */ int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc) { - int iframe; + int iframe, iformat; INIT_LIST_HEAD(&video->req_free); spin_lock_init(&video->req_lock); + spin_lock_init(&video->frame_lock); INIT_WORK(&video->pump, uvcg_video_pump); if (list_empty(&uvc->header->formats)) @@ -547,6 +548,10 @@ int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc) if (!video->cur_format) return -EINVAL; + iformat = find_format_index(uvc, video->cur_format); + if (!iformat) + return -EINVAL; + iframe = uvc_frame_default(video->cur_format); if (!iframe) return -EINVAL; @@ -557,6 +562,11 @@ int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc) video->cur_ival = uvc_default_frame_interval(video); + uvc_fill_streaming_control(uvc, &video->probe, iframe, iformat, + video->cur_ival); + uvc_fill_streaming_control(uvc, &video->commit, iframe, iformat, + video->cur_ival); + /* Initialize the video buffers queue. */ uvcg_queue_init(&video->queue, uvc->v4l2_dev.dev->parent, V4L2_BUF_TYPE_VIDEO_OUTPUT, &video->mutex);