mirror of
https://github.com/torvalds/linux.git
synced 2024-12-23 11:21:33 +00:00
5d9955f8a9
The UVC gadget driver borrowed code from the UVC host driver without changing the symbol names. This results in a namespace clash with multiple definitions of several symbols when compiling both drivers in the kernel. Make all generic UVC functions and variables static in the UVC gadget driver, as the symbols are not referenced outside of the gadget driver. Rename the uvc_trace_param global variable to uvc_gadget_trace_param. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
387 lines
9.4 KiB
C
387 lines
9.4 KiB
C
/*
|
|
* uvc_video.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/usb/ch9.h>
|
|
#include <linux/usb/gadget.h>
|
|
|
|
#include <media/v4l2-dev.h>
|
|
|
|
#include "uvc.h"
|
|
#include "uvc_queue.h"
|
|
|
|
/* --------------------------------------------------------------------------
|
|
* Video codecs
|
|
*/
|
|
|
|
static int
|
|
uvc_video_encode_header(struct uvc_video *video, struct uvc_buffer *buf,
|
|
u8 *data, int len)
|
|
{
|
|
data[0] = 2;
|
|
data[1] = UVC_STREAM_EOH | video->fid;
|
|
|
|
if (buf->buf.bytesused - video->queue.buf_used <= len - 2)
|
|
data[1] |= UVC_STREAM_EOF;
|
|
|
|
return 2;
|
|
}
|
|
|
|
static int
|
|
uvc_video_encode_data(struct uvc_video *video, struct uvc_buffer *buf,
|
|
u8 *data, int len)
|
|
{
|
|
struct uvc_video_queue *queue = &video->queue;
|
|
unsigned int nbytes;
|
|
void *mem;
|
|
|
|
/* Copy video data to the USB buffer. */
|
|
mem = queue->mem + buf->buf.m.offset + queue->buf_used;
|
|
nbytes = min((unsigned int)len, buf->buf.bytesused - queue->buf_used);
|
|
|
|
memcpy(data, mem, nbytes);
|
|
queue->buf_used += nbytes;
|
|
|
|
return nbytes;
|
|
}
|
|
|
|
static void
|
|
uvc_video_encode_bulk(struct usb_request *req, struct uvc_video *video,
|
|
struct uvc_buffer *buf)
|
|
{
|
|
void *mem = req->buf;
|
|
int len = video->req_size;
|
|
int ret;
|
|
|
|
/* Add a header at the beginning of the payload. */
|
|
if (video->payload_size == 0) {
|
|
ret = uvc_video_encode_header(video, buf, mem, len);
|
|
video->payload_size += ret;
|
|
mem += ret;
|
|
len -= ret;
|
|
}
|
|
|
|
/* Process video data. */
|
|
len = min((int)(video->max_payload_size - video->payload_size), len);
|
|
ret = uvc_video_encode_data(video, buf, mem, len);
|
|
|
|
video->payload_size += ret;
|
|
len -= ret;
|
|
|
|
req->length = video->req_size - len;
|
|
req->zero = video->payload_size == video->max_payload_size;
|
|
|
|
if (buf->buf.bytesused == video->queue.buf_used) {
|
|
video->queue.buf_used = 0;
|
|
buf->state = UVC_BUF_STATE_DONE;
|
|
uvc_queue_next_buffer(&video->queue, buf);
|
|
video->fid ^= UVC_STREAM_FID;
|
|
|
|
video->payload_size = 0;
|
|
}
|
|
|
|
if (video->payload_size == video->max_payload_size ||
|
|
buf->buf.bytesused == video->queue.buf_used)
|
|
video->payload_size = 0;
|
|
}
|
|
|
|
static void
|
|
uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
|
|
struct uvc_buffer *buf)
|
|
{
|
|
void *mem = req->buf;
|
|
int len = video->req_size;
|
|
int ret;
|
|
|
|
/* Add the header. */
|
|
ret = uvc_video_encode_header(video, buf, mem, len);
|
|
mem += ret;
|
|
len -= ret;
|
|
|
|
/* Process video data. */
|
|
ret = uvc_video_encode_data(video, buf, mem, len);
|
|
len -= ret;
|
|
|
|
req->length = video->req_size - len;
|
|
|
|
if (buf->buf.bytesused == video->queue.buf_used) {
|
|
video->queue.buf_used = 0;
|
|
buf->state = UVC_BUF_STATE_DONE;
|
|
uvc_queue_next_buffer(&video->queue, buf);
|
|
video->fid ^= UVC_STREAM_FID;
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------
|
|
* Request handling
|
|
*/
|
|
|
|
/*
|
|
* I somehow feel that synchronisation won't be easy to achieve here. We have
|
|
* three events that control USB requests submission:
|
|
*
|
|
* - USB request completion: the completion handler will resubmit the request
|
|
* if a video buffer is available.
|
|
*
|
|
* - USB interface setting selection: in response to a SET_INTERFACE request,
|
|
* the handler will start streaming if a video buffer is available and if
|
|
* video is not currently streaming.
|
|
*
|
|
* - V4L2 buffer queueing: the driver will start streaming if video is not
|
|
* currently streaming.
|
|
*
|
|
* Race conditions between those 3 events might lead to deadlocks or other
|
|
* nasty side effects.
|
|
*
|
|
* The "video currently streaming" condition can't be detected by the irqqueue
|
|
* being empty, as a request can still be in flight. A separate "queue paused"
|
|
* flag is thus needed.
|
|
*
|
|
* The paused flag will be set when we try to retrieve the irqqueue head if the
|
|
* queue is empty, and cleared when we queue a buffer.
|
|
*
|
|
* The USB request completion handler will get the buffer at the irqqueue head
|
|
* under protection of the queue spinlock. If the queue is empty, the streaming
|
|
* paused flag will be set. Right after releasing the spinlock a userspace
|
|
* application can queue a buffer. The flag will then cleared, and the ioctl
|
|
* handler will restart the video stream.
|
|
*/
|
|
static void
|
|
uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
|
|
{
|
|
struct uvc_video *video = req->context;
|
|
struct uvc_buffer *buf;
|
|
unsigned long flags;
|
|
int ret;
|
|
|
|
switch (req->status) {
|
|
case 0:
|
|
break;
|
|
|
|
case -ESHUTDOWN:
|
|
printk(KERN_INFO "VS request cancelled.\n");
|
|
goto requeue;
|
|
|
|
default:
|
|
printk(KERN_INFO "VS request completed with status %d.\n",
|
|
req->status);
|
|
goto requeue;
|
|
}
|
|
|
|
spin_lock_irqsave(&video->queue.irqlock, flags);
|
|
buf = uvc_queue_head(&video->queue);
|
|
if (buf == NULL) {
|
|
spin_unlock_irqrestore(&video->queue.irqlock, flags);
|
|
goto requeue;
|
|
}
|
|
|
|
video->encode(req, video, buf);
|
|
|
|
if ((ret = usb_ep_queue(ep, req, GFP_ATOMIC)) < 0) {
|
|
printk(KERN_INFO "Failed to queue request (%d).\n", ret);
|
|
usb_ep_set_halt(ep);
|
|
spin_unlock_irqrestore(&video->queue.irqlock, flags);
|
|
goto requeue;
|
|
}
|
|
spin_unlock_irqrestore(&video->queue.irqlock, flags);
|
|
|
|
return;
|
|
|
|
requeue:
|
|
spin_lock_irqsave(&video->req_lock, flags);
|
|
list_add_tail(&req->list, &video->req_free);
|
|
spin_unlock_irqrestore(&video->req_lock, flags);
|
|
}
|
|
|
|
static int
|
|
uvc_video_free_requests(struct uvc_video *video)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < UVC_NUM_REQUESTS; ++i) {
|
|
if (video->req[i]) {
|
|
usb_ep_free_request(video->ep, video->req[i]);
|
|
video->req[i] = NULL;
|
|
}
|
|
|
|
if (video->req_buffer[i]) {
|
|
kfree(video->req_buffer[i]);
|
|
video->req_buffer[i] = NULL;
|
|
}
|
|
}
|
|
|
|
INIT_LIST_HEAD(&video->req_free);
|
|
video->req_size = 0;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
uvc_video_alloc_requests(struct uvc_video *video)
|
|
{
|
|
unsigned int i;
|
|
int ret = -ENOMEM;
|
|
|
|
BUG_ON(video->req_size);
|
|
|
|
for (i = 0; i < UVC_NUM_REQUESTS; ++i) {
|
|
video->req_buffer[i] = kmalloc(video->ep->maxpacket, GFP_KERNEL);
|
|
if (video->req_buffer[i] == NULL)
|
|
goto error;
|
|
|
|
video->req[i] = usb_ep_alloc_request(video->ep, GFP_KERNEL);
|
|
if (video->req[i] == NULL)
|
|
goto error;
|
|
|
|
video->req[i]->buf = video->req_buffer[i];
|
|
video->req[i]->length = 0;
|
|
video->req[i]->dma = DMA_ADDR_INVALID;
|
|
video->req[i]->complete = uvc_video_complete;
|
|
video->req[i]->context = video;
|
|
|
|
list_add_tail(&video->req[i]->list, &video->req_free);
|
|
}
|
|
|
|
video->req_size = video->ep->maxpacket;
|
|
return 0;
|
|
|
|
error:
|
|
uvc_video_free_requests(video);
|
|
return ret;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------
|
|
* Video streaming
|
|
*/
|
|
|
|
/*
|
|
* uvc_video_pump - Pump video data into the USB requests
|
|
*
|
|
* This function fills the available USB requests (listed in req_free) with
|
|
* video data from the queued buffers.
|
|
*/
|
|
static int
|
|
uvc_video_pump(struct uvc_video *video)
|
|
{
|
|
struct usb_request *req;
|
|
struct uvc_buffer *buf;
|
|
unsigned long flags;
|
|
int ret;
|
|
|
|
/* FIXME TODO Race between uvc_video_pump and requests completion
|
|
* handler ???
|
|
*/
|
|
|
|
while (1) {
|
|
/* Retrieve the first available USB request, protected by the
|
|
* request lock.
|
|
*/
|
|
spin_lock_irqsave(&video->req_lock, flags);
|
|
if (list_empty(&video->req_free)) {
|
|
spin_unlock_irqrestore(&video->req_lock, flags);
|
|
return 0;
|
|
}
|
|
req = list_first_entry(&video->req_free, struct usb_request,
|
|
list);
|
|
list_del(&req->list);
|
|
spin_unlock_irqrestore(&video->req_lock, flags);
|
|
|
|
/* Retrieve the first available video buffer and fill the
|
|
* request, protected by the video queue irqlock.
|
|
*/
|
|
spin_lock_irqsave(&video->queue.irqlock, flags);
|
|
buf = uvc_queue_head(&video->queue);
|
|
if (buf == NULL) {
|
|
spin_unlock_irqrestore(&video->queue.irqlock, flags);
|
|
break;
|
|
}
|
|
|
|
video->encode(req, video, buf);
|
|
|
|
/* Queue the USB request */
|
|
if ((ret = usb_ep_queue(video->ep, req, GFP_KERNEL)) < 0) {
|
|
printk(KERN_INFO "Failed to queue request (%d)\n", ret);
|
|
usb_ep_set_halt(video->ep);
|
|
spin_unlock_irqrestore(&video->queue.irqlock, flags);
|
|
break;
|
|
}
|
|
spin_unlock_irqrestore(&video->queue.irqlock, flags);
|
|
}
|
|
|
|
spin_lock_irqsave(&video->req_lock, flags);
|
|
list_add_tail(&req->list, &video->req_free);
|
|
spin_unlock_irqrestore(&video->req_lock, flags);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Enable or disable the video stream.
|
|
*/
|
|
static int
|
|
uvc_video_enable(struct uvc_video *video, int enable)
|
|
{
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
if (video->ep == NULL) {
|
|
printk(KERN_INFO "Video enable failed, device is "
|
|
"uninitialized.\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (!enable) {
|
|
for (i = 0; i < UVC_NUM_REQUESTS; ++i)
|
|
usb_ep_dequeue(video->ep, video->req[i]);
|
|
|
|
uvc_video_free_requests(video);
|
|
uvc_queue_enable(&video->queue, 0);
|
|
return 0;
|
|
}
|
|
|
|
if ((ret = uvc_queue_enable(&video->queue, 1)) < 0)
|
|
return ret;
|
|
|
|
if ((ret = uvc_video_alloc_requests(video)) < 0)
|
|
return ret;
|
|
|
|
if (video->max_payload_size) {
|
|
video->encode = uvc_video_encode_bulk;
|
|
video->payload_size = 0;
|
|
} else
|
|
video->encode = uvc_video_encode_isoc;
|
|
|
|
return uvc_video_pump(video);
|
|
}
|
|
|
|
/*
|
|
* Initialize the UVC video stream.
|
|
*/
|
|
static int
|
|
uvc_video_init(struct uvc_video *video)
|
|
{
|
|
INIT_LIST_HEAD(&video->req_free);
|
|
spin_lock_init(&video->req_lock);
|
|
|
|
video->fcc = V4L2_PIX_FMT_YUYV;
|
|
video->bpp = 16;
|
|
video->width = 320;
|
|
video->height = 240;
|
|
video->imagesize = 320 * 240 * 2;
|
|
|
|
/* Initialize the video buffers queue. */
|
|
uvc_queue_init(&video->queue, V4L2_BUF_TYPE_VIDEO_OUTPUT);
|
|
return 0;
|
|
}
|
|
|