summaryrefslogblamecommitdiffstats
path: root/drivers/media/platform/omap3isp/ispqueue.c
blob: e15f01342058579413c5742b366b0a8c5f39ac56 (plain) (tree)




















































































































































































































































































































































                                                                                
                                                

























                                                                           
                          








































                                                                               

                                                                               








                                                                               
                                                 


































































































































































































































                                                                                
                                                         

























                                                                             
                                                                    

































































































































































































                                                                                



                                                  



































                                                                        

                                                                            
  




                                                                                















































































































































































































































                                                                                
/*
 * ispqueue.c
 *
 * TI OMAP3 ISP - Video buffers queue handling
 *
 * Copyright (C) 2010 Nokia Corporation
 *
 * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
 *	     Sakari Ailus <sakari.ailus@iki.fi>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#include <asm/cacheflush.h>
#include <linux/dma-mapping.h>
#include <linux/mm.h>
#include <linux/pagemap.h>
#include <linux/poll.h>
#include <linux/scatterlist.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>

#include "ispqueue.h"

/* -----------------------------------------------------------------------------
 * Video buffers management
 */

/*
 * isp_video_buffer_cache_sync - Keep the buffers coherent between CPU and ISP
 *
 * The typical operation required here is Cache Invalidation across
 * the (user space) buffer address range. And this _must_ be done
 * at QBUF stage (and *only* at QBUF).
 *
 * We try to use optimal cache invalidation function:
 * - dmac_map_area:
 *    - used when the number of pages are _low_.
 *    - it becomes quite slow as the number of pages increase.
 *       - for 648x492 viewfinder (150 pages) it takes 1.3 ms.
 *       - for 5 Mpix buffer (2491 pages) it takes between 25-50 ms.
 *
 * - flush_cache_all:
 *    - used when the number of pages are _high_.
 *    - time taken in the range of 500-900 us.
 *    - has a higher penalty but, as whole dcache + icache is invalidated
 */
/*
 * FIXME: dmac_inv_range crashes randomly on the user space buffer
 *        address. Fall back to flush_cache_all for now.
 */
#define ISP_CACHE_FLUSH_PAGES_MAX       0

static void isp_video_buffer_cache_sync(struct isp_video_buffer *buf)
{
	if (buf->skip_cache)
		return;

	if (buf->vbuf.m.userptr == 0 || buf->npages == 0 ||
	    buf->npages > ISP_CACHE_FLUSH_PAGES_MAX)
		flush_cache_all();
	else {
		dmac_map_area((void *)buf->vbuf.m.userptr, buf->vbuf.length,
			      DMA_FROM_DEVICE);
		outer_inv_range(buf->vbuf.m.userptr,
				buf->vbuf.m.userptr + buf->vbuf.length);
	}
}

/*
 * isp_video_buffer_lock_vma - Prevent VMAs from being unmapped
 *
 * Lock the VMAs underlying the given buffer into memory. This avoids the
 * userspace buffer mapping from being swapped out, making VIPT cache handling
 * easier.
 *
 * Note that the pages will not be freed as the buffers have been locked to
 * memory using by a call to get_user_pages(), but the userspace mapping could
 * still disappear if the VMAs are not locked. This is caused by the memory
 * management code trying to be as lock-less as possible, which results in the
 * userspace mapping manager not finding out that the pages are locked under
 * some conditions.
 */
static int isp_video_buffer_lock_vma(struct isp_video_buffer *buf, int lock)
{
	struct vm_area_struct *vma;
	unsigned long start;
	unsigned long end;
	int ret = 0;

	if (buf->vbuf.memory == V4L2_MEMORY_MMAP)
		return 0;

	/* We can be called from workqueue context if the current task dies to
	 * unlock the VMAs. In that case there's no current memory management
	 * context so unlocking can't be performed, but the VMAs have been or
	 * are getting destroyed anyway so it doesn't really matter.
	 */
	if (!current || !current->mm)
		return lock ? -EINVAL : 0;

	start = buf->vbuf.m.userptr;
	end = buf->vbuf.m.userptr + buf->vbuf.length - 1;

	down_write(&current->mm->mmap_sem);
	spin_lock(&current->mm->page_table_lock);

	do {
		vma = find_vma(current->mm, start);
		if (vma == NULL) {
			ret = -EFAULT;
			goto out;
		}

		if (lock)
			vma->vm_flags |= VM_LOCKED;
		else
			vma->vm_flags &= ~VM_LOCKED;

		start = vma->vm_end + 1;
	} while (vma->vm_end < end);

	if (lock)
		buf->vm_flags |= VM_LOCKED;
	else
		buf->vm_flags &= ~VM_LOCKED;

out:
	spin_unlock(&current->mm->page_table_lock);
	up_write(&current->mm->mmap_sem);
	return ret;
}

/*
 * isp_video_buffer_sglist_kernel - Build a scatter list for a vmalloc'ed buffer
 *
 * Iterate over the vmalloc'ed area and create a scatter list entry for every
 * page.
 */
static int isp_video_buffer_sglist_kernel(struct isp_video_buffer *buf)
{
	struct scatterlist *sglist;
	unsigned int npages;
	unsigned int i;
	void *addr;

	addr = buf->vaddr;
	npages = PAGE_ALIGN(buf->vbuf.length) >> PAGE_SHIFT;

	sglist = vmalloc(npages * sizeof(*sglist));
	if (sglist == NULL)
		return -ENOMEM;

	sg_init_table(sglist, npages);

	for (i = 0; i < npages; ++i, addr += PAGE_SIZE) {
		struct page *page = vmalloc_to_page(addr);

		if (page == NULL || PageHighMem(page)) {
			vfree(sglist);
			return -EINVAL;
		}

		sg_set_page(&sglist[i], page, PAGE_SIZE, 0);
	}

	buf->sglen = npages;
	buf->sglist = sglist;

	return 0;
}

/*
 * isp_video_buffer_sglist_user - Build a scatter list for a userspace buffer
 *
 * Walk the buffer pages list and create a 1:1 mapping to a scatter list.
 */
static int isp_video_buffer_sglist_user(struct isp_video_buffer *buf)
{
	struct scatterlist *sglist;
	unsigned int offset = buf->offset;
	unsigned int i;

	sglist = vmalloc(buf->npages * sizeof(*sglist));
	if (sglist == NULL)
		return -ENOMEM;

	sg_init_table(sglist, buf->npages);

	for (i = 0; i < buf->npages; ++i) {
		if (PageHighMem(buf->pages[i])) {
			vfree(sglist);
			return -EINVAL;
		}

		sg_set_page(&sglist[i], buf->pages[i], PAGE_SIZE - offset,
			    offset);
		offset = 0;
	}

	buf->sglen = buf->npages;
	buf->sglist = sglist;

	return 0;
}

/*
 * isp_video_buffer_sglist_pfnmap - Build a scatter list for a VM_PFNMAP buffer
 *
 * Create a scatter list of physically contiguous pages starting at the buffer
 * memory physical address.
 */
static int isp_video_buffer_sglist_pfnmap(struct isp_video_buffer *buf)
{
	struct scatterlist *sglist;
	unsigned int offset = buf->offset;
	unsigned long pfn = buf->paddr >> PAGE_SHIFT;
	unsigned int i;

	sglist = vmalloc(buf->npages * sizeof(*sglist));
	if (sglist == NULL)
		return -ENOMEM;

	sg_init_table(sglist, buf->npages);

	for (i = 0; i < buf->npages; ++i, ++pfn) {
		sg_set_page(&sglist[i], pfn_to_page(pfn), PAGE_SIZE - offset,
			    offset);
		/* PFNMAP buffers will not get DMA-mapped, set the DMA address
		 * manually.
		 */
		sg_dma_address(&sglist[i]) = (pfn << PAGE_SHIFT) + offset;
		offset = 0;
	}

	buf->sglen = buf->npages;
	buf->sglist = sglist;

	return 0;
}

/*
 * isp_video_buffer_cleanup - Release pages for a userspace VMA.
 *
 * Release pages locked by a call isp_video_buffer_prepare_user and free the
 * pages table.
 */
static void isp_video_buffer_cleanup(struct isp_video_buffer *buf)
{
	enum dma_data_direction direction;
	unsigned int i;

	if (buf->queue->ops->buffer_cleanup)
		buf->queue->ops->buffer_cleanup(buf);

	if (!(buf->vm_flags & VM_PFNMAP)) {
		direction = buf->vbuf.type == V4L2_BUF_TYPE_VIDEO_CAPTURE
			  ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
		dma_unmap_sg(buf->queue->dev, buf->sglist, buf->sglen,
			     direction);
	}

	vfree(buf->sglist);
	buf->sglist = NULL;
	buf->sglen = 0;

	if (buf->pages != NULL) {
		isp_video_buffer_lock_vma(buf, 0);

		for (i = 0; i < buf->npages; ++i)
			page_cache_release(buf->pages[i]);

		vfree(buf->pages);
		buf->pages = NULL;
	}

	buf->npages = 0;
	buf->skip_cache = false;
}

/*
 * isp_video_buffer_prepare_user - Pin userspace VMA pages to memory.
 *
 * This function creates a list of pages for a userspace VMA. The number of
 * pages is first computed based on the buffer size, and pages are then
 * retrieved by a call to get_user_pages.
 *
 * Pages are pinned to memory by get_user_pages, making them available for DMA
 * transfers. However, due to memory management optimization, it seems the
 * get_user_pages doesn't guarantee that the pinned pages will not be written
 * to swap and removed from the userspace mapping(s). When this happens, a page
 * fault can be generated when accessing those unmapped pages.
 *
 * If the fault is triggered by a page table walk caused by VIPT cache
 * management operations, the page fault handler might oops if the MM semaphore
 * is held, as it can't handle kernel page faults in that case. To fix that, a
 * fixup entry needs to be added to the cache management code, or the userspace
 * VMA must be locked to avoid removing pages from the userspace mapping in the
 * first place.
 *
 * If the number of pages retrieved is smaller than the number required by the
 * buffer size, the function returns -EFAULT.
 */
static int isp_video_buffer_prepare_user(struct isp_video_buffer *buf)
{
	unsigned long data;
	unsigned int first;
	unsigned int last;
	int ret;

	data = buf->vbuf.m.userptr;
	first = (data & PAGE_MASK) >> PAGE_SHIFT;
	last = ((data + buf->vbuf.length - 1) & PAGE_MASK) >> PAGE_SHIFT;

	buf->offset = data & ~PAGE_MASK;
	buf->npages = last - first + 1;
	buf->pages = vmalloc(buf->npages * sizeof(buf->pages[0]));
	if (buf->pages == NULL)
		return -ENOMEM;

	down_read(&current->mm->mmap_sem);
	ret = get_user_pages(current, current->mm, data & PAGE_MASK,
			     buf->npages,
			     buf->vbuf.type == V4L2_BUF_TYPE_VIDEO_CAPTURE, 0,
			     buf->pages, NULL);
	up_read(&current->mm->mmap_sem);

	if (ret != buf->npages) {
		buf->npages = ret < 0 ? 0 : ret;
		isp_video_buffer_cleanup(buf);
		return -EFAULT;
	}

	ret = isp_video_buffer_lock_vma(buf, 1);
	if (ret < 0)
		isp_video_buffer_cleanup(buf);

	return ret;
}

/*
 * isp_video_buffer_prepare_pfnmap - Validate a VM_PFNMAP userspace buffer
 *
 * Userspace VM_PFNMAP buffers are supported only if they are contiguous in
 * memory and if they span a single VMA.
 *
 * Return 0 if the buffer is valid, or -EFAULT otherwise.
 */
static int isp_video_buffer_prepare_pfnmap(struct isp_video_buffer *buf)
{
	struct vm_area_struct *vma;
	unsigned long prev_pfn;
	unsigned long this_pfn;
	unsigned long start;
	unsigned long end;
	dma_addr_t pa = 0;
	int ret = -EFAULT;

	start = buf->vbuf.m.userptr;
	end = buf->vbuf.m.userptr + buf->vbuf.length - 1;

	buf->offset = start & ~PAGE_MASK;
	buf->npages = (end >> PAGE_SHIFT) - (start >> PAGE_SHIFT) + 1;
	buf->pages = NULL;

	down_read(&current->mm->mmap_sem);
	vma = find_vma(current->mm, start);
	if (vma == NULL || vma->vm_end < end)
		goto done;

	for (prev_pfn = 0; start <= end; start += PAGE_SIZE) {
		ret = follow_pfn(vma, start, &this_pfn);
		if (ret)
			goto done;

		if (prev_pfn == 0)
			pa = this_pfn << PAGE_SHIFT;
		else if (this_pfn != prev_pfn + 1) {
			ret = -EFAULT;
			goto done;
		}

		prev_pfn = this_pfn;
	}

	buf->paddr = pa + buf->offset;
	ret = 0;

done:
	up_read(&current->mm->mmap_sem);
	return ret;
}

/*
 * isp_video_buffer_prepare_vm_flags - Get VMA flags for a userspace address
 *
 * This function locates the VMAs for the buffer's userspace address and checks
 * that their flags match. The only flag that we need to care for at the moment
 * is VM_PFNMAP.
 *
 * The buffer vm_flags field is set to the first VMA flags.
 *
 * Return -EFAULT if no VMA can be found for part of the buffer, or if the VMAs
 * have incompatible flags.
 */
static int isp_video_buffer_prepare_vm_flags(struct isp_video_buffer *buf)
{
	struct vm_area_struct *vma;
	pgprot_t uninitialized_var(vm_page_prot);
	unsigned long start;
	unsigned long end;
	int ret = -EFAULT;

	start = buf->vbuf.m.userptr;
	end = buf->vbuf.m.userptr + buf->vbuf.length - 1;

	down_read(&current->mm->mmap_sem);

	do {
		vma = find_vma(current->mm, start);
		if (vma == NULL)
			goto done;

		if (start == buf->vbuf.m.userptr) {
			buf->vm_flags = vma->vm_flags;
			vm_page_prot = vma->vm_page_prot;
		}

		if ((buf->vm_flags ^ vma->vm_flags) & VM_PFNMAP)
			goto done;

		if (vm_page_prot != vma->vm_page_prot)
			goto done;

		start = vma->vm_end + 1;
	} while (vma->vm_end < end);

	/* Skip cache management to enhance performances for non-cached or
	 * write-combining buffers.
	 */
	if (vm_page_prot == pgprot_noncached(vm_page_prot) ||
	    vm_page_prot == pgprot_writecombine(vm_page_prot))
		buf->skip_cache = true;

	ret = 0;

done:
	up_read(&current->mm->mmap_sem);
	return ret;
}

/*
 * isp_video_buffer_prepare - Make a buffer ready for operation
 *
 * Preparing a buffer involves:
 *
 * - validating VMAs (userspace buffers only)
 * - locking pages and VMAs into memory (userspace buffers only)
 * - building page and scatter-gather lists
 * - mapping buffers for DMA operation
 * - performing driver-specific preparation
 *
 * The function must be called in userspace context with a valid mm context
 * (this excludes cleanup paths such as sys_close when the userspace process
 * segfaults).
 */
static int isp_video_buffer_prepare(struct isp_video_buffer *buf)
{
	enum dma_data_direction direction;
	int ret;

	switch (buf->vbuf.memory) {
	case V4L2_MEMORY_MMAP:
		ret = isp_video_buffer_sglist_kernel(buf);
		break;

	case V4L2_MEMORY_USERPTR:
		ret = isp_video_buffer_prepare_vm_flags(buf);
		if (ret < 0)
			return ret;

		if (buf->vm_flags & VM_PFNMAP) {
			ret = isp_video_buffer_prepare_pfnmap(buf);
			if (ret < 0)
				return ret;

			ret = isp_video_buffer_sglist_pfnmap(buf);
		} else {
			ret = isp_video_buffer_prepare_user(buf);
			if (ret < 0)
				return ret;

			ret = isp_video_buffer_sglist_user(buf);
		}
		break;

	default:
		return -EINVAL;
	}

	if (ret < 0)
		goto done;

	if (!(buf->vm_flags & VM_PFNMAP)) {
		direction = buf->vbuf.type == V4L2_BUF_TYPE_VIDEO_CAPTURE
			  ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
		ret = dma_map_sg(buf->queue->dev, buf->sglist, buf->sglen,
				 direction);
		if (ret != buf->sglen) {
			ret = -EFAULT;
			goto done;
		}
	}

	if (buf->queue->ops->buffer_prepare)
		ret = buf->queue->ops->buffer_prepare(buf);

done:
	if (ret < 0) {
		isp_video_buffer_cleanup(buf);
		return ret;
	}

	return ret;
}

/*
 * isp_video_queue_query - Query the status of a given buffer
 *
 * Locking: must be called with the queue lock held.
 */
static void isp_video_buffer_query(struct isp_video_buffer *buf,
				   struct v4l2_buffer *vbuf)
{
	memcpy(vbuf, &buf->vbuf, sizeof(*vbuf));

	if (buf->vma_use_count)
		vbuf->flags |= V4L2_BUF_FLAG_MAPPED;

	switch (buf->state) {
	case ISP_BUF_STATE_ERROR:
		vbuf->flags |= V4L2_BUF_FLAG_ERROR;
	case ISP_BUF_STATE_DONE:
		vbuf->flags |= V4L2_BUF_FLAG_DONE;
	case ISP_BUF_STATE_QUEUED:
	case ISP_BUF_STATE_ACTIVE:
		vbuf->flags |= V4L2_BUF_FLAG_QUEUED;
		break;
	case ISP_BUF_STATE_IDLE:
	default:
		break;
	}
}

/*
 * isp_video_buffer_wait - Wait for a buffer to be ready
 *
 * In non-blocking mode, return immediately with 0 if the buffer is ready or
 * -EAGAIN if the buffer is in the QUEUED or ACTIVE state.
 *
 * In blocking mode, wait (interruptibly but with no timeout) on the buffer wait
 * queue using the same condition.
 */
static int isp_video_buffer_wait(struct isp_video_buffer *buf, int nonblocking)
{
	if (nonblocking) {
		return (buf->state != ISP_BUF_STATE_QUEUED &&
			buf->state != ISP_BUF_STATE_ACTIVE)
			? 0 : -EAGAIN;
	}

	return wait_event_interruptible(buf->wait,
		buf->state != ISP_BUF_STATE_QUEUED &&
		buf->state != ISP_BUF_STATE_ACTIVE);
}

/* -----------------------------------------------------------------------------
 * Queue management
 */

/*
 * isp_video_queue_free - Free video buffers memory
 *
 * Buffers can only be freed if the queue isn't streaming and if no buffer is
 * mapped to userspace. Return -EBUSY if those conditions aren't statisfied.
 *
 * This function must be called with the queue lock held.
 */
static int isp_video_queue_free(struct isp_video_queue *queue)
{
	unsigned int i;

	if (queue->streaming)
		return -EBUSY;

	for (i = 0; i < queue->count; ++i) {
		if (queue->buffers[i]->vma_use_count != 0)
			return -EBUSY;
	}

	for (i = 0; i < queue->count; ++i) {
		struct isp_video_buffer *buf = queue->buffers[i];

		isp_video_buffer_cleanup(buf);

		vfree(buf->vaddr);
		buf->vaddr = NULL;

		kfree(buf);
		queue->buffers[i] = NULL;
	}

	INIT_LIST_HEAD(&queue->queue);
	queue->count = 0;
	return 0;
}

/*
 * isp_video_queue_alloc - Allocate video buffers memory
 *
 * This function must be called with the queue lock held.
 */
static int isp_video_queue_alloc(struct isp_video_queue *queue,
				 unsigned int nbuffers,
				 unsigned int size, enum v4l2_memory memory)
{
	struct isp_video_buffer *buf;
	unsigned int i;
	void *mem;
	int ret;

	/* Start by freeing the buffers. */
	ret = isp_video_queue_free(queue);
	if (ret < 0)
		return ret;

	/* Bail out if no buffers should be allocated. */
	if (nbuffers == 0)
		return 0;

	/* Initialize the allocated buffers. */
	for (i = 0; i < nbuffers; ++i) {
		buf = kzalloc(queue->bufsize, GFP_KERNEL);
		if (buf == NULL)
			break;

		if (memory == V4L2_MEMORY_MMAP) {
			/* Allocate video buffers memory for mmap mode. Align
			 * the size to the page size.
			 */
			mem = vmalloc_32_user(PAGE_ALIGN(size));
			if (mem == NULL) {
				kfree(buf);
				break;
			}

			buf->vbuf.m.offset = i * PAGE_ALIGN(size);
			buf->vaddr = mem;
		}

		buf->vbuf.index = i;
		buf->vbuf.length = size;
		buf->vbuf.type = queue->type;
		buf->vbuf.flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
		buf->vbuf.field = V4L2_FIELD_NONE;
		buf->vbuf.memory = memory;

		buf->queue = queue;
		init_waitqueue_head(&buf->wait);

		queue->buffers[i] = buf;
	}

	if (i == 0)
		return -ENOMEM;

	queue->count = i;
	return nbuffers;
}

/**
 * omap3isp_video_queue_cleanup - Clean up the video buffers queue
 * @queue: Video buffers queue
 *
 * Free all allocated resources and clean up the video buffers queue. The queue
 * must not be busy (no ongoing video stream) and buffers must have been
 * unmapped.
 *
 * Return 0 on success or -EBUSY if the queue is busy or buffers haven't been
 * unmapped.
 */
int omap3isp_video_queue_cleanup(struct isp_video_queue *queue)
{
	return isp_video_queue_free(queue);
}

/**
 * omap3isp_video_queue_init - Initialize the video buffers queue
 * @queue: Video buffers queue
 * @type: V4L2 buffer type (capture or output)
 * @ops: Driver-specific queue operations
 * @dev: Device used for DMA operations
 * @bufsize: Size of the driver-specific buffer structure
 *
 * Initialize the video buffers queue with the supplied parameters.
 *
 * The queue type must be one of V4L2_BUF_TYPE_VIDEO_CAPTURE or
 * V4L2_BUF_TYPE_VIDEO_OUTPUT. Other buffer types are not supported yet.
 *
 * Buffer objects will be allocated using the given buffer size to allow room
 * for driver-specific fields. Driver-specific buffer structures must start
 * with a struct isp_video_buffer field. Drivers with no driver-specific buffer
 * structure must pass the size of the isp_video_buffer structure in the bufsize
 * parameter.
 *
 * Return 0 on success.
 */
int omap3isp_video_queue_init(struct isp_video_queue *queue,
			      enum v4l2_buf_type type,
			      const struct isp_video_queue_operations *ops,
			      struct device *dev, unsigned int bufsize)
{
	INIT_LIST_HEAD(&queue->queue);
	mutex_init(&queue->lock);
	spin_lock_init(&queue->irqlock);

	queue->type = type;
	queue->ops = ops;
	queue->dev = dev;
	queue->bufsize = bufsize;

	return 0;
}

/* -----------------------------------------------------------------------------
 * V4L2 operations
 */

/**
 * omap3isp_video_queue_reqbufs - Allocate video buffers memory
 *
 * This function is intended to be used as a VIDIOC_REQBUFS ioctl handler. It
 * allocated video buffer objects and, for MMAP buffers, buffer memory.
 *
 * If the number of buffers is 0, all buffers are freed and the function returns
 * without performing any allocation.
 *
 * If the number of buffers is not 0, currently allocated buffers (if any) are
 * freed and the requested number of buffers are allocated. Depending on
 * driver-specific requirements and on memory availability, a number of buffer
 * smaller or bigger than requested can be allocated. This isn't considered as
 * an error.
 *
 * Return 0 on success or one of the following error codes:
 *
 * -EINVAL if the buffer type or index are invalid
 * -EBUSY if the queue is busy (streaming or buffers mapped)
 * -ENOMEM if the buffers can't be allocated due to an out-of-memory condition
 */
int omap3isp_video_queue_reqbufs(struct isp_video_queue *queue,
				 struct v4l2_requestbuffers *rb)
{
	unsigned int nbuffers = rb->count;
	unsigned int size;
	int ret;

	if (rb->type != queue->type)
		return -EINVAL;

	queue->ops->queue_prepare(queue, &nbuffers, &size);
	if (size == 0)
		return -EINVAL;

	nbuffers = min_t(unsigned int, nbuffers, ISP_VIDEO_MAX_BUFFERS);

	mutex_lock(&queue->lock);

	ret = isp_video_queue_alloc(queue, nbuffers, size, rb->memory);
	if (ret < 0)
		goto done;

	rb->count = ret;
	ret = 0;

done:
	mutex_unlock(&queue->lock);
	return ret;
}

/**
 * omap3isp_video_queue_querybuf - Query the status of a buffer in a queue
 *
 * This function is intended to be used as a VIDIOC_QUERYBUF ioctl handler. It
 * returns the status of a given video buffer.
 *
 * Return 0 on success or -EINVAL if the buffer type or index are invalid.
 */
int omap3isp_video_queue_querybuf(struct isp_video_queue *queue,
				  struct v4l2_buffer *vbuf)
{
	struct isp_video_buffer *buf;
	int ret = 0;

	if (vbuf->type != queue->type)
		return -EINVAL;

	mutex_lock(&queue->lock);

	if (vbuf->index >= queue->count) {
		ret = -EINVAL;
		goto done;
	}

	buf = queue->buffers[vbuf->index];
	isp_video_buffer_query(buf, vbuf);

done:
	mutex_unlock(&queue->lock);
	return ret;
}

/**
 * omap3isp_video_queue_qbuf - Queue a buffer
 *
 * This function is intended to be used as a VIDIOC_QBUF ioctl handler.
 *
 * The v4l2_buffer structure passed from userspace is first sanity tested. If
 * sane, the buffer is then processed and added to the main queue and, if the
 * queue is streaming, to the IRQ queue.
 *
 * Before being enqueued, USERPTR buffers are checked for address changes. If
 * the buffer has a different userspace address, the old memory area is unlocked
 * and the new memory area is locked.
 */
int omap3isp_video_queue_qbuf(struct isp_video_queue *queue,
			      struct v4l2_buffer *vbuf)
{
	struct isp_video_buffer *buf;
	unsigned long flags;
	int ret = -EINVAL;

	if (vbuf->type != queue->type)
		goto done;

	mutex_lock(&queue->lock);

	if (vbuf->index >= queue->count)
		goto done;

	buf = queue->buffers[vbuf->index];

	if (vbuf->memory != buf->vbuf.memory)
		goto done;

	if (buf->state != ISP_BUF_STATE_IDLE)
		goto done;

	if (vbuf->memory == V4L2_MEMORY_USERPTR &&
	    vbuf->length < buf->vbuf.length)
		goto done;

	if (vbuf->memory == V4L2_MEMORY_USERPTR &&
	    vbuf->m.userptr != buf->vbuf.m.userptr) {
		isp_video_buffer_cleanup(buf);
		buf->vbuf.m.userptr = vbuf->m.userptr;
		buf->prepared = 0;
	}

	if (!buf->prepared) {
		ret = isp_video_buffer_prepare(buf);
		if (ret < 0)
			goto done;
		buf->prepared = 1;
	}

	isp_video_buffer_cache_sync(buf);

	buf->state = ISP_BUF_STATE_QUEUED;
	list_add_tail(&buf->stream, &queue->queue);

	if (queue->streaming) {
		spin_lock_irqsave(&queue->irqlock, flags);
		queue->ops->buffer_queue(buf);
		spin_unlock_irqrestore(&queue->irqlock, flags);
	}

	ret = 0;

done:
	mutex_unlock(&queue->lock);
	return ret;
}

/**
 * omap3isp_video_queue_dqbuf - Dequeue a buffer
 *
 * This function is intended to be used as a VIDIOC_DQBUF ioctl handler.
 *
 * Wait until a buffer is ready to be dequeued, remove it from the queue and
 * copy its information to the v4l2_buffer structure.
 *
 * If the nonblocking argument is not zero and no buffer is ready, return
 * -EAGAIN immediately instead of waiting.
 *
 * If no buffer has been enqueued, or if the requested buffer type doesn't match
 * the queue type, return -EINVAL.
 */
int omap3isp_video_queue_dqbuf(struct isp_video_queue *queue,
			       struct v4l2_buffer *vbuf, int nonblocking)
{
	struct isp_video_buffer *buf;
	int ret;

	if (vbuf->type != queue->type)
		return -EINVAL;

	mutex_lock(&queue->lock);

	if (list_empty(&queue->queue)) {
		ret = -EINVAL;
		goto done;
	}

	buf = list_first_entry(&queue->queue, struct isp_video_buffer, stream);
	ret = isp_video_buffer_wait(buf, nonblocking);
	if (ret < 0)
		goto done;

	list_del(&buf->stream);

	isp_video_buffer_query(buf, vbuf);
	buf->state = ISP_BUF_STATE_IDLE;
	vbuf->flags &= ~V4L2_BUF_FLAG_QUEUED;

done:
	mutex_unlock(&queue->lock);
	return ret;
}

/**
 * omap3isp_video_queue_streamon - Start streaming
 *
 * This function is intended to be used as a VIDIOC_STREAMON ioctl handler. It
 * starts streaming on the queue and calls the buffer_queue operation for all
 * queued buffers.
 *
 * Return 0 on success.
 */
int omap3isp_video_queue_streamon(struct isp_video_queue *queue)
{
	struct isp_video_buffer *buf;
	unsigned long flags;

	mutex_lock(&queue->lock);

	if (queue->streaming)
		goto done;

	queue->streaming = 1;

	spin_lock_irqsave(&queue->irqlock, flags);
	list_for_each_entry(buf, &queue->queue, stream)
		queue->ops->buffer_queue(buf);
	spin_unlock_irqrestore(&queue->irqlock, flags);

done:
	mutex_unlock(&queue->lock);
	return 0;
}

/**
 * omap3isp_video_queue_streamoff - Stop streaming
 *
 * This function is intended to be used as a VIDIOC_STREAMOFF ioctl handler. It
 * stops streaming on the queue and wakes up all the buffers.
 *
 * Drivers must stop the hardware and synchronize with interrupt handlers and/or
 * delayed works before calling this function to make sure no buffer will be
 * touched by the driver and/or hardware.
 */
void omap3isp_video_queue_streamoff(struct isp_video_queue *queue)
{
	struct isp_video_buffer *buf;
	unsigned long flags;
	unsigned int i;

	mutex_lock(&queue->lock);

	if (!queue->streaming)
		goto done;

	queue->streaming = 0;

	spin_lock_irqsave(&queue->irqlock, flags);
	for (i = 0; i < queue->count; ++i) {
		buf = queue->buffers[i];

		if (buf->state == ISP_BUF_STATE_ACTIVE)
			wake_up(&buf->wait);

		buf->state = ISP_BUF_STATE_IDLE;
	}
	spin_unlock_irqrestore(&queue->irqlock, flags);

	INIT_LIST_HEAD(&queue->queue);

done:
	mutex_unlock(&queue->lock);
}

/**
 * omap3isp_video_queue_discard_done - Discard all buffers marked as DONE
 *
 * This function is intended to be used with suspend/resume operations. It
 * discards all 'done' buffers as they would be too old to be requested after
 * resume.
 *
 * Drivers must stop the hardware and synchronize with interrupt handlers and/or
 * delayed works before calling this function to make sure no buffer will be
 * touched by the driver and/or hardware.
 */
void omap3isp_video_queue_discard_done(struct isp_video_queue *queue)
{
	struct isp_video_buffer *buf;
	unsigned int i;

	mutex_lock(&queue->lock);

	if (!queue->streaming)
		goto done;

	for (i = 0; i < queue->count; ++i) {
		buf = queue->buffers[i];

		if (buf->state == ISP_BUF_STATE_DONE)
			buf->state = ISP_BUF_STATE_ERROR;
	}

done:
	mutex_unlock(&queue->lock);
}

static void isp_video_queue_vm_open(struct vm_area_struct *vma)
{
	struct isp_video_buffer *buf = vma->vm_private_data;

	buf->vma_use_count++;
}

static void isp_video_queue_vm_close(struct vm_area_struct *vma)
{
	struct isp_video_buffer *buf = vma->vm_private_data;

	buf->vma_use_count--;
}

static const struct vm_operations_struct isp_video_queue_vm_ops = {
	.open = isp_video_queue_vm_open,
	.close = isp_video_queue_vm_close,
};

/**
 * omap3isp_video_queue_mmap - Map buffers to userspace
 *
 * This function is intended to be used as an mmap() file operation handler. It
 * maps a buffer to userspace based on the VMA offset.
 *
 * Only buffers of memory type MMAP are supported.
 */
int omap3isp_video_queue_mmap(struct isp_video_queue *queue,
			 struct vm_area_struct *vma)
{
	struct isp_video_buffer *uninitialized_var(buf);
	unsigned long size;
	unsigned int i;
	int ret = 0;

	mutex_lock(&queue->lock);

	for (i = 0; i < queue->count; ++i) {
		buf = queue->buffers[i];
		if ((buf->vbuf.m.offset >> PAGE_SHIFT) == vma->vm_pgoff)
			break;
	}

	if (i == queue->count) {
		ret = -EINVAL;
		goto done;
	}

	size = vma->vm_end - vma->vm_start;

	if (buf->vbuf.memory != V4L2_MEMORY_MMAP ||
	    size != PAGE_ALIGN(buf->vbuf.length)) {
		ret = -EINVAL;
		goto done;
	}

	ret = remap_vmalloc_range(vma, buf->vaddr, 0);
	if (ret < 0)
		goto done;

	vma->vm_ops = &isp_video_queue_vm_ops;
	vma->vm_private_data = buf;
	isp_video_queue_vm_open(vma);

done:
	mutex_unlock(&queue->lock);
	return ret;
}

/**
 * omap3isp_video_queue_poll - Poll video queue state
 *
 * This function is intended to be used as a poll() file operation handler. It
 * polls the state of the video buffer at the front of the queue and returns an
 * events mask.
 *
 * If no buffer is present at the front of the queue, POLLERR is returned.
 */
unsigned int omap3isp_video_queue_poll(struct isp_video_queue *queue,
				       struct file *file, poll_table *wait)
{
	struct isp_video_buffer *buf;
	unsigned int mask = 0;

	mutex_lock(&queue->lock);
	if (list_empty(&queue->queue)) {
		mask |= POLLERR;
		goto done;
	}
	buf = list_first_entry(&queue->queue, struct isp_video_buffer, stream);

	poll_wait(file, &buf->wait, wait);
	if (buf->state == ISP_BUF_STATE_DONE ||
	    buf->state == ISP_BUF_STATE_ERROR) {
		if (queue->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
			mask |= POLLIN | POLLRDNORM;
		else
			mask |= POLLOUT | POLLWRNORM;
	}

done:
	mutex_unlock(&queue->lock);
	return mask;
}