diff options
Diffstat (limited to 'drivers/media/usb/uvc/uvc_video.c')
-rw-r--r-- | drivers/media/usb/uvc/uvc_video.c | 274 |
1 files changed, 171 insertions, 103 deletions
diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c index 86a99f461fd8..84525ff04745 100644 --- a/drivers/media/usb/uvc/uvc_video.c +++ b/drivers/media/usb/uvc/uvc_video.c @@ -1094,21 +1094,54 @@ static int uvc_video_decode_start(struct uvc_streaming *stream, return data[0]; } -static void uvc_video_decode_data(struct uvc_streaming *stream, +/* + * uvc_video_decode_data_work: Asynchronous memcpy processing + * + * Copy URB data to video buffers in process context, releasing buffer + * references and requeuing the URB when done. + */ +static void uvc_video_copy_data_work(struct work_struct *work) +{ + struct uvc_urb *uvc_urb = container_of(work, struct uvc_urb, work); + unsigned int i; + int ret; + + for (i = 0; i < uvc_urb->async_operations; i++) { + struct uvc_copy_op *op = &uvc_urb->copy_operations[i]; + + memcpy(op->dst, op->src, op->len); + + /* Release reference taken on this buffer. */ + uvc_queue_buffer_release(op->buf); + } + + ret = usb_submit_urb(uvc_urb->urb, GFP_KERNEL); + if (ret < 0) + uvc_printk(KERN_ERR, "Failed to resubmit video URB (%d).\n", + ret); +} + +static void uvc_video_decode_data(struct uvc_urb *uvc_urb, struct uvc_buffer *buf, const u8 *data, int len) { - unsigned int maxlen, nbytes; - void *mem; + unsigned int active_op = uvc_urb->async_operations; + struct uvc_copy_op *op = &uvc_urb->copy_operations[active_op]; + unsigned int maxlen; if (len <= 0) return; - /* Copy the video data to the buffer. */ maxlen = buf->length - buf->bytesused; - mem = buf->mem + buf->bytesused; - nbytes = min((unsigned int)len, maxlen); - memcpy(mem, data, nbytes); - buf->bytesused += nbytes; + + /* Take a buffer reference for async work. */ + kref_get(&buf->ref); + + op->buf = buf; + op->src = data; + op->dst = buf->mem + buf->bytesused; + op->len = min_t(unsigned int, len, maxlen); + + buf->bytesused += op->len; /* Complete the current frame if the buffer size was exceeded. */ if (len > maxlen) { @@ -1116,6 +1149,8 @@ static void uvc_video_decode_data(struct uvc_streaming *stream, buf->error = 1; buf->state = UVC_BUF_STATE_READY; } + + uvc_urb->async_operations++; } static void uvc_video_decode_end(struct uvc_streaming *stream, @@ -1291,9 +1326,11 @@ static void uvc_video_next_buffers(struct uvc_streaming *stream, *video_buf = uvc_queue_next_buffer(&stream->queue, *video_buf); } -static void uvc_video_decode_isoc(struct urb *urb, struct uvc_streaming *stream, +static void uvc_video_decode_isoc(struct uvc_urb *uvc_urb, struct uvc_buffer *buf, struct uvc_buffer *meta_buf) { + struct urb *urb = uvc_urb->urb; + struct uvc_streaming *stream = uvc_urb->stream; u8 *mem; int ret, i; @@ -1322,7 +1359,7 @@ static void uvc_video_decode_isoc(struct urb *urb, struct uvc_streaming *stream, uvc_video_decode_meta(stream, meta_buf, mem, ret); /* Decode the payload data. */ - uvc_video_decode_data(stream, buf, mem + ret, + uvc_video_decode_data(uvc_urb, buf, mem + ret, urb->iso_frame_desc[i].actual_length - ret); /* Process the header again. */ @@ -1334,9 +1371,11 @@ static void uvc_video_decode_isoc(struct urb *urb, struct uvc_streaming *stream, } } -static void uvc_video_decode_bulk(struct urb *urb, struct uvc_streaming *stream, +static void uvc_video_decode_bulk(struct uvc_urb *uvc_urb, struct uvc_buffer *buf, struct uvc_buffer *meta_buf) { + struct urb *urb = uvc_urb->urb; + struct uvc_streaming *stream = uvc_urb->stream; u8 *mem; int len, ret; @@ -1380,9 +1419,9 @@ static void uvc_video_decode_bulk(struct urb *urb, struct uvc_streaming *stream, * sure buf is never dereferenced if NULL. */ - /* Process video data. */ + /* Prepare video data for processing. */ if (!stream->bulk.skip_payload && buf != NULL) - uvc_video_decode_data(stream, buf, mem, len); + uvc_video_decode_data(uvc_urb, buf, mem, len); /* Detect the payload end by a URB smaller than the maximum size (or * a payload size equal to the maximum) and process the header again. @@ -1402,9 +1441,12 @@ static void uvc_video_decode_bulk(struct urb *urb, struct uvc_streaming *stream, } } -static void uvc_video_encode_bulk(struct urb *urb, struct uvc_streaming *stream, +static void uvc_video_encode_bulk(struct uvc_urb *uvc_urb, struct uvc_buffer *buf, struct uvc_buffer *meta_buf) { + struct urb *urb = uvc_urb->urb; + struct uvc_streaming *stream = uvc_urb->stream; + u8 *mem = urb->transfer_buffer; int len = stream->urb_size, ret; @@ -1447,7 +1489,8 @@ static void uvc_video_encode_bulk(struct urb *urb, struct uvc_streaming *stream, static void uvc_video_complete(struct urb *urb) { - struct uvc_streaming *stream = urb->context; + struct uvc_urb *uvc_urb = urb->context; + struct uvc_streaming *stream = uvc_urb->stream; struct uvc_video_queue *queue = &stream->queue; struct uvc_video_queue *qmeta = &stream->meta.queue; struct vb2_queue *vb2_qmeta = stream->meta.vdev.queue; @@ -1464,7 +1507,7 @@ static void uvc_video_complete(struct urb *urb) uvc_printk(KERN_WARNING, "Non-zero status (%d) in video " "completion handler.\n", urb->status); /* fall through */ - case -ENOENT: /* usb_kill_urb() called. */ + case -ENOENT: /* usb_poison_urb() called. */ if (stream->frozen) return; /* fall through */ @@ -1476,11 +1519,7 @@ static void uvc_video_complete(struct urb *urb) return; } - spin_lock_irqsave(&queue->irqlock, flags); - if (!list_empty(&queue->irqqueue)) - buf = list_first_entry(&queue->irqqueue, struct uvc_buffer, - queue); - spin_unlock_irqrestore(&queue->irqlock, flags); + buf = uvc_queue_get_current_buffer(queue); if (vb2_qmeta) { spin_lock_irqsave(&qmeta->irqlock, flags); @@ -1490,12 +1529,26 @@ static void uvc_video_complete(struct urb *urb) spin_unlock_irqrestore(&qmeta->irqlock, flags); } - stream->decode(urb, stream, buf, buf_meta); + /* Re-initialise the URB async work. */ + uvc_urb->async_operations = 0; - if ((ret = usb_submit_urb(urb, GFP_ATOMIC)) < 0) { - uvc_printk(KERN_ERR, "Failed to resubmit video URB (%d).\n", - ret); + /* + * Process the URB headers, and optionally queue expensive memcpy tasks + * to be deferred to a work queue. + */ + stream->decode(uvc_urb, buf, buf_meta); + + /* If no async work is needed, resubmit the URB immediately. */ + if (!uvc_urb->async_operations) { + ret = usb_submit_urb(uvc_urb->urb, GFP_ATOMIC); + if (ret < 0) + uvc_printk(KERN_ERR, + "Failed to resubmit video URB (%d).\n", + ret); + return; } + + queue_work(stream->async_wq, &uvc_urb->work); } /* @@ -1503,18 +1556,19 @@ static void uvc_video_complete(struct urb *urb) */ static void uvc_free_urb_buffers(struct uvc_streaming *stream) { - unsigned int i; + struct uvc_urb *uvc_urb; + + for_each_uvc_urb(uvc_urb, stream) { + if (!uvc_urb->buffer) + continue; - for (i = 0; i < UVC_URBS; ++i) { - if (stream->urb_buffer[i]) { #ifndef CONFIG_DMA_NONCOHERENT - usb_free_coherent(stream->dev->udev, stream->urb_size, - stream->urb_buffer[i], stream->urb_dma[i]); + usb_free_coherent(stream->dev->udev, stream->urb_size, + uvc_urb->buffer, uvc_urb->dma); #else - kfree(stream->urb_buffer[i]); + kfree(uvc_urb->buffer); #endif - stream->urb_buffer[i] = NULL; - } + uvc_urb->buffer = NULL; } stream->urb_size = 0; @@ -1551,19 +1605,23 @@ static int uvc_alloc_urb_buffers(struct uvc_streaming *stream, /* Retry allocations until one succeed. */ for (; npackets > 1; npackets /= 2) { for (i = 0; i < UVC_URBS; ++i) { + struct uvc_urb *uvc_urb = &stream->uvc_urb[i]; + stream->urb_size = psize * npackets; #ifndef CONFIG_DMA_NONCOHERENT - stream->urb_buffer[i] = usb_alloc_coherent( + uvc_urb->buffer = usb_alloc_coherent( stream->dev->udev, stream->urb_size, - gfp_flags | __GFP_NOWARN, &stream->urb_dma[i]); + gfp_flags | __GFP_NOWARN, &uvc_urb->dma); #else - stream->urb_buffer[i] = + uvc_urb->buffer = kmalloc(stream->urb_size, gfp_flags | __GFP_NOWARN); #endif - if (!stream->urb_buffer[i]) { + if (!uvc_urb->buffer) { uvc_free_urb_buffers(stream); break; } + + uvc_urb->stream = stream; } if (i == UVC_URBS) { @@ -1582,21 +1640,26 @@ static int uvc_alloc_urb_buffers(struct uvc_streaming *stream, /* * Uninitialize isochronous/bulk URBs and free transfer buffers. */ -static void uvc_uninit_video(struct uvc_streaming *stream, int free_buffers) +static void uvc_video_stop_transfer(struct uvc_streaming *stream, + int free_buffers) { - struct urb *urb; - unsigned int i; + struct uvc_urb *uvc_urb; uvc_video_stats_stop(stream); - for (i = 0; i < UVC_URBS; ++i) { - urb = stream->urb[i]; - if (urb == NULL) - continue; + /* + * We must poison the URBs rather than kill them to ensure that even + * after the completion handler returns, any asynchronous workqueues + * will be prevented from resubmitting the URBs. + */ + for_each_uvc_urb(uvc_urb, stream) + usb_poison_urb(uvc_urb->urb); - usb_kill_urb(urb); - usb_free_urb(urb); - stream->urb[i] = NULL; + flush_workqueue(stream->async_wq); + + for_each_uvc_urb(uvc_urb, stream) { + usb_free_urb(uvc_urb->urb); + uvc_urb->urb = NULL; } if (free_buffers) @@ -1637,7 +1700,8 @@ static int uvc_init_video_isoc(struct uvc_streaming *stream, struct usb_host_endpoint *ep, gfp_t gfp_flags) { struct urb *urb; - unsigned int npackets, i, j; + struct uvc_urb *uvc_urb; + unsigned int npackets, i; u16 psize; u32 size; @@ -1650,35 +1714,35 @@ static int uvc_init_video_isoc(struct uvc_streaming *stream, size = npackets * psize; - for (i = 0; i < UVC_URBS; ++i) { + for_each_uvc_urb(uvc_urb, stream) { urb = usb_alloc_urb(npackets, gfp_flags); if (urb == NULL) { - uvc_uninit_video(stream, 1); + uvc_video_stop_transfer(stream, 1); return -ENOMEM; } urb->dev = stream->dev->udev; - urb->context = stream; + urb->context = uvc_urb; urb->pipe = usb_rcvisocpipe(stream->dev->udev, ep->desc.bEndpointAddress); #ifndef CONFIG_DMA_NONCOHERENT urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; - urb->transfer_dma = stream->urb_dma[i]; + urb->transfer_dma = uvc_urb->dma; #else urb->transfer_flags = URB_ISO_ASAP; #endif urb->interval = ep->desc.bInterval; - urb->transfer_buffer = stream->urb_buffer[i]; + urb->transfer_buffer = uvc_urb->buffer; urb->complete = uvc_video_complete; urb->number_of_packets = npackets; urb->transfer_buffer_length = size; - for (j = 0; j < npackets; ++j) { - urb->iso_frame_desc[j].offset = j * psize; - urb->iso_frame_desc[j].length = psize; + for (i = 0; i < npackets; ++i) { + urb->iso_frame_desc[i].offset = i * psize; + urb->iso_frame_desc[i].length = psize; } - stream->urb[i] = urb; + uvc_urb->urb = urb; } return 0; @@ -1692,7 +1756,8 @@ static int uvc_init_video_bulk(struct uvc_streaming *stream, struct usb_host_endpoint *ep, gfp_t gfp_flags) { struct urb *urb; - unsigned int npackets, pipe, i; + struct uvc_urb *uvc_urb; + unsigned int npackets, pipe; u16 psize; u32 size; @@ -1716,22 +1781,21 @@ 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_each_uvc_urb(uvc_urb, stream) { urb = usb_alloc_urb(0, gfp_flags); if (urb == NULL) { - uvc_uninit_video(stream, 1); + uvc_video_stop_transfer(stream, 1); return -ENOMEM; } - usb_fill_bulk_urb(urb, stream->dev->udev, pipe, - stream->urb_buffer[i], size, uvc_video_complete, - stream); + usb_fill_bulk_urb(urb, stream->dev->udev, pipe, uvc_urb->buffer, + size, uvc_video_complete, uvc_urb); #ifndef CONFIG_DMA_NONCOHERENT urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; - urb->transfer_dma = stream->urb_dma[i]; + urb->transfer_dma = uvc_urb->dma; #endif - stream->urb[i] = urb; + uvc_urb->urb = urb; } return 0; @@ -1740,10 +1804,12 @@ static int uvc_init_video_bulk(struct uvc_streaming *stream, /* * Initialize isochronous/bulk URBs and allocate transfer buffers. */ -static int uvc_init_video(struct uvc_streaming *stream, gfp_t gfp_flags) +static int uvc_video_start_transfer(struct uvc_streaming *stream, + gfp_t gfp_flags) { struct usb_interface *intf = stream->intf; struct usb_host_endpoint *ep; + struct uvc_urb *uvc_urb; unsigned int i; int ret; @@ -1821,12 +1887,12 @@ 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) { - ret = usb_submit_urb(stream->urb[i], gfp_flags); + for_each_uvc_urb(uvc_urb, stream) { + ret = usb_submit_urb(uvc_urb->urb, gfp_flags); if (ret < 0) { - uvc_printk(KERN_ERR, "Failed to submit URB %u " - "(%d).\n", i, ret); - uvc_uninit_video(stream, 1); + uvc_printk(KERN_ERR, "Failed to submit URB %u (%d).\n", + uvc_urb_index(uvc_urb), ret); + uvc_video_stop_transfer(stream, 1); return ret; } } @@ -1857,7 +1923,7 @@ int uvc_video_suspend(struct uvc_streaming *stream) return 0; stream->frozen = 1; - uvc_uninit_video(stream, 0); + uvc_video_stop_transfer(stream, 0); usb_set_interface(stream->dev->udev, stream->intfnum, 0); return 0; } @@ -1893,7 +1959,7 @@ int uvc_video_resume(struct uvc_streaming *stream, int reset) if (ret < 0) return ret; - return uvc_init_video(stream, GFP_NOIO); + return uvc_video_start_transfer(stream, GFP_NOIO); } /* ------------------------------------------------------------------------ @@ -1915,6 +1981,7 @@ int uvc_video_init(struct uvc_streaming *stream) struct uvc_streaming_control *probe = &stream->ctrl; struct uvc_format *format = NULL; struct uvc_frame *frame = NULL; + struct uvc_urb *uvc_urb; unsigned int i; int ret; @@ -2000,41 +2067,17 @@ int uvc_video_init(struct uvc_streaming *stream) } } + /* Prepare asynchronous work items. */ + for_each_uvc_urb(uvc_urb, stream) + INIT_WORK(&uvc_urb->work, uvc_video_copy_data_work); + return 0; } -/* - * Enable or disable the video stream. - */ -int uvc_video_enable(struct uvc_streaming *stream, int enable) +int uvc_video_start_streaming(struct uvc_streaming *stream) { int ret; - if (!enable) { - uvc_uninit_video(stream, 1); - if (stream->intf->num_altsetting > 1) { - usb_set_interface(stream->dev->udev, - stream->intfnum, 0); - } else { - /* UVC doesn't specify how to inform a bulk-based device - * when the video stream is stopped. Windows sends a - * CLEAR_FEATURE(HALT) request to the video streaming - * bulk endpoint, mimic the same behaviour. - */ - unsigned int epnum = stream->header.bEndpointAddress - & USB_ENDPOINT_NUMBER_MASK; - unsigned int dir = stream->header.bEndpointAddress - & USB_ENDPOINT_DIR_MASK; - unsigned int pipe; - - pipe = usb_sndbulkpipe(stream->dev->udev, epnum) | dir; - usb_clear_halt(stream->dev->udev, pipe); - } - - uvc_video_clock_cleanup(stream); - return 0; - } - ret = uvc_video_clock_init(stream); if (ret < 0) return ret; @@ -2044,7 +2087,7 @@ int uvc_video_enable(struct uvc_streaming *stream, int enable) if (ret < 0) goto error_commit; - ret = uvc_init_video(stream, GFP_KERNEL); + ret = uvc_video_start_transfer(stream, GFP_KERNEL); if (ret < 0) goto error_video; @@ -2057,3 +2100,28 @@ error_commit: return ret; } + +void uvc_video_stop_streaming(struct uvc_streaming *stream) +{ + uvc_video_stop_transfer(stream, 1); + + if (stream->intf->num_altsetting > 1) { + usb_set_interface(stream->dev->udev, stream->intfnum, 0); + } else { + /* UVC doesn't specify how to inform a bulk-based device + * when the video stream is stopped. Windows sends a + * CLEAR_FEATURE(HALT) request to the video streaming + * bulk endpoint, mimic the same behaviour. + */ + unsigned int epnum = stream->header.bEndpointAddress + & USB_ENDPOINT_NUMBER_MASK; + unsigned int dir = stream->header.bEndpointAddress + & USB_ENDPOINT_DIR_MASK; + unsigned int pipe; + + pipe = usb_sndbulkpipe(stream->dev->udev, epnum) | dir; + usb_clear_halt(stream->dev->udev, pipe); + } + + uvc_video_clock_cleanup(stream); +} |