/* * 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. * */ #define pr_fmt(fmt) "%s():%d: " fmt, __func__, __LINE__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ion_priv.h" #define DEBUG /* 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("buffer already found."); 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; 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; 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; buffer->heap->ops->free(buffer); mutex_lock(&dev->lock); rb_erase(&buffer->node, &dev->buffers); mutex_unlock(&dev->lock); kfree(buffer); } 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); } 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_destroy(struct kref *kref) { struct ion_handle *handle = container_of(kref, struct ion_handle, ref); /* XXX Can a handle be destroyed while it's map count is non-zero?: if (handle->map_cnt) unmap */ ion_buffer_put(handle->buffer); mutex_lock(&handle->client->lock); if (!RB_EMPTY_NODE(&handle->node)) rb_erase(&handle->node, &handle->client->handles); mutex_unlock(&handle->client->lock); kfree(handle); } struct ion_buffer *ion_handle_buffer(struct ion_handle *handle) { return handle->buffer; } void ion_handle_get(struct ion_handle *handle) { kref_get(&handle->ref); } 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; } 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; } WARN(1, "invalid handle passed h=0x%x,comm=%d\n", handle, current->group_leader->comm); return false; } 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 */ 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 (IS_ERR_OR_NULL(buffer)) return ERR_PTR(PTR_ERR(buffer)); handle = ion_handle_create(client, buffer); if (IS_ERR_OR_NULL(handle)) goto end; /* * 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); mutex_lock(&client->lock); ion_handle_add(client, handle); mutex_unlock(&client->lock); return handle; end: ion_buffer_put(buffer); 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(1, "%s: invalid handle passed to free.\n", __func__); return; } ion_handle_put(handle); } static bool _ion_map(int *buffer_cnt, int *handle_cnt) { bool map; BUG_ON(*handle_cnt != 0 && *buffer_cnt == 0); if (*buffer_cnt) map = false; else map = true; if (*handle_cnt == 0) (*buffer_cnt)++; (*handle_cnt)++; return map; } static bool _ion_unmap(int *buffer_cnt, int *handle_cnt) { BUG_ON(*handle_cnt == 0); (*handle_cnt)--; if (*handle_cnt != 0) return false; BUG_ON(*buffer_cnt == 0); (*buffer_cnt)--; if (*buffer_cnt == 0) return true; return false; } 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("ion_phys is not implemented by this heap.\n"); mutex_unlock(&client->lock); return -ENODEV; } mutex_unlock(&client->lock); ret = buffer->heap->ops->phys(buffer->heap, buffer, addr, len); return ret; } void *ion_map_kernel(struct ion_client *client, struct ion_handle *handle) { struct ion_buffer *buffer; void *vaddr; mutex_lock(&client->lock); if (!ion_handle_validate(client, handle)) { WARN(1, "invalid handle passed to map_kernel.\n"); mutex_unlock(&client->lock); return ERR_PTR(-EINVAL); } buffer = handle->buffer; mutex_lock(&buffer->lock); if (!handle->buffer->heap->ops->map_kernel) { pr_err("map_kernel is not implemented by this heap.\n"); mutex_unlock(&buffer->lock); mutex_unlock(&client->lock); return ERR_PTR(-ENODEV); } if (_ion_map(&buffer->kmap_cnt, &handle->kmap_cnt)) { vaddr = buffer->heap->ops->map_kernel(buffer->heap, buffer); if (IS_ERR_OR_NULL(vaddr)) _ion_unmap(&buffer->kmap_cnt, &handle->kmap_cnt); buffer->vaddr = vaddr; } else { vaddr = buffer->vaddr; } mutex_unlock(&buffer->lock); mutex_unlock(&client->lock); return vaddr; } struct scatterlist *ion_map_dma(struct ion_client *client, struct ion_handle *handle) { struct ion_buffer *buffer; struct scatterlist *sglist; mutex_lock(&client->lock); if (!ion_handle_validate(client, handle)) { WARN(1, "invalid handle passed to map_dma.\n"); mutex_unlock(&client->lock); return ERR_PTR(-EINVAL); } buffer = handle->buffer; mutex_lock(&buffer->lock); if (!handle->buffer->heap->ops->map_dma) { pr_err("map_kernel is not implemented by this heap.\n"); mutex_unlock(&buffer->lock); mutex_unlock(&client->lock); return ERR_PTR(-ENODEV); } if (_ion_map(&buffer->dmap_cnt, &handle->dmap_cnt)) { sglist = buffer->heap->ops->map_dma(buffer->heap, buffer); if (IS_ERR_OR_NULL(sglist)) _ion_unmap(&buffer->dmap_cnt, &handle->dmap_cnt); buffer->sglist = sglist; } else { sglist = buffer->sglist; } mutex_unlock(&buffer->lock); mutex_unlock(&client->lock); return sglist; } struct scatterlist *iommu_heap_remap_dma(struct ion_heap *heap, struct ion_buffer *buf, unsigned long addr); int ion_remap_dma(struct ion_client *client, struct ion_handle *handle, unsigned long addr) { struct ion_buffer *buffer; int ret; mutex_lock(&client->lock); if (!ion_handle_validate(client, handle)) { pr_err("invalid handle passed to map_dma.\n"); mutex_unlock(&client->lock); return -EINVAL; } buffer = handle->buffer; mutex_lock(&buffer->lock); ret = iommu_heap_remap_dma(buffer->heap, buffer, addr); mutex_unlock(&buffer->lock); mutex_unlock(&client->lock); return ret; } 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); if (_ion_unmap(&buffer->kmap_cnt, &handle->kmap_cnt)) { buffer->heap->ops->unmap_kernel(buffer->heap, buffer); buffer->vaddr = NULL; } mutex_unlock(&buffer->lock); mutex_unlock(&client->lock); } void ion_unmap_dma(struct ion_client *client, struct ion_handle *handle) { struct ion_buffer *buffer; mutex_lock(&client->lock); buffer = handle->buffer; mutex_lock(&buffer->lock); if (_ion_unmap(&buffer->dmap_cnt, &handle->dmap_cnt)) { buffer->heap->ops->unmap_dma(buffer->heap, buffer); buffer->sglist = NULL; } mutex_unlock(&buffer->lock); mutex_unlock(&client->lock); } struct ion_buffer *ion_share(struct ion_client *client, struct ion_handle *handle) { bool valid_handle; mutex_lock(&client->lock); valid_handle = ion_handle_validate(client, handle); mutex_unlock(&client->lock); if (!valid_handle) { WARN(1, "%s: invalid handle passed to share.\n", __func__); return ERR_PTR(-EINVAL); } /* do not take an extra reference here, the burden is on the caller * to make sure the buffer doesn't go away while it's passing it * to another client -- ion_free should not be called on this handle * until the buffer has been imported into the other client */ return handle->buffer; } struct ion_handle *ion_import(struct ion_client *client, struct ion_buffer *buffer) { struct ion_handle *handle = NULL; 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)) { pr_err("error during handle create\n"); goto end; } ion_handle_add(client, handle); end: mutex_unlock(&client->lock); return handle; } static const struct file_operations ion_share_fops; struct ion_handle *ion_import_fd(struct ion_client *client, int fd) { struct file *file = fget(fd); struct ion_handle *handle; if (!file) { pr_err("imported fd not found in file table.\n"); return ERR_PTR(-EINVAL); } if (file->f_op != &ion_share_fops) { pr_err("imported file is not a shared ion file.\n"); handle = ERR_PTR(-EINVAL); goto end; } handle = ion_import(client, file->private_data); end: fput(file); return handle; } static int ion_debug_client_show(struct seq_file *s, void *unused) { struct ion_client *client = s->private; struct rb_node *n; size_t sizes[ION_NUM_HEAPS] = {0}; const char *names[ION_NUM_HEAPS] = {0}; int i; 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); enum ion_heap_type type = handle->buffer->heap->type; if (!names[type]) names[type] = handle->buffer->heap->name; sizes[type] += handle->buffer->size; } mutex_unlock(&client->lock); seq_printf(s, "%16.16s: %16.16s\n", "heap_name", "size_in_bytes"); for (i = 0; i < ION_NUM_HEAPS; i++) { if (!names[i]) continue; seq_printf(s, "%16.16s: %16u %d\n", names[i], sizes[i], atomic_read(&client->ref.refcount)); } 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, }; static struct ion_client *ion_client_lookup(struct ion_device *dev, struct task_struct *task) { struct rb_node *n = dev->user_clients.rb_node; struct ion_client *client; mutex_lock(&dev->lock); while (n) { client = rb_entry(n, struct ion_client, node); if (task == client->task) { ion_client_get(client); mutex_unlock(&dev->lock); return client; } else if (task < client->task) { n = n->rb_left; } else if (task > client->task) { n = n->rb_right; } } mutex_unlock(&dev->lock); return NULL; } 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); /* if this isn't a kernel thread, see if a client already exists */ if (task) { client = ion_client_lookup(dev, task); if (!IS_ERR_OR_NULL(client)) { put_task_struct(current->group_leader); return client; } } client = kzalloc(sizeof(struct ion_client), GFP_KERNEL); if (!client) { 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; kref_init(&client->ref); mutex_lock(&dev->lock); if (task) { p = &dev->user_clients.rb_node; while (*p) { parent = *p; entry = rb_entry(parent, struct ion_client, node); if (task < entry->task) p = &(*p)->rb_left; else if (task > entry->task) p = &(*p)->rb_right; } rb_link_node(&client->node, parent, p); rb_insert_color(&client->node, &dev->user_clients); } else { p = &dev->kernel_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->kernel_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; } static void _ion_client_destroy(struct kref *kref) { struct ion_client *client = container_of(kref, struct ion_client, ref); struct ion_device *dev = client->dev; struct rb_node *n; pr_debug("\n"); 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) { rb_erase(&client->node, &dev->user_clients); put_task_struct(client->task); } else { rb_erase(&client->node, &dev->kernel_clients); } debugfs_remove_recursive(client->debug_root); mutex_unlock(&dev->lock); kfree(client); } void ion_client_get(struct ion_client *client) { kref_get(&client->ref); } int ion_client_put(struct ion_client *client) { return kref_put(&client->ref, _ion_client_destroy); } void ion_client_destroy(struct ion_client *client) { ion_client_put(client); } static int ion_share_release(struct inode *inode, struct file* file) { struct ion_buffer *buffer = file->private_data; pr_debug("\n"); /* drop the reference to the buffer -- this prevents the buffer from going away because the client holding it exited while it was being passed */ ion_buffer_put(buffer); return 0; } static void ion_vma_open(struct vm_area_struct *vma) { struct ion_buffer *buffer = vma->vm_file->private_data; struct ion_handle *handle = vma->vm_private_data; struct ion_client *client; pr_debug("\n"); /* check that the client still exists and take a reference so it can't go away until this vma is closed */ client = ion_client_lookup(buffer->dev, current->group_leader); if (IS_ERR_OR_NULL(client)) { vma->vm_private_data = NULL; return; } ion_buffer_get(buffer); ion_handle_get(handle); pr_debug("client_cnt %d handle_cnt %d alloc_cnt %d\n", atomic_read(&client->ref.refcount), atomic_read(&handle->ref.refcount), atomic_read(&buffer->ref.refcount)); } static void ion_vma_close(struct vm_area_struct *vma) { struct ion_handle *handle = vma->vm_private_data; struct ion_buffer *buffer = vma->vm_file->private_data; struct ion_client *client; pr_debug("\n"); /* this indicates the client is gone, nothing to do here */ if (!handle) return; client = handle->client; pr_debug("client_cnt %d handle_cnt %d alloc_cnt %d\n", atomic_read(&client->ref.refcount), atomic_read(&handle->ref.refcount), atomic_read(&buffer->ref.refcount)); ion_handle_put(handle); ion_client_put(client); ion_buffer_put(buffer); pr_debug("client_cnt %d handle_cnt %d alloc_cnt %d\n", atomic_read(&client->ref.refcount), atomic_read(&handle->ref.refcount), atomic_read(&buffer->ref.refcount)); } static struct vm_operations_struct ion_vm_ops = { .open = ion_vma_open, .close = ion_vma_close, }; static int ion_share_mmap(struct file *file, struct vm_area_struct *vma) { struct ion_buffer *buffer = file->private_data; unsigned long size = vma->vm_end - vma->vm_start; struct ion_client *client; struct ion_handle *handle; int ret; pr_debug("\n"); /* make sure the client still exists, it's possible for the client to have gone away but the map/share fd still to be around, take a reference to it so it can't go away while this mapping exists */ client = ion_client_lookup(buffer->dev, current->group_leader); if (IS_ERR_OR_NULL(client)) { WARN(1, "trying to mmap an ion handle in a process with no " "ion client\n"); return -EINVAL; } if ((size > buffer->size) || (size + (vma->vm_pgoff << PAGE_SHIFT) > buffer->size)) { WARN(1, "trying to map larger area than handle has available" "\n"); ret = -EINVAL; goto err; } /* find the handle and take a reference to it */ handle = ion_import(client, buffer); if (IS_ERR_OR_NULL(handle)) { ret = -EINVAL; goto err; } ion_buffer_get(buffer); if (!handle->buffer->heap->ops->map_user) { pr_err("this heap does not define a method for mapping " "to userspace\n"); ret = -EINVAL; goto err1; } mutex_lock(&buffer->lock); /* now map it to userspace */ ret = buffer->heap->ops->map_user(buffer->heap, buffer, vma); mutex_unlock(&buffer->lock); if (ret) { pr_err("failure mapping buffer to userspace\n"); goto err1; } vma->vm_ops = &ion_vm_ops; /* move the handle into the vm_private_data so we can access it from vma_open/close */ vma->vm_private_data = handle; pr_debug("client_cnt %d handle_cnt %d alloc_cnt %d\n", atomic_read(&client->ref.refcount), atomic_read(&handle->ref.refcount), atomic_read(&buffer->ref.refcount)); return 0; err1: /* drop the reference to the handle */ ion_handle_put(handle); err: /* drop the reference to the client */ ion_client_put(client); return ret; } static const struct file_operations ion_share_fops = { .owner = THIS_MODULE, .release = ion_share_release, .mmap = ion_share_mmap, }; static int ion_ioctl_share(struct file *parent, struct ion_client *client, struct ion_handle *handle) { int fd = get_unused_fd(); struct file *file; if (fd < 0) return -ENFILE; file = anon_inode_getfile("ion_share_fd", &ion_share_fops, handle->buffer, O_RDWR); if (IS_ERR_OR_NULL(file)) goto err; ion_buffer_get(handle->buffer); fd_install(fd, file); return fd; err: put_unused_fd(fd); return -ENFILE; } 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 (copy_to_user((void __user *)arg, &data, sizeof(data))) 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_MAP: case ION_IOC_SHARE: { struct ion_fd_data data; if (copy_from_user(&data, (void __user *)arg, sizeof(data))) return -EFAULT; mutex_lock(&client->lock); if (!ion_handle_validate(client, data.handle)) { WARN(1, "invalid handle passed to share ioctl.\n"); mutex_unlock(&client->lock); return -EINVAL; } data.fd = ion_ioctl_share(filp, client, data.handle); mutex_unlock(&client->lock); 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_fd(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); } default: return -ENOTTY; } return 0; } static int ion_release(struct inode *inode, struct file *file) { struct ion_client *client = file->private_data; pr_debug("\n"); ion_client_put(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("\n"); 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_type type) { 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->type == type) 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->user_clients); n; n = rb_next(n)) { struct ion_client *client = rb_entry(n, struct ion_client, node); char task_comm[TASK_COMM_LEN]; size_t size = ion_debug_heap_total(client, heap->type); if (!size) continue; get_task_comm(task_comm, client->task); seq_printf(s, "%16.s %16u %16u\n", task_comm, client->pid, size); } for (n = rb_first(&dev->kernel_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->type); if (!size) continue; seq_printf(s, "%16.s %16u %16u\n", client->name, client->pid, size); } 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; 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("can not insert multiple heaps with " "id %d\n", 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); } 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->user_clients = RB_ROOT; idev->kernel_clients = RB_ROOT; return idev; } void ion_device_destroy(struct ion_device *dev) { misc_deregister(&dev->dev); /* XXX need to free the heaps and clients ? */ kfree(dev); } struct ion_client *ion_client_get_file(int fd) { struct ion_client *client = ERR_PTR(-EFAULT); struct file *f = fget(fd); if (!f) return ERR_PTR(-EINVAL); if (f->f_op == &ion_fops) { client = f->private_data; ion_client_get(client); } fput(f); return client; }