/**
 * Copyright (c) 2011 Trusted Logic S.A.
 * All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

#include <asm/atomic.h>
#include <linux/uaccess.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/page-flags.h>
#include <linux/pm.h>
#include <linux/syscore_ops.h>
#include <linux/vmalloc.h>
#include <linux/signal.h>
#ifdef CONFIG_ANDROID
#include <linux/device.h>
#endif

#include "tf_protocol.h"
#include "tf_defs.h"
#include "tf_util.h"
#include "tf_conn.h"
#include "tf_comm.h"
#ifdef CONFIG_TF_ZEBRA
#include <plat/cpu.h>
#include "tf_zebra.h"
#endif

#include "s_version.h"

#ifdef CONFIG_TF_ION
extern struct ion_device *omap_ion_device;
#endif

/*----------------------------------------------------------------------------
 * Forward Declarations
 *----------------------------------------------------------------------------*/

/*
 * Creates and registers the device to be managed by the specified driver.
 *
 * Returns zero upon successful completion, or an appropriate error code upon
 * failure.
 */
static int tf_device_register(void);


/*
 * Implements the device Open callback.
 */
static int tf_device_open(
		struct inode *inode,
		struct file *file);


/*
 * Implements the device Release callback.
 */
static int tf_device_release(
		struct inode *inode,
		struct file *file);


/*
 * Implements the device ioctl callback.
 */
static long tf_device_ioctl(
		struct file *file,
		unsigned int ioctl_num,
		unsigned long ioctl_param);


/*
 * Implements the device shutdown callback.
 */
static void tf_device_shutdown(void);


/*
 * Implements the device suspend callback.
 */
static int tf_device_suspend(void);


/*
 * Implements the device resume callback.
 */
static void tf_device_resume(void);


/*---------------------------------------------------------------------------
 * Module Parameters
 *---------------------------------------------------------------------------*/

/*
 * The device major number used to register a unique character device driver.
 * Let the default value be 122
 */
static int device_major_number = 122;

module_param(device_major_number, int, 0000);
MODULE_PARM_DESC(device_major_number,
	"The device major number used to register a unique character "
	"device driver");

#ifdef CONFIG_TF_TRUSTZONE
/**
 * The softint interrupt line used by the Secure World.
 */
static int soft_interrupt = -1;

module_param(soft_interrupt, int, 0000);
MODULE_PARM_DESC(soft_interrupt,
	"The softint interrupt line used by the Secure world");
#endif

#ifdef CONFIG_ANDROID
static struct class *tf_class;
#endif

/*----------------------------------------------------------------------------
 * Global Variables
 *----------------------------------------------------------------------------*/

/*
 * tf_driver character device definitions.
 * read and write methods are not defined
 * and will return an error if used by user space
 */
static const struct file_operations g_tf_device_file_ops = {
	.owner = THIS_MODULE,
	.open = tf_device_open,
	.release = tf_device_release,
	.unlocked_ioctl = tf_device_ioctl,
	.llseek = no_llseek,
};

/* The single device supported by this driver */
static struct tf_device g_tf_dev = {0, };

/*----------------------------------------------------------------------------
 * Implementations
 *----------------------------------------------------------------------------*/

struct tf_device *tf_get_device(void)
{
	return &g_tf_dev;
}

/*
 * displays the driver stats
 */
static ssize_t kobject_show(struct kobject *kobj,
	struct attribute *attribute, char *buf)
{
	struct tf_device_stats *dev_stats = &g_tf_dev.stats;
	u32 pages_allocated;
	u32 pages_locked;
	u32 memories_allocated;

	memories_allocated =
		atomic_read(&(dev_stats->stat_memories_allocated));
	pages_allocated =
		atomic_read(&(dev_stats->stat_pages_allocated));
	pages_locked = atomic_read(&(dev_stats->stat_pages_locked));

	/*
	 * AFY: could we add the number of context switches (call to the SMI
	 * instruction)
	 */

	return snprintf(buf, PAGE_SIZE,
		"stat.memories.allocated: %d\n"
		"stat.pages.allocated:    %d\n"
		"stat.pages.locked:       %d\n",
		memories_allocated,
		pages_allocated,
		pages_locked);
}

static const struct sysfs_ops kobj_sysfs_operations = {
	.show = kobject_show,
};

/*----------------------------------------------------------------------------*/

static const struct syscore_ops g_tf_syscore_ops = {
	.shutdown = tf_device_shutdown,
	.suspend = tf_device_suspend,
	.resume = tf_device_resume,
};

/*
 * First routine called when the kernel module is loaded
 */
static int __init tf_device_register(void)
{
	int error;
	struct tf_device *dev = &g_tf_dev;
	struct tf_device_stats *dev_stats = &dev->stats;

	dprintk(KERN_INFO "tf_device_register()\n");

	/*
	 * Initialize the device
	 */
	dev->dev_number = MKDEV(device_major_number,
		TF_DEVICE_MINOR_NUMBER);
	cdev_init(&dev->cdev, &g_tf_device_file_ops);
	dev->cdev.owner = THIS_MODULE;

	INIT_LIST_HEAD(&dev->connection_list);
	spin_lock_init(&dev->connection_list_lock);

	/* register the sysfs object driver stats */
	dev_stats->kobj_type.sysfs_ops = &kobj_sysfs_operations;

	dev_stats->kobj_stat_attribute.name = "info";
	dev_stats->kobj_stat_attribute.mode = S_IRUGO;
	dev_stats->kobj_attribute_list[0] =
		&dev_stats->kobj_stat_attribute;

	dev_stats->kobj_type.default_attrs =
		dev_stats->kobj_attribute_list,
	error = kobject_init_and_add(&(dev_stats->kobj),
		 &(dev_stats->kobj_type), NULL, "%s",
		 TF_DEVICE_BASE_NAME);
	if (error) {
		kobject_put(&dev_stats->kobj);
		goto kobject_init_and_add_failed;
	}

	register_syscore_ops((struct syscore_ops *)&g_tf_syscore_ops);

	/*
	 * Register the char device.
	 */
	printk(KERN_INFO "Registering char device %s (%u:%u)\n",
		TF_DEVICE_BASE_NAME,
		MAJOR(dev->dev_number),
		MINOR(dev->dev_number));
	error = register_chrdev_region(dev->dev_number, 1,
		TF_DEVICE_BASE_NAME);
	if (error != 0) {
		printk(KERN_ERR "tf_device_register():"
			" register_chrdev_region failed (error %d)!\n",
			error);
		goto register_chrdev_region_failed;
	}

	error = cdev_add(&dev->cdev, dev->dev_number, 1);
	if (error != 0) {
		printk(KERN_ERR "tf_device_register(): "
			"cdev_add failed (error %d)!\n",
			error);
		goto cdev_add_failed;
	}

	/*
	 * Initialize the communication with the Secure World.
	 */
#ifdef CONFIG_TF_TRUSTZONE
	dev->sm.soft_int_irq = soft_interrupt;
#endif
	error = tf_init(&g_tf_dev.sm);
	if (error != S_SUCCESS) {
		dprintk(KERN_ERR "tf_device_register(): "
			"tf_init failed (error %d)!\n",
			error);
		goto init_failed;
	}

#ifdef CONFIG_ANDROID
	tf_class = class_create(THIS_MODULE, TF_DEVICE_BASE_NAME);
	device_create(tf_class, NULL,
		dev->dev_number,
		NULL, TF_DEVICE_BASE_NAME);
#endif

#ifdef CONFIG_TF_ZEBRA
	/*
	 * Initializes the /dev/tf_ctrl device node.
	 */
	error = tf_ctrl_device_register();
	if (error)
		goto init_failed;
#endif

#ifdef CONFIG_BENCH_SECURE_CYCLE
	run_bogo_mips();
	address_cache_property((unsigned long) &tf_device_register);
#endif
	/*
	 * Successful completion.
	 */

	dprintk(KERN_INFO "tf_device_register(): Success\n");
	return 0;

	/*
	 * Error: undo all operations in the reverse order
	 */
init_failed:
	cdev_del(&dev->cdev);
cdev_add_failed:
	unregister_chrdev_region(dev->dev_number, 1);
register_chrdev_region_failed:
	unregister_syscore_ops((struct syscore_ops *)&g_tf_syscore_ops);
kobject_init_and_add_failed:
	kobject_del(&g_tf_dev.stats.kobj);

	dprintk(KERN_INFO "tf_device_register(): Failure (error %d)\n",
		error);
	return error;
}

/*----------------------------------------------------------------------------*/

static int tf_device_open(struct inode *inode, struct file *file)
{
	int error;
	struct tf_device *dev = &g_tf_dev;
	struct tf_connection *connection = NULL;

	dprintk(KERN_INFO "tf_device_open(%u:%u, %p)\n",
		imajor(inode), iminor(inode), file);

	/* Dummy lseek for non-seekable driver */
	error = nonseekable_open(inode, file);
	if (error != 0) {
		dprintk(KERN_ERR "tf_device_open(%p): "
			"nonseekable_open failed (error %d)!\n",
			file, error);
		goto error;
	}

#ifndef CONFIG_ANDROID
	/*
	 * Check file flags. We only autthorize the O_RDWR access
	 */
	if (file->f_flags != O_RDWR) {
		dprintk(KERN_ERR "tf_device_open(%p): "
			"Invalid access mode %u\n",
			file, file->f_flags);
		error = -EACCES;
		goto error;
	}
#endif

	/*
	 * Open a new connection.
	 */

	error = tf_open(dev, file, &connection);
	if (error != 0) {
		dprintk(KERN_ERR "tf_device_open(%p): "
			"tf_open failed (error %d)!\n",
			file, error);
		goto error;
	}

	file->private_data = connection;

	/*
	 * Send the CreateDeviceContext command to the secure
	 */
	error = tf_create_device_context(connection);
	if (error != 0) {
		dprintk(KERN_ERR "tf_device_open(%p): "
			"tf_create_device_context failed (error %d)!\n",
			file, error);
		goto error1;
	}

	/*
	 * Successful completion.
	 */

	dprintk(KERN_INFO "tf_device_open(%p): Success (connection=%p)\n",
		file, connection);
	return 0;

	/*
	 * Error handling.
	 */

error1:
	tf_close(connection);
error:
	dprintk(KERN_INFO "tf_device_open(%p): Failure (error %d)\n",
		file, error);
	return error;
}

/*----------------------------------------------------------------------------*/

static int tf_device_release(struct inode *inode, struct file *file)
{
	struct tf_connection *connection;

	dprintk(KERN_INFO "tf_device_release(%u:%u, %p)\n",
		imajor(inode), iminor(inode), file);

	connection = tf_conn_from_file(file);
	tf_close(connection);

	dprintk(KERN_INFO "tf_device_release(%p): Success\n", file);
	return 0;
}

/*----------------------------------------------------------------------------*/

static long tf_device_ioctl(struct file *file, unsigned int ioctl_num,
	unsigned long ioctl_param)
{
	int result = S_SUCCESS;
	struct tf_connection *connection;
	union tf_command command;
	struct tf_command_header header;
	union tf_answer answer;
	u32 command_size;
	u32 answer_size;
	void *user_answer;

	dprintk(KERN_INFO "tf_device_ioctl(%p, %u, %p)\n",
		file, ioctl_num, (void *) ioctl_param);

	switch (ioctl_num) {
	case IOCTL_TF_GET_VERSION:
		/* ioctl is asking for the driver interface version */
		result = TF_DRIVER_INTERFACE_VERSION;
		goto exit;

#ifdef CONFIG_TF_ION
	case IOCTL_TF_ION_REGISTER: {
		int ion_register;
		/* ioctl is asking to register an ion handle */
		if (copy_from_user(&ion_register,
				(int *) ioctl_param,
				sizeof(int))) {
			dprintk(KERN_ERR "tf_device_ioctl(%p): "
				"copy_from_user failed\n",
				file);
			result = -EFAULT;
			goto exit;
		}

		connection = tf_conn_from_file(file);
		BUG_ON(connection == NULL);

		/* Initialize ION connection */
		if (connection->ion_client == NULL) {
			connection->ion_client = ion_client_create(
						omap_ion_device,
						(1 << ION_HEAP_TYPE_CARVEOUT),
						"smc");
		}

		if (connection->ion_client == NULL) {
			dprintk(KERN_ERR "tf_device_ioctl(%p): "
				"unable to create ion client\n",
				file);
			result = -EFAULT;
			goto exit;
		}

		/*
		 * TODO: We should use a reference count on this handle in order
		 * to not unregistered it while using it.
		 */
	       return (long)ion_import_fd(connection->ion_client, ion_register);
	}

	case IOCTL_TF_ION_UNREGISTER: {
		int ion_register;
		/* ioctl is asking to unregister an ion handle */

		if (copy_from_user(&ion_register,
				(int *) ioctl_param,
				sizeof(int))) {
			dprintk(KERN_ERR "tf_device_ioctl(%p): "
				"copy_from_user failed\n",
				file);
			result = -EFAULT;
			goto exit;
		}

		connection = tf_conn_from_file(file);
		BUG_ON(connection == NULL);

		if (connection->ion_client == NULL) {
			dprintk(KERN_ERR "tf_device_ioctl(%p): "
				"ion client does not exist\n",
				file);
			result = -EFAULT;
			goto exit;
		}

		ion_free(connection->ion_client,
			(struct ion_handle *) ion_register);

		return S_SUCCESS;
	}
#endif

	case IOCTL_TF_EXCHANGE:
		/*
		 * ioctl is asking to perform a message exchange with the Secure
		 * Module
		 */

		/*
		 * Make a local copy of the data from the user application
		 * This routine checks the data is readable
		 *
		 * Get the header first.
		 */
		if (copy_from_user(&header,
				(struct tf_command_header *)ioctl_param,
				sizeof(struct tf_command_header))) {
			dprintk(KERN_ERR "tf_device_ioctl(%p): "
				"Cannot access ioctl parameter %p\n",
				file, (void *) ioctl_param);
			result = -EFAULT;
			goto exit;
		}

		/* size in words of u32 */
		command_size = header.message_size +
			sizeof(struct tf_command_header)/sizeof(u32);
		if (command_size > sizeof(command)/sizeof(u32)) {
			dprintk(KERN_ERR "tf_device_ioctl(%p): "
				"Buffer overflow: too many bytes to copy %d\n",
				file, command_size);
			result = -EFAULT;
			goto exit;
		}

		if (copy_from_user(&command,
				(union tf_command *)ioctl_param,
				command_size * sizeof(u32))) {
			dprintk(KERN_ERR "tf_device_ioctl(%p): "
				"Cannot access ioctl parameter %p\n",
				file, (void *) ioctl_param);
			result = -EFAULT;
			goto exit;
		}

		connection = tf_conn_from_file(file);
		BUG_ON(connection == NULL);

		/*
		 * The answer memory space address is in the operation_id field
		 */
		user_answer = (void *) command.header.operation_id;

		atomic_inc(&(connection->pending_op_count));

		dprintk(KERN_WARNING "tf_device_ioctl(%p): "
			"Sending message type  0x%08x\n",
			file, command.header.message_type);

		switch (command.header.message_type) {
		case TF_MESSAGE_TYPE_OPEN_CLIENT_SESSION:
			result = tf_open_client_session(connection,
				&command, &answer);
			break;

		case TF_MESSAGE_TYPE_CLOSE_CLIENT_SESSION:
			result = tf_close_client_session(connection,
				&command, &answer);
			break;

		case TF_MESSAGE_TYPE_REGISTER_SHARED_MEMORY:
			result = tf_register_shared_memory(connection,
				&command, &answer);
			break;

		case TF_MESSAGE_TYPE_RELEASE_SHARED_MEMORY:
			result = tf_release_shared_memory(connection,
				&command, &answer);
			break;

		case TF_MESSAGE_TYPE_INVOKE_CLIENT_COMMAND:
			result = tf_invoke_client_command(connection,
				&command, &answer);
			break;

		case TF_MESSAGE_TYPE_CANCEL_CLIENT_COMMAND:
			result = tf_cancel_client_command(connection,
				&command, &answer);
			break;

		default:
			dprintk(KERN_ERR "tf_device_ioctl(%p): "
				"Incorrect message type (0x%08x)!\n",
				connection, command.header.message_type);
			result = -EOPNOTSUPP;
			break;
		}

		atomic_dec(&(connection->pending_op_count));

		if (result != 0) {
			dprintk(KERN_WARNING "tf_device_ioctl(%p): "
				"Operation returning error code 0x%08x)!\n",
				file, result);
			goto exit;
		}

		/*
		 * Copy the answer back to the user space application.
		 * The driver does not check this field, only copy back to user
		 * space the data handed over by Secure World
		 */
		answer_size = answer.header.message_size +
			sizeof(struct tf_answer_header)/sizeof(u32);
		if (copy_to_user(user_answer,
				&answer, answer_size * sizeof(u32))) {
			dprintk(KERN_WARNING "tf_device_ioctl(%p): "
				"Failed to copy back the full command "
				"answer to %p\n", file, user_answer);
			result = -EFAULT;
			goto exit;
		}

		/* successful completion */
		dprintk(KERN_INFO "tf_device_ioctl(%p): Success\n", file);
		break;

	case  IOCTL_TF_GET_DESCRIPTION: {
		/* ioctl asking for the version information buffer */
		struct tf_version_information_buffer *pInfoBuffer;

		dprintk(KERN_INFO "IOCTL_TF_GET_DESCRIPTION:(%p, %u, %p)\n",
			file, ioctl_num, (void *) ioctl_param);

		pInfoBuffer =
			((struct tf_version_information_buffer *) ioctl_param);

		dprintk(KERN_INFO "IOCTL_TF_GET_DESCRIPTION1: "
			"driver_description=\"%64s\"\n", S_VERSION_STRING);

		if (copy_to_user(pInfoBuffer->driver_description,
				S_VERSION_STRING,
				strlen(S_VERSION_STRING) + 1)) {
			dprintk(KERN_ERR "tf_device_ioctl(%p): "
				"Fail to copy back the driver description "
				"to  %p\n",
				file, pInfoBuffer->driver_description);
			result = -EFAULT;
			goto exit;
		}

		dprintk(KERN_INFO "IOCTL_TF_GET_DESCRIPTION2: "
			"secure_world_description=\"%64s\"\n",
			tf_get_description(&g_tf_dev.sm));

		if (copy_to_user(pInfoBuffer->secure_world_description,
				tf_get_description(&g_tf_dev.sm),
				TF_DESCRIPTION_BUFFER_LENGTH)) {
			dprintk(KERN_WARNING "tf_device_ioctl(%p): "
				"Failed to copy back the secure world "
				"description to %p\n",
				file, pInfoBuffer->secure_world_description);
			result = -EFAULT;
			goto exit;
		}
		break;
	}

	default:
		dprintk(KERN_ERR "tf_device_ioctl(%p): "
			"Unknown IOCTL code 0x%08x!\n",
			file, ioctl_num);
		result = -EOPNOTSUPP;
		goto exit;
	}

exit:
	return result;
}

/*----------------------------------------------------------------------------*/

static void tf_device_shutdown(void)
{
	tf_power_management(&g_tf_dev.sm, TF_POWER_OPERATION_SHUTDOWN);
}

/*----------------------------------------------------------------------------*/

static int tf_device_suspend(void)
{
	dprintk(KERN_INFO "%s\n", __func__);
	return tf_power_management(&g_tf_dev.sm, TF_POWER_OPERATION_HIBERNATE);
}


/*----------------------------------------------------------------------------*/

static void tf_device_resume(void)
{
	tf_power_management(&g_tf_dev.sm, TF_POWER_OPERATION_RESUME);
}


/*----------------------------------------------------------------------------*/

module_init(tf_device_register);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Trusted Logic S.A.");
