/*
 * Copyright (C) 2013 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/bitops.h>
#include <linux/circ_buf.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/uaccess.h>

#include <video/adf_client.h>
#include <video/adf_format.h>

#include "sw_sync.h"
#include "sync.h"

#include "adf.h"
#include "adf_fops.h"
#include "adf_sysfs.h"

#ifdef CONFIG_COMPAT
#include "adf_fops32.h"
#endif

static int adf_obj_set_event(struct adf_obj *obj, struct adf_file *file,
		struct adf_set_event __user *arg)
{
	struct adf_set_event data;
	bool enabled;
	unsigned long flags;
	int err;

	if (copy_from_user(&data, arg, sizeof(data)))
		return -EFAULT;

	err = adf_obj_check_supports_event(obj, data.type);
	if (err < 0)
		return err;

	spin_lock_irqsave(&obj->file_lock, flags);
	if (data.enabled)
		enabled = test_and_set_bit(data.type,
				file->event_subscriptions);
	else
		enabled = test_and_clear_bit(data.type,
				file->event_subscriptions);
	spin_unlock_irqrestore(&obj->file_lock, flags);

	if (data.enabled == enabled)
		return -EALREADY;

	if (data.enabled)
		adf_event_get(obj, data.type);
	else
		adf_event_put(obj, data.type);

	return 0;
}

static int adf_obj_copy_custom_data_to_user(struct adf_obj *obj,
		void __user *dst, size_t *dst_size)
{
	void *custom_data;
	size_t custom_data_size;
	int ret;

	if (!obj->ops || !obj->ops->custom_data) {
		dev_dbg(&obj->dev, "%s: no custom_data op\n", __func__);
		return 0;
	}

	custom_data = kzalloc(ADF_MAX_CUSTOM_DATA_SIZE, GFP_KERNEL);
	if (!custom_data)
		return -ENOMEM;

	ret = obj->ops->custom_data(obj, custom_data, &custom_data_size);
	if (ret < 0)
		goto done;

	if (copy_to_user(dst, custom_data, min(*dst_size, custom_data_size))) {
		ret = -EFAULT;
		goto done;
	}
	*dst_size = custom_data_size;

done:
	kfree(custom_data);
	return ret;
}

static int adf_eng_get_data(struct adf_overlay_engine *eng,
		struct adf_overlay_engine_data __user *arg)
{
	struct adf_device *dev = adf_overlay_engine_parent(eng);
	struct adf_overlay_engine_data data;
	size_t n_supported_formats;
	u32 *supported_formats = NULL;
	int ret = 0;

	if (copy_from_user(&data, arg, sizeof(data)))
		return -EFAULT;

	strlcpy(data.name, eng->base.name, sizeof(data.name));

	if (data.n_supported_formats > ADF_MAX_SUPPORTED_FORMATS)
		return -EINVAL;

	n_supported_formats = data.n_supported_formats;
	data.n_supported_formats = eng->ops->n_supported_formats;

	if (n_supported_formats) {
		supported_formats = kzalloc(n_supported_formats *
				sizeof(supported_formats[0]), GFP_KERNEL);
		if (!supported_formats)
			return -ENOMEM;
	}

	memcpy(supported_formats, eng->ops->supported_formats,
			sizeof(u32) * min(n_supported_formats,
					eng->ops->n_supported_formats));

	mutex_lock(&dev->client_lock);
	ret = adf_obj_copy_custom_data_to_user(&eng->base, arg->custom_data,
			&data.custom_data_size);
	mutex_unlock(&dev->client_lock);

	if (ret < 0)
		goto done;

	if (copy_to_user(arg, &data, sizeof(data))) {
		ret = -EFAULT;
		goto done;
	}

	if (supported_formats && copy_to_user(arg->supported_formats,
			supported_formats,
			n_supported_formats * sizeof(supported_formats[0])))
		ret = -EFAULT;

done:
	kfree(supported_formats);
	return ret;
}

static int adf_buffer_import(struct adf_device *dev,
		struct adf_buffer_config __user *cfg, struct adf_buffer *buf)
{
	struct adf_buffer_config user_buf;
	size_t i;
	int ret = 0;

	if (copy_from_user(&user_buf, cfg, sizeof(user_buf)))
		return -EFAULT;

	memset(buf, 0, sizeof(*buf));

	if (user_buf.n_planes > ADF_MAX_PLANES) {
		dev_err(&dev->base.dev, "invalid plane count %u\n",
				user_buf.n_planes);
		return -EINVAL;
	}

	buf->overlay_engine = idr_find(&dev->overlay_engines,
			user_buf.overlay_engine);
	if (!buf->overlay_engine) {
		dev_err(&dev->base.dev, "invalid overlay engine id %u\n",
				user_buf.overlay_engine);
		return -ENOENT;
	}

	buf->w = user_buf.w;
	buf->h = user_buf.h;
	buf->format = user_buf.format;
	for (i = 0; i < user_buf.n_planes; i++) {
		buf->dma_bufs[i] = dma_buf_get(user_buf.fd[i]);
		if (IS_ERR(buf->dma_bufs[i])) {
			ret = PTR_ERR(buf->dma_bufs[i]);
			dev_err(&dev->base.dev, "importing dma_buf fd %d failed: %d\n",
					user_buf.fd[i], ret);
			buf->dma_bufs[i] = NULL;
			goto done;
		}
		buf->offset[i] = user_buf.offset[i];
		buf->pitch[i] = user_buf.pitch[i];
	}
	buf->n_planes = user_buf.n_planes;

	if (user_buf.acquire_fence >= 0) {
		buf->acquire_fence = sync_fence_fdget(user_buf.acquire_fence);
		if (!buf->acquire_fence) {
			dev_err(&dev->base.dev, "getting fence fd %d failed\n",
					user_buf.acquire_fence);
			ret = -EINVAL;
			goto done;
		}
	}

done:
	if (ret < 0)
		adf_buffer_cleanup(buf);
	return ret;
}

static int adf_device_post_config(struct adf_device *dev,
		struct adf_post_config __user *arg)
{
	struct sync_fence *complete_fence;
	int complete_fence_fd;
	struct adf_buffer *bufs = NULL;
	struct adf_interface **intfs = NULL;
	size_t n_intfs, n_bufs, i;
	void *custom_data = NULL;
	size_t custom_data_size;
	int ret = 0;

	complete_fence_fd = get_unused_fd();
	if (complete_fence_fd < 0)
		return complete_fence_fd;

	if (get_user(n_intfs, &arg->n_interfaces)) {
		ret = -EFAULT;
		goto err_get_user;
	}

	if (n_intfs > ADF_MAX_INTERFACES) {
		ret = -EINVAL;
		goto err_get_user;
	}

	if (get_user(n_bufs, &arg->n_bufs)) {
		ret = -EFAULT;
		goto err_get_user;
	}

	if (n_bufs > ADF_MAX_BUFFERS) {
		ret = -EINVAL;
		goto err_get_user;
	}

	if (get_user(custom_data_size, &arg->custom_data_size)) {
		ret = -EFAULT;
		goto err_get_user;
	}

	if (custom_data_size > ADF_MAX_CUSTOM_DATA_SIZE) {
		ret = -EINVAL;
		goto err_get_user;
	}

	if (n_intfs) {
		intfs = kmalloc(sizeof(intfs[0]) * n_intfs, GFP_KERNEL);
		if (!intfs) {
			ret = -ENOMEM;
			goto err_get_user;
		}
	}

	for (i = 0; i < n_intfs; i++) {
		u32 intf_id;
		if (get_user(intf_id, &arg->interfaces[i])) {
			ret = -EFAULT;
			goto err_get_user;
		}

		intfs[i] = idr_find(&dev->interfaces, intf_id);
		if (!intfs[i]) {
			ret = -EINVAL;
			goto err_get_user;
		}
	}

	if (n_bufs) {
		bufs = kzalloc(sizeof(bufs[0]) * n_bufs, GFP_KERNEL);
		if (!bufs) {
			ret = -ENOMEM;
			goto err_get_user;
		}
	}

	for (i = 0; i < n_bufs; i++) {
		ret = adf_buffer_import(dev, &arg->bufs[i], &bufs[i]);
		if (ret < 0) {
			memset(&bufs[i], 0, sizeof(bufs[i]));
			goto err_import;
		}
	}

	if (custom_data_size) {
		custom_data = kzalloc(custom_data_size, GFP_KERNEL);
		if (!custom_data) {
			ret = -ENOMEM;
			goto err_import;
		}

		if (copy_from_user(custom_data, arg->custom_data,
				custom_data_size)) {
			ret = -EFAULT;
			goto err_import;
		}
	}

	if (put_user(complete_fence_fd, &arg->complete_fence)) {
		ret = -EFAULT;
		goto err_import;
	}

	complete_fence = adf_device_post_nocopy(dev, intfs, n_intfs, bufs,
			n_bufs, custom_data, custom_data_size);
	if (IS_ERR(complete_fence)) {
		ret = PTR_ERR(complete_fence);
		goto err_import;
	}

	sync_fence_install(complete_fence, complete_fence_fd);
	return 0;

err_import:
	for (i = 0; i < n_bufs; i++)
		adf_buffer_cleanup(&bufs[i]);

err_get_user:
	kfree(custom_data);
	kfree(bufs);
	kfree(intfs);
	put_unused_fd(complete_fence_fd);
	return ret;
}

static int adf_intf_simple_post_config(struct adf_interface *intf,
		struct adf_simple_post_config __user *arg)
{
	struct adf_device *dev = intf->base.parent;
	struct sync_fence *complete_fence;
	int complete_fence_fd;
	struct adf_buffer buf;
	int ret = 0;

	complete_fence_fd = get_unused_fd();
	if (complete_fence_fd < 0)
		return complete_fence_fd;

	ret = adf_buffer_import(dev, &arg->buf, &buf);
	if (ret < 0)
		goto err_import;

	if (put_user(complete_fence_fd, &arg->complete_fence)) {
		ret = -EFAULT;
		goto err_put_user;
	}

	complete_fence = adf_interface_simple_post(intf, &buf);
	if (IS_ERR(complete_fence)) {
		ret = PTR_ERR(complete_fence);
		goto err_put_user;
	}

	sync_fence_install(complete_fence, complete_fence_fd);
	return 0;

err_put_user:
	adf_buffer_cleanup(&buf);
err_import:
	put_unused_fd(complete_fence_fd);
	return ret;
}

static int adf_intf_simple_buffer_alloc(struct adf_interface *intf,
		struct adf_simple_buffer_alloc __user *arg)
{
	struct adf_simple_buffer_alloc data;
	struct dma_buf *dma_buf;
	int ret = 0;

	if (copy_from_user(&data, arg, sizeof(data)))
		return -EFAULT;

	data.fd = get_unused_fd_flags(O_CLOEXEC);
	if (data.fd < 0)
		return data.fd;

	ret = adf_interface_simple_buffer_alloc(intf, data.w, data.h,
			data.format, &dma_buf, &data.offset, &data.pitch);
	if (ret < 0)
		goto err_alloc;

	if (copy_to_user(arg, &data, sizeof(*arg))) {
		ret = -EFAULT;
		goto err_copy;
	}

	fd_install(data.fd, dma_buf->file);
	return 0;

err_copy:
	dma_buf_put(dma_buf);

err_alloc:
	put_unused_fd(data.fd);
	return ret;
}

static int adf_copy_attachment_list_to_user(
		struct adf_attachment_config __user *to, size_t n_to,
		struct adf_attachment *from, size_t n_from)
{
	struct adf_attachment_config *temp;
	size_t n = min(n_to, n_from);
	size_t i;
	int ret = 0;

	if (!n)
		return 0;

	temp = kzalloc(n * sizeof(temp[0]), GFP_KERNEL);
	if (!temp)
		return -ENOMEM;

	for (i = 0; i < n; i++) {
		temp[i].interface = from[i].interface->base.id;
		temp[i].overlay_engine = from[i].overlay_engine->base.id;
	}

	if (copy_to_user(to, temp, n * sizeof(to[0]))) {
		ret = -EFAULT;
		goto done;
	}

done:
	kfree(temp);
	return ret;
}

static int adf_device_get_data(struct adf_device *dev,
		struct adf_device_data __user *arg)
{
	struct adf_device_data data;
	size_t n_attach;
	struct adf_attachment *attach = NULL;
	size_t n_allowed_attach;
	struct adf_attachment *allowed_attach = NULL;
	int ret = 0;

	if (copy_from_user(&data, arg, sizeof(data)))
		return -EFAULT;

	if (data.n_attachments > ADF_MAX_ATTACHMENTS ||
			data.n_allowed_attachments > ADF_MAX_ATTACHMENTS)
		return -EINVAL;

	strlcpy(data.name, dev->base.name, sizeof(data.name));

	if (data.n_attachments) {
		attach = kzalloc(data.n_attachments * sizeof(attach[0]),
				GFP_KERNEL);
		if (!attach)
			return -ENOMEM;
	}
	n_attach = adf_device_attachments(dev, attach, data.n_attachments);

	if (data.n_allowed_attachments) {
		allowed_attach = kzalloc(data.n_allowed_attachments *
				sizeof(allowed_attach[0]), GFP_KERNEL);
		if (!allowed_attach) {
			ret = -ENOMEM;
			goto done;
		}
	}
	n_allowed_attach = adf_device_attachments_allowed(dev, allowed_attach,
			data.n_allowed_attachments);

	mutex_lock(&dev->client_lock);
	ret = adf_obj_copy_custom_data_to_user(&dev->base, arg->custom_data,
			&data.custom_data_size);
	mutex_unlock(&dev->client_lock);

	if (ret < 0)
		goto done;

	ret = adf_copy_attachment_list_to_user(arg->attachments,
			data.n_attachments, attach, n_attach);
	if (ret < 0)
		goto done;

	ret = adf_copy_attachment_list_to_user(arg->allowed_attachments,
			data.n_allowed_attachments, allowed_attach,
			n_allowed_attach);
	if (ret < 0)
		goto done;

	data.n_attachments = n_attach;
	data.n_allowed_attachments = n_allowed_attach;

	if (copy_to_user(arg, &data, sizeof(data)))
		ret = -EFAULT;

done:
	kfree(allowed_attach);
	kfree(attach);
	return ret;
}

static int adf_device_handle_attachment(struct adf_device *dev,
		struct adf_attachment_config __user *arg, bool attach)
{
	struct adf_attachment_config data;
	struct adf_overlay_engine *eng;
	struct adf_interface *intf;

	if (copy_from_user(&data, arg, sizeof(data)))
		return -EFAULT;

	eng = idr_find(&dev->overlay_engines, data.overlay_engine);
	if (!eng) {
		dev_err(&dev->base.dev, "invalid overlay engine id %u\n",
				data.overlay_engine);
		return -EINVAL;
	}

	intf = idr_find(&dev->interfaces, data.interface);
	if (!intf) {
		dev_err(&dev->base.dev, "invalid interface id %u\n",
				data.interface);
		return -EINVAL;
	}

	if (attach)
		return adf_device_attach(dev, eng, intf);
	else
		return adf_device_detach(dev, eng, intf);
}

static int adf_intf_set_mode(struct adf_interface *intf,
		struct drm_mode_modeinfo __user *arg)
{
	struct drm_mode_modeinfo mode;

	if (copy_from_user(&mode, arg, sizeof(mode)))
		return -EFAULT;

	return adf_interface_set_mode(intf, &mode);
}

static int adf_intf_get_data(struct adf_interface *intf,
		struct adf_interface_data __user *arg)
{
	struct adf_device *dev = adf_interface_parent(intf);
	struct adf_interface_data data;
	struct drm_mode_modeinfo *modelist;
	size_t modelist_size;
	int err;
	int ret = 0;
	unsigned long flags;

	if (copy_from_user(&data, arg, sizeof(data)))
		return -EFAULT;

	strlcpy(data.name, intf->base.name, sizeof(data.name));

	data.type = intf->type;
	data.id = intf->idx;
	data.flags = intf->flags;

	err = adf_interface_get_screen_size(intf, &data.width_mm,
			&data.height_mm);
	if (err < 0) {
		data.width_mm = 0;
		data.height_mm = 0;
	}

	modelist = kmalloc(sizeof(modelist[0]) * ADF_MAX_MODES, GFP_KERNEL);
	if (!modelist)
		return -ENOMEM;

	mutex_lock(&dev->client_lock);
	read_lock_irqsave(&intf->hotplug_modelist_lock, flags);
	data.hotplug_detect = intf->hotplug_detect;
	modelist_size = min(data.n_available_modes, intf->n_modes) *
			sizeof(intf->modelist[0]);
	memcpy(modelist, intf->modelist, modelist_size);
	data.n_available_modes = intf->n_modes;
	read_unlock_irqrestore(&intf->hotplug_modelist_lock, flags);

	if (copy_to_user(arg->available_modes, modelist, modelist_size)) {
		ret = -EFAULT;
		goto done;
	}

	data.dpms_state = intf->dpms_state;
	memcpy(&data.current_mode, &intf->current_mode,
			sizeof(intf->current_mode));

	ret = adf_obj_copy_custom_data_to_user(&intf->base, arg->custom_data,
			&data.custom_data_size);
done:
	mutex_unlock(&dev->client_lock);
	kfree(modelist);

	if (ret < 0)
		return ret;

	if (copy_to_user(arg, &data, sizeof(data)))
		ret = -EFAULT;

	return ret;
}

static inline long adf_obj_custom_ioctl(struct adf_obj *obj, unsigned int cmd,
		unsigned long arg)
{
	if (obj->ops && obj->ops->ioctl)
		return obj->ops->ioctl(obj, cmd, arg);
	return -ENOTTY;
}

static long adf_overlay_engine_ioctl(struct adf_overlay_engine *eng,
		struct adf_file *file, unsigned int cmd, unsigned long arg)
{
	switch (cmd) {
	case ADF_SET_EVENT:
		return adf_obj_set_event(&eng->base, file,
				(struct adf_set_event __user *)arg);

	case ADF_GET_OVERLAY_ENGINE_DATA:
		return adf_eng_get_data(eng,
			(struct adf_overlay_engine_data __user *)arg);

	case ADF_BLANK:
	case ADF_POST_CONFIG:
	case ADF_SET_MODE:
	case ADF_GET_DEVICE_DATA:
	case ADF_GET_INTERFACE_DATA:
	case ADF_SIMPLE_POST_CONFIG:
	case ADF_SIMPLE_BUFFER_ALLOC:
	case ADF_ATTACH:
	case ADF_DETACH:
		return -EINVAL;

	default:
		return adf_obj_custom_ioctl(&eng->base, cmd, arg);
	}
}

static long adf_interface_ioctl(struct adf_interface *intf,
		struct adf_file *file, unsigned int cmd, unsigned long arg)
{
	switch (cmd) {
	case ADF_SET_EVENT:
		return adf_obj_set_event(&intf->base, file,
				(struct adf_set_event __user *)arg);

	case ADF_BLANK:
		return adf_interface_blank(intf, arg);

	case ADF_SET_MODE:
		return adf_intf_set_mode(intf,
				(struct drm_mode_modeinfo __user *)arg);

	case ADF_GET_INTERFACE_DATA:
		return adf_intf_get_data(intf,
				(struct adf_interface_data __user *)arg);

	case ADF_SIMPLE_POST_CONFIG:
		return adf_intf_simple_post_config(intf,
				(struct adf_simple_post_config __user *)arg);

	case ADF_SIMPLE_BUFFER_ALLOC:
		return adf_intf_simple_buffer_alloc(intf,
				(struct adf_simple_buffer_alloc __user *)arg);

	case ADF_POST_CONFIG:
	case ADF_GET_DEVICE_DATA:
	case ADF_GET_OVERLAY_ENGINE_DATA:
	case ADF_ATTACH:
	case ADF_DETACH:
		return -EINVAL;

	default:
		return adf_obj_custom_ioctl(&intf->base, cmd, arg);
	}
}

static long adf_device_ioctl(struct adf_device *dev, struct adf_file *file,
		unsigned int cmd, unsigned long arg)
{
	switch (cmd) {
	case ADF_SET_EVENT:
		return adf_obj_set_event(&dev->base, file,
				(struct adf_set_event __user *)arg);

	case ADF_POST_CONFIG:
		return adf_device_post_config(dev,
				(struct adf_post_config __user *)arg);

	case ADF_GET_DEVICE_DATA:
		return adf_device_get_data(dev,
				(struct adf_device_data __user *)arg);

	case ADF_ATTACH:
		return adf_device_handle_attachment(dev,
				(struct adf_attachment_config __user *)arg,
				true);

	case ADF_DETACH:
		return adf_device_handle_attachment(dev,
				(struct adf_attachment_config __user *)arg,
				false);

	case ADF_BLANK:
	case ADF_SET_MODE:
	case ADF_GET_INTERFACE_DATA:
	case ADF_GET_OVERLAY_ENGINE_DATA:
	case ADF_SIMPLE_POST_CONFIG:
	case ADF_SIMPLE_BUFFER_ALLOC:
		return -EINVAL;

	default:
		return adf_obj_custom_ioctl(&dev->base, cmd, arg);
	}
}

static int adf_file_open(struct inode *inode, struct file *file)
{
	struct adf_obj *obj;
	struct adf_file *fpriv = NULL;
	unsigned long flags;
	int ret = 0;

	obj = adf_obj_sysfs_find(iminor(inode));
	if (!obj)
		return -ENODEV;

	dev_dbg(&obj->dev, "opening %s\n", dev_name(&obj->dev));

	if (!try_module_get(obj->parent->ops->owner)) {
		dev_err(&obj->dev, "getting owner module failed\n");
		return -ENODEV;
	}

	fpriv = kzalloc(sizeof(*fpriv), GFP_KERNEL);
	if (!fpriv) {
		ret = -ENOMEM;
		goto done;
	}

	INIT_LIST_HEAD(&fpriv->head);
	fpriv->obj = obj;
	init_waitqueue_head(&fpriv->event_wait);

	file->private_data = fpriv;

	if (obj->ops && obj->ops->open) {
		ret = obj->ops->open(obj, inode, file);
		if (ret < 0)
			goto done;
	}

	spin_lock_irqsave(&obj->file_lock, flags);
	list_add_tail(&fpriv->head, &obj->file_list);
	spin_unlock_irqrestore(&obj->file_lock, flags);

done:
	if (ret < 0) {
		kfree(fpriv);
		module_put(obj->parent->ops->owner);
	}
	return ret;
}

static int adf_file_release(struct inode *inode, struct file *file)
{
	struct adf_file *fpriv = file->private_data;
	struct adf_obj *obj = fpriv->obj;
	enum adf_event_type event_type;
	unsigned long flags;

	if (obj->ops && obj->ops->release)
		obj->ops->release(obj, inode, file);

	spin_lock_irqsave(&obj->file_lock, flags);
	list_del(&fpriv->head);
	spin_unlock_irqrestore(&obj->file_lock, flags);

	for_each_set_bit(event_type, fpriv->event_subscriptions,
			ADF_EVENT_TYPE_MAX) {
		adf_event_put(obj, event_type);
	}

	kfree(fpriv);
	module_put(obj->parent->ops->owner);

	dev_dbg(&obj->dev, "released %s\n", dev_name(&obj->dev));
	return 0;
}

long adf_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	struct adf_file *fpriv = file->private_data;
	struct adf_obj *obj = fpriv->obj;
	long ret = -EINVAL;

	dev_dbg(&obj->dev, "%s ioctl %u\n", dev_name(&obj->dev), _IOC_NR(cmd));

	switch (obj->type) {
	case ADF_OBJ_OVERLAY_ENGINE:
		ret = adf_overlay_engine_ioctl(adf_obj_to_overlay_engine(obj),
				fpriv, cmd, arg);
		break;

	case ADF_OBJ_INTERFACE:
		ret = adf_interface_ioctl(adf_obj_to_interface(obj), fpriv, cmd,
				arg);
		break;

	case ADF_OBJ_DEVICE:
		ret = adf_device_ioctl(adf_obj_to_device(obj), fpriv, cmd, arg);
		break;
	}

	return ret;
}

static inline bool adf_file_event_available(struct adf_file *fpriv)
{
	int head = fpriv->event_head;
	int tail = fpriv->event_tail;
	return CIRC_CNT(head, tail, sizeof(fpriv->event_buf)) != 0;
}

void adf_file_queue_event(struct adf_file *fpriv, struct adf_event *event)
{
	int head = fpriv->event_head;
	int tail = fpriv->event_tail;
	size_t space = CIRC_SPACE(head, tail, sizeof(fpriv->event_buf));
	size_t space_to_end =
			CIRC_SPACE_TO_END(head, tail, sizeof(fpriv->event_buf));

	if (space < event->length) {
		dev_dbg(&fpriv->obj->dev,
				"insufficient buffer space for event %u\n",
				event->type);
		return;
	}

	if (space_to_end >= event->length) {
		memcpy(fpriv->event_buf + head, event, event->length);
	} else {
		memcpy(fpriv->event_buf + head, event, space_to_end);
		memcpy(fpriv->event_buf, (u8 *)event + space_to_end,
				event->length - space_to_end);
	}

	smp_wmb();
	fpriv->event_head = (fpriv->event_head + event->length) &
			(sizeof(fpriv->event_buf) - 1);
	wake_up_interruptible_all(&fpriv->event_wait);
}

static ssize_t adf_file_copy_to_user(struct adf_file *fpriv,
		char __user *buffer, size_t buffer_size)
{
	int head, tail;
	u8 *event_buf;
	size_t cnt, cnt_to_end, copy_size = 0;
	ssize_t ret = 0;
	unsigned long flags;

	event_buf = kmalloc(min(buffer_size, sizeof(fpriv->event_buf)),
			GFP_KERNEL);
	if (!event_buf)
		return -ENOMEM;

	spin_lock_irqsave(&fpriv->obj->file_lock, flags);

	if (!adf_file_event_available(fpriv))
		goto out;

	head = fpriv->event_head;
	tail = fpriv->event_tail;

	cnt = CIRC_CNT(head, tail, sizeof(fpriv->event_buf));
	cnt_to_end = CIRC_CNT_TO_END(head, tail, sizeof(fpriv->event_buf));
	copy_size = min(buffer_size, cnt);

	if (cnt_to_end >= copy_size) {
		memcpy(event_buf, fpriv->event_buf + tail, copy_size);
	} else {
		memcpy(event_buf, fpriv->event_buf + tail, cnt_to_end);
		memcpy(event_buf + cnt_to_end, fpriv->event_buf,
				copy_size - cnt_to_end);
	}

	fpriv->event_tail = (fpriv->event_tail + copy_size) &
			(sizeof(fpriv->event_buf) - 1);

out:
	spin_unlock_irqrestore(&fpriv->obj->file_lock, flags);
	if (copy_size) {
		if (copy_to_user(buffer, event_buf, copy_size))
			ret = -EFAULT;
		else
			ret = copy_size;
	}
	kfree(event_buf);
	return ret;
}

ssize_t adf_file_read(struct file *filp, char __user *buffer,
		 size_t count, loff_t *offset)
{
	struct adf_file *fpriv = filp->private_data;
	int err;

	err = wait_event_interruptible(fpriv->event_wait,
			adf_file_event_available(fpriv));
	if (err < 0)
		return err;

	return adf_file_copy_to_user(fpriv, buffer, count);
}

unsigned int adf_file_poll(struct file *filp, struct poll_table_struct *wait)
{
	struct adf_file *fpriv = filp->private_data;
	unsigned int mask = 0;

	poll_wait(filp, &fpriv->event_wait, wait);

	if (adf_file_event_available(fpriv))
		mask |= POLLIN | POLLRDNORM;

	return mask;
}

const struct file_operations adf_fops = {
	.owner = THIS_MODULE,
	.unlocked_ioctl = adf_file_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = adf_file_compat_ioctl,
#endif
	.open = adf_file_open,
	.release = adf_file_release,
	.llseek = default_llseek,
	.read = adf_file_read,
	.poll = adf_file_poll,
};
