linux_dsm_epyc7002/drivers/usb/gadget/uvc_v4l2.c
Bhupesh Sharma d692522577 usb: gadget/uvc: Port UVC webcam gadget to use videobuf2 framework
This patch reworks the videobuffer management logic present in the UVC
webcam gadget and ports it to use the "more apt" videobuf2 framework for
video buffer management.

To support routing video data captured from a real V4L2 video capture
device with a "zero copy" operation on videobuffers (as they pass from
the V4L2 domain to UVC domain via a user-space application), we need to
support USER_PTR IO method at the UVC gadget side.

So the V4L2 capture device driver can still continue to use MMAP IO
method and now the user-space application can just pass a pointer to the
video buffers being dequeued from the V4L2 device side while queueing
them at the UVC gadget end. This ensures that we have a "zero-copy"
design as the videobuffers pass from the V4L2 capture device to the UVC
gadget.

Note that there will still be a need to apply UVC specific payload
headers on top of each UVC payload data, which will still require a copy
operation to be performed in the 'encode' routines of the UVC gadget.

This patch also addresses one issue found out while porting the UVC
gadget to videobuf2 framework:
	- In case the usb requests queued by the gadget get completed
	  with a status of -ESHUTDOWN (disconnected from host),
	  the queue of videobuf2 should be cancelled to ensure that the
	  application space daemon is not left in a state waiting for
	  a vb2 to be successfully absorbed at the USB side.

Signed-off-by: Bhupesh Sharma <bhupesh.sharma@st.com>
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
2013-04-02 11:42:48 +03:00

351 lines
7.8 KiB
C

/*
* uvc_v4l2.c -- USB Video Class Gadget driver
*
* Copyright (C) 2009-2010
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/videodev2.h>
#include <linux/vmalloc.h>
#include <linux/wait.h>
#include <media/v4l2-dev.h>
#include <media/v4l2-event.h>
#include <media/v4l2-ioctl.h>
#include "uvc.h"
#include "uvc_queue.h"
/* --------------------------------------------------------------------------
* 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);
}
/* --------------------------------------------------------------------------
* V4L2
*/
struct uvc_format
{
u8 bpp;
u32 fcc;
};
static struct uvc_format uvc_formats[] = {
{ 16, V4L2_PIX_FMT_YUYV },
{ 0, V4L2_PIX_FMT_MJPEG },
};
static int
uvc_v4l2_get_format(struct uvc_video *video, struct v4l2_format *fmt)
{
fmt->fmt.pix.pixelformat = video->fcc;
fmt->fmt.pix.width = video->width;
fmt->fmt.pix.height = video->height;
fmt->fmt.pix.field = V4L2_FIELD_NONE;
fmt->fmt.pix.bytesperline = video->bpp * video->width / 8;
fmt->fmt.pix.sizeimage = video->imagesize;
fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
fmt->fmt.pix.priv = 0;
return 0;
}
static int
uvc_v4l2_set_format(struct uvc_video *video, struct v4l2_format *fmt)
{
struct uvc_format *format;
unsigned int imagesize;
unsigned int bpl;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(uvc_formats); ++i) {
format = &uvc_formats[i];
if (format->fcc == fmt->fmt.pix.pixelformat)
break;
}
if (i == ARRAY_SIZE(uvc_formats)) {
printk(KERN_INFO "Unsupported format 0x%08x.\n",
fmt->fmt.pix.pixelformat);
return -EINVAL;
}
bpl = format->bpp * fmt->fmt.pix.width / 8;
imagesize = bpl ? bpl * fmt->fmt.pix.height : fmt->fmt.pix.sizeimage;
video->fcc = format->fcc;
video->bpp = format->bpp;
video->width = fmt->fmt.pix.width;
video->height = fmt->fmt.pix.height;
video->imagesize = imagesize;
fmt->fmt.pix.field = V4L2_FIELD_NONE;
fmt->fmt.pix.bytesperline = bpl;
fmt->fmt.pix.sizeimage = imagesize;
fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
fmt->fmt.pix.priv = 0;
return 0;
}
static int
uvc_v4l2_open(struct file *file)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvc_file_handle *handle;
handle = kzalloc(sizeof(*handle), GFP_KERNEL);
if (handle == NULL)
return -ENOMEM;
v4l2_fh_init(&handle->vfh, vdev);
v4l2_fh_add(&handle->vfh);
handle->device = &uvc->video;
file->private_data = &handle->vfh;
uvc_function_connect(uvc);
return 0;
}
static int
uvc_v4l2_release(struct file *file)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvc_file_handle *handle = to_uvc_file_handle(file->private_data);
struct uvc_video *video = handle->device;
uvc_function_disconnect(uvc);
uvc_video_enable(video, 0);
uvc_free_buffers(&video->queue);
file->private_data = NULL;
v4l2_fh_del(&handle->vfh);
v4l2_fh_exit(&handle->vfh);
kfree(handle);
return 0;
}
static long
uvc_v4l2_do_ioctl(struct file *file, unsigned int cmd, void *arg)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvc_file_handle *handle = to_uvc_file_handle(file->private_data);
struct usb_composite_dev *cdev = uvc->func.config->cdev;
struct uvc_video *video = &uvc->video;
int ret = 0;
switch (cmd) {
/* Query capabilities */
case VIDIOC_QUERYCAP:
{
struct v4l2_capability *cap = arg;
memset(cap, 0, sizeof *cap);
strlcpy(cap->driver, "g_uvc", sizeof(cap->driver));
strlcpy(cap->card, cdev->gadget->name, sizeof(cap->card));
strlcpy(cap->bus_info, dev_name(&cdev->gadget->dev),
sizeof cap->bus_info);
cap->version = DRIVER_VERSION_NUMBER;
cap->capabilities = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;
break;
}
/* Get & Set format */
case VIDIOC_G_FMT:
{
struct v4l2_format *fmt = arg;
if (fmt->type != video->queue.queue.type)
return -EINVAL;
return uvc_v4l2_get_format(video, fmt);
}
case VIDIOC_S_FMT:
{
struct v4l2_format *fmt = arg;
if (fmt->type != video->queue.queue.type)
return -EINVAL;
return uvc_v4l2_set_format(video, fmt);
}
/* Buffers & streaming */
case VIDIOC_REQBUFS:
{
struct v4l2_requestbuffers *rb = arg;
if (rb->type != video->queue.queue.type)
return -EINVAL;
ret = uvc_alloc_buffers(&video->queue, rb);
if (ret < 0)
return ret;
ret = 0;
break;
}
case VIDIOC_QUERYBUF:
{
struct v4l2_buffer *buf = arg;
return uvc_query_buffer(&video->queue, buf);
}
case VIDIOC_QBUF:
if ((ret = uvc_queue_buffer(&video->queue, arg)) < 0)
return ret;
return uvc_video_pump(video);
case VIDIOC_DQBUF:
return uvc_dequeue_buffer(&video->queue, arg,
file->f_flags & O_NONBLOCK);
case VIDIOC_STREAMON:
{
int *type = arg;
if (*type != video->queue.queue.type)
return -EINVAL;
/* Enable UVC video. */
ret = uvc_video_enable(video, 1);
if (ret < 0)
return ret;
/*
* Complete the alternate setting selection setup phase now that
* userspace is ready to provide video frames.
*/
uvc_function_setup_continue(uvc);
uvc->state = UVC_STATE_STREAMING;
return 0;
}
case VIDIOC_STREAMOFF:
{
int *type = arg;
if (*type != video->queue.queue.type)
return -EINVAL;
return uvc_video_enable(video, 0);
}
/* Events */
case VIDIOC_DQEVENT:
{
struct v4l2_event *event = arg;
ret = v4l2_event_dequeue(&handle->vfh, event,
file->f_flags & O_NONBLOCK);
if (ret == 0 && event->type == UVC_EVENT_SETUP) {
struct uvc_event *uvc_event = (void *)&event->u.data;
/* Tell the complete callback to generate an event for
* the next request that will be enqueued by
* uvc_event_write.
*/
uvc->event_setup_out =
!(uvc_event->req.bRequestType & USB_DIR_IN);
uvc->event_length = uvc_event->req.wLength;
}
return ret;
}
case VIDIOC_SUBSCRIBE_EVENT:
{
struct v4l2_event_subscription *sub = arg;
if (sub->type < UVC_EVENT_FIRST || sub->type > UVC_EVENT_LAST)
return -EINVAL;
return v4l2_event_subscribe(&handle->vfh, arg, 2, NULL);
}
case VIDIOC_UNSUBSCRIBE_EVENT:
return v4l2_event_unsubscribe(&handle->vfh, arg);
case UVCIOC_SEND_RESPONSE:
ret = uvc_send_response(uvc, arg);
break;
default:
return -ENOIOCTLCMD;
}
return ret;
}
static long
uvc_v4l2_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
return video_usercopy(file, cmd, arg, uvc_v4l2_do_ioctl);
}
static int
uvc_v4l2_mmap(struct file *file, struct vm_area_struct *vma)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
return uvc_queue_mmap(&uvc->video.queue, vma);
}
static unsigned int
uvc_v4l2_poll(struct file *file, poll_table *wait)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
return uvc_queue_poll(&uvc->video.queue, file, wait);
}
static struct v4l2_file_operations uvc_v4l2_fops = {
.owner = THIS_MODULE,
.open = uvc_v4l2_open,
.release = uvc_v4l2_release,
.ioctl = uvc_v4l2_ioctl,
.mmap = uvc_v4l2_mmap,
.poll = uvc_v4l2_poll,
};