Files
ubports_kernel_google_msm/drivers/gpu/ion/ion.c
Laura Abbott 878705f82d gpu: ion: Prevent deadlock with cache flushing
Cache flushing takes the mmap semaphore to validate the
address range. This can cause a deadlock in the following
scenario with two threads in the same client:

Thread 1			Thread 2
(cache operation)		(mmap)
-----------------------------------------
lock(client_lock)
				down_write(mmap_semaphore)
down_read(mmap_semaphore)
				lock(client_lock)

Fix this by doing the check for the address range before
taking the client lock. This is independent of Ion so there
is no need to have this lock locked.

Change-Id: Ibe372cf232fbad7e031ab2d38cf3a34823f43bf9
Signed-off-by: Laura Abbott <lauraa@codeaurora.org>
2013-02-07 17:43:36 -08:00

1316 lines
31 KiB
C

/*
* drivers/gpu/ion/ion.c
*
* Copyright (C) 2011 Google, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/module.h>
#include <linux/device.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/anon_inodes.h>
#include <linux/ion.h>
#include <linux/list.h>
#include <linux/memblock.h>
#include <linux/miscdevice.h>
#include <linux/export.h>
#include <linux/mm.h>
#include <linux/mm_types.h>
#include <linux/rbtree.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
#include <linux/debugfs.h>
#include <linux/dma-buf.h>
#include "ion_priv.h"
#define DEBUG
/**
* struct ion_device - the metadata of the ion device node
* @dev: the actual misc device
* @buffers: an rb tree of all the existing buffers
* @lock: lock protecting the buffers & heaps trees
* @heaps: list of all the heaps in the system
* @user_clients: list of all the clients created from userspace
*/
struct ion_device {
struct miscdevice dev;
struct rb_root buffers;
struct mutex lock;
struct rb_root heaps;
long (*custom_ioctl) (struct ion_client *client, unsigned int cmd,
unsigned long arg);
struct rb_root clients;
struct dentry *debug_root;
};
/**
* struct ion_client - a process/hw block local address space
* @node: node in the tree of all clients
* @dev: backpointer to ion device
* @handles: an rb tree of all the handles in this client
* @lock: lock protecting the tree of handles
* @heap_mask: mask of all supported heaps
* @name: used for debugging
* @task: used for debugging
*
* A client represents a list of buffers this client may access.
* The mutex stored here is used to protect both handles tree
* as well as the handles themselves, and should be held while modifying either.
*/
struct ion_client {
struct rb_node node;
struct ion_device *dev;
struct rb_root handles;
struct mutex lock;
unsigned int heap_mask;
const char *name;
struct task_struct *task;
pid_t pid;
struct dentry *debug_root;
};
/**
* ion_handle - a client local reference to a buffer
* @ref: reference count
* @client: back pointer to the client the buffer resides in
* @buffer: pointer to the buffer
* @node: node in the client's handle rbtree
* @kmap_cnt: count of times this client has mapped to kernel
* @dmap_cnt: count of times this client has mapped for dma
*
* Modifications to node, map_cnt or mapping should be protected by the
* lock in the client. Other fields are never changed after initialization.
*/
struct ion_handle {
struct kref ref;
struct ion_client *client;
struct ion_buffer *buffer;
struct rb_node node;
unsigned int kmap_cnt;
};
/* this function should only be called while dev->lock is held */
static void ion_buffer_add(struct ion_device *dev,
struct ion_buffer *buffer)
{
struct rb_node **p = &dev->buffers.rb_node;
struct rb_node *parent = NULL;
struct ion_buffer *entry;
while (*p) {
parent = *p;
entry = rb_entry(parent, struct ion_buffer, node);
if (buffer < entry) {
p = &(*p)->rb_left;
} else if (buffer > entry) {
p = &(*p)->rb_right;
} else {
pr_err("%s: buffer already found.", __func__);
BUG();
}
}
rb_link_node(&buffer->node, parent, p);
rb_insert_color(&buffer->node, &dev->buffers);
}
/* this function should only be called while dev->lock is held */
static struct ion_buffer *ion_buffer_create(struct ion_heap *heap,
struct ion_device *dev,
unsigned long len,
unsigned long align,
unsigned long flags)
{
struct ion_buffer *buffer;
struct sg_table *table;
int ret;
buffer = kzalloc(sizeof(struct ion_buffer), GFP_KERNEL);
if (!buffer)
return ERR_PTR(-ENOMEM);
buffer->heap = heap;
kref_init(&buffer->ref);
ret = heap->ops->allocate(heap, buffer, len, align, flags);
if (ret) {
kfree(buffer);
return ERR_PTR(ret);
}
buffer->dev = dev;
buffer->size = len;
table = buffer->heap->ops->map_dma(buffer->heap, buffer);
if (IS_ERR_OR_NULL(table)) {
heap->ops->free(buffer);
kfree(buffer);
return ERR_PTR(PTR_ERR(table));
}
buffer->sg_table = table;
mutex_init(&buffer->lock);
ion_buffer_add(dev, buffer);
return buffer;
}
static void ion_buffer_destroy(struct kref *kref)
{
struct ion_buffer *buffer = container_of(kref, struct ion_buffer, ref);
struct ion_device *dev = buffer->dev;
if (WARN_ON(buffer->kmap_cnt > 0))
buffer->heap->ops->unmap_kernel(buffer->heap, buffer);
buffer->heap->ops->unmap_dma(buffer->heap, buffer);
buffer->heap->ops->free(buffer);
mutex_lock(&dev->lock);
rb_erase(&buffer->node, &dev->buffers);
mutex_unlock(&dev->lock);
kfree(buffer);
}
static void ion_buffer_get(struct ion_buffer *buffer)
{
kref_get(&buffer->ref);
}
static int ion_buffer_put(struct ion_buffer *buffer)
{
return kref_put(&buffer->ref, ion_buffer_destroy);
}
static struct ion_handle *ion_handle_create(struct ion_client *client,
struct ion_buffer *buffer)
{
struct ion_handle *handle;
handle = kzalloc(sizeof(struct ion_handle), GFP_KERNEL);
if (!handle)
return ERR_PTR(-ENOMEM);
kref_init(&handle->ref);
rb_init_node(&handle->node);
handle->client = client;
ion_buffer_get(buffer);
handle->buffer = buffer;
return handle;
}
static void ion_handle_kmap_put(struct ion_handle *);
static void ion_handle_destroy(struct kref *kref)
{
struct ion_handle *handle = container_of(kref, struct ion_handle, ref);
struct ion_client *client = handle->client;
struct ion_buffer *buffer = handle->buffer;
mutex_lock(&client->lock);
mutex_lock(&buffer->lock);
while (handle->kmap_cnt)
ion_handle_kmap_put(handle);
mutex_unlock(&buffer->lock);
if (!RB_EMPTY_NODE(&handle->node))
rb_erase(&handle->node, &client->handles);
mutex_unlock(&client->lock);
ion_buffer_put(buffer);
kfree(handle);
}
struct ion_buffer *ion_handle_buffer(struct ion_handle *handle)
{
return handle->buffer;
}
static void ion_handle_get(struct ion_handle *handle)
{
kref_get(&handle->ref);
}
static int ion_handle_put(struct ion_handle *handle)
{
return kref_put(&handle->ref, ion_handle_destroy);
}
static struct ion_handle *ion_handle_lookup(struct ion_client *client,
struct ion_buffer *buffer)
{
struct rb_node *n;
for (n = rb_first(&client->handles); n; n = rb_next(n)) {
struct ion_handle *handle = rb_entry(n, struct ion_handle,
node);
if (handle->buffer == buffer)
return handle;
}
return NULL;
}
static bool ion_handle_validate(struct ion_client *client, struct ion_handle *handle)
{
struct rb_node *n = client->handles.rb_node;
while (n) {
struct ion_handle *handle_node = rb_entry(n, struct ion_handle,
node);
if (handle < handle_node)
n = n->rb_left;
else if (handle > handle_node)
n = n->rb_right;
else
return true;
}
return false;
}
static void ion_handle_add(struct ion_client *client, struct ion_handle *handle)
{
struct rb_node **p = &client->handles.rb_node;
struct rb_node *parent = NULL;
struct ion_handle *entry;
while (*p) {
parent = *p;
entry = rb_entry(parent, struct ion_handle, node);
if (handle < entry)
p = &(*p)->rb_left;
else if (handle > entry)
p = &(*p)->rb_right;
else
WARN(1, "%s: buffer already found.", __func__);
}
rb_link_node(&handle->node, parent, p);
rb_insert_color(&handle->node, &client->handles);
}
struct ion_handle *ion_alloc(struct ion_client *client, size_t len,
size_t align, unsigned int flags)
{
struct rb_node *n;
struct ion_handle *handle;
struct ion_device *dev = client->dev;
struct ion_buffer *buffer = NULL;
/*
* traverse the list of heaps available in this system in priority
* order. If the heap type is supported by the client, and matches the
* request of the caller allocate from it. Repeat until allocate has
* succeeded or all heaps have been tried
*/
if (WARN_ON(!len))
return ERR_PTR(-EINVAL);
len = PAGE_ALIGN(len);
mutex_lock(&dev->lock);
for (n = rb_first(&dev->heaps); n != NULL; n = rb_next(n)) {
struct ion_heap *heap = rb_entry(n, struct ion_heap, node);
/* if the client doesn't support this heap type */
if (!((1 << heap->type) & client->heap_mask))
continue;
/* if the caller didn't specify this heap type */
if (!((1 << heap->id) & flags))
continue;
buffer = ion_buffer_create(heap, dev, len, align, flags);
if (!IS_ERR_OR_NULL(buffer))
break;
}
mutex_unlock(&dev->lock);
if (buffer == NULL)
return ERR_PTR(-ENODEV);
if (IS_ERR(buffer))
return ERR_PTR(PTR_ERR(buffer));
handle = ion_handle_create(client, buffer);
/*
* ion_buffer_create will create a buffer with a ref_cnt of 1,
* and ion_handle_create will take a second reference, drop one here
*/
ion_buffer_put(buffer);
if (!IS_ERR(handle)) {
mutex_lock(&client->lock);
ion_handle_add(client, handle);
mutex_unlock(&client->lock);
}
return handle;
}
void ion_free(struct ion_client *client, struct ion_handle *handle)
{
bool valid_handle;
BUG_ON(client != handle->client);
mutex_lock(&client->lock);
valid_handle = ion_handle_validate(client, handle);
mutex_unlock(&client->lock);
if (!valid_handle) {
WARN("%s: invalid handle passed to free.\n", __func__);
return;
}
ion_handle_put(handle);
}
int ion_phys(struct ion_client *client, struct ion_handle *handle,
ion_phys_addr_t *addr, size_t *len)
{
struct ion_buffer *buffer;
int ret;
mutex_lock(&client->lock);
if (!ion_handle_validate(client, handle)) {
mutex_unlock(&client->lock);
return -EINVAL;
}
buffer = handle->buffer;
if (!buffer->heap->ops->phys) {
pr_err("%s: ion_phys is not implemented by this heap.\n",
__func__);
mutex_unlock(&client->lock);
return -ENODEV;
}
mutex_unlock(&client->lock);
ret = buffer->heap->ops->phys(buffer->heap, buffer, addr, len);
return ret;
}
static void *ion_buffer_kmap_get(struct ion_buffer *buffer)
{
void *vaddr;
if (buffer->kmap_cnt) {
buffer->kmap_cnt++;
return buffer->vaddr;
}
vaddr = buffer->heap->ops->map_kernel(buffer->heap, buffer);
if (IS_ERR_OR_NULL(vaddr))
return vaddr;
buffer->vaddr = vaddr;
buffer->kmap_cnt++;
return vaddr;
}
static void *ion_handle_kmap_get(struct ion_handle *handle)
{
struct ion_buffer *buffer = handle->buffer;
void *vaddr;
if (handle->kmap_cnt) {
handle->kmap_cnt++;
return buffer->vaddr;
}
vaddr = ion_buffer_kmap_get(buffer);
if (IS_ERR_OR_NULL(vaddr))
return vaddr;
handle->kmap_cnt++;
return vaddr;
}
static void ion_buffer_kmap_put(struct ion_buffer *buffer)
{
buffer->kmap_cnt--;
if (!buffer->kmap_cnt) {
buffer->heap->ops->unmap_kernel(buffer->heap, buffer);
buffer->vaddr = NULL;
}
}
static void ion_handle_kmap_put(struct ion_handle *handle)
{
struct ion_buffer *buffer = handle->buffer;
handle->kmap_cnt--;
if (!handle->kmap_cnt)
ion_buffer_kmap_put(buffer);
}
void *ion_map_kernel(struct ion_client *client, struct ion_handle *handle,
unsigned long flags)
{
struct ion_buffer *buffer;
void *vaddr;
mutex_lock(&client->lock);
if (!ion_handle_validate(client, handle)) {
pr_err("%s: invalid handle passed to map_kernel.\n",
__func__);
mutex_unlock(&client->lock);
return ERR_PTR(-EINVAL);
}
buffer = handle->buffer;
if (!handle->buffer->heap->ops->map_kernel) {
pr_err("%s: map_kernel is not implemented by this heap.\n",
__func__);
mutex_unlock(&client->lock);
return ERR_PTR(-ENODEV);
}
if (buffer->kmap_cnt || buffer->dmap_cnt || buffer->umap_cnt) {
if (buffer->flags != flags) {
pr_err("%s: buffer was already mapped with flags %lx,"
" cannot map with flags %lx\n", __func__,
buffer->flags, flags);
mutex_unlock(&client->lock);
return ERR_PTR(-EEXIST);
}
} else {
buffer->flags = flags;
}
mutex_lock(&buffer->lock);
vaddr = ion_handle_kmap_get(handle);
mutex_unlock(&buffer->lock);
mutex_unlock(&client->lock);
return vaddr;
}
void ion_unmap_kernel(struct ion_client *client, struct ion_handle *handle)
{
struct ion_buffer *buffer;
mutex_lock(&client->lock);
buffer = handle->buffer;
mutex_lock(&buffer->lock);
ion_handle_kmap_put(handle);
mutex_unlock(&buffer->lock);
mutex_unlock(&client->lock);
}
static int check_vaddr_bounds(unsigned long start, unsigned long end)
{
struct mm_struct *mm = current->active_mm;
struct vm_area_struct *vma;
int ret = 1;
if (end < start)
goto out;
down_read(&mm->mmap_sem);
vma = find_vma(mm, start);
if (vma && vma->vm_start < end) {
if (start < vma->vm_start)
goto out_up;
if (end > vma->vm_end)
goto out_up;
ret = 0;
}
out_up:
up_read(&mm->mmap_sem);
out:
return ret;
}
int ion_do_cache_op(struct ion_client *client, struct ion_handle *handle,
void *uaddr, unsigned long offset, unsigned long len,
unsigned int cmd)
{
struct ion_buffer *buffer;
int ret = -EINVAL;
mutex_lock(&client->lock);
if (!ion_handle_validate(client, handle)) {
pr_err("%s: invalid handle passed to do_cache_op.\n",
__func__);
mutex_unlock(&client->lock);
return -EINVAL;
}
buffer = handle->buffer;
mutex_lock(&buffer->lock);
if (!ION_IS_CACHED(buffer->flags)) {
ret = 0;
goto out;
}
if (!handle->buffer->heap->ops->cache_op) {
pr_err("%s: cache_op is not implemented by this heap.\n",
__func__);
ret = -ENODEV;
goto out;
}
ret = buffer->heap->ops->cache_op(buffer->heap, buffer, uaddr,
offset, len, cmd);
out:
mutex_unlock(&buffer->lock);
mutex_unlock(&client->lock);
return ret;
}
static int ion_debug_client_show(struct seq_file *s, void *unused)
{
struct ion_client *client = s->private;
struct rb_node *n;
seq_printf(s, "%16.16s: %16.16s : %16.16s : %16.16s\n", "heap_name",
"size_in_bytes", "handle refcount", "buffer");
mutex_lock(&client->lock);
for (n = rb_first(&client->handles); n; n = rb_next(n)) {
struct ion_handle *handle = rb_entry(n, struct ion_handle,
node);
seq_printf(s, "%16.16s: %16x : %16d : %16p\n",
handle->buffer->heap->name,
handle->buffer->size,
atomic_read(&handle->ref.refcount),
handle->buffer);
}
mutex_unlock(&client->lock);
return 0;
}
static int ion_debug_client_open(struct inode *inode, struct file *file)
{
return single_open(file, ion_debug_client_show, inode->i_private);
}
static const struct file_operations debug_client_fops = {
.open = ion_debug_client_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
struct ion_client *ion_client_create(struct ion_device *dev,
unsigned int heap_mask,
const char *name)
{
struct ion_client *client;
struct task_struct *task;
struct rb_node **p;
struct rb_node *parent = NULL;
struct ion_client *entry;
char debug_name[64];
pid_t pid;
get_task_struct(current->group_leader);
task_lock(current->group_leader);
pid = task_pid_nr(current->group_leader);
/* don't bother to store task struct for kernel threads,
they can't be killed anyway */
if (current->group_leader->flags & PF_KTHREAD) {
put_task_struct(current->group_leader);
task = NULL;
} else {
task = current->group_leader;
}
task_unlock(current->group_leader);
client = kzalloc(sizeof(struct ion_client), GFP_KERNEL);
if (!client) {
if (task)
put_task_struct(current->group_leader);
return ERR_PTR(-ENOMEM);
}
client->dev = dev;
client->handles = RB_ROOT;
mutex_init(&client->lock);
client->name = name;
client->heap_mask = heap_mask;
client->task = task;
client->pid = pid;
mutex_lock(&dev->lock);
p = &dev->clients.rb_node;
while (*p) {
parent = *p;
entry = rb_entry(parent, struct ion_client, node);
if (client < entry)
p = &(*p)->rb_left;
else if (client > entry)
p = &(*p)->rb_right;
}
rb_link_node(&client->node, parent, p);
rb_insert_color(&client->node, &dev->clients);
snprintf(debug_name, 64, "%u", client->pid);
client->debug_root = debugfs_create_file(debug_name, 0664,
dev->debug_root, client,
&debug_client_fops);
mutex_unlock(&dev->lock);
return client;
}
void ion_client_destroy(struct ion_client *client)
{
struct ion_device *dev = client->dev;
struct rb_node *n;
pr_debug("%s: %d\n", __func__, __LINE__);
while ((n = rb_first(&client->handles))) {
struct ion_handle *handle = rb_entry(n, struct ion_handle,
node);
ion_handle_destroy(&handle->ref);
}
mutex_lock(&dev->lock);
if (client->task)
put_task_struct(client->task);
rb_erase(&client->node, &dev->clients);
debugfs_remove_recursive(client->debug_root);
mutex_unlock(&dev->lock);
kfree(client);
}
int ion_handle_get_flags(struct ion_client *client, struct ion_handle *handle,
unsigned long *flags)
{
struct ion_buffer *buffer;
mutex_lock(&client->lock);
if (!ion_handle_validate(client, handle)) {
pr_err("%s: invalid handle passed to %s.\n",
__func__, __func__);
mutex_unlock(&client->lock);
return -EINVAL;
}
buffer = handle->buffer;
mutex_lock(&buffer->lock);
*flags = buffer->flags;
mutex_unlock(&buffer->lock);
mutex_unlock(&client->lock);
return 0;
}
EXPORT_SYMBOL(ion_handle_get_flags);
struct sg_table *ion_sg_table(struct ion_client *client,
struct ion_handle *handle)
{
struct ion_buffer *buffer;
struct sg_table *table;
mutex_lock(&client->lock);
if (!ion_handle_validate(client, handle)) {
pr_err("%s: invalid handle passed to map_dma.\n",
__func__);
mutex_unlock(&client->lock);
return ERR_PTR(-EINVAL);
}
buffer = handle->buffer;
table = buffer->sg_table;
mutex_unlock(&client->lock);
return table;
}
static struct sg_table *ion_map_dma_buf(struct dma_buf_attachment *attachment,
enum dma_data_direction direction)
{
struct dma_buf *dmabuf = attachment->dmabuf;
struct ion_buffer *buffer = dmabuf->priv;
return buffer->sg_table;
}
static void ion_unmap_dma_buf(struct dma_buf_attachment *attachment,
struct sg_table *table,
enum dma_data_direction direction)
{
}
static void ion_vma_open(struct vm_area_struct *vma)
{
struct ion_buffer *buffer = vma->vm_private_data;
pr_debug("%s: %d\n", __func__, __LINE__);
mutex_lock(&buffer->lock);
buffer->umap_cnt++;
mutex_unlock(&buffer->lock);
}
static void ion_vma_close(struct vm_area_struct *vma)
{
struct ion_buffer *buffer = vma->vm_private_data;
pr_debug("%s: %d\n", __func__, __LINE__);
mutex_lock(&buffer->lock);
buffer->umap_cnt--;
mutex_unlock(&buffer->lock);
}
static struct vm_operations_struct ion_vm_ops = {
.open = ion_vma_open,
.close = ion_vma_close,
};
static int ion_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma)
{
struct ion_buffer *buffer = dmabuf->priv;
int ret;
if (!buffer->heap->ops->map_user) {
pr_err("%s: this heap does not define a method for mapping "
"to userspace\n", __func__);
return -EINVAL;
}
mutex_lock(&buffer->lock);
/* now map it to userspace */
ret = buffer->heap->ops->map_user(buffer->heap, buffer, vma);
if (ret) {
mutex_unlock(&buffer->lock);
pr_err("%s: failure mapping buffer to userspace\n",
__func__);
} else {
buffer->umap_cnt++;
mutex_unlock(&buffer->lock);
vma->vm_ops = &ion_vm_ops;
/*
* move the buffer into the vm_private_data so we can access it
* from vma_open/close
*/
vma->vm_private_data = buffer;
}
return ret;
}
static void ion_dma_buf_release(struct dma_buf *dmabuf)
{
struct ion_buffer *buffer = dmabuf->priv;
ion_buffer_put(buffer);
}
static void *ion_dma_buf_kmap(struct dma_buf *dmabuf, unsigned long offset)
{
struct ion_buffer *buffer = dmabuf->priv;
return buffer->vaddr + offset;
}
static void ion_dma_buf_kunmap(struct dma_buf *dmabuf, unsigned long offset,
void *ptr)
{
return;
}
static int ion_dma_buf_begin_cpu_access(struct dma_buf *dmabuf, size_t start,
size_t len,
enum dma_data_direction direction)
{
struct ion_buffer *buffer = dmabuf->priv;
void *vaddr;
if (!buffer->heap->ops->map_kernel) {
pr_err("%s: map kernel is not implemented by this heap.\n",
__func__);
return -ENODEV;
}
mutex_lock(&buffer->lock);
vaddr = ion_buffer_kmap_get(buffer);
mutex_unlock(&buffer->lock);
if (IS_ERR(vaddr))
return PTR_ERR(vaddr);
if (!vaddr)
return -ENOMEM;
return 0;
}
static void ion_dma_buf_end_cpu_access(struct dma_buf *dmabuf, size_t start,
size_t len,
enum dma_data_direction direction)
{
struct ion_buffer *buffer = dmabuf->priv;
mutex_lock(&buffer->lock);
ion_buffer_kmap_put(buffer);
mutex_unlock(&buffer->lock);
}
struct dma_buf_ops dma_buf_ops = {
.map_dma_buf = ion_map_dma_buf,
.unmap_dma_buf = ion_unmap_dma_buf,
.mmap = ion_mmap,
.release = ion_dma_buf_release,
.begin_cpu_access = ion_dma_buf_begin_cpu_access,
.end_cpu_access = ion_dma_buf_end_cpu_access,
.kmap_atomic = ion_dma_buf_kmap,
.kunmap_atomic = ion_dma_buf_kunmap,
.kmap = ion_dma_buf_kmap,
.kunmap = ion_dma_buf_kunmap,
};
int ion_share_dma_buf(struct ion_client *client, struct ion_handle *handle)
{
struct ion_buffer *buffer;
struct dma_buf *dmabuf;
bool valid_handle;
int fd;
mutex_lock(&client->lock);
valid_handle = ion_handle_validate(client, handle);
mutex_unlock(&client->lock);
if (!valid_handle) {
WARN("%s: invalid handle passed to share.\n", __func__);
return -EINVAL;
}
buffer = handle->buffer;
ion_buffer_get(buffer);
dmabuf = dma_buf_export(buffer, &dma_buf_ops, buffer->size, O_RDWR);
if (IS_ERR(dmabuf)) {
ion_buffer_put(buffer);
return PTR_ERR(dmabuf);
}
fd = dma_buf_fd(dmabuf, O_CLOEXEC);
if (fd < 0) {
dma_buf_put(dmabuf);
ion_buffer_put(buffer);
}
return fd;
}
struct ion_handle *ion_import_dma_buf(struct ion_client *client, int fd)
{
struct dma_buf *dmabuf;
struct ion_buffer *buffer;
struct ion_handle *handle;
dmabuf = dma_buf_get(fd);
if (IS_ERR_OR_NULL(dmabuf))
return ERR_PTR(PTR_ERR(dmabuf));
/* if this memory came from ion */
if (dmabuf->ops != &dma_buf_ops) {
pr_err("%s: can not import dmabuf from another exporter\n",
__func__);
dma_buf_put(dmabuf);
return ERR_PTR(-EINVAL);
}
buffer = dmabuf->priv;
mutex_lock(&client->lock);
/* if a handle exists for this buffer just take a reference to it */
handle = ion_handle_lookup(client, buffer);
if (!IS_ERR_OR_NULL(handle)) {
ion_handle_get(handle);
goto end;
}
handle = ion_handle_create(client, buffer);
if (IS_ERR_OR_NULL(handle))
goto end;
ion_handle_add(client, handle);
end:
mutex_unlock(&client->lock);
dma_buf_put(dmabuf);
return handle;
}
static long ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct ion_client *client = filp->private_data;
switch (cmd) {
case ION_IOC_ALLOC:
{
struct ion_allocation_data data;
if (copy_from_user(&data, (void __user *)arg, sizeof(data)))
return -EFAULT;
data.handle = ion_alloc(client, data.len, data.align,
data.flags);
if (IS_ERR(data.handle))
return PTR_ERR(data.handle);
if (copy_to_user((void __user *)arg, &data, sizeof(data))) {
ion_free(client, data.handle);
return -EFAULT;
}
break;
}
case ION_IOC_FREE:
{
struct ion_handle_data data;
bool valid;
if (copy_from_user(&data, (void __user *)arg,
sizeof(struct ion_handle_data)))
return -EFAULT;
mutex_lock(&client->lock);
valid = ion_handle_validate(client, data.handle);
mutex_unlock(&client->lock);
if (!valid)
return -EINVAL;
ion_free(client, data.handle);
break;
}
case ION_IOC_SHARE:
{
struct ion_fd_data data;
if (copy_from_user(&data, (void __user *)arg, sizeof(data)))
return -EFAULT;
data.fd = ion_share_dma_buf(client, data.handle);
if (copy_to_user((void __user *)arg, &data, sizeof(data)))
return -EFAULT;
break;
}
case ION_IOC_IMPORT:
{
struct ion_fd_data data;
if (copy_from_user(&data, (void __user *)arg,
sizeof(struct ion_fd_data)))
return -EFAULT;
data.handle = ion_import_dma_buf(client, data.fd);
if (IS_ERR(data.handle))
data.handle = NULL;
if (copy_to_user((void __user *)arg, &data,
sizeof(struct ion_fd_data)))
return -EFAULT;
break;
}
case ION_IOC_CUSTOM:
{
struct ion_device *dev = client->dev;
struct ion_custom_data data;
if (!dev->custom_ioctl)
return -ENOTTY;
if (copy_from_user(&data, (void __user *)arg,
sizeof(struct ion_custom_data)))
return -EFAULT;
return dev->custom_ioctl(client, data.cmd, data.arg);
}
case ION_IOC_CLEAN_CACHES:
case ION_IOC_INV_CACHES:
case ION_IOC_CLEAN_INV_CACHES:
{
struct ion_flush_data data;
unsigned long start, end;
if (copy_from_user(&data, (void __user *)arg,
sizeof(struct ion_flush_data)))
return -EFAULT;
start = (unsigned long) data.vaddr;
end = (unsigned long) data.vaddr + data.length;
if (check_vaddr_bounds(start, end)) {
pr_err("%s: virtual address %p is out of bounds\n",
__func__, data.vaddr);
return -EINVAL;
}
return ion_do_cache_op(client, data.handle, data.vaddr,
data.offset, data.length, cmd);
}
case ION_IOC_GET_FLAGS:
{
struct ion_flag_data data;
int ret;
if (copy_from_user(&data, (void __user *)arg,
sizeof(struct ion_flag_data)))
return -EFAULT;
ret = ion_handle_get_flags(client, data.handle, &data.flags);
if (ret < 0)
return ret;
if (copy_to_user((void __user *)arg, &data,
sizeof(struct ion_flag_data)))
return -EFAULT;
break;
}
default:
return -ENOTTY;
}
return 0;
}
static int ion_release(struct inode *inode, struct file *file)
{
struct ion_client *client = file->private_data;
pr_debug("%s: %d\n", __func__, __LINE__);
ion_client_destroy(client);
return 0;
}
static int ion_open(struct inode *inode, struct file *file)
{
struct miscdevice *miscdev = file->private_data;
struct ion_device *dev = container_of(miscdev, struct ion_device, dev);
struct ion_client *client;
pr_debug("%s: %d\n", __func__, __LINE__);
client = ion_client_create(dev, -1, "user");
if (IS_ERR_OR_NULL(client))
return PTR_ERR(client);
file->private_data = client;
return 0;
}
static const struct file_operations ion_fops = {
.owner = THIS_MODULE,
.open = ion_open,
.release = ion_release,
.unlocked_ioctl = ion_ioctl,
};
static size_t ion_debug_heap_total(struct ion_client *client,
enum ion_heap_ids id)
{
size_t size = 0;
struct rb_node *n;
mutex_lock(&client->lock);
for (n = rb_first(&client->handles); n; n = rb_next(n)) {
struct ion_handle *handle = rb_entry(n,
struct ion_handle,
node);
if (handle->buffer->heap->id == id)
size += handle->buffer->size;
}
mutex_unlock(&client->lock);
return size;
}
static int ion_debug_heap_show(struct seq_file *s, void *unused)
{
struct ion_heap *heap = s->private;
struct ion_device *dev = heap->dev;
struct rb_node *n;
seq_printf(s, "%16.s %16.s %16.s\n", "client", "pid", "size");
for (n = rb_first(&dev->clients); n; n = rb_next(n)) {
struct ion_client *client = rb_entry(n, struct ion_client,
node);
size_t size = ion_debug_heap_total(client, heap->id);
if (!size)
continue;
if (client->task) {
char task_comm[TASK_COMM_LEN];
get_task_comm(task_comm, client->task);
seq_printf(s, "%16.s %16u %16u\n", task_comm,
client->pid, size);
} else {
seq_printf(s, "%16.s %16u %16u\n", client->name,
client->pid, size);
}
}
if (heap->ops->get_allocated) {
seq_printf(s, "total bytes currently allocated: %lx\n",
heap->ops->get_allocated(heap));
}
if (heap->ops->get_total) {
seq_printf(s, "total heap size: %lx\n",
heap->ops->get_total(heap));
}
return 0;
}
static int ion_debug_heap_open(struct inode *inode, struct file *file)
{
return single_open(file, ion_debug_heap_show, inode->i_private);
}
static const struct file_operations debug_heap_fops = {
.open = ion_debug_heap_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
void ion_device_add_heap(struct ion_device *dev, struct ion_heap *heap)
{
struct rb_node **p = &dev->heaps.rb_node;
struct rb_node *parent = NULL;
struct ion_heap *entry;
if (!heap->ops->allocate || !heap->ops->free || !heap->ops->map_dma ||
!heap->ops->unmap_dma)
pr_err("%s: can not add heap with invalid ops struct.\n",
__func__);
heap->dev = dev;
mutex_lock(&dev->lock);
while (*p) {
parent = *p;
entry = rb_entry(parent, struct ion_heap, node);
if (heap->id < entry->id) {
p = &(*p)->rb_left;
} else if (heap->id > entry->id ) {
p = &(*p)->rb_right;
} else {
pr_err("%s: can not insert multiple heaps with "
"id %d\n", __func__, heap->id);
goto end;
}
}
rb_link_node(&heap->node, parent, p);
rb_insert_color(&heap->node, &dev->heaps);
debugfs_create_file(heap->name, 0664, dev->debug_root, heap,
&debug_heap_fops);
end:
mutex_unlock(&dev->lock);
}
static int ion_debug_leak_show(struct seq_file *s, void *unused)
{
struct ion_device *dev = s->private;
struct rb_node *n;
struct rb_node *n2;
/* mark all buffers as 1 */
seq_printf(s, "%16.s %16.s %16.s %16.s\n", "buffer", "heap", "size",
"ref cnt");
mutex_lock(&dev->lock);
for (n = rb_first(&dev->buffers); n; n = rb_next(n)) {
struct ion_buffer *buf = rb_entry(n, struct ion_buffer,
node);
buf->marked = 1;
}
/* now see which buffers we can access */
for (n = rb_first(&dev->clients); n; n = rb_next(n)) {
struct ion_client *client = rb_entry(n, struct ion_client,
node);
mutex_lock(&client->lock);
for (n2 = rb_first(&client->handles); n2; n2 = rb_next(n2)) {
struct ion_handle *handle = rb_entry(n2,
struct ion_handle, node);
handle->buffer->marked = 0;
}
mutex_unlock(&client->lock);
}
/* And anyone still marked as a 1 means a leaked handle somewhere */
for (n = rb_first(&dev->buffers); n; n = rb_next(n)) {
struct ion_buffer *buf = rb_entry(n, struct ion_buffer,
node);
if (buf->marked == 1)
seq_printf(s, "%16.x %16.s %16.x %16.d\n",
(int)buf, buf->heap->name, buf->size,
atomic_read(&buf->ref.refcount));
}
mutex_unlock(&dev->lock);
return 0;
}
static int ion_debug_leak_open(struct inode *inode, struct file *file)
{
return single_open(file, ion_debug_leak_show, inode->i_private);
}
static const struct file_operations debug_leak_fops = {
.open = ion_debug_leak_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
struct ion_device *ion_device_create(long (*custom_ioctl)
(struct ion_client *client,
unsigned int cmd,
unsigned long arg))
{
struct ion_device *idev;
int ret;
idev = kzalloc(sizeof(struct ion_device), GFP_KERNEL);
if (!idev)
return ERR_PTR(-ENOMEM);
idev->dev.minor = MISC_DYNAMIC_MINOR;
idev->dev.name = "ion";
idev->dev.fops = &ion_fops;
idev->dev.parent = NULL;
ret = misc_register(&idev->dev);
if (ret) {
pr_err("ion: failed to register misc device.\n");
return ERR_PTR(ret);
}
idev->debug_root = debugfs_create_dir("ion", NULL);
if (IS_ERR_OR_NULL(idev->debug_root))
pr_err("ion: failed to create debug files.\n");
idev->custom_ioctl = custom_ioctl;
idev->buffers = RB_ROOT;
mutex_init(&idev->lock);
idev->heaps = RB_ROOT;
idev->clients = RB_ROOT;
debugfs_create_file("check_leaked_fds", 0664, idev->debug_root, idev,
&debug_leak_fops);
return idev;
}
void ion_device_destroy(struct ion_device *dev)
{
misc_deregister(&dev->dev);
/* XXX need to free the heaps and clients ? */
kfree(dev);
}
void __init ion_reserve(struct ion_platform_data *data)
{
int i, ret;
for (i = 0; i < data->nr; i++) {
if (data->heaps[i].size == 0)
continue;
ret = memblock_reserve(data->heaps[i].base,
data->heaps[i].size);
if (ret)
pr_err("memblock reserve of %x@%lx failed\n",
data->heaps[i].size,
data->heaps[i].base);
}
}