/*
 *----------------------------------------------------------------------------
 *	LSI Corporation
 *	1621 Barber Lane
 *	Milpitas, California 95035
 *----------------------------------------------------------------------------
 * Copyright © 2004-2006, LSI Corporation All Rights Reserved.
 * 
 * LSI's source code is an unpublished work and the use of copyright
 * notice does not imply otherwise. This source code contains confidential,
 * trade secret material of LSI Corporation. Any attempt or
 * participation in deciphering, decoding, reverse engineering or
 * in any way altering the source code is strictly prohibited, unless the
 * prior written consent of LSI Corporation.
 *---------------------------------------------------------------------------- 
 */

/** 
 *@file osl_char.c
 *
 *@brief
 * Operating System Layer Character driver.
 *
 * This file contains the character driver related implementation of MegaSWR
 * driver. This is used for sending IOCTL's down to the driver. 
 *
 *@bug
 *	None
 *
 *@warning
 *	None
 *
 *@note
 *	01-11-2004	: Created this file 
 */

#include "linux_osl.h"
#include "osl_char.h"

static int	chrdev_major_num_g = 0;
static DECLARE_MUTEX(lsraid_async_queue_mutex);
extern struct fasync_struct *lsraid_async_queue;
extern struct pci_driver lsraid_pci_driver_g;

void * lin_get_linux_adp(void *data);
uint32_t lin_fire_ioctl_aen(struct megasas_aen *aen);
void * oss_set_old_pkt(void *old_pkt, int adp_index, void *frames, uint32_t *buffer_offset);
boolean_t lin_is_aen_reg_enabled(pvt_data_t *adp);
pvt_data_t * lin_get_linux_adp_from_host_no(uint8_t host_no, uint8_t *adp_index);
void lin_issue_ioctl(uint8_t adp_index, void *old_pkt, void *dma_addr);

void_t osl_ioctl_success(pvt_data_t *os, uint8_t cmd_status)
{
	linux_adp_t *lxadp = (linux_adp_t *)os;

	con_log(CL_IOCTL, ("megasr[osl]: ioctl success handle called\n"));

	lxadp->ioctl_data.status			= cmd_status;
	lxadp->ioctl_data.activity_state	= LSI_RAID_IOCTL_FINISHED;

	wake_up(&lxadp->ioctl_data.wait_queue);
}

void_t osl_ioctl_failure(pvt_data_t *os, uint8_t cmd_status)
{
	linux_adp_t *lxadp = (linux_adp_t *)os;

	con_log(CL_IOCTL, ("megasr[osl]: ioctl failure handle called\n"));

	lxadp->ioctl_data.status			= cmd_status;
	lxadp->ioctl_data.activity_state	= LSI_RAID_IOCTL_FINISHED;

	wake_up(&lxadp->ioctl_data.wait_queue);
}

/**
 *@brief
 * Posts an event asynchronously to storelib.
 *@param os_context: pointer to the os_context structure.
 *@param event_detail: pointer to the structure containing the event information
 *@return LSI_TRUE if operation was successful, LSI_FALSE otherwise.
 */
boolean_t osl_post_aen(pvt_data_t *os_context, pvt_data_t *event_detail)
{
	linux_adp_t	*li_adp	= (linux_adp_t *)os_context;

	if (lin_is_aen_reg_enabled(li_adp->adp) == LSI_TRUE){
		kill_fasync( &lsraid_async_queue, SIGIO, POLL_IN );
	}
	return LSI_TRUE;
}

uint32_t lin_handle_ioctl_aen(unsigned long arg)
{
	struct megasas_aen	aen;
	
	if (copy_from_user(&aen, (uint8_t *)arg, 12))
		return -EFAULT;

	if (lin_fire_ioctl_aen(&aen) != 0) return -EINVAL;

	return 0;
}

static int
lsraid_handle_ioctl_normal(struct megasas_iocpacket *kern_ioc)
{
	uint8_t				adp_index;
	linux_ioctl_data_t	*ioctl_data;
	void				*old_pkt_data_buff;
	linux_adp_t			*lxadp;
	unsigned long		flags=0;
	uint32_t			buffer_offset = 0;

	lxadp = lin_get_linux_adp_from_host_no(kern_ioc->host_no, &adp_index);

	if (!lxadp) {
		con_log(CL_ERR, ("megasr[osl: Invalid host_no from app\n"));
		return -ENODEV;
	}

	ioctl_data = &lxadp->ioctl_data;

	/* We allow one IOCTL call at a time */
	down(&ioctl_data->ioctl_lock);

	/* 
	 * Rest of the driver works on old_pkt; let's build it first 
	 */
	old_pkt_data_buff = oss_set_old_pkt(ioctl_data->dma.vir, adp_index, kern_ioc->frame.raw, &buffer_offset);

	if (kern_ioc->sgl[0].iov_len > (LSI_RAID_IOCTL_MAX_DATA_SIZE - buffer_offset)) {

		con_log(CL_ERR, ("megasr[ioc]: too many bytes for ioctl, given [%x], max [%x]\n",
			(uint32_t)kern_ioc->sgl[0].iov_len, (LSI_RAID_IOCTL_MAX_DATA_SIZE - buffer_offset)));

		up(&ioctl_data->ioctl_lock);
		return 0;
	}

	if (kern_ioc->sgl[0].iov_len) {
		if (copy_from_user(old_pkt_data_buff, kern_ioc->sgl[0].iov_base, 
												kern_ioc->sgl[0].iov_len)) {
			con_log(CL_ERR, ("megaswr[osl]: err copy ioctl data failed\n"));
			up(&ioctl_data->ioctl_lock);
			return -EFAULT;
		}
	}


	ioctl_data->activity_state = LSI_RAID_IOCTL_ACTIVE;

	spin_lock_irqsave(lxadp->host_lock, flags);

	lin_issue_ioctl(adp_index, ioctl_data->dma.vir, (void *)(addr_t)ioctl_data->dma.dma_addr);

	spin_unlock_irqrestore(lxadp->host_lock, flags);
	
	wait_event(ioctl_data->wait_queue, (ioctl_data->activity_state != LSI_RAID_IOCTL_ACTIVE));

	ioctl_data->activity_state = LSI_RAID_IOCTL_NOT_ACTIVE;

	if (kern_ioc->sgl[0].iov_len) {
		if (copy_to_user( kern_ioc->sgl[0].iov_base, (uint8_t*)old_pkt_data_buff, 
													kern_ioc->sgl[0].iov_len)) {
			con_log(CL_ERR, ("megaswr[osl]: copy out to user buff failed\n"));
			up(&ioctl_data->ioctl_lock);
			return -EFAULT;
		}
	}

	((struct megasas_hdr *)kern_ioc->frame.raw)->cmd_status = ioctl_data->status;

	up(&ioctl_data->ioctl_lock);
	return 0;
}

#ifndef __VMKERNEL_MODULE__
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16)
static long
lsraid_ioctl_entry(struct file *file, unsigned int cmd, unsigned long arg)
#else
static int 
lsraid_ioctl_entry(struct inode *inode, struct file *filep,
									unsigned int cmd, unsigned long arg)
#endif
#else
#ifdef __VMWARE__NO_BUG_FIX
static long
lsraid_ioctl_entry(struct inode *inode, struct file *filep,
									unsigned int cmd, unsigned long arg)
#else
static int
lsraid_ioctl_entry(struct inode *inode, struct file *filep,
									unsigned int cmd, unsigned long arg)
#endif
#endif
{
	int							ret;
	struct megasas_hdr			*khdr;
	struct megasas_hdr			*uhdr;
	struct megasas_iocpacket	kern_ioc;
	struct megasas_iocpacket	*user_ioc = (struct megasas_iocpacket*) arg;

	switch(cmd) {
	case LSRAID_IOC_GET_AEN:
		return lin_handle_ioctl_aen(arg);

	case LSRAID_IOC_FIRMWARE:
		if (copy_from_user(&kern_ioc, user_ioc, sizeof(kern_ioc))) {
			con_log(CL_ERR, ("megaswr[osl]: copy_from_user failed\n"));
			return -EFAULT;
		}

		if (kern_ioc.sge_count > 1) {
			con_log(CL_IOCTL, ("megaswr[osl]: err sge_count=%x > one\n",
												kern_ioc.sge_count));
			return -EINVAL;
		}

		/* 
		 * The statements below are important; apps set the status to 0xff 
		 */
		khdr = (struct megasas_hdr *)kern_ioc.frame.raw;
		uhdr = (struct megasas_hdr *)user_ioc->frame.raw;

		khdr->cmd_status = 0;
		khdr->scsi_status = 0;
		ret = lsraid_handle_ioctl_normal(&kern_ioc);

		if (!ret) {
			ret = copy_to_user((void *)&uhdr->cmd_status, (void *)&khdr->cmd_status, sizeof(uint8_t));
			if (ret == 0) {
				ret = copy_to_user((void *)&uhdr->scsi_status, (void *)&khdr->scsi_status, sizeof(uint8_t));
			}
			return ret;
		}
	}

	return -ENOTTY;
}

#ifdef CONFIG_COMPAT

static INLINE void
lsraid_convert_compat_iocpacket(struct megasas_iocpacket *ioc,
				struct compat_megasas_iocpacket* compat_ioc)
{
	ioc->host_no			= compat_ioc->host_no;
	ioc->sgl_off			= compat_ioc->sgl_off;
	ioc->sense_off			= compat_ioc->sense_off;
	ioc->sense_len			= compat_ioc->sense_len;
	ioc->sge_count			= compat_ioc->sge_count;

	ioc->sgl[0].iov_len		= compat_ioc->sgl[0].iov_len;
	ioc->sgl[0].iov_base	= (void_t *)(addr_t)compat_ioc->sgl[0].iov_base;

	memcpy(ioc->frame.raw, compat_ioc->frame.raw, 128);
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16)
static long
lsraid_compat_ioctl_entry(struct file *file, unsigned int cmd,
			  unsigned long arg)
#else
static int
lsraid_compat_ioctl_entry(unsigned int fd, unsigned int cmd,
		unsigned long arg, struct file *filep)
#endif
{
	int								ret;
	struct megasas_iocpacket		kern_ioc;
	struct compat_megasas_iocpacket *compat_user_ioc;
	struct compat_megasas_iocpacket compat_kern_ioc;

	struct megasas_hdr				*khdr;
	struct megasas_hdr				*uhdr;

	switch(cmd) {
	case LSRAID_IOC_GET_AEN:
		return lin_handle_ioctl_aen(arg);

	case LSRAID_IOC_FIRMWARE_COMPAT:

		/*
		 * This IOCTL is coming to a 64 bit compiled driver from a 32 bit app.
		 * Our IOCTL packet (struct megasas_iocpacket) has a variable called
		 * iovec (basically an SGL) whose size depends upon whether the driver
		 * is 32 or 64 bit compiled.
		 *
		 * Because we are in 64 bit world here, struct megasas_iocpacket is 64
		 * bit packet. We received 32 bit packet from the app. We need to morph
		 * that into 64 bit packet so that we can call the same functions.
		 */

		compat_user_ioc = (struct compat_megasas_iocpacket *)arg;

		/* Copy compat packet from user to the kernel space */
		if (copy_from_user(&compat_kern_ioc, compat_user_ioc, 
									sizeof(compat_kern_ioc))) {
			con_log(CL_ERR, ("megasr[osl]: copy_from_user failed\n"));
			return -EFAULT;
		}

		if (compat_kern_ioc.sge_count > 1) {
			con_log(CL_IOCTL, ("megasr[osl]: err sge_count=%x > one\n",
				kern_ioc.sge_count));
			return -EINVAL;
		}

		lsraid_convert_compat_iocpacket(&kern_ioc, &compat_kern_ioc);

		/* 
		 * The statement below is important; apps set it to 0xff 
		 */
		khdr = (struct megasas_hdr *)kern_ioc.frame.raw;
		uhdr = (struct megasas_hdr *)compat_user_ioc->frame.raw;

		khdr->cmd_status = 0;

		ret = lsraid_handle_ioctl_normal(&kern_ioc);

		if (!ret) {
			ret = copy_to_user( (void *)&uhdr->cmd_status, (void *)&khdr->cmd_status, sizeof(uint8_t));
			return ret;
		}
	}

	return -ENOTTY;
}
#endif

/**
 *@brief
 * LSI MegaSWR ioctl open 
 *
 * It is called when an application opens character driver node. This function
 * is used to increase the driver use count to prevent the driver unload while
 * it is in use.
 *@param	inode	Pointer to inode structure
 *@param	filep	Pointer to file structure
 *@return	0		if function completes successfully
 *@return	non-zero if function fails
 */
static int lsraid_ioctl_open (struct inode *inode, struct file *filep)
{
	con_log(CL_IOCTL, ("megaswr[osl]: lsraid ioctl called!!\n"));
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0)
	if( !capable(CAP_SYS_ADMIN) ) return -EACCES;
#else
#ifdef MODULE
	MOD_INC_USE_COUNT;
#endif
#endif
	return 0;
}

/**
 * lsraid_mgmt_fasync -	Async notifier registration from applications
 *
 * This function adds the calling process to a driver global queue. When an
 * event occurs, SIGIO will be sent to all processes in this queue.
 */
static int
lsraid_mgmt_fasync(int fd, struct file *filep, int mode)
{
	int rc;

	down( &lsraid_async_queue_mutex );

	rc = fasync_helper(fd, filep, mode, &lsraid_async_queue);

	con_log(CL_IOCTL, ("megaswr[osl]: lsraid_mgmt_fasync registered !!\n"));
	up(&lsraid_async_queue_mutex);

	if (rc >= 0) {
		/* For sanity check when we get ioctl */
		filep->private_data = filep;
		return 0;
	}

	return rc;
}


#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)

static int lsraid_ioctl_release (struct inode *inode, struct file *filep)
{
	con_log(CL_IOCTL, ("megaswr[osl]: lsraid ioctl close called!!\n"));
	fasync_helper(-1, filep, 0, &lsraid_async_queue); 
	return 0;
}

static struct file_operations lsraid_ioctl_fops = {
	.owner		= THIS_MODULE,
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,16)
	.ioctl 		= lsraid_ioctl_entry,
#else
	.unlocked_ioctl = lsraid_ioctl_entry,
#ifdef CONFIG_COMPAT
	.compat_ioctl = lsraid_compat_ioctl_entry,
#endif
#endif
	.open 		= lsraid_ioctl_open,
	.release	= lsraid_ioctl_release,
	.fasync		= lsraid_mgmt_fasync,
};
#else

/**
 *@brief
 * LSI MegaSWR Register ioctl close
 *
 * It is called when an application closes character driver node. It is used
 * by 2.4 kernels only.
 *@param	inode	Pointer to inode structure
 *@param	filep	Pointer to file structure
 *@return	0 if function completes successfully
 *@return	non-zero if function fails
 */
static int lsraid_ioctl_close (struct inode *inode, struct file *filep)
{
	con_log(CL_IOCTL, ("megaswr[osl]: lsraid ioctl close called!!\n"));
	fasync_helper(-1, filep, 0, &lsraid_async_queue); 

#ifdef MODULE
	MOD_DEC_USE_COUNT;
#endif
	return 0;
}

static struct file_operations lsraid_ioctl_fops = {
	.owner		= THIS_MODULE,
	.ioctl 		= lsraid_ioctl_entry,
	.open 		= lsraid_ioctl_open,
	.release	= lsraid_ioctl_close,
	.fasync		= lsraid_mgmt_fasync,
};
#endif

/**
 *@brief
 * LSI MegaSWR Register Character driver. 
 *
 * It is called when LSI MegaSWR driver loads character driver for IOCTL
 * interface.
 *
 *@param	None 
 *@return	LSI_TRUE	: 	If function call success
 *@return	LSI_FALSE	:	If function call failed 
 *@remarks
 * This is the character driver entry function
 */
uint32_t lsraid_register_char_driver(void_t)
{
	con_log(CL_IOCTL, ("megaswr[osl]: register char driver is called!!\n"));

	chrdev_major_num_g = register_chrdev(0, LSI_RAID_CHAR_DRIVER_NAME,
			&lsraid_ioctl_fops);

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,16)
#ifdef CONFIG_COMPAT 
	/* Register the 32-bit ioctl conversion	 */
	register_ioctl32_conversion(LSRAID_IOC_FIRMWARE_COMPAT, lsraid_compat_ioctl_entry);
	register_ioctl32_conversion(LSRAID_IOC_GET_AEN, lsraid_compat_ioctl_entry);
#endif
#endif

	return LSI_TRUE;
}

/**
 *@brief
 * Load IOCTL module for this controller. Allocate IOCTL related memory
 * and do initialization if required. This is called for each adapter
 * 
 *@param	adp			Adapter structure pointer 
 *@return	LSI_TRUE	If function call success
 *@return	LSI_FALSE	If function call failed 
 */
uint32_t lsraid_load_ioctl_module(pvt_data_t *adp)
{
	linux_adp_t			*lxadp	= 
				(linux_adp_t*) lin_get_linux_adp(adp);
	linux_ioctl_data_t	*ioctl_data	= &lxadp->ioctl_data;

	con_log(CL_IOCTL, ("megaswr[osl]: load ioctl module called\n"));
	
	/* Semaphore for IOCTL */
	init_MUTEX (&ioctl_data->ioctl_lock);
	
	if (megasr_dma_alloc(lxadp->pdev, LSI_RAID_IOCTL_MAX_DATA_SIZE, &ioctl_data->dma, LINUX_DMA_NOWAIT) != LSI_TRUE) {
		con_log(CL_ERR, ("megaswr[osl]: err ioctl module memory allocation failed\n"));
		return LSI_FALSE;
	}

	init_waitqueue_head(&ioctl_data->wait_queue);

	return LSI_TRUE;
}

/**
 *@brief
 * Unload IOCTL module for this controller. Remove any IOCTL related memory
 * allocated. This is called for each adapter.
 *@param	adp			Adapter structure pointer 
 *@return	None	
 */
void_t lsraid_unload_ioctl_module(pvt_data_t *lxpv)
{
	linux_adp_t			*lxadp	= (linux_adp_t*) lxpv;
	linux_ioctl_data_t	*ioctl_data	= &lxadp->ioctl_data;
	
	con_log(CL_IOCTL, ("megaswr[osl]: unload ioctl module called\n"));

	/* freeing memory allocated for IOCTL */
	if (ioctl_data->dma.dma_addr) {
		megasr_dma_free(lxadp->pdev, &ioctl_data->dma);
	}
}
/**
 *@brief
 * LSI MegaSWR Unregister Character driver. 
 *
 * It is called when LSI MegaSWR driver unloads and character driver for
 * IOCTL interface no more required.
 *
 *@param None 
 *@return	LSI_TRUE	: 	If function call success
 *@return	LSI_FALSE	:	If function call failed 
 *@remarks
 * This is the character driver exit function
 */
uint32_t lsraid_unregister_char_driver(void_t)
{
	con_log(CL_IOCTL, ("megaswr[osl]: unregister char driver is called\n"));

	unregister_chrdev(chrdev_major_num_g, LSI_RAID_CHAR_DRIVER_NAME);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,16)
#ifdef CONFIG_COMPAT 
	unregister_ioctl32_conversion(LSRAID_IOC_FIRMWARE_COMPAT);
	unregister_ioctl32_conversion(LSRAID_IOC_GET_AEN);
#endif
#endif
	return LSI_TRUE;
}

/* vim: set ts=4 sw=4 tw=78 ai wrap si: */

