Skip to content

Commit

Permalink
usb: gadget: uvc: add v4l2 enumeration api calls
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Michael Grzeschik authored and Greg Kroah-Hartman committed Sep 22, 2022
1 parent 6b028df commit 588b9e8
Show file tree
Hide file tree
Showing 3 changed files with 208 additions and 0 deletions.
30 changes: 30 additions & 0 deletions drivers/usb/gadget/function/f_uvc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);

Expand All @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions drivers/usb/gadget/function/uvc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
176 changes: 176 additions & 0 deletions drivers/usb/gadget/function/uvc_v4l2.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,92 @@
#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
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit 588b9e8

Please sign in to comment.