summaryrefslogtreecommitdiffstats
path: root/drivers/usb/gadget/function/uvc_video.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-18 18:50:03 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-18 18:50:03 +0000
commit01a69402cf9d38ff180345d55c2ee51c7e89fbc7 (patch)
treeb406c5242a088c4f59c6e4b719b783f43aca6ae9 /drivers/usb/gadget/function/uvc_video.c
parentAdding upstream version 6.7.12. (diff)
downloadlinux-01a69402cf9d38ff180345d55c2ee51c7e89fbc7.tar.xz
linux-01a69402cf9d38ff180345d55c2ee51c7e89fbc7.zip
Adding upstream version 6.8.9.upstream/6.8.9
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/usb/gadget/function/uvc_video.c')
-rw-r--r--drivers/usb/gadget/function/uvc_video.c453
1 files changed, 344 insertions, 109 deletions
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 91af3b1ef0..53e4cd81ea 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -35,6 +35,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;
@@ -227,6 +230,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;
@@ -247,14 +272,127 @@ static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
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 %
+ DIV_ROUND_UP(video->uvc_num_requests, 4))) {
+ 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;
+}
+
+/*
+ * Must only be called from uvcg_video_enable - since after that we only want to
+ * queue requests to the endpoint from the uvc_video_complete complete handler.
+ * This function is needed in order to 'kick start' the flow of requests from
+ * gadget driver to the usb controller.
+ */
+static void uvc_video_ep_queue_initial_requests(struct uvc_video *video)
+{
+ struct usb_request *req = NULL;
+ unsigned long flags = 0;
+ unsigned int count = 0;
+ int ret = 0;
+
+ /*
+ * We only queue half of the free list since we still want to have
+ * some free usb_requests in the free list for the video_pump async_wq
+ * thread to encode uvc buffers into. Otherwise we could get into a
+ * situation where the free list does not have any usb requests to
+ * encode into - we always end up queueing 0 length requests to the
+ * end point.
+ */
+ unsigned int half_list_size = video->uvc_num_requests / 2;
+
+ spin_lock_irqsave(&video->req_lock, flags);
+ /*
+ * Take these requests off the free list and queue them all to the
+ * endpoint. Since we queue 0 length requests with the req_lock held,
+ * there isn't any 'data' race involved here with the complete handler.
+ */
+ while (count < half_list_size) {
+ req = list_first_entry(&video->req_free, struct usb_request,
+ list);
+ list_del(&req->list);
+ req->length = 0;
+ ret = uvcg_video_ep_queue(video, req);
+ if (ret < 0) {
+ uvcg_queue_cancel(&video->queue, 0);
+ break;
+ }
+ count++;
+ }
+ spin_unlock_irqrestore(&video->req_lock, flags);
+}
+
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;
+ bool is_bulk = video->max_payload_size;
+ int ret = 0;
+
+ spin_lock_irqsave(&video->req_lock, flags);
+ 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:
@@ -277,44 +415,85 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
uvcg_queue_cancel(queue, 0);
}
- if (ureq->last_buf) {
- uvcg_complete_buffer(&video->queue, ureq->last_buf);
- ureq->last_buf = NULL;
+ 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);
- list_add_tail(&req->list, &video->req_free);
+ /*
+ * 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) {
+ /*
+ * 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.
+ */
+ struct usb_request *to_queue = req;
+
+ to_queue->length = 0;
+ if (!list_empty(&video->req_ready)) {
+ to_queue = list_first_entry(&video->req_ready,
+ struct usb_request, list);
+ list_del(&to_queue->list);
+ 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.
+ */
+ queue_work(video->async_wq, &video->pump);
+ }
+ /*
+ * 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, to_queue, !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(&to_queue->list, &video->req_free);
+ }
+ } else {
+ uvc_video_free_request(ureq, ep);
+ ret = 0;
+ }
spin_unlock_irqrestore(&video->req_lock, flags);
-
- if (uvc->state == UVC_STATE_STREAMING)
- queue_work(video->async_wq, &video->pump);
+ if (ret < 0)
+ uvcg_queue_cancel(queue, 0);
}
static int
uvc_video_free_requests(struct uvc_video *video)
{
- unsigned int i;
-
- if (video->ureq) {
- for (i = 0; i < video->uvc_num_requests; ++i) {
- sg_free_table(&video->ureq[i].sgt);
+ struct uvc_request *ureq, *temp;
- if (video->ureq[i].req) {
- usb_ep_free_request(video->ep, video->ureq[i].req);
- video->ureq[i].req = NULL;
- }
-
- if (video->ureq[i].req_buffer) {
- kfree(video->ureq[i].req_buffer);
- video->ureq[i].req_buffer = NULL;
- }
- }
-
- kfree(video->ureq);
- video->ureq = NULL;
- }
+ 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);
+ INIT_LIST_HEAD(&video->req_ready);
video->req_size = 0;
return 0;
}
@@ -322,6 +501,7 @@ uvc_video_free_requests(struct uvc_video *video)
static int
uvc_video_alloc_requests(struct uvc_video *video)
{
+ struct uvc_request *ureq;
unsigned int req_size;
unsigned int i;
int ret = -ENOMEM;
@@ -332,29 +512,33 @@ uvc_video_alloc_requests(struct uvc_video *video)
* max_t(unsigned int, video->ep->maxburst, 1)
* (video->ep->mult);
- video->ureq = kcalloc(video->uvc_num_requests, sizeof(struct uvc_request), GFP_KERNEL);
- if (video->ureq == NULL)
- return -ENOMEM;
+ 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);
- 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)
+ list_add_tail(&ureq->list, &video->ureqs);
+
+ ureq->req_buffer = kmalloc(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;
- video->ureq[i].last_buf = NULL;
+ 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,
+ sg_alloc_table(&ureq->sgt,
DIV_ROUND_UP(req_size - UVCG_REQUEST_HEADER_LEN,
PAGE_SIZE) + 2, GFP_KERNEL);
}
@@ -387,16 +571,18 @@ static void uvcg_video_pump(struct work_struct *work)
struct usb_request *req = NULL;
struct uvc_buffer *buf;
unsigned long flags;
- bool buf_done;
- int ret;
+ int ret = 0;
+
+ while (true) {
+ if (!video->ep->enabled)
+ return;
- while (video->ep->enabled) {
/*
- * Retrieve the first available USB request, protected by the
- * request lock.
+ * 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;
}
@@ -411,19 +597,7 @@ static void uvcg_video_pump(struct work_struct *work)
*/
spin_lock_irqsave(&queue->irqlock, flags);
buf = uvcg_queue_head(queue);
-
- if (buf != NULL) {
- video->encode(req, video, buf);
- buf_done = buf->state == UVC_BUF_STATE_DONE;
- } else if (!(queue->flags & UVC_QUEUE_DISCONNECTED) && !is_bulk) {
- /*
- * No video buffer available; the queue is still connected and
- * we're transferring over ISOC. Queue a 0 length request to
- * prevent missed ISOC transfers.
- */
- req->length = 0;
- buf_done = false;
- } else {
+ if (!buf) {
/*
* Either the queue has been disconnected or no video buffer
* available for bulk transfer. Either way, stop processing
@@ -433,82 +607,140 @@ static void uvcg_video_pump(struct work_struct *work)
break;
}
- /*
- * 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) || buf_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;
- }
+ video->encode(req, video, buf);
- /* 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;
}
- /* Endpoint now owns the request */
+ /* The request is owned by the endpoint / ready list. */
req = NULL;
- video->req_int_count++;
}
if (!req)
return;
spin_lock_irqsave(&video->req_lock, flags);
- list_add_tail(&req->list, &video->req_free);
+ 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);
- return;
}
/*
- * Enable or disable the video stream.
+ * Disable the video stream
*/
-int uvcg_video_enable(struct uvc_video *video, int enable)
+int
+uvcg_video_disable(struct uvc_video *video)
{
- unsigned int i;
- int ret;
+ 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 enable failed, device is uninitialized.\n");
+ "Video disable failed, device is uninitialized.\n");
return -ENODEV;
}
- if (!enable) {
- cancel_work_sync(&video->pump);
- uvcg_queue_cancel(&video->queue, 0);
+ 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);
+ /*
+ * 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);
+ }
- 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);
+ INIT_LIST_HEAD(&video->ureqs);
+ INIT_LIST_HEAD(&video->req_free);
+ INIT_LIST_HEAD(&video->req_ready);
+ video->req_size = 0;
+ spin_unlock_irqrestore(&video->req_lock, flags);
- uvc_video_free_requests(video);
- uvcg_queue_enable(&video->queue, 0);
- return 0;
+ /*
+ * 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 the video stream.
+ */
+int uvcg_video_enable(struct uvc_video *video)
+{
+ int ret;
+
+ if (video->ep == NULL) {
+ uvcg_info(&video->uvc->func,
+ "Video enable failed, device is uninitialized.\n");
+ return -ENODEV;
+ }
+
+ /*
+ * 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;
@@ -525,7 +757,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
video->req_int_count = 0;
- queue_work(video->async_wq, &video->pump);
+ uvc_video_ep_queue_initial_requests(video);
return ret;
}
@@ -535,7 +767,10 @@ 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);