summaryrefslogtreecommitdiff
path: root/drivers/usb/gadget/function/uvc_video.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/gadget/function/uvc_video.c')
-rw-r--r--drivers/usb/gadget/function/uvc_video.c551
1 files changed, 448 insertions, 103 deletions
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 7f59a0c47402..fb77b0b21790 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -12,13 +12,14 @@
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
#include <linux/usb/video.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#include <media/v4l2-dev.h>
#include "uvc.h"
#include "uvc_queue.h"
#include "uvc_video.h"
+#include "uvc_trace.h"
/* --------------------------------------------------------------------------
* Video codecs
@@ -35,6 +36,9 @@ uvc_video_encode_header(struct uvc_video *video, struct uvc_buffer *buf,
data[1] = UVC_STREAM_EOH | video->fid;
+ if (video->queue.flags & UVC_QUEUE_DROP_INCOMPLETE)
+ data[1] |= UVC_STREAM_ERR;
+
if (video->queue.buf_used == 0 && ts.tv_sec) {
/* dwClockFrequency is 48 MHz */
u32 pts = ((u64)ts.tv_sec * USEC_PER_SEC + ts.tv_nsec / NSEC_PER_USEC) * 48;
@@ -75,7 +79,7 @@ uvc_video_encode_data(struct uvc_video *video, struct uvc_buffer *buf,
/* Copy video data to the USB buffer. */
mem = buf->mem + queue->buf_used;
- nbytes = min((unsigned int)len, buf->bytesused - queue->buf_used);
+ nbytes = min_t(unsigned int, len, buf->bytesused - queue->buf_used);
memcpy(data, mem, nbytes);
queue->buf_used += nbytes;
@@ -88,6 +92,7 @@ uvc_video_encode_bulk(struct usb_request *req, struct uvc_video *video,
struct uvc_buffer *buf)
{
void *mem = req->buf;
+ struct uvc_request *ureq = req->context;
int len = video->req_size;
int ret;
@@ -100,7 +105,7 @@ uvc_video_encode_bulk(struct usb_request *req, struct uvc_video *video,
}
/* Process video data. */
- len = min((int)(video->max_payload_size - video->payload_size), len);
+ len = min_t(int, video->max_payload_size - video->payload_size, len);
ret = uvc_video_encode_data(video, buf, mem, len);
video->payload_size += ret;
@@ -112,13 +117,15 @@ uvc_video_encode_bulk(struct usb_request *req, struct uvc_video *video,
if (buf->bytesused == video->queue.buf_used) {
video->queue.buf_used = 0;
buf->state = UVC_BUF_STATE_DONE;
- uvcg_queue_next_buffer(&video->queue, buf);
+ list_del(&buf->queue);
video->fid ^= UVC_STREAM_FID;
+ ureq->last_buf = buf;
video->payload_size = 0;
}
if (video->payload_size == video->max_payload_size ||
+ video->queue.flags & UVC_QUEUE_DROP_INCOMPLETE ||
buf->bytesused == video->queue.buf_used)
video->payload_size = 0;
}
@@ -130,7 +137,7 @@ uvc_video_encode_isoc_sg(struct usb_request *req, struct uvc_video *video,
unsigned int pending = buf->bytesused - video->queue.buf_used;
struct uvc_request *ureq = req->context;
struct scatterlist *sg, *iter;
- unsigned int len = video->req_size;
+ unsigned int len = buf->req_payload_size;
unsigned int sg_left, part = 0;
unsigned int i;
int header_len;
@@ -140,24 +147,24 @@ uvc_video_encode_isoc_sg(struct usb_request *req, struct uvc_video *video,
/* Init the header. */
header_len = uvc_video_encode_header(video, buf, ureq->header,
- video->req_size);
+ buf->req_payload_size);
sg_set_buf(sg, ureq->header, header_len);
len -= header_len;
if (pending <= len)
len = pending;
- req->length = (len == pending) ?
- len + header_len : video->req_size;
+ req->length = (len == pending) ? len + header_len :
+ buf->req_payload_size;
/* Init the pending sgs with payload */
sg = sg_next(sg);
for_each_sg(sg, iter, ureq->sgt.nents - 1, i) {
- if (!len || !buf->sg)
+ if (!len || !buf->sg || !buf->sg->length)
break;
- sg_left = sg_dma_len(buf->sg) - buf->offset;
+ sg_left = buf->sg->length - buf->offset;
part = min_t(unsigned int, len, sg_left);
sg_set_page(iter, sg_page(buf->sg), part, buf->offset);
@@ -179,12 +186,14 @@ uvc_video_encode_isoc_sg(struct usb_request *req, struct uvc_video *video,
req->length -= len;
video->queue.buf_used += req->length - header_len;
- if (buf->bytesused == video->queue.buf_used || !buf->sg) {
+ if (buf->bytesused == video->queue.buf_used || !buf->sg ||
+ video->queue.flags & UVC_QUEUE_DROP_INCOMPLETE) {
video->queue.buf_used = 0;
buf->state = UVC_BUF_STATE_DONE;
buf->offset = 0;
- uvcg_queue_next_buffer(&video->queue, buf);
+ list_del(&buf->queue);
video->fid ^= UVC_STREAM_FID;
+ ureq->last_buf = buf;
}
}
@@ -193,7 +202,8 @@ 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;
+ struct uvc_request *ureq = req->context;
+ int len = buf->req_payload_size;
int ret;
/* Add the header. */
@@ -205,13 +215,15 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
ret = uvc_video_encode_data(video, buf, mem, len);
len -= ret;
- req->length = video->req_size - len;
+ req->length = buf->req_payload_size - len;
- if (buf->bytesused == video->queue.buf_used) {
+ if (buf->bytesused == video->queue.buf_used ||
+ video->queue.flags & UVC_QUEUE_DROP_INCOMPLETE) {
video->queue.buf_used = 0;
buf->state = UVC_BUF_STATE_DONE;
- uvcg_queue_next_buffer(&video->queue, buf);
+ list_del(&buf->queue);
video->fid ^= UVC_STREAM_FID;
+ ureq->last_buf = buf;
}
}
@@ -219,6 +231,28 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
* Request handling
*/
+/*
+ * Callers must take care to hold req_lock when this function may be called
+ * from multiple threads. For example, when frames are streaming to the host.
+ */
+static void
+uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
+{
+ sg_free_table(&ureq->sgt);
+ if (ureq->req && ep) {
+ usb_ep_free_request(ep, ureq->req);
+ ureq->req = NULL;
+ }
+
+ kfree(ureq->req_buffer);
+ ureq->req_buffer = NULL;
+
+ if (!list_empty(&ureq->list))
+ list_del_init(&ureq->list);
+
+ kfree(ureq);
+}
+
static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
{
int ret;
@@ -236,112 +270,324 @@ static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
}
}
+ atomic_inc(&video->queued);
+
+ trace_uvcg_video_queue(req, atomic_read(&video->queued));
+
return ret;
}
+/* This function must be called with video->req_lock held. */
+static int uvcg_video_usb_req_queue(struct uvc_video *video,
+ struct usb_request *req, bool queue_to_ep)
+{
+ bool is_bulk = video->max_payload_size;
+ struct list_head *list = NULL;
+
+ if (!video->is_enabled)
+ return -ENODEV;
+
+ if (queue_to_ep) {
+ struct uvc_request *ureq = req->context;
+ /*
+ * With USB3 handling more requests at a higher speed, we can't
+ * afford to generate an interrupt for every request. Decide to
+ * interrupt:
+ *
+ * - When no more requests are available in the free queue, as
+ * this may be our last chance to refill the endpoint's
+ * request queue.
+ *
+ * - When this is request is the last request for the video
+ * buffer, as we want to start sending the next video buffer
+ * ASAP in case it doesn't get started already in the next
+ * iteration of this loop.
+ *
+ * - Four times over the length of the requests queue (as
+ * indicated by video->uvc_num_requests), as a trade-off
+ * between latency and interrupt load.
+ */
+ if (list_empty(&video->req_free) || ureq->last_buf ||
+ !(video->req_int_count %
+ min(DIV_ROUND_UP(video->uvc_num_requests, 4), UVCG_REQ_MAX_INT_COUNT))) {
+ video->req_int_count = 0;
+ req->no_interrupt = 0;
+ } else {
+ req->no_interrupt = 1;
+ }
+ video->req_int_count++;
+ return uvcg_video_ep_queue(video, req);
+ }
+ /*
+ * If we're not queuing to the ep, for isoc we're queuing
+ * to the req_ready list, otherwise req_free.
+ */
+ list = is_bulk ? &video->req_free : &video->req_ready;
+ list_add_tail(&req->list, list);
+ return 0;
+}
+
static void
uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
{
struct uvc_request *ureq = req->context;
struct uvc_video *video = ureq->video;
struct uvc_video_queue *queue = &video->queue;
- struct uvc_device *uvc = video->uvc;
+ struct uvc_buffer *last_buf;
unsigned long flags;
+ spin_lock_irqsave(&video->req_lock, flags);
+ atomic_dec(&video->queued);
+ if (!video->is_enabled) {
+ /*
+ * When is_enabled is false, uvcg_video_disable() ensures
+ * that in-flight uvc_buffers are returned, so we can
+ * safely call free_request without worrying about
+ * last_buf.
+ */
+ uvc_video_free_request(ureq, ep);
+ spin_unlock_irqrestore(&video->req_lock, flags);
+ return;
+ }
+
+ last_buf = ureq->last_buf;
+ ureq->last_buf = NULL;
+ spin_unlock_irqrestore(&video->req_lock, flags);
+
switch (req->status) {
case 0:
break;
+ case -EXDEV:
+ uvcg_dbg(&video->uvc->func, "VS request missed xfer.\n");
+ if (req->length != 0)
+ queue->flags |= UVC_QUEUE_DROP_INCOMPLETE;
+ break;
+
case -ESHUTDOWN: /* disconnect from host. */
uvcg_dbg(&video->uvc->func, "VS request cancelled.\n");
uvcg_queue_cancel(queue, 1);
break;
default:
- uvcg_info(&video->uvc->func,
+ uvcg_warn(&video->uvc->func,
"VS request completed with status %d.\n",
req->status);
uvcg_queue_cancel(queue, 0);
}
+ if (last_buf) {
+ spin_lock_irqsave(&queue->irqlock, flags);
+ uvcg_complete_buffer(queue, last_buf);
+ spin_unlock_irqrestore(&queue->irqlock, flags);
+ }
+
spin_lock_irqsave(&video->req_lock, flags);
+ /*
+ * Video stream might have been disabled while we were
+ * processing the current usb_request. So make sure
+ * we're still streaming before queueing the usb_request
+ * back to req_free
+ */
+ if (!video->is_enabled) {
+ uvc_video_free_request(ureq, ep);
+ spin_unlock_irqrestore(&video->req_lock, flags);
+ uvcg_queue_cancel(queue, 0);
+
+ return;
+ }
+
list_add_tail(&req->list, &video->req_free);
+ /*
+ * Queue work to the wq as well since it is possible that a
+ * buffer may not have been completely encoded with the set of
+ * in-flight usb requests for whih the complete callbacks are
+ * firing.
+ * In that case, if we do not queue work to the worker thread,
+ * the buffer will never be marked as complete - and therefore
+ * not be returned to userpsace. As a result,
+ * dequeue -> queue -> dequeue flow of uvc buffers will not
+ * happen. Since there are is a new free request wake up the pump.
+ */
+ queue_work(video->async_wq, &video->pump);
+
+ trace_uvcg_video_complete(req, atomic_read(&video->queued));
+
spin_unlock_irqrestore(&video->req_lock, flags);
- if (uvc->state == UVC_STATE_STREAMING)
- schedule_work(&video->pump);
+ kthread_queue_work(video->kworker, &video->hw_submit);
}
-static int
-uvc_video_free_requests(struct uvc_video *video)
+static void uvcg_video_hw_submit(struct kthread_work *work)
{
- unsigned int i;
+ struct uvc_video *video = container_of(work, struct uvc_video, hw_submit);
+ bool is_bulk = video->max_payload_size;
+ unsigned long flags;
+ struct usb_request *req;
+ int ret = 0;
- if (video->ureq) {
- for (i = 0; i < video->uvc_num_requests; ++i) {
- sg_free_table(&video->ureq[i].sgt);
+ while (true) {
+ if (!video->ep->enabled)
+ return;
+ spin_lock_irqsave(&video->req_lock, flags);
+ /*
+ * Here we check whether any request is available in the ready
+ * list. If it is, queue it to the ep and add the current
+ * usb_request to the req_free list - for video_pump to fill in.
+ * Otherwise, just use the current usb_request to queue a 0
+ * length request to the ep. Since we always add to the req_free
+ * list if we dequeue from the ready list, there will never
+ * be a situation where the req_free list is completely out of
+ * requests and cannot recover.
+ */
+ if (!list_empty(&video->req_ready)) {
+ req = list_first_entry(&video->req_ready,
+ struct usb_request, list);
+ } else {
+ if (list_empty(&video->req_free) ||
+ (atomic_read(&video->queued) > UVCG_REQ_MAX_ZERO_COUNT)) {
+ spin_unlock_irqrestore(&video->req_lock, flags);
- if (video->ureq[i].req) {
- usb_ep_free_request(video->ep, video->ureq[i].req);
- video->ureq[i].req = NULL;
+ return;
}
+ req = list_first_entry(&video->req_free, struct usb_request,
+ list);
+ req->length = 0;
+ }
+ list_del(&req->list);
+
+ /*
+ * Queue to the endpoint. The actual queueing to ep will
+ * only happen on one thread - the async_wq for bulk endpoints
+ * and this thread for isoc endpoints.
+ */
+ ret = uvcg_video_usb_req_queue(video, req, !is_bulk);
+ if (ret < 0) {
+ /*
+ * Endpoint error, but the stream is still enabled.
+ * Put request back in req_free for it to be cleaned
+ * up later.
+ */
+ list_add_tail(&req->list, &video->req_free);
+ /*
+ * There is a new free request - wake up the pump.
+ */
+ queue_work(video->async_wq, &video->pump);
- if (video->ureq[i].req_buffer) {
- kfree(video->ureq[i].req_buffer);
- video->ureq[i].req_buffer = NULL;
- }
}
- kfree(video->ureq);
- video->ureq = NULL;
+ spin_unlock_irqrestore(&video->req_lock, flags);
}
+}
+
+static int
+uvc_video_free_requests(struct uvc_video *video)
+{
+ struct uvc_request *ureq, *temp;
+ list_for_each_entry_safe(ureq, temp, &video->ureqs, list)
+ uvc_video_free_request(ureq, video->ep);
+
+ INIT_LIST_HEAD(&video->ureqs);
INIT_LIST_HEAD(&video->req_free);
- video->req_size = 0;
+ INIT_LIST_HEAD(&video->req_ready);
return 0;
}
+static void
+uvc_video_prep_requests(struct uvc_video *video)
+{
+ struct uvc_device *uvc = container_of(video, struct uvc_device, video);
+ struct usb_composite_dev *cdev = uvc->func.config->cdev;
+ unsigned int interval_duration = video->ep->desc->bInterval * 1250;
+ unsigned int max_req_size, req_size, header_size;
+ unsigned int nreq;
+
+ max_req_size = video->ep->maxpacket
+ * max_t(unsigned int, video->ep->maxburst, 1)
+ * (video->ep->mult);
+
+ if (!usb_endpoint_xfer_isoc(video->ep->desc)) {
+ video->req_size = max_req_size;
+ video->reqs_per_frame = video->uvc_num_requests =
+ DIV_ROUND_UP(video->imagesize, max_req_size);
+
+ return;
+ }
+
+ if (cdev->gadget->speed < USB_SPEED_HIGH)
+ interval_duration = video->ep->desc->bInterval * 10000;
+
+ nreq = DIV_ROUND_UP(video->interval, interval_duration);
+
+ header_size = nreq * UVCG_REQUEST_HEADER_LEN;
+
+ req_size = DIV_ROUND_UP(video->imagesize + header_size, nreq);
+
+ if (req_size > max_req_size) {
+ /* The prepared interval length and expected buffer size
+ * is not possible to stream with the currently configured
+ * isoc bandwidth. Fallback to the maximum.
+ */
+ req_size = max_req_size;
+ }
+ video->req_size = req_size;
+
+ /* We need to compensate the amount of requests to be
+ * allocated with the maximum amount of zero length requests.
+ * Since it is possible that hw_submit will initially
+ * enqueue some zero length requests and we then will not be
+ * able to fully encode one frame.
+ */
+ video->uvc_num_requests = nreq + UVCG_REQ_MAX_ZERO_COUNT;
+ video->reqs_per_frame = nreq;
+}
+
static int
uvc_video_alloc_requests(struct uvc_video *video)
{
- unsigned int req_size;
+ struct uvc_request *ureq;
unsigned int i;
int ret = -ENOMEM;
- BUG_ON(video->req_size);
+ /*
+ * calculate in uvc_video_prep_requests
+ * - video->uvc_num_requests
+ * - video->req_size
+ */
+ uvc_video_prep_requests(video);
- req_size = video->ep->maxpacket
- * max_t(unsigned int, video->ep->maxburst, 1)
- * (video->ep->mult);
+ for (i = 0; i < video->uvc_num_requests; i++) {
+ ureq = kzalloc(sizeof(struct uvc_request), GFP_KERNEL);
+ if (ureq == NULL)
+ goto error;
+
+ INIT_LIST_HEAD(&ureq->list);
- video->ureq = kcalloc(video->uvc_num_requests, sizeof(struct uvc_request), GFP_KERNEL);
- if (video->ureq == NULL)
- return -ENOMEM;
+ list_add_tail(&ureq->list, &video->ureqs);
- for (i = 0; i < video->uvc_num_requests; ++i) {
- video->ureq[i].req_buffer = kmalloc(req_size, GFP_KERNEL);
- if (video->ureq[i].req_buffer == NULL)
+ ureq->req_buffer = kmalloc(video->req_size, GFP_KERNEL);
+ if (ureq->req_buffer == NULL)
goto error;
- video->ureq[i].req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
- if (video->ureq[i].req == NULL)
+ ureq->req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
+ if (ureq->req == NULL)
goto error;
- video->ureq[i].req->buf = video->ureq[i].req_buffer;
- video->ureq[i].req->length = 0;
- video->ureq[i].req->complete = uvc_video_complete;
- video->ureq[i].req->context = &video->ureq[i];
- video->ureq[i].video = video;
+ ureq->req->buf = ureq->req_buffer;
+ ureq->req->length = 0;
+ ureq->req->complete = uvc_video_complete;
+ ureq->req->context = ureq;
+ ureq->video = video;
+ ureq->last_buf = NULL;
- list_add_tail(&video->ureq[i].req->list, &video->req_free);
+ list_add_tail(&ureq->req->list, &video->req_free);
/* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */
- sg_alloc_table(&video->ureq[i].sgt,
- DIV_ROUND_UP(req_size - UVCG_REQUEST_HEADER_LEN,
+ sg_alloc_table(&ureq->sgt,
+ DIV_ROUND_UP(video->req_size - UVCG_REQUEST_HEADER_LEN,
PAGE_SIZE) + 2, GFP_KERNEL);
}
- video->req_size = req_size;
-
return 0;
error:
@@ -363,17 +609,23 @@ static void uvcg_video_pump(struct work_struct *work)
{
struct uvc_video *video = container_of(work, struct uvc_video, pump);
struct uvc_video_queue *queue = &video->queue;
+ /* video->max_payload_size is only set when using bulk transfer */
+ bool is_bulk = video->max_payload_size;
struct usb_request *req = NULL;
struct uvc_buffer *buf;
unsigned long flags;
- int ret;
+ int ret = 0;
- while (video->ep->enabled) {
- /* Retrieve the first available USB request, protected by the
- * request lock.
+ while (true) {
+ if (!video->ep->enabled)
+ return;
+
+ /*
+ * Check is_enabled and retrieve the first available USB
+ * request, protected by the request lock.
*/
spin_lock_irqsave(&video->req_lock, flags);
- if (list_empty(&video->req_free)) {
+ if (!video->is_enabled || list_empty(&video->req_free)) {
spin_unlock_irqrestore(&video->req_lock, flags);
return;
}
@@ -382,57 +634,133 @@ static void uvcg_video_pump(struct work_struct *work)
list_del(&req->list);
spin_unlock_irqrestore(&video->req_lock, flags);
- /* Retrieve the first available video buffer and fill the
+ /*
+ * Retrieve the first available video buffer and fill the
* request, protected by the video queue irqlock.
*/
spin_lock_irqsave(&queue->irqlock, flags);
buf = uvcg_queue_head(queue);
- if (buf == NULL) {
+ if (!buf) {
+ /*
+ * Either the queue has been disconnected or no video buffer
+ * available for bulk transfer. Either way, stop processing
+ * further.
+ */
spin_unlock_irqrestore(&queue->irqlock, flags);
break;
}
video->encode(req, video, buf);
- /* With usb3 we have more requests. This will decrease the
- * interrupt load to a quarter but also catches the corner
- * cases, which needs to be handled */
- if (list_empty(&video->req_free) ||
- buf->state == UVC_BUF_STATE_DONE ||
- !(video->req_int_count %
- DIV_ROUND_UP(video->uvc_num_requests, 4))) {
- video->req_int_count = 0;
- req->no_interrupt = 0;
- } else {
- req->no_interrupt = 1;
- }
-
- /* Queue the USB request */
- ret = uvcg_video_ep_queue(video, req);
spin_unlock_irqrestore(&queue->irqlock, flags);
+ spin_lock_irqsave(&video->req_lock, flags);
+ /* For bulk end points we queue from the worker thread
+ * since we would preferably not want to wait on requests
+ * to be ready, in the uvcg_video_complete() handler.
+ * For isoc endpoints we add the request to the ready list
+ * and only queue it to the endpoint from the complete handler.
+ */
+ ret = uvcg_video_usb_req_queue(video, req, is_bulk);
+ spin_unlock_irqrestore(&video->req_lock, flags);
+
if (ret < 0) {
uvcg_queue_cancel(queue, 0);
break;
}
- video->req_int_count++;
+ }
+ spin_lock_irqsave(&video->req_lock, flags);
+ if (video->is_enabled)
+ list_add_tail(&req->list, &video->req_free);
+ else
+ uvc_video_free_request(req->context, video->ep);
+ spin_unlock_irqrestore(&video->req_lock, flags);
+}
+
+/*
+ * Disable the video stream
+ */
+int
+uvcg_video_disable(struct uvc_video *video)
+{
+ unsigned long flags;
+ struct list_head inflight_bufs;
+ struct usb_request *req, *temp;
+ struct uvc_buffer *buf, *btemp;
+ struct uvc_request *ureq, *utemp;
+
+ if (video->ep == NULL) {
+ uvcg_info(&video->uvc->func,
+ "Video disable failed, device is uninitialized.\n");
+ return -ENODEV;
}
- if (!req)
- return;
+ INIT_LIST_HEAD(&inflight_bufs);
+ spin_lock_irqsave(&video->req_lock, flags);
+ video->is_enabled = false;
+
+ /*
+ * Remove any in-flight buffers from the uvc_requests
+ * because we want to return them before cancelling the
+ * queue. This ensures that we aren't stuck waiting for
+ * all complete callbacks to come through before disabling
+ * vb2 queue.
+ */
+ list_for_each_entry(ureq, &video->ureqs, list) {
+ if (ureq->last_buf) {
+ list_add_tail(&ureq->last_buf->queue, &inflight_bufs);
+ ureq->last_buf = NULL;
+ }
+ }
+ spin_unlock_irqrestore(&video->req_lock, flags);
+
+ cancel_work_sync(&video->pump);
+ uvcg_queue_cancel(&video->queue, 0);
spin_lock_irqsave(&video->req_lock, flags);
- list_add_tail(&req->list, &video->req_free);
+ /*
+ * Remove all uvc_requests from ureqs with list_del_init
+ * This lets uvc_video_free_request correctly identify
+ * if the uvc_request is attached to a list or not when freeing
+ * memory.
+ */
+ list_for_each_entry_safe(ureq, utemp, &video->ureqs, list)
+ list_del_init(&ureq->list);
+
+ list_for_each_entry_safe(req, temp, &video->req_free, list) {
+ list_del(&req->list);
+ uvc_video_free_request(req->context, video->ep);
+ }
+
+ list_for_each_entry_safe(req, temp, &video->req_ready, list) {
+ list_del(&req->list);
+ uvc_video_free_request(req->context, video->ep);
+ }
+
+ INIT_LIST_HEAD(&video->ureqs);
+ INIT_LIST_HEAD(&video->req_free);
+ INIT_LIST_HEAD(&video->req_ready);
spin_unlock_irqrestore(&video->req_lock, flags);
- return;
+
+ /*
+ * Return all the video buffers before disabling the queue.
+ */
+ spin_lock_irqsave(&video->queue.irqlock, flags);
+ list_for_each_entry_safe(buf, btemp, &inflight_bufs, queue) {
+ list_del(&buf->queue);
+ uvcg_complete_buffer(&video->queue, buf);
+ }
+ spin_unlock_irqrestore(&video->queue.irqlock, flags);
+
+ uvcg_queue_enable(&video->queue, 0);
+ return 0;
}
/*
- * Enable or disable the video stream.
+ * Enable the video stream.
*/
-int uvcg_video_enable(struct uvc_video *video, int enable)
+int uvcg_video_enable(struct uvc_video *video)
{
- unsigned int i;
int ret;
if (video->ep == NULL) {
@@ -441,18 +769,13 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
return -ENODEV;
}
- if (!enable) {
- cancel_work_sync(&video->pump);
- uvcg_queue_cancel(&video->queue, 0);
-
- for (i = 0; i < video->uvc_num_requests; ++i)
- if (video->ureq && video->ureq[i].req)
- usb_ep_dequeue(video->ep, video->ureq[i].req);
-
- uvc_video_free_requests(video);
- uvcg_queue_enable(&video->queue, 0);
- return 0;
- }
+ /*
+ * Safe to access request related fields without req_lock because
+ * this is the only thread currently active, and no other
+ * request handling thread will become active until this function
+ * returns.
+ */
+ video->is_enabled = true;
if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
return ret;
@@ -469,7 +792,10 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
video->req_int_count = 0;
- schedule_work(&video->pump);
+ atomic_set(&video->queued, 0);
+
+ kthread_queue_work(video->kworker, &video->hw_submit);
+ queue_work(video->async_wq, &video->pump);
return ret;
}
@@ -479,20 +805,39 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
*/
int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
{
+ video->is_enabled = false;
+ INIT_LIST_HEAD(&video->ureqs);
INIT_LIST_HEAD(&video->req_free);
+ INIT_LIST_HEAD(&video->req_ready);
spin_lock_init(&video->req_lock);
INIT_WORK(&video->pump, uvcg_video_pump);
+ /* Allocate a work queue for asynchronous video pump handler. */
+ video->async_wq = alloc_workqueue("uvcgadget", WQ_UNBOUND | WQ_HIGHPRI, 0);
+ if (!video->async_wq)
+ return -EINVAL;
+
+ /* Allocate a kthread for asynchronous hw submit handler. */
+ video->kworker = kthread_run_worker(0, "UVCG");
+ if (IS_ERR(video->kworker)) {
+ uvcg_err(&video->uvc->func, "failed to create UVCG kworker\n");
+ return PTR_ERR(video->kworker);
+ }
+
+ kthread_init_work(&video->hw_submit, uvcg_video_hw_submit);
+
+ sched_set_fifo(video->kworker->task);
+
video->uvc = uvc;
video->fcc = V4L2_PIX_FMT_YUYV;
video->bpp = 16;
video->width = 320;
video->height = 240;
video->imagesize = 320 * 240 * 2;
+ video->interval = 666666;
/* Initialize the video buffers queue. */
uvcg_queue_init(&video->queue, uvc->v4l2_dev.dev->parent,
V4L2_BUF_TYPE_VIDEO_OUTPUT, &video->mutex);
return 0;
}
-