usb: gadget: uvc: add v4l2 enumeration api calls
authorMichael Grzeschik <m.grzeschik@pengutronix.de>
Fri, 9 Sep 2022 22:13:34 +0000 (00:13 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 22 Sep 2022 13:52:30 +0000 (15:52 +0200)
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 <m.grzeschik@pengutronix.de>
Link: https://lore.kernel.org/r/20220909221335.15033-4-m.grzeschik@pengutronix.de
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/gadget/function/f_uvc.c
drivers/usb/gadget/function/uvc.h
drivers/usb/gadget/function/uvc_v4l2.c

index 09961f4..e6948cf 100644 (file)
@@ -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);
 }
@@ -945,6 +946,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)
@@ -977,6 +979,29 @@ 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)
+               goto err_config;
+
+       header = config_group_find_item(to_config_group(streaming), "header");
+       config_item_put(streaming);
+       if (!header)
+               goto err_config;
+
+       h = config_group_find_item(to_config_group(header), "h");
+       config_item_put(header);
+       if (!h)
+               goto err_config;
+
+       uvc->header = to_uvcg_streaming_header(h);
+       config_item_put(h);
+       if (!uvc->header->linked) {
+               mutex_unlock(&opts->lock);
+               kfree(uvc);
+               return ERR_PTR(-EBUSY);
+       }
+
        ++opts->refcnt;
        mutex_unlock(&opts->lock);
 
@@ -992,6 +1017,11 @@ static struct usb_function *uvc_alloc(struct usb_function_instance *fi)
        uvc->func.bind_deactivated = true;
 
        return &uvc->func;
+
+err_config:
+       mutex_unlock(&opts->lock);
+       kfree(uvc);
+       return ERR_PTR(-ENOENT);
 }
 
 DECLARE_USB_FUNCTION_INIT(uvc, uvc_alloc_inst, uvc_alloc);
index 1a31e6c..40226b1 100644 (file)
@@ -134,6 +134,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;
index d6dbf9b..417655e 100644 (file)
 #include <media/v4l2-dev.h>
 #include <media/v4l2-event.h>
 #include <media/v4l2-ioctl.h>
+#include <media/v4l2-uvc.h>
 
 #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;
+}
+
+static 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;
+}
+
+static 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
@@ -135,6 +215,99 @@ uvc_v4l2_set_format(struct file *file, void *fh, struct v4l2_format *fmt)
 }
 
 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)
 {
        struct video_device *vdev = video_devdata(file);
@@ -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,