/*
 *
 * (C) COPYRIGHT 2011-2014 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 <linux/ump.h>
#include <linux/dma-mapping.h>
#include "ion.h"
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/vmalloc.h>

struct ion_wrapping_info
{
	struct ion_client *   ion_client;
	struct ion_handle *   ion_handle;
	int                   num_phys_blocks;
	struct scatterlist *  sglist;
};

static struct ion_device * ion_device_get(void)
{
	/* < Customer to provide implementation >
	 * Return a pointer to the global ion_device on the system
	 */
	return NULL;
}

static int import_ion_client_create(void** const custom_session_data)
{
	struct ion_client ** ion_client;

	ion_client = (struct ion_client**)custom_session_data;

	*ion_client = ion_client_create(ion_device_get(), "ump");

	return PTR_RET(*ion_client);
}


static void import_ion_client_destroy(void* custom_session_data)
{
	struct ion_client * ion_client;

	ion_client = (struct ion_client*)custom_session_data;
	BUG_ON(!ion_client);

	ion_client_destroy(ion_client);
}


static void import_ion_final_release_callback(const ump_dd_handle handle, void * info)
{
	struct ion_wrapping_info * ion_info;

	BUG_ON(!info);

	(void)handle;
	ion_info = (struct ion_wrapping_info*)info;

	dma_unmap_sg(NULL, ion_info->sglist, ion_info->num_phys_blocks, DMA_BIDIRECTIONAL);

	ion_free(ion_info->ion_client, ion_info->ion_handle);
	kfree(ion_info);
	module_put(THIS_MODULE);
}

static ump_dd_handle import_ion_import(void * custom_session_data, void * pfd, ump_alloc_flags flags)
{
	int fd;
	ump_dd_handle ump_handle;
	struct scatterlist * sg;
	int num_dma_blocks;
	ump_dd_physical_block_64 * phys_blocks;
	unsigned long i;
	struct sg_table * sgt;

	struct ion_wrapping_info * ion_info;

	BUG_ON(!custom_session_data);
	BUG_ON(!pfd);

	ion_info = kzalloc(GFP_KERNEL, sizeof(*ion_info));
	if (NULL == ion_info)
	{
		return UMP_DD_INVALID_MEMORY_HANDLE;
	}

	ion_info->ion_client = (struct ion_client*)custom_session_data;

	if (get_user(fd, (int*)pfd))
	{
		goto out;
	}

	ion_info->ion_handle = ion_import_dma_buf(ion_info->ion_client, fd);

	if (IS_ERR_OR_NULL(ion_info->ion_handle))
	{
		goto out;
	}

	sgt = ion_sg_table(ion_info->ion_client, ion_info->ion_handle);
	if (IS_ERR_OR_NULL(sgt))
	{
		goto ion_dma_map_failed;
	}

	ion_info->sglist = sgt->sgl;

	sg = ion_info->sglist;
	while (sg)
	{
		ion_info->num_phys_blocks++;
		sg = sg_next(sg);
	}

	num_dma_blocks = dma_map_sg(NULL, ion_info->sglist, ion_info->num_phys_blocks, DMA_BIDIRECTIONAL);

	if (0 == num_dma_blocks)
	{
		goto linux_dma_map_failed;
	}

	phys_blocks = vmalloc(num_dma_blocks * sizeof(*phys_blocks));
	if (NULL == phys_blocks)
	{
		goto vmalloc_failed;
	}

	for_each_sg(ion_info->sglist, sg, num_dma_blocks, i)
	{
		phys_blocks[i].addr = sg_phys(sg);
		phys_blocks[i].size = sg_dma_len(sg);
	}

	ump_handle = ump_dd_create_from_phys_blocks_64(phys_blocks, num_dma_blocks, flags, NULL, import_ion_final_release_callback, ion_info);

	vfree(phys_blocks);

	if (ump_handle != UMP_DD_INVALID_MEMORY_HANDLE)
	{
		/*
		 * As we have a final release callback installed
		 * we must keep the module locked until
		 * the callback has been triggered
		 * */
		__module_get(THIS_MODULE);
		return ump_handle;
	}

	/* failed*/
vmalloc_failed:
	dma_unmap_sg(NULL, ion_info->sglist, ion_info->num_phys_blocks, DMA_BIDIRECTIONAL);
linux_dma_map_failed:
ion_dma_map_failed:
	ion_free(ion_info->ion_client, ion_info->ion_handle);
out:
	kfree(ion_info);
	return UMP_DD_INVALID_MEMORY_HANDLE;
}

struct ump_import_handler import_handler_ion =
{
	.linux_module =  THIS_MODULE,
	.session_begin = import_ion_client_create,
	.session_end =   import_ion_client_destroy,
	.import =        import_ion_import
};

static int __init import_ion_initialize_module(void)
{
	/* register with UMP */
	return ump_import_module_register(UMP_EXTERNAL_MEM_TYPE_ION, &import_handler_ion);
}

static void __exit import_ion_cleanup_module(void)
{
	/* unregister import handler */
	ump_import_module_unregister(UMP_EXTERNAL_MEM_TYPE_ION);
}

/* Setup init and exit functions for this module */
module_init(import_ion_initialize_module);
module_exit(import_ion_cleanup_module);

/* And some module information */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ARM Ltd.");
MODULE_VERSION("1.0");
