Re: [RFC/RFT PATCH 6/6] uvcvideo: Move decode processing to process context

From: Kieran Bingham
Date: Sat Jan 06 2018 - 13:29:58 EST


Hi Guennadi,

Thank you for taking the time to review this series,

On 04/01/18 18:54, Guennadi Liakhovetski wrote:
> On Wed, 3 Jan 2018, Kieran Bingham wrote:
>
>> From: Kieran Bingham <kieran.bingham@xxxxxxxxxxxxxxxx>
>>
>> Newer high definition cameras, and cameras with multiple lenses such as
>> the range of stereovision cameras now available have ever increasing
>> data rates.
>>
>> The inclusion of a variable length packet header in URB packets mean
>> that we must memcpy the frame data out to our destination 'manually'.
>> This can result in data rates of up to 2 gigabits per second being
>> processed.
>>
>> To improve efficiency, and maximise throughput, handle the URB decode
>> processing through a work queue to move it from interrupt context, and
>> allow multiple processors to work on URBs in parallel.
>>
>> Signed-off-by: Kieran Bingham <kieran.bingham@xxxxxxxxxxxxxxxx>
>> ---
>> drivers/media/usb/uvc/uvc_queue.c | 12 +++-
>> drivers/media/usb/uvc/uvc_video.c | 114 ++++++++++++++++++++++++++-----
>> drivers/media/usb/uvc/uvcvideo.h | 24 +++++++-
>> 3 files changed, 132 insertions(+), 18 deletions(-)
>>
>> diff --git a/drivers/media/usb/uvc/uvc_queue.c b/drivers/media/usb/uvc/uvc_queue.c
>> index 204dd91a8526..07fcbfc132c9 100644
>> --- a/drivers/media/usb/uvc/uvc_queue.c
>> +++ b/drivers/media/usb/uvc/uvc_queue.c
>> @@ -179,10 +179,22 @@ static void uvc_stop_streaming(struct vb2_queue *vq)
>> struct uvc_video_queue *queue = vb2_get_drv_priv(vq);
>> struct uvc_streaming *stream = uvc_queue_to_stream(queue);
>>
>> + /* Prevent new buffers coming in. */
>> + spin_lock_irq(&queue->irqlock);
>> + queue->flags |= UVC_QUEUE_STOPPING;
>> + spin_unlock_irq(&queue->irqlock);
>> +
>> + /*
>> + * All pending work should be completed before disabling the stream, as
>> + * all URBs will be free'd during uvc_video_enable(s, 0).
>> + */
>> + flush_workqueue(stream->async_wq);
>> +
>> uvc_video_enable(stream, 0);
>>
>> spin_lock_irq(&queue->irqlock);
>> uvc_queue_return_buffers(queue, UVC_BUF_STATE_ERROR);
>> + queue->flags &= ~UVC_QUEUE_STOPPING;
>> spin_unlock_irq(&queue->irqlock);
>> }
>>
>> diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c
>> index 045ac655313c..b7b32a6bc2dc 100644
>> --- a/drivers/media/usb/uvc/uvc_video.c
>> +++ b/drivers/media/usb/uvc/uvc_video.c
>> @@ -1058,21 +1058,70 @@ static int uvc_video_decode_start(struct uvc_streaming *stream,
>> return data[0];
>> }
>>
>> -static void uvc_video_decode_data(struct uvc_streaming *stream,
>> - struct uvc_buffer *buf, const __u8 *data, int len)
>> +/*
>> + * uvc_video_decode_data_work: Asynchronous memcpy processing
>> + *
>> + * Perform memcpy tasks in process context, with completion handlers
>> + * to return the URB, and buffer handles.
>> + *
>> + * The work submitter must pre-determine that the work is safe
>> + */
>> +static void uvc_video_decode_data_work(struct work_struct *work)
>> {
>> - unsigned int maxlen, nbytes;
>> - void *mem;
>> + struct uvc_urb *uvc_urb = container_of(work, struct uvc_urb, work);
>> + struct uvc_streaming *stream = uvc_urb->stream;
>> + struct uvc_video_queue *queue = &stream->queue;
>> + unsigned int i;
>> + bool stopping;
>> + int ret;
>> +
>> + for (i = 0; i < uvc_urb->packets; i++) {
>> + struct uvc_decode_op *op = &uvc_urb->decodes[i];
>> +
>> + memcpy(op->dst, op->src, op->len);
>> +
>> + /* Release reference taken on this buffer */
>> + uvc_queue_buffer_release(op->buf);
>> + }
>> +
>> + /*
>> + * Prevent resubmitting URBs when shutting down to ensure that no new
>> + * work item will be scheduled after uvc_stop_streaming() flushes the
>> + * work queue.
>> + */
>> + spin_lock_irq(&queue->irqlock);
>> + stopping = queue->flags & UVC_QUEUE_STOPPING;
>> + spin_unlock_irq(&queue->irqlock);
>
> Are you sure this locking really helps? What if uvc_stop_streaming() runs
> here?

Quite - there is a race there. It protects the flag though ;-)

I thought I had pulled the lock out to only check the flag, to prevent
surrounding the usb_submit_urb(), but now I look again - I see no reason not to
lock for the whole critical section.

I've tested locally on my laptop, and it seems safe to hold the lock during the
usb_submit_urb() (unless anyone sees something I'm missing), so I'll update this
for the next spin to something more like:

spin_lock_irq(&queue->irqlock);
if (!(queue->flags & UVC_QUEUE_STOPPING)) {
ret = usb_submit_urb(uvc_urb->urb, GFP_ATOMIC);
if (ret < 0)
uvc_printk(KERN_ERR,
"Failed to resubmit video URB (%d).\n",
ret);
}
spin_unlock_irq(&queue->irqlock);


> Thanks
> Guennadi
>
>> +
>> + if (stopping)
>> + return;
>> +
>> + ret = usb_submit_urb(uvc_urb->urb, GFP_ATOMIC);
>> + if (ret < 0)
>> + uvc_printk(KERN_ERR, "Failed to resubmit video URB (%d).\n",
>> + ret);
>> +}


Regards
--
Kieran