Linux Audio

Check our new training course

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