linux/drivers/media/video/videobuf2-vmalloc.c
<<
>>
Prefs
   1/*
   2 * videobuf2-vmalloc.c - vmalloc memory allocator for videobuf2
   3 *
   4 * Copyright (C) 2010 Samsung Electronics
   5 *
   6 * Author: Pawel Osciak <pawel@osciak.com>
   7 *
   8 * This program is free software; you can redistribute it and/or modify
   9 * it under the terms of the GNU General Public License as published by
  10 * the Free Software Foundation.
  11 */
  12
  13#include <linux/module.h>
  14#include <linux/mm.h>
  15#include <linux/sched.h>
  16#include <linux/slab.h>
  17#include <linux/vmalloc.h>
  18
  19#include <media/videobuf2-core.h>
  20#include <media/videobuf2-memops.h>
  21
  22struct vb2_vmalloc_buf {
  23        void                            *vaddr;
  24        struct page                     **pages;
  25        int                             write;
  26        unsigned long                   size;
  27        unsigned int                    n_pages;
  28        atomic_t                        refcount;
  29        struct vb2_vmarea_handler       handler;
  30};
  31
  32static void vb2_vmalloc_put(void *buf_priv);
  33
  34static void *vb2_vmalloc_alloc(void *alloc_ctx, unsigned long size)
  35{
  36        struct vb2_vmalloc_buf *buf;
  37
  38        buf = kzalloc(sizeof(*buf), GFP_KERNEL);
  39        if (!buf)
  40                return NULL;
  41
  42        buf->size = size;
  43        buf->vaddr = vmalloc_user(buf->size);
  44        buf->handler.refcount = &buf->refcount;
  45        buf->handler.put = vb2_vmalloc_put;
  46        buf->handler.arg = buf;
  47
  48        if (!buf->vaddr) {
  49                pr_debug("vmalloc of size %ld failed\n", buf->size);
  50                kfree(buf);
  51                return NULL;
  52        }
  53
  54        atomic_inc(&buf->refcount);
  55        return buf;
  56}
  57
  58static void vb2_vmalloc_put(void *buf_priv)
  59{
  60        struct vb2_vmalloc_buf *buf = buf_priv;
  61
  62        if (atomic_dec_and_test(&buf->refcount)) {
  63                vfree(buf->vaddr);
  64                kfree(buf);
  65        }
  66}
  67
  68static void *vb2_vmalloc_get_userptr(void *alloc_ctx, unsigned long vaddr,
  69                                     unsigned long size, int write)
  70{
  71        struct vb2_vmalloc_buf *buf;
  72        unsigned long first, last;
  73        int n_pages, offset;
  74
  75        buf = kzalloc(sizeof(*buf), GFP_KERNEL);
  76        if (!buf)
  77                return NULL;
  78
  79        buf->write = write;
  80        offset = vaddr & ~PAGE_MASK;
  81        buf->size = size;
  82
  83        first = vaddr >> PAGE_SHIFT;
  84        last  = (vaddr + size - 1) >> PAGE_SHIFT;
  85        buf->n_pages = last - first + 1;
  86        buf->pages = kzalloc(buf->n_pages * sizeof(struct page *), GFP_KERNEL);
  87        if (!buf->pages)
  88                goto fail_pages_array_alloc;
  89
  90        /* current->mm->mmap_sem is taken by videobuf2 core */
  91        n_pages = get_user_pages(current, current->mm, vaddr & PAGE_MASK,
  92                                 buf->n_pages, write, 1, /* force */
  93                                 buf->pages, NULL);
  94        if (n_pages != buf->n_pages)
  95                goto fail_get_user_pages;
  96
  97        buf->vaddr = vm_map_ram(buf->pages, buf->n_pages, -1, PAGE_KERNEL);
  98        if (!buf->vaddr)
  99                goto fail_get_user_pages;
 100
 101        buf->vaddr += offset;
 102        return buf;
 103
 104fail_get_user_pages:
 105        pr_debug("get_user_pages requested/got: %d/%d]\n", n_pages,
 106                 buf->n_pages);
 107        while (--n_pages >= 0)
 108                put_page(buf->pages[n_pages]);
 109        kfree(buf->pages);
 110
 111fail_pages_array_alloc:
 112        kfree(buf);
 113
 114        return NULL;
 115}
 116
 117static void vb2_vmalloc_put_userptr(void *buf_priv)
 118{
 119        struct vb2_vmalloc_buf *buf = buf_priv;
 120        unsigned long vaddr = (unsigned long)buf->vaddr & PAGE_MASK;
 121        unsigned int i;
 122
 123        if (vaddr)
 124                vm_unmap_ram((void *)vaddr, buf->n_pages);
 125        for (i = 0; i < buf->n_pages; ++i) {
 126                if (buf->write)
 127                        set_page_dirty_lock(buf->pages[i]);
 128                put_page(buf->pages[i]);
 129        }
 130        kfree(buf->pages);
 131        kfree(buf);
 132}
 133
 134static void *vb2_vmalloc_vaddr(void *buf_priv)
 135{
 136        struct vb2_vmalloc_buf *buf = buf_priv;
 137
 138        if (!buf->vaddr) {
 139                pr_err("Address of an unallocated plane requested "
 140                       "or cannot map user pointer\n");
 141                return NULL;
 142        }
 143
 144        return buf->vaddr;
 145}
 146
 147static unsigned int vb2_vmalloc_num_users(void *buf_priv)
 148{
 149        struct vb2_vmalloc_buf *buf = buf_priv;
 150        return atomic_read(&buf->refcount);
 151}
 152
 153static int vb2_vmalloc_mmap(void *buf_priv, struct vm_area_struct *vma)
 154{
 155        struct vb2_vmalloc_buf *buf = buf_priv;
 156        int ret;
 157
 158        if (!buf) {
 159                pr_err("No memory to map\n");
 160                return -EINVAL;
 161        }
 162
 163        ret = remap_vmalloc_range(vma, buf->vaddr, 0);
 164        if (ret) {
 165                pr_err("Remapping vmalloc memory, error: %d\n", ret);
 166                return ret;
 167        }
 168
 169        /*
 170         * Make sure that vm_areas for 2 buffers won't be merged together
 171         */
 172        vma->vm_flags           |= VM_DONTEXPAND;
 173
 174        /*
 175         * Use common vm_area operations to track buffer refcount.
 176         */
 177        vma->vm_private_data    = &buf->handler;
 178        vma->vm_ops             = &vb2_common_vm_ops;
 179
 180        vma->vm_ops->open(vma);
 181
 182        return 0;
 183}
 184
 185const struct vb2_mem_ops vb2_vmalloc_memops = {
 186        .alloc          = vb2_vmalloc_alloc,
 187        .put            = vb2_vmalloc_put,
 188        .get_userptr    = vb2_vmalloc_get_userptr,
 189        .put_userptr    = vb2_vmalloc_put_userptr,
 190        .vaddr          = vb2_vmalloc_vaddr,
 191        .mmap           = vb2_vmalloc_mmap,
 192        .num_users      = vb2_vmalloc_num_users,
 193};
 194EXPORT_SYMBOL_GPL(vb2_vmalloc_memops);
 195
 196MODULE_DESCRIPTION("vmalloc memory handling routines for videobuf2");
 197MODULE_AUTHOR("Pawel Osciak <pawel@osciak.com>");
 198MODULE_LICENSE("GPL");
 199