|  | /* | 
|  | * videobuf2-vmalloc.c - vmalloc memory allocator for videobuf2 | 
|  | * | 
|  | * Copyright (C) 2010 Samsung Electronics | 
|  | * | 
|  | * Author: Pawel Osciak <pawel@osciak.com> | 
|  | * | 
|  | * 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. | 
|  | */ | 
|  |  | 
|  | #include <linux/module.h> | 
|  | #include <linux/mm.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/vmalloc.h> | 
|  |  | 
|  | #include <media/videobuf2-core.h> | 
|  | #include <media/videobuf2-memops.h> | 
|  |  | 
|  | struct vb2_vmalloc_buf { | 
|  | void				*vaddr; | 
|  | unsigned long			size; | 
|  | atomic_t			refcount; | 
|  | struct vb2_vmarea_handler	handler; | 
|  | }; | 
|  |  | 
|  | static void vb2_vmalloc_put(void *buf_priv); | 
|  |  | 
|  | static void *vb2_vmalloc_alloc(void *alloc_ctx, unsigned long size) | 
|  | { | 
|  | struct vb2_vmalloc_buf *buf; | 
|  |  | 
|  | buf = kzalloc(sizeof *buf, GFP_KERNEL); | 
|  | if (!buf) | 
|  | return NULL; | 
|  |  | 
|  | buf->size = size; | 
|  | buf->vaddr = vmalloc_user(buf->size); | 
|  | buf->handler.refcount = &buf->refcount; | 
|  | buf->handler.put = vb2_vmalloc_put; | 
|  | buf->handler.arg = buf; | 
|  |  | 
|  | if (!buf->vaddr) { | 
|  | printk(KERN_ERR "vmalloc of size %ld failed\n", buf->size); | 
|  | kfree(buf); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | atomic_inc(&buf->refcount); | 
|  | printk(KERN_DEBUG "Allocated vmalloc buffer of size %ld at vaddr=%p\n", | 
|  | buf->size, buf->vaddr); | 
|  |  | 
|  | return buf; | 
|  | } | 
|  |  | 
|  | static void vb2_vmalloc_put(void *buf_priv) | 
|  | { | 
|  | struct vb2_vmalloc_buf *buf = buf_priv; | 
|  |  | 
|  | if (atomic_dec_and_test(&buf->refcount)) { | 
|  | printk(KERN_DEBUG "%s: Freeing vmalloc mem at vaddr=%p\n", | 
|  | __func__, buf->vaddr); | 
|  | vfree(buf->vaddr); | 
|  | kfree(buf); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void *vb2_vmalloc_vaddr(void *buf_priv) | 
|  | { | 
|  | struct vb2_vmalloc_buf *buf = buf_priv; | 
|  |  | 
|  | BUG_ON(!buf); | 
|  |  | 
|  | if (!buf->vaddr) { | 
|  | printk(KERN_ERR "Address of an unallocated plane requested\n"); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | return buf->vaddr; | 
|  | } | 
|  |  | 
|  | static unsigned int vb2_vmalloc_num_users(void *buf_priv) | 
|  | { | 
|  | struct vb2_vmalloc_buf *buf = buf_priv; | 
|  | return atomic_read(&buf->refcount); | 
|  | } | 
|  |  | 
|  | static int vb2_vmalloc_mmap(void *buf_priv, struct vm_area_struct *vma) | 
|  | { | 
|  | struct vb2_vmalloc_buf *buf = buf_priv; | 
|  | int ret; | 
|  |  | 
|  | if (!buf) { | 
|  | printk(KERN_ERR "No memory to map\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | ret = remap_vmalloc_range(vma, buf->vaddr, 0); | 
|  | if (ret) { | 
|  | printk(KERN_ERR "Remapping vmalloc memory, error: %d\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Make sure that vm_areas for 2 buffers won't be merged together | 
|  | */ | 
|  | vma->vm_flags		|= VM_DONTEXPAND; | 
|  |  | 
|  | /* | 
|  | * Use common vm_area operations to track buffer refcount. | 
|  | */ | 
|  | vma->vm_private_data	= &buf->handler; | 
|  | vma->vm_ops		= &vb2_common_vm_ops; | 
|  |  | 
|  | vma->vm_ops->open(vma); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | const struct vb2_mem_ops vb2_vmalloc_memops = { | 
|  | .alloc		= vb2_vmalloc_alloc, | 
|  | .put		= vb2_vmalloc_put, | 
|  | .vaddr		= vb2_vmalloc_vaddr, | 
|  | .mmap		= vb2_vmalloc_mmap, | 
|  | .num_users	= vb2_vmalloc_num_users, | 
|  | }; | 
|  | EXPORT_SYMBOL_GPL(vb2_vmalloc_memops); | 
|  |  | 
|  | MODULE_DESCRIPTION("vmalloc memory handling routines for videobuf2"); | 
|  | MODULE_AUTHOR("Pawel Osciak <pawel@osciak.com>"); | 
|  | MODULE_LICENSE("GPL"); |