diff options
Diffstat (limited to 'drivers/media/usb/uvc')
| -rw-r--r-- | drivers/media/usb/uvc/uvc_driver.c | 34 | ||||
| -rw-r--r-- | drivers/media/usb/uvc/uvc_video.c | 94 | ||||
| -rw-r--r-- | drivers/media/usb/uvc/uvcvideo.h | 5 | 
3 files changed, 106 insertions, 27 deletions
| diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index 30ef2a3110f7..9a791d8ef200 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -1712,10 +1712,35 @@ static int uvc_scan_chain_forward(struct uvc_video_chain *chain,  			if (forward->bNrInPins != 1) {  				uvc_dbg(chain->dev, DESCR,  					"Extension unit %d has more than 1 input pin\n", -					entity->id); +					forward->id);  				return -EINVAL;  			} +			/* +			 * Some devices reference an output terminal as the +			 * source of extension units. This is incorrect, as +			 * output terminals only have an input pin, and thus +			 * can't be connected to any entity in the forward +			 * direction. The resulting topology would cause issues +			 * when registering the media controller graph. To +			 * avoid this problem, connect the extension unit to +			 * the source of the output terminal instead. +			 */ +			if (UVC_ENTITY_IS_OTERM(entity)) { +				struct uvc_entity *source; + +				source = uvc_entity_by_id(chain->dev, +							  entity->baSourceID[0]); +				if (!source) { +					uvc_dbg(chain->dev, DESCR, +						"Can't connect extension unit %u in chain\n", +						forward->id); +					break; +				} + +				forward->baSourceID[0] = source->id; +			} +  			list_add_tail(&forward->chain, &chain->entities);  			if (!found)  				uvc_dbg_cont(PROBE, " (->"); @@ -1735,6 +1760,13 @@ static int uvc_scan_chain_forward(struct uvc_video_chain *chain,  				return -EINVAL;  			} +			if (UVC_ENTITY_IS_OTERM(entity)) { +				uvc_dbg(chain->dev, DESCR, +					"Unsupported connection between output terminals %u and %u\n", +					entity->id, forward->id); +				break; +			} +  			list_add_tail(&forward->chain, &chain->entities);  			if (!found)  				uvc_dbg_cont(PROBE, " (->"); diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c index f2f565281e63..a777b389a66e 100644 --- a/drivers/media/usb/uvc/uvc_video.c +++ b/drivers/media/usb/uvc/uvc_video.c @@ -6,11 +6,14 @@   *          Laurent Pinchart (laurent.pinchart@ideasonboard.com)   */ +#include <linux/dma-mapping.h> +#include <linux/highmem.h>  #include <linux/kernel.h>  #include <linux/list.h>  #include <linux/module.h>  #include <linux/slab.h>  #include <linux/usb.h> +#include <linux/usb/hcd.h>  #include <linux/videodev2.h>  #include <linux/vmalloc.h>  #include <linux/wait.h> @@ -1096,6 +1099,29 @@ static int uvc_video_decode_start(struct uvc_streaming *stream,  	return data[0];  } +static inline enum dma_data_direction uvc_stream_dir( +				struct uvc_streaming *stream) +{ +	if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) +		return DMA_FROM_DEVICE; +	else +		return DMA_TO_DEVICE; +} + +static inline struct device *uvc_stream_to_dmadev(struct uvc_streaming *stream) +{ +	return bus_to_hcd(stream->dev->udev->bus)->self.sysdev; +} + +static int uvc_submit_urb(struct uvc_urb *uvc_urb, gfp_t mem_flags) +{ +	/* Sync DMA. */ +	dma_sync_sgtable_for_device(uvc_stream_to_dmadev(uvc_urb->stream), +				    uvc_urb->sgt, +				    uvc_stream_dir(uvc_urb->stream)); +	return usb_submit_urb(uvc_urb->urb, mem_flags); +} +  /*   * uvc_video_decode_data_work: Asynchronous memcpy processing   * @@ -1117,7 +1143,7 @@ static void uvc_video_copy_data_work(struct work_struct *work)  		uvc_queue_buffer_release(op->buf);  	} -	ret = usb_submit_urb(uvc_urb->urb, GFP_KERNEL); +	ret = uvc_submit_urb(uvc_urb, GFP_KERNEL);  	if (ret < 0)  		dev_err(&uvc_urb->stream->intf->dev,  			"Failed to resubmit video URB (%d).\n", ret); @@ -1537,6 +1563,12 @@ static void uvc_video_complete(struct urb *urb)  	/* Re-initialise the URB async work. */  	uvc_urb->async_operations = 0; +	/* Sync DMA and invalidate vmap range. */ +	dma_sync_sgtable_for_cpu(uvc_stream_to_dmadev(uvc_urb->stream), +				 uvc_urb->sgt, uvc_stream_dir(stream)); +	invalidate_kernel_vmap_range(uvc_urb->buffer, +				     uvc_urb->stream->urb_size); +  	/*  	 * Process the URB headers, and optionally queue expensive memcpy tasks  	 * to be deferred to a work queue. @@ -1545,7 +1577,7 @@ static void uvc_video_complete(struct urb *urb)  	/* If no async work is needed, resubmit the URB immediately. */  	if (!uvc_urb->async_operations) { -		ret = usb_submit_urb(uvc_urb->urb, GFP_ATOMIC); +		ret = uvc_submit_urb(uvc_urb, GFP_ATOMIC);  		if (ret < 0)  			dev_err(&stream->intf->dev,  				"Failed to resubmit video URB (%d).\n", ret); @@ -1560,24 +1592,49 @@ static void uvc_video_complete(struct urb *urb)   */  static void uvc_free_urb_buffers(struct uvc_streaming *stream)  { +	struct device *dma_dev = uvc_stream_to_dmadev(stream);  	struct uvc_urb *uvc_urb;  	for_each_uvc_urb(uvc_urb, stream) {  		if (!uvc_urb->buffer)  			continue; -#ifndef CONFIG_DMA_NONCOHERENT -		usb_free_coherent(stream->dev->udev, stream->urb_size, -				  uvc_urb->buffer, uvc_urb->dma); -#else -		kfree(uvc_urb->buffer); -#endif +		dma_vunmap_noncontiguous(dma_dev, uvc_urb->buffer); +		dma_free_noncontiguous(dma_dev, stream->urb_size, uvc_urb->sgt, +				       uvc_stream_dir(stream)); +  		uvc_urb->buffer = NULL; +		uvc_urb->sgt = NULL;  	}  	stream->urb_size = 0;  } +static bool uvc_alloc_urb_buffer(struct uvc_streaming *stream, +				 struct uvc_urb *uvc_urb, gfp_t gfp_flags) +{ +	struct device *dma_dev = uvc_stream_to_dmadev(stream); + +	uvc_urb->sgt = dma_alloc_noncontiguous(dma_dev, stream->urb_size, +					       uvc_stream_dir(stream), +					       gfp_flags, 0); +	if (!uvc_urb->sgt) +		return false; +	uvc_urb->dma = uvc_urb->sgt->sgl->dma_address; + +	uvc_urb->buffer = dma_vmap_noncontiguous(dma_dev, stream->urb_size, +						 uvc_urb->sgt); +	if (!uvc_urb->buffer) { +		dma_free_noncontiguous(dma_dev, stream->urb_size, +				       uvc_urb->sgt, +				       uvc_stream_dir(stream)); +		uvc_urb->sgt = NULL; +		return false; +	} + +	return true; +} +  /*   * Allocate transfer buffers. This function can be called with buffers   * already allocated when resuming from suspend, in which case it will @@ -1608,19 +1665,12 @@ static int uvc_alloc_urb_buffers(struct uvc_streaming *stream,  	/* Retry allocations until one succeed. */  	for (; npackets > 1; npackets /= 2) { +		stream->urb_size = psize * npackets; +  		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 -			uvc_urb->buffer = usb_alloc_coherent( -				stream->dev->udev, stream->urb_size, -				gfp_flags | __GFP_NOWARN, &uvc_urb->dma); -#else -			uvc_urb->buffer = -			    kmalloc(stream->urb_size, gfp_flags | __GFP_NOWARN); -#endif -			if (!uvc_urb->buffer) { +			if (!uvc_alloc_urb_buffer(stream, uvc_urb, gfp_flags)) {  				uvc_free_urb_buffers(stream);  				break;  			} @@ -1730,12 +1780,8 @@ static int uvc_init_video_isoc(struct uvc_streaming *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 = uvc_urb->dma; -#else -		urb->transfer_flags = URB_ISO_ASAP; -#endif  		urb->interval = ep->desc.bInterval;  		urb->transfer_buffer = uvc_urb->buffer;  		urb->complete = uvc_video_complete; @@ -1795,10 +1841,8 @@ static int uvc_init_video_bulk(struct uvc_streaming *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 = uvc_urb->dma; -#endif  		uvc_urb->urb = urb;  	} @@ -1895,7 +1939,7 @@ static int uvc_video_start_transfer(struct uvc_streaming *stream,  	/* Submit the URBs. */  	for_each_uvc_urb(uvc_urb, stream) { -		ret = usb_submit_urb(uvc_urb->urb, gfp_flags); +		ret = uvc_submit_urb(uvc_urb, gfp_flags);  		if (ret < 0) {  			dev_err(&stream->intf->dev,  				"Failed to submit URB %u (%d).\n", diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index 97df5ecd66c9..cce5e38133cd 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -219,6 +219,7 @@   */  struct gpio_desc; +struct sg_table;  struct uvc_device;  /* TODO: Put the most frequently accessed fields at the beginning of @@ -545,7 +546,8 @@ struct uvc_copy_op {   * @urb: the URB described by this context structure   * @stream: UVC streaming context   * @buffer: memory storage for the URB - * @dma: DMA coherent addressing for the urb_buffer + * @dma: Allocated DMA handle + * @sgt: sgt_table with the urb locations in memory   * @async_operations: counter to indicate the number of copy operations   * @copy_operations: work descriptors for asynchronous copy operations   * @work: work queue entry for asynchronous decode @@ -556,6 +558,7 @@ struct uvc_urb {  	char *buffer;  	dma_addr_t dma; +	struct sg_table *sgt;  	unsigned int async_operations;  	struct uvc_copy_op copy_operations[UVC_MAX_PACKETS]; | 
