]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - drivers/media/video/videobuf-dma-contig.c
Merge branch 'for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab...
[karo-tx-linux.git] / drivers / media / video / videobuf-dma-contig.c
index 08d3e17c450a322e3a57cd2fc8a1877bea599d4c..d09ce83a9429af98060b7d3dd27e21923f2431ea 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/init.h>
 #include <linux/module.h>
 #include <linux/mm.h>
+#include <linux/pagemap.h>
 #include <linux/dma-mapping.h>
 #include <media/videobuf-dma-contig.h>
 
@@ -25,6 +26,7 @@ struct videobuf_dma_contig_memory {
        void *vaddr;
        dma_addr_t dma_handle;
        unsigned long size;
+       int is_userptr;
 };
 
 #define MAGIC_DC_MEM 0x0733ac61
@@ -108,6 +110,82 @@ static struct vm_operations_struct videobuf_vm_ops = {
        .close    = videobuf_vm_close,
 };
 
+/**
+ * videobuf_dma_contig_user_put() - reset pointer to user space buffer
+ * @mem: per-buffer private videobuf-dma-contig data
+ *
+ * This function resets the user space pointer
+ */
+static void videobuf_dma_contig_user_put(struct videobuf_dma_contig_memory *mem)
+{
+       mem->is_userptr = 0;
+       mem->dma_handle = 0;
+       mem->size = 0;
+}
+
+/**
+ * videobuf_dma_contig_user_get() - setup user space memory pointer
+ * @mem: per-buffer private videobuf-dma-contig data
+ * @vb: video buffer to map
+ *
+ * This function validates and sets up a pointer to user space memory.
+ * Only physically contiguous pfn-mapped memory is accepted.
+ *
+ * Returns 0 if successful.
+ */
+static int videobuf_dma_contig_user_get(struct videobuf_dma_contig_memory *mem,
+                                       struct videobuf_buffer *vb)
+{
+       struct mm_struct *mm = current->mm;
+       struct vm_area_struct *vma;
+       unsigned long prev_pfn, this_pfn;
+       unsigned long pages_done, user_address;
+       int ret;
+
+       mem->size = PAGE_ALIGN(vb->size);
+       mem->is_userptr = 0;
+       ret = -EINVAL;
+
+       down_read(&mm->mmap_sem);
+
+       vma = find_vma(mm, vb->baddr);
+       if (!vma)
+               goto out_up;
+
+       if ((vb->baddr + mem->size) > vma->vm_end)
+               goto out_up;
+
+       pages_done = 0;
+       prev_pfn = 0; /* kill warning */
+       user_address = vb->baddr;
+
+       while (pages_done < (mem->size >> PAGE_SHIFT)) {
+               ret = follow_pfn(vma, user_address, &this_pfn);
+               if (ret)
+                       break;
+
+               if (pages_done == 0)
+                       mem->dma_handle = this_pfn << PAGE_SHIFT;
+               else if (this_pfn != (prev_pfn + 1))
+                       ret = -EFAULT;
+
+               if (ret)
+                       break;
+
+               prev_pfn = this_pfn;
+               user_address += PAGE_SIZE;
+               pages_done++;
+       }
+
+       if (!ret)
+               mem->is_userptr = 1;
+
+ out_up:
+       up_read(&current->mm->mmap_sem);
+
+       return ret;
+}
+
 static void *__videobuf_alloc(size_t size)
 {
        struct videobuf_dma_contig_memory *mem;
@@ -154,12 +232,11 @@ static int __videobuf_iolock(struct videobuf_queue *q,
        case V4L2_MEMORY_USERPTR:
                dev_dbg(q->dev, "%s memory method USERPTR\n", __func__);
 
-               /* The only USERPTR currently supported is the one needed for
-                  read() method.
-                */
+               /* handle pointer from user space */
                if (vb->baddr)
-                       return -EINVAL;
+                       return videobuf_dma_contig_user_get(mem, vb);
 
+               /* allocate memory for the read() method */
                mem->size = PAGE_ALIGN(vb->size);
                mem->vaddr = dma_alloc_coherent(q->dev, mem->size,
                                                &mem->dma_handle, GFP_KERNEL);
@@ -386,7 +463,7 @@ void videobuf_dma_contig_free(struct videobuf_queue *q,
           So, it should free memory only if the memory were allocated for
           read() operation.
         */
-       if ((buf->memory != V4L2_MEMORY_USERPTR) || buf->baddr)
+       if (buf->memory != V4L2_MEMORY_USERPTR)
                return;
 
        if (!mem)
@@ -394,6 +471,13 @@ void videobuf_dma_contig_free(struct videobuf_queue *q,
 
        MAGIC_CHECK(mem->magic, MAGIC_DC_MEM);
 
+       /* handle user space pointer case */
+       if (buf->baddr) {
+               videobuf_dma_contig_user_put(mem);
+               return;
+       }
+
+       /* read() method */
        dma_free_coherent(q->dev, mem->size, mem->vaddr, mem->dma_handle);
        mem->vaddr = NULL;
 }