blob: 9613ffcfd69640c5cd9d20650ec5dd59e9b48793 [file] [log] [blame]
/*
*
* (C) COPYRIGHT 2012-2013 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");