Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / media / v4l2-core / videobuf-vmalloc.c
diff --git a/kernel/drivers/media/v4l2-core/videobuf-vmalloc.c b/kernel/drivers/media/v4l2-core/videobuf-vmalloc.c
new file mode 100644 (file)
index 0000000..2ff7fcc
--- /dev/null
@@ -0,0 +1,349 @@
+/*
+ * helper functions for vmalloc video4linux capture buffers
+ *
+ * The functions expect the hardware being able to scatter gather
+ * (i.e. the buffers are not linear in physical memory, but fragmented
+ * into PAGE_SIZE chunks).  They also assume the driver does not need
+ * to touch the video data.
+ *
+ * (c) 2007 Mauro Carvalho Chehab, <mchehab@infradead.org>
+ *
+ * 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
+ * the Free Software Foundation; either version 2
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+
+#include <linux/pci.h>
+#include <linux/vmalloc.h>
+#include <linux/pagemap.h>
+#include <asm/page.h>
+#include <asm/pgtable.h>
+
+#include <media/videobuf-vmalloc.h>
+
+#define MAGIC_DMABUF   0x17760309
+#define MAGIC_VMAL_MEM 0x18221223
+
+#define MAGIC_CHECK(is, should)                                                \
+       if (unlikely((is) != (should))) {                               \
+               printk(KERN_ERR "magic mismatch: %x (expected %x)\n",   \
+                               is, should);                            \
+               BUG();                                                  \
+       }
+
+static int debug;
+module_param(debug, int, 0644);
+
+MODULE_DESCRIPTION("helper module to manage video4linux vmalloc buffers");
+MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@infradead.org>");
+MODULE_LICENSE("GPL");
+
+#define dprintk(level, fmt, arg...)                                    \
+       if (debug >= level)                                             \
+               printk(KERN_DEBUG "vbuf-vmalloc: " fmt , ## arg)
+
+
+/***************************************************************************/
+
+static void videobuf_vm_open(struct vm_area_struct *vma)
+{
+       struct videobuf_mapping *map = vma->vm_private_data;
+
+       dprintk(2, "vm_open %p [count=%u,vma=%08lx-%08lx]\n", map,
+               map->count, vma->vm_start, vma->vm_end);
+
+       map->count++;
+}
+
+static void videobuf_vm_close(struct vm_area_struct *vma)
+{
+       struct videobuf_mapping *map = vma->vm_private_data;
+       struct videobuf_queue *q = map->q;
+       int i;
+
+       dprintk(2, "vm_close %p [count=%u,vma=%08lx-%08lx]\n", map,
+               map->count, vma->vm_start, vma->vm_end);
+
+       map->count--;
+       if (0 == map->count) {
+               struct videobuf_vmalloc_memory *mem;
+
+               dprintk(1, "munmap %p q=%p\n", map, q);
+               videobuf_queue_lock(q);
+
+               /* We need first to cancel streams, before unmapping */
+               if (q->streaming)
+                       videobuf_queue_cancel(q);
+
+               for (i = 0; i < VIDEO_MAX_FRAME; i++) {
+                       if (NULL == q->bufs[i])
+                               continue;
+
+                       if (q->bufs[i]->map != map)
+                               continue;
+
+                       mem = q->bufs[i]->priv;
+                       if (mem) {
+                               /* This callback is called only if kernel has
+                                  allocated memory and this memory is mmapped.
+                                  In this case, memory should be freed,
+                                  in order to do memory unmap.
+                                */
+
+                               MAGIC_CHECK(mem->magic, MAGIC_VMAL_MEM);
+
+                               /* vfree is not atomic - can't be
+                                  called with IRQ's disabled
+                                */
+                               dprintk(1, "%s: buf[%d] freeing (%p)\n",
+                                       __func__, i, mem->vaddr);
+
+                               vfree(mem->vaddr);
+                               mem->vaddr = NULL;
+                       }
+
+                       q->bufs[i]->map   = NULL;
+                       q->bufs[i]->baddr = 0;
+               }
+
+               kfree(map);
+
+               videobuf_queue_unlock(q);
+       }
+
+       return;
+}
+
+static const struct vm_operations_struct videobuf_vm_ops = {
+       .open     = videobuf_vm_open,
+       .close    = videobuf_vm_close,
+};
+
+/* ---------------------------------------------------------------------
+ * vmalloc handlers for the generic methods
+ */
+
+/* Allocated area consists on 3 parts:
+       struct video_buffer
+       struct <driver>_buffer (cx88_buffer, saa7134_buf, ...)
+       struct videobuf_dma_sg_memory
+ */
+
+static struct videobuf_buffer *__videobuf_alloc_vb(size_t size)
+{
+       struct videobuf_vmalloc_memory *mem;
+       struct videobuf_buffer *vb;
+
+       vb = kzalloc(size + sizeof(*mem), GFP_KERNEL);
+       if (!vb)
+               return vb;
+
+       mem = vb->priv = ((char *)vb) + size;
+       mem->magic = MAGIC_VMAL_MEM;
+
+       dprintk(1, "%s: allocated at %p(%ld+%ld) & %p(%ld)\n",
+               __func__, vb, (long)sizeof(*vb), (long)size - sizeof(*vb),
+               mem, (long)sizeof(*mem));
+
+       return vb;
+}
+
+static int __videobuf_iolock(struct videobuf_queue *q,
+                            struct videobuf_buffer *vb,
+                            struct v4l2_framebuffer *fbuf)
+{
+       struct videobuf_vmalloc_memory *mem = vb->priv;
+       int pages;
+
+       BUG_ON(!mem);
+
+       MAGIC_CHECK(mem->magic, MAGIC_VMAL_MEM);
+
+       switch (vb->memory) {
+       case V4L2_MEMORY_MMAP:
+               dprintk(1, "%s memory method MMAP\n", __func__);
+
+               /* All handling should be done by __videobuf_mmap_mapper() */
+               if (!mem->vaddr) {
+                       printk(KERN_ERR "memory is not alloced/mmapped.\n");
+                       return -EINVAL;
+               }
+               break;
+       case V4L2_MEMORY_USERPTR:
+               pages = PAGE_ALIGN(vb->size);
+
+               dprintk(1, "%s memory method USERPTR\n", __func__);
+
+               if (vb->baddr) {
+                       printk(KERN_ERR "USERPTR is currently not supported\n");
+                       return -EINVAL;
+               }
+
+               /* The only USERPTR currently supported is the one needed for
+                * read() method.
+                */
+
+               mem->vaddr = vmalloc_user(pages);
+               if (!mem->vaddr) {
+                       printk(KERN_ERR "vmalloc (%d pages) failed\n", pages);
+                       return -ENOMEM;
+               }
+               dprintk(1, "vmalloc is at addr %p (%d pages)\n",
+                       mem->vaddr, pages);
+
+#if 0
+               int rc;
+               /* Kernel userptr is used also by read() method. In this case,
+                  there's no need to remap, since data will be copied to user
+                */
+               if (!vb->baddr)
+                       return 0;
+
+               /* FIXME: to properly support USERPTR, remap should occur.
+                  The code below won't work, since mem->vma = NULL
+                */
+               /* Try to remap memory */
+               rc = remap_vmalloc_range(mem->vma, (void *)vb->baddr, 0);
+               if (rc < 0) {
+                       printk(KERN_ERR "mmap: remap failed with error %d", rc);
+                       return -ENOMEM;
+               }
+#endif
+
+               break;
+       case V4L2_MEMORY_OVERLAY:
+       default:
+               dprintk(1, "%s memory method OVERLAY/unknown\n", __func__);
+
+               /* Currently, doesn't support V4L2_MEMORY_OVERLAY */
+               printk(KERN_ERR "Memory method currently unsupported.\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int __videobuf_mmap_mapper(struct videobuf_queue *q,
+                                 struct videobuf_buffer *buf,
+                                 struct vm_area_struct *vma)
+{
+       struct videobuf_vmalloc_memory *mem;
+       struct videobuf_mapping *map;
+       int retval, pages;
+
+       dprintk(1, "%s\n", __func__);
+
+       /* create mapping + update buffer list */
+       map = kzalloc(sizeof(struct videobuf_mapping), GFP_KERNEL);
+       if (NULL == map)
+               return -ENOMEM;
+
+       buf->map = map;
+       map->q     = q;
+
+       buf->baddr = vma->vm_start;
+
+       mem = buf->priv;
+       BUG_ON(!mem);
+       MAGIC_CHECK(mem->magic, MAGIC_VMAL_MEM);
+
+       pages = PAGE_ALIGN(vma->vm_end - vma->vm_start);
+       mem->vaddr = vmalloc_user(pages);
+       if (!mem->vaddr) {
+               printk(KERN_ERR "vmalloc (%d pages) failed\n", pages);
+               goto error;
+       }
+       dprintk(1, "vmalloc is at addr %p (%d pages)\n", mem->vaddr, pages);
+
+       /* Try to remap memory */
+       retval = remap_vmalloc_range(vma, mem->vaddr, 0);
+       if (retval < 0) {
+               printk(KERN_ERR "mmap: remap failed with error %d. ", retval);
+               vfree(mem->vaddr);
+               goto error;
+       }
+
+       vma->vm_ops          = &videobuf_vm_ops;
+       vma->vm_flags       |= VM_DONTEXPAND | VM_DONTDUMP;
+       vma->vm_private_data = map;
+
+       dprintk(1, "mmap %p: q=%p %08lx-%08lx (%lx) pgoff %08lx buf %d\n",
+               map, q, vma->vm_start, vma->vm_end,
+               (long int)buf->bsize,
+               vma->vm_pgoff, buf->i);
+
+       videobuf_vm_open(vma);
+
+       return 0;
+
+error:
+       mem = NULL;
+       kfree(map);
+       return -ENOMEM;
+}
+
+static struct videobuf_qtype_ops qops = {
+       .magic        = MAGIC_QTYPE_OPS,
+
+       .alloc_vb     = __videobuf_alloc_vb,
+       .iolock       = __videobuf_iolock,
+       .mmap_mapper  = __videobuf_mmap_mapper,
+       .vaddr        = videobuf_to_vmalloc,
+};
+
+void videobuf_queue_vmalloc_init(struct videobuf_queue *q,
+                        const struct videobuf_queue_ops *ops,
+                        struct device *dev,
+                        spinlock_t *irqlock,
+                        enum v4l2_buf_type type,
+                        enum v4l2_field field,
+                        unsigned int msize,
+                        void *priv,
+                        struct mutex *ext_lock)
+{
+       videobuf_queue_core_init(q, ops, dev, irqlock, type, field, msize,
+                                priv, &qops, ext_lock);
+}
+EXPORT_SYMBOL_GPL(videobuf_queue_vmalloc_init);
+
+void *videobuf_to_vmalloc(struct videobuf_buffer *buf)
+{
+       struct videobuf_vmalloc_memory *mem = buf->priv;
+       BUG_ON(!mem);
+       MAGIC_CHECK(mem->magic, MAGIC_VMAL_MEM);
+
+       return mem->vaddr;
+}
+EXPORT_SYMBOL_GPL(videobuf_to_vmalloc);
+
+void videobuf_vmalloc_free(struct videobuf_buffer *buf)
+{
+       struct videobuf_vmalloc_memory *mem = buf->priv;
+
+       /* mmapped memory can't be freed here, otherwise mmapped region
+          would be released, while still needed. In this case, the memory
+          release should happen inside videobuf_vm_close().
+          So, it should free memory only if the memory were allocated for
+          read() operation.
+        */
+       if ((buf->memory != V4L2_MEMORY_USERPTR) || buf->baddr)
+               return;
+
+       if (!mem)
+               return;
+
+       MAGIC_CHECK(mem->magic, MAGIC_VMAL_MEM);
+
+       vfree(mem->vaddr);
+       mem->vaddr = NULL;
+
+       return;
+}
+EXPORT_SYMBOL_GPL(videobuf_vmalloc_free);
+