aboutsummaryrefslogtreecommitdiff
path: root/drivers/base/dma_buf_lock/src/dma_buf_lock.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/base/dma_buf_lock/src/dma_buf_lock.c')
-rwxr-xr-xdrivers/base/dma_buf_lock/src/dma_buf_lock.c481
1 files changed, 481 insertions, 0 deletions
diff --git a/drivers/base/dma_buf_lock/src/dma_buf_lock.c b/drivers/base/dma_buf_lock/src/dma_buf_lock.c
new file mode 100755
index 00000000000..9a0b35b05db
--- /dev/null
+++ b/drivers/base/dma_buf_lock/src/dma_buf_lock.c
@@ -0,0 +1,481 @@
+/*
+ *
+ * (C) COPYRIGHT ARM Limited. All rights reserved.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms
+ * of such GNU licence.
+ *
+ * A copy of the licence is included with the program, and can also be obtained
+ * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+
+#include <asm/uaccess.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/atomic.h>
+#include <linux/dma-buf.h>
+#include <linux/kds.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/poll.h>
+#include <linux/anon_inodes.h>
+#include <linux/file.h>
+
+#include "dma_buf_lock.h"
+
+/* Maximum number of buffers that a single handle can address */
+#define DMA_BUF_LOCK_BUF_MAX 32
+
+#define DMA_BUF_LOCK_DEBUG 1
+
+static dev_t dma_buf_lock_dev;
+static struct cdev dma_buf_lock_cdev;
+static struct class *dma_buf_lock_class;
+static char dma_buf_lock_dev_name[] = "dma_buf_lock";
+
+#ifdef HAVE_UNLOCKED_IOCTL
+static long dma_buf_lock_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
+#else
+static int dma_buf_lock_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
+#endif
+
+static struct file_operations dma_buf_lock_fops =
+{
+ .owner = THIS_MODULE,
+#ifdef HAVE_UNLOCKED_IOCTL
+ .unlocked_ioctl = dma_buf_lock_ioctl,
+#else
+ .ioctl = dma_buf_lock_ioctl,
+#endif
+ .compat_ioctl = dma_buf_lock_ioctl,
+};
+
+typedef struct dma_buf_lock_resource
+{
+ int *list_of_dma_buf_fds; /* List of buffers copied from userspace */
+ atomic_t locked; /* Status of lock */
+ struct dma_buf **dma_bufs;
+ struct kds_resource **kds_resources; /* List of KDS resources associated with buffers */
+ struct kds_resource_set *resource_set;
+ unsigned long exclusive; /* Exclusive access bitmap */
+ wait_queue_head_t wait;
+ struct kds_callback cb;
+ struct kref refcount;
+ struct list_head link;
+ int count;
+} dma_buf_lock_resource;
+
+static LIST_HEAD(dma_buf_lock_resource_list);
+static DEFINE_MUTEX(dma_buf_lock_mutex);
+
+static inline int is_dma_buf_lock_file(struct file *);
+static void dma_buf_lock_dounlock(struct kref *ref);
+
+static int dma_buf_lock_handle_release(struct inode *inode, struct file *file)
+{
+ dma_buf_lock_resource *resource;
+
+ if (!is_dma_buf_lock_file(file))
+ return -EINVAL;
+
+ resource = file->private_data;
+#if DMA_BUF_LOCK_DEBUG
+ printk("dma_buf_lock_handle_release\n");
+#endif
+ mutex_lock(&dma_buf_lock_mutex);
+ kref_put(&resource->refcount, dma_buf_lock_dounlock);
+ mutex_unlock(&dma_buf_lock_mutex);
+
+ return 0;
+}
+
+static void dma_buf_lock_kds_callback(void *param1, void *param2)
+{
+ dma_buf_lock_resource *resource = param1;
+#if DMA_BUF_LOCK_DEBUG
+ printk("dma_buf_lock_kds_callback\n");
+#endif
+ atomic_set(&resource->locked, 1);
+
+ wake_up(&resource->wait);
+}
+
+static unsigned int dma_buf_lock_handle_poll(struct file *file,
+ struct poll_table_struct *wait)
+{
+ dma_buf_lock_resource *resource;
+ unsigned int ret = 0;
+
+ if (!is_dma_buf_lock_file(file))
+ return POLLERR;
+
+ resource = file->private_data;
+#if DMA_BUF_LOCK_DEBUG
+ printk("dma_buf_lock_handle_poll\n");
+#endif
+ if (1 == atomic_read(&resource->locked))
+ {
+ /* Resources have been locked */
+ ret = POLLIN | POLLRDNORM;
+ if (resource->exclusive)
+ {
+ ret |= POLLOUT | POLLWRNORM;
+ }
+ }
+ else
+ {
+ if (!poll_does_not_wait(wait))
+ {
+ poll_wait(file, &resource->wait, wait);
+ }
+ }
+#if DMA_BUF_LOCK_DEBUG
+ printk("dma_buf_lock_handle_poll : return %i\n", ret);
+#endif
+ return ret;
+}
+
+static const struct file_operations dma_buf_lock_handle_fops = {
+ .release = dma_buf_lock_handle_release,
+ .poll = dma_buf_lock_handle_poll,
+};
+
+/*
+ * is_dma_buf_lock_file - Check if struct file* is associated with dma_buf_lock
+ */
+static inline int is_dma_buf_lock_file(struct file *file)
+{
+ return file->f_op == &dma_buf_lock_handle_fops;
+}
+
+
+
+/*
+ * Start requested lock.
+ *
+ * Allocates required memory, copies dma_buf_fd list from userspace,
+ * acquires related KDS resources, and starts the lock.
+ */
+static int dma_buf_lock_dolock(dma_buf_lock_k_request *request)
+{
+ dma_buf_lock_resource *resource;
+ int size;
+ int fd;
+ int i;
+ int ret;
+
+ if (NULL == request->list_of_dma_buf_fds)
+ {
+ return -EINVAL;
+ }
+ if (request->count <= 0)
+ {
+ return -EINVAL;
+ }
+ if (request->count > DMA_BUF_LOCK_BUF_MAX)
+ {
+ return -EINVAL;
+ }
+ if (request->exclusive != DMA_BUF_LOCK_NONEXCLUSIVE &&
+ request->exclusive != DMA_BUF_LOCK_EXCLUSIVE)
+ {
+ return -EINVAL;
+ }
+
+ resource = kzalloc(sizeof(dma_buf_lock_resource), GFP_KERNEL);
+ if (NULL == resource)
+ {
+ return -ENOMEM;
+ }
+
+ atomic_set(&resource->locked, 0);
+ kref_init(&resource->refcount);
+ INIT_LIST_HEAD(&resource->link);
+ resource->count = request->count;
+
+ /* Allocate space to store dma_buf_fds received from user space */
+ size = request->count * sizeof(int);
+ resource->list_of_dma_buf_fds = kmalloc(size, GFP_KERNEL);
+
+ if (NULL == resource->list_of_dma_buf_fds)
+ {
+ kfree(resource);
+ return -ENOMEM;
+ }
+
+ /* Allocate space to store dma_buf pointers associated with dma_buf_fds */
+ size = sizeof(struct dma_buf *) * request->count;
+ resource->dma_bufs = kmalloc(size, GFP_KERNEL);
+
+ if (NULL == resource->dma_bufs)
+ {
+ kfree(resource->list_of_dma_buf_fds);
+ kfree(resource);
+ return -ENOMEM;
+ }
+ /* Allocate space to store kds_resources associated with dma_buf_fds */
+ size = sizeof(struct kds_resource *) * request->count;
+ resource->kds_resources = kmalloc(size, GFP_KERNEL);
+
+ if (NULL == resource->kds_resources)
+ {
+ kfree(resource->dma_bufs);
+ kfree(resource->list_of_dma_buf_fds);
+ kfree(resource);
+ return -ENOMEM;
+ }
+
+ /* Copy requested list of dma_buf_fds from user space */
+ size = request->count * sizeof(int);
+ if (0 != copy_from_user(resource->list_of_dma_buf_fds, (void __user *)request->list_of_dma_buf_fds, size))
+ {
+ kfree(resource->list_of_dma_buf_fds);
+ kfree(resource->dma_bufs);
+ kfree(resource->kds_resources);
+ kfree(resource);
+ return -ENOMEM;
+ }
+#if DMA_BUF_LOCK_DEBUG
+ for (i = 0; i < request->count; i++)
+ {
+ printk("dma_buf %i = %X\n", i, resource->list_of_dma_buf_fds[i]);
+ }
+#endif
+
+ /* Add resource to global list */
+ mutex_lock(&dma_buf_lock_mutex);
+
+ list_add(&resource->link, &dma_buf_lock_resource_list);
+
+ mutex_unlock(&dma_buf_lock_mutex);
+
+ for (i = 0; i < request->count; i++)
+ {
+ /* Convert fd into dma_buf structure */
+ resource->dma_bufs[i] = dma_buf_get(resource->list_of_dma_buf_fds[i]);
+
+ if (IS_ERR_VALUE(PTR_ERR(resource->dma_bufs[i])))
+ {
+ mutex_lock(&dma_buf_lock_mutex);
+ kref_put(&resource->refcount, dma_buf_lock_dounlock);
+ mutex_unlock(&dma_buf_lock_mutex);
+ return -EINVAL;
+ }
+
+ /*Get kds_resource associated with dma_buf */
+ resource->kds_resources[i] = get_dma_buf_kds_resource(resource->dma_bufs[i]);
+
+ if (NULL == resource->kds_resources[i])
+ {
+ mutex_lock(&dma_buf_lock_mutex);
+ kref_put(&resource->refcount, dma_buf_lock_dounlock);
+ mutex_unlock(&dma_buf_lock_mutex);
+ return -EINVAL;
+ }
+#if DMA_BUF_LOCK_DEBUG
+ printk("dma_buf_lock_dolock : dma_buf_fd %i dma_buf %X kds_resource %X\n", resource->list_of_dma_buf_fds[i],
+ (unsigned int)resource->dma_bufs[i], (unsigned int)resource->kds_resources[i]);
+#endif
+ }
+
+ kds_callback_init(&resource->cb, 1, dma_buf_lock_kds_callback);
+ init_waitqueue_head(&resource->wait);
+
+ kref_get(&resource->refcount);
+
+ /* Create file descriptor associated with lock request */
+ fd = anon_inode_getfd("dma_buf_lock", &dma_buf_lock_handle_fops,
+ (void *)resource, 0);
+ if (fd < 0)
+ {
+ mutex_lock(&dma_buf_lock_mutex);
+ kref_put(&resource->refcount, dma_buf_lock_dounlock);
+ kref_put(&resource->refcount, dma_buf_lock_dounlock);
+ mutex_unlock(&dma_buf_lock_mutex);
+ return fd;
+ }
+
+ resource->exclusive = request->exclusive;
+
+ /* Start locking process */
+ ret = kds_async_waitall(&resource->resource_set,
+ &resource->cb, resource, NULL,
+ request->count, &resource->exclusive,
+ resource->kds_resources);
+
+ if (IS_ERR_VALUE(ret))
+ {
+ put_unused_fd(fd);
+
+ mutex_lock(&dma_buf_lock_mutex);
+ kref_put(&resource->refcount, dma_buf_lock_dounlock);
+ mutex_unlock(&dma_buf_lock_mutex);
+
+ return ret;
+ }
+
+#if DMA_BUF_LOCK_DEBUG
+ printk("dma_buf_lock_dolock : complete\n");
+#endif
+ mutex_lock(&dma_buf_lock_mutex);
+ kref_put(&resource->refcount, dma_buf_lock_dounlock);
+ mutex_unlock(&dma_buf_lock_mutex);
+
+ return fd;
+}
+
+static void dma_buf_lock_dounlock(struct kref *ref)
+{
+ int i;
+ dma_buf_lock_resource *resource = container_of(ref, dma_buf_lock_resource, refcount);
+
+ atomic_set(&resource->locked, 0);
+
+ kds_callback_term(&resource->cb);
+
+ kds_resource_set_release(&resource->resource_set);
+
+ list_del(&resource->link);
+
+ for (i = 0; i < resource->count; i++)
+ {
+ dma_buf_put(resource->dma_bufs[i]);
+ }
+
+ kfree(resource->kds_resources);
+ kfree(resource->dma_bufs);
+ kfree(resource->list_of_dma_buf_fds);
+ kfree(resource);
+}
+
+static int __init dma_buf_lock_init(void)
+{
+ int err;
+#if DMA_BUF_LOCK_DEBUG
+ printk("dma_buf_lock_init\n");
+#endif
+ err = alloc_chrdev_region(&dma_buf_lock_dev, 0, 1, dma_buf_lock_dev_name);
+
+ if (0 == err)
+ {
+ cdev_init(&dma_buf_lock_cdev, &dma_buf_lock_fops);
+
+ err = cdev_add(&dma_buf_lock_cdev, dma_buf_lock_dev, 1);
+
+ if (0 == err)
+ {
+ dma_buf_lock_class = class_create(THIS_MODULE, dma_buf_lock_dev_name);
+ if (IS_ERR(dma_buf_lock_class))
+ {
+ err = PTR_ERR(dma_buf_lock_class);
+ }
+ else
+ {
+ struct device *mdev;
+ mdev = device_create(dma_buf_lock_class, NULL, dma_buf_lock_dev, NULL, dma_buf_lock_dev_name);
+ if (!IS_ERR(mdev))
+ {
+ return 0;
+ }
+
+ err = PTR_ERR(mdev);
+ class_destroy(dma_buf_lock_class);
+ }
+ cdev_del(&dma_buf_lock_cdev);
+ }
+
+ unregister_chrdev_region(dma_buf_lock_dev, 1);
+ }
+#if DMA_BUF_LOCK_DEBUG
+ printk("dma_buf_lock_init failed\n");
+#endif
+ return err;
+}
+
+static void __exit dma_buf_lock_exit(void)
+{
+#if DMA_BUF_LOCK_DEBUG
+ printk("dma_buf_lock_exit\n");
+#endif
+
+ /* Unlock all outstanding references */
+ while (1)
+ {
+ mutex_lock(&dma_buf_lock_mutex);
+ if (list_empty(&dma_buf_lock_resource_list))
+ {
+ mutex_unlock(&dma_buf_lock_mutex);
+ break;
+ }
+ else
+ {
+ dma_buf_lock_resource *resource = list_entry(dma_buf_lock_resource_list.next,
+ dma_buf_lock_resource, link);
+ kref_put(&resource->refcount, dma_buf_lock_dounlock);
+ mutex_unlock(&dma_buf_lock_mutex);
+ }
+ }
+
+ device_destroy(dma_buf_lock_class, dma_buf_lock_dev);
+
+ class_destroy(dma_buf_lock_class);
+
+ cdev_del(&dma_buf_lock_cdev);
+
+ unregister_chrdev_region(dma_buf_lock_dev, 1);
+}
+
+#ifdef HAVE_UNLOCKED_IOCTL
+static long dma_buf_lock_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+#else
+static int dma_buf_lock_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
+#endif
+{
+ dma_buf_lock_k_request request;
+ int size = _IOC_SIZE(cmd);
+
+ if (_IOC_TYPE(cmd) != DMA_BUF_LOCK_IOC_MAGIC)
+ {
+ return -ENOTTY;
+
+ }
+ if ((_IOC_NR(cmd) < DMA_BUF_LOCK_IOC_MINNR) || (_IOC_NR(cmd) > DMA_BUF_LOCK_IOC_MAXNR))
+ {
+ return -ENOTTY;
+ }
+
+ switch (cmd)
+ {
+ case DMA_BUF_LOCK_FUNC_LOCK_ASYNC:
+ if (size != sizeof(dma_buf_lock_k_request))
+ {
+ return -ENOTTY;
+ }
+ if (copy_from_user(&request, (void __user *)arg, size))
+ {
+ return -EFAULT;
+ }
+#if DMA_BUF_LOCK_DEBUG
+ printk("DMA_BUF_LOCK_FUNC_LOCK_ASYNC - %i\n", request.count);
+#endif
+ return dma_buf_lock_dolock(&request);
+ }
+
+ return -ENOTTY;
+}
+
+module_init(dma_buf_lock_init);
+module_exit(dma_buf_lock_exit);
+
+MODULE_LICENSE("GPL");
+