Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

usb: gadget: uvc: only enqueue zero length requests in potential underrun

The complete handler will at least be called after 16 requests have
completed, but will still handle all finisher requests. Since we have
to maintain a costant filling in the isoc queue we ensure this by
adding zero length requests.

By counting the amount enqueued requests we can ensure that the queue is
never underrun and only need to get active if the queue is running
critical. This patch is setting 32 as the critical level, which
is twice the request amount that is needed to create interrupts.

To properly solve the amount of zero length requests that needs to
be held in the hardware after one interrupt needs to be measured
and depends on the runtime of the first enqueue run after the interrupt
triggered. For now we just use twice the amount of requests between an
interrupt.

Signed-off-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Link: https://lore.kernel.org/r/20240403-uvc_request_length_by_interval-v7-2-e224bb1035f0@pengutronix.de
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Michael Grzeschik and committed by
Greg Kroah-Hartman
dc97c956 adc292d5

+21 -1
+5
drivers/usb/gadget/function/uvc.h
··· 71 71 72 72 #define UVCG_REQUEST_HEADER_LEN 12 73 73 74 + #define UVCG_REQ_MAX_INT_COUNT 16 75 + #define UVCG_REQ_MAX_ZERO_COUNT (2 * UVCG_REQ_MAX_INT_COUNT) 76 + 74 77 /* ------------------------------------------------------------------------ 75 78 * Structures 76 79 */ ··· 93 90 94 91 struct work_struct pump; 95 92 struct workqueue_struct *async_wq; 93 + 94 + atomic_t queued; 96 95 97 96 /* Frame parameters */ 98 97 u8 bpp;
+16 -1
drivers/usb/gadget/function/uvc_video.c
··· 269 269 } 270 270 } 271 271 272 + atomic_inc(&video->queued); 273 + 272 274 return ret; 273 275 } 274 276 ··· 306 304 */ 307 305 if (list_empty(&video->req_free) || ureq->last_buf || 308 306 !(video->req_int_count % 309 - DIV_ROUND_UP(video->uvc_num_requests, 4))) { 307 + min(DIV_ROUND_UP(video->uvc_num_requests, 4), UVCG_REQ_MAX_INT_COUNT))) { 310 308 video->req_int_count = 0; 311 309 req->no_interrupt = 0; 312 310 } else { ··· 381 379 int ret = 0; 382 380 383 381 spin_lock_irqsave(&video->req_lock, flags); 382 + atomic_dec(&video->queued); 384 383 if (!video->is_enabled) { 385 384 /* 386 385 * When is_enabled is false, uvcg_video_disable() ensures ··· 469 466 * happen. 470 467 */ 471 468 queue_work(video->async_wq, &video->pump); 469 + } else if (atomic_read(&video->queued) > UVCG_REQ_MAX_ZERO_COUNT) { 470 + list_add_tail(&to_queue->list, &video->req_free); 471 + /* 472 + * There is a new free request - wake up the pump. 473 + */ 474 + queue_work(video->async_wq, &video->pump); 475 + 476 + spin_unlock_irqrestore(&video->req_lock, flags); 477 + 478 + return; 472 479 } 473 480 /* 474 481 * Queue to the endpoint. The actual queueing to ep will ··· 768 755 uvc_video_encode_isoc_sg : uvc_video_encode_isoc; 769 756 770 757 video->req_int_count = 0; 758 + 759 + atomic_set(&video->queued, 0); 771 760 772 761 uvc_video_ep_queue_initial_requests(video); 773 762 queue_work(video->async_wq, &video->pump);