[media] uvcvideo: improve urb buffer allocation
Henry Lin [Wed, 21 Sep 2016 16:02:03 +0000 (00:02 +0800)]
Some UVC device (e.g. StereoLabs ZED camera) may have strict timing
requirement for transferring video payload. This change improves URB
buffer allocation for UVC device with bulk VideoStreaming interface. The
criteria for buffer allocation are:
- Let an URB able to receive a complete UVC payload
- Prepare and submit URBs for a complete video frame

If system memory is large enough, URB buffers will consume memory size
up to a complete video frame.

Bug 1674178

Change-Id: If5f366582ee1c1cb559e890176d74484634dccef
Signed-off-by: Henry Lin <henryl@nvidia.com>
Reviewed-on: http://git-master/r/1225266
Reviewed-by: WK Tsai <wtsai@nvidia.com>
Reviewed-by: Hans Yang <hansy@nvidia.com>
Reviewed-by: Ashutosh Jha <ajha@nvidia.com>
GVS: Gerrit_Virtual_Submit
(cherry picked from commit 4797f84301fe6ff42313c688e72e4f7c0d52b2e6)
Reviewed-on: http://git-master/r/1239557
Reviewed-by: Peter Yu <pyu@nvidia.com>
Tested-by: Peter Yu <pyu@nvidia.com>
Reviewed-by: Vinayak Pane <vpane@nvidia.com>

drivers/media/usb/uvc/uvc_video.c
drivers/media/usb/uvc/uvcvideo.h

index c081812..a91753e 100644 (file)
@@ -3,6 +3,7 @@
  *
  *      Copyright (C) 2005-2010
  *          Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ *      Copyright (C) 2016, NVIDIA Corporation.  All rights reserved.
  *
  *      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
@@ -1338,6 +1339,40 @@ static void uvc_video_complete(struct urb *urb)
        }
 }
 
+static void uvc_free_urbs(struct uvc_streaming *stream)
+{
+       kfree(stream->urb);
+       kfree(stream->urb_buffer);
+       kfree(stream->urb_dma);
+
+       stream->urb = NULL;
+       stream->urb_buffer = NULL;
+       stream->urb_dma = NULL;
+}
+
+static int uvc_init_urbs(struct uvc_streaming *stream)
+{
+       int num = stream->urb_num;
+
+       stream->urb = kcalloc(num, sizeof(struct urb *), GFP_KERNEL);
+       if (stream->urb == NULL)
+               goto error;
+
+       stream->urb_buffer = kcalloc(num, sizeof(char *), GFP_KERNEL);
+       if (stream->urb_buffer == NULL)
+               goto error;
+
+       stream->urb_dma = kcalloc(num, sizeof(dma_addr_t), GFP_KERNEL);
+       if (stream->urb_dma == NULL)
+               goto error;
+
+       return 0;
+error:
+       uvc_free_urbs(stream);
+
+       return -ENOMEM;
+}
+
 /*
  * Free transfer buffers.
  */
@@ -1345,7 +1380,7 @@ static void uvc_free_urb_buffers(struct uvc_streaming *stream)
 {
        unsigned int i;
 
-       for (i = 0; i < UVC_URBS; ++i) {
+       for (i = 0; i < stream->urb_num; ++i) {
                if (stream->urb_buffer[i]) {
 #ifndef CONFIG_DMA_NONCOHERENT
                        usb_free_coherent(stream->dev->udev, stream->urb_size,
@@ -1381,16 +1416,22 @@ static int uvc_alloc_urb_buffers(struct uvc_streaming *stream,
        if (stream->urb_size)
                return stream->urb_size / psize;
 
+       if (uvc_init_urbs(stream) != 0) {
+               uvc_trace(UVC_TRACE_VIDEO,
+                       "Failed to init %d URBs.\n", stream->urb_num);
+               return 0;
+       }
+
        /* Compute the number of packets. Bulk endpoints might transfer UVC
         * payloads across multiple URBs.
         */
        npackets = DIV_ROUND_UP(size, psize);
-       if (npackets > UVC_MAX_PACKETS)
-               npackets = UVC_MAX_PACKETS;
+       if (npackets > stream->urb_max_packets)
+               npackets = stream->urb_max_packets;
 
        /* Retry allocations until one succeed. */
        for (; npackets > 1; npackets /= 2) {
-               for (i = 0; i < UVC_URBS; ++i) {
+               for (i = 0; i < stream->urb_num; ++i) {
                        stream->urb_size = psize * npackets;
 #ifndef CONFIG_DMA_NONCOHERENT
                        stream->urb_buffer[i] = usb_alloc_coherent(
@@ -1406,14 +1447,15 @@ static int uvc_alloc_urb_buffers(struct uvc_streaming *stream,
                        }
                }
 
-               if (i == UVC_URBS) {
-                       uvc_trace(UVC_TRACE_VIDEO, "Allocated %u URB buffers "
-                               "of %ux%u bytes each.\n", UVC_URBS, npackets,
-                               psize);
+               if (i == stream->urb_num) {
+                       uvc_trace(UVC_TRACE_VIDEO,
+                               "Allocated %u URB buffers of %ux%u bytes each.\n",
+                               stream->urb_num, npackets, psize);
                        return npackets;
                }
        }
 
+       uvc_free_urbs(stream);
        uvc_trace(UVC_TRACE_VIDEO, "Failed to allocate URB buffers (%u bytes "
                "per packet).\n", psize);
        return 0;
@@ -1429,7 +1471,7 @@ static void uvc_uninit_video(struct uvc_streaming *stream, int free_buffers)
 
        uvc_video_stats_stop(stream);
 
-       for (i = 0; i < UVC_URBS; ++i) {
+       for (i = 0; i < stream->urb_num; ++i) {
                urb = stream->urb[i];
                if (urb == NULL)
                        continue;
@@ -1439,8 +1481,10 @@ static void uvc_uninit_video(struct uvc_streaming *stream, int free_buffers)
                stream->urb[i] = NULL;
        }
 
-       if (free_buffers)
+       if (free_buffers) {
                uvc_free_urb_buffers(stream);
+               uvc_free_urbs(stream);
+       }
 }
 
 /*
@@ -1478,13 +1522,16 @@ static int uvc_init_video_isoc(struct uvc_streaming *stream,
        psize = uvc_endpoint_max_bpi(stream->dev->udev, ep);
        size = stream->ctrl.dwMaxVideoFrameSize;
 
+       stream->urb_num = UVC_URBS;
+       stream->urb_max_packets = UVC_MAX_PACKETS;
+
        npackets = uvc_alloc_urb_buffers(stream, size, psize, gfp_flags);
        if (npackets == 0)
                return -ENOMEM;
 
        size = npackets * psize;
 
-       for (i = 0; i < UVC_URBS; ++i) {
+       for (i = 0; i < stream->urb_num; ++i) {
                urb = usb_alloc_urb(npackets, gfp_flags);
                if (urb == NULL) {
                        uvc_uninit_video(stream, 1);
@@ -1528,12 +1575,37 @@ static int uvc_init_video_bulk(struct uvc_streaming *stream,
        struct urb *urb;
        unsigned int npackets, pipe, i;
        u16 psize;
-       u32 size;
+       u32 size, max_video_frame_size;
 
        psize = usb_endpoint_maxp(&ep->desc) & 0x7ff;
        size = stream->ctrl.dwMaxPayloadTransferSize;
        stream->bulk.max_payload_size = size;
 
+       /*
+        * Criteria:
+        * - An URB can accommodate an uvc payload.
+        *   => urb_size (urb_max_packets * psize) >= size
+        *   => urb_size ~= size
+        * - Enough URBs for an uvc frame are needed
+        *   => urb_num >= payload_num for a uvc frame
+        *   => urb_num ~= payload_num
+        * - urb_size * urb_num >= dwMaxVideoFrameSize +
+        *   payload_header_size * payload_num
+        * - size is much larger than payload_header_size
+        *   (payload_header_size is usually 12 bytes.)
+        */
+       stream->urb_max_packets = DIV_ROUND_UP(size, psize);
+       max_video_frame_size = stream->ctrl.dwMaxVideoFrameSize;
+
+       /*
+        * Prevent system to allocate too much urb/memory.
+        * limit allocated memory to a 1080p frame size
+        * 1920*1080*2(yuv)=4147200
+        */
+       if (max_video_frame_size > 4147200)
+               max_video_frame_size = 4147200;
+       stream->urb_num = max_video_frame_size / size + 1;
+
        npackets = uvc_alloc_urb_buffers(stream, size, psize, gfp_flags);
        if (npackets == 0)
                return -ENOMEM;
@@ -1550,7 +1622,7 @@ static int uvc_init_video_bulk(struct uvc_streaming *stream,
        if (stream->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
                size = 0;
 
-       for (i = 0; i < UVC_URBS; ++i) {
+       for (i = 0; i < stream->urb_num; ++i) {
                urb = usb_alloc_urb(0, gfp_flags);
                if (urb == NULL) {
                        uvc_uninit_video(stream, 1);
@@ -1655,7 +1727,7 @@ static int uvc_init_video(struct uvc_streaming *stream, gfp_t gfp_flags)
                return ret;
 
        /* Submit the URBs. */
-       for (i = 0; i < UVC_URBS; ++i) {
+       for (i = 0; i < stream->urb_num; ++i) {
                ret = usb_submit_urb(stream->urb[i], gfp_flags);
                if (ret < 0) {
                        uvc_printk(KERN_ERR, "Failed to submit URB %u "
index af505fd..8605052 100644 (file)
@@ -1,3 +1,10 @@
+/*
+ * Copyright (C) 2016, NVIDIA Corporation.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ */
 #ifndef _USB_VIDEO_H_
 #define _USB_VIDEO_H_
 
@@ -466,11 +473,14 @@ struct uvc_streaming {
                __u32 max_payload_size;
        } bulk;
 
-       struct urb *urb[UVC_URBS];
-       char *urb_buffer[UVC_URBS];
-       dma_addr_t urb_dma[UVC_URBS];
+       struct urb **urb;
+       char **urb_buffer;
+       dma_addr_t *urb_dma;
        unsigned int urb_size;
 
+       unsigned int urb_num;
+       unsigned int urb_max_packets;
+
        __u32 sequence;
        __u8 last_fid;