/************************************************************/
/*                                                          */
/* Module ID  - cmdmain.c                                   */
/*                                                          */
/* Function   - Allow privileged caller to invoke a CP      */
/*              command and read response.                  */
/*                                                          */
/* Called By  - N/A.                                        */
/*                                                          */
/* Calling To - N/A.                                        */
/*                                                          */
/* Parameters - See individual entry points.                */
/*                                                          */
/* Notes      - (1) Code published under GPL. Copyright     */
/*                  Neale Ferguson, Sterling Software.      */
/*                                                          */
/*                                                          */
/* Name       - Neale Ferguson.                             */
/*                                                          */
/* Date       - January, 2000.                              */
/*                                                          */
/*                                                          */
/* Associated    - (1) Refer To ........................... */
/* Documentation                                            */
/*                 (2) Refer To ........................... */
/*                                                          */
/************************************************************/

/************************************************************/
/*                                                          */
/*                     DEFINES                              */
/*                     -------                              */
/*                                                          */
/************************************************************/

#ifndef __KERNEL__
#  define __KERNEL__
#endif
#ifndef MODULE
#  define MODULE
#endif

#define __NO_VERSION__		/* don't define kernel_verion in module.h */

/*=============== End of Defines ===========================*/

/************************************************************/
/*                                                          */
/*              INCLUDE STATEMENTS                          */
/*              ------------------                          */
/*                                                          */
/************************************************************/

#include <linux/module.h>
#include <linux/version.h>

#include <linux/kernel.h>	/* printk() */
#include <linux/slab.h>		/* kmalloc() */
#include <linux/fs.h>		/* everything... */
#include <linux/errno.h>	/* error codes */
#include <linux/types.h>	/* size_t */
#include <linux/proc_fs.h>
#include <linux/fcntl.h>	/* O_ACCMODE */

#include <asm/system.h>		/* cli(), *_flags */
#include <asm/segment.h>	/* memcpy and such */
#include <asm/ebcdic.h>		/* ebcdic stuff */
#include <asm/uaccess.h>	/* copy to/from user space */

#include "cpint.h"		/* local definitions */
#include "cpcmd.h"		/* specific definitions */

/*================== End of Include Statements =============*/

/************************************************************/
/*                                                          */
/*              TYPE DEFINITIONS                            */
/*              ----------------                            */
/*                                                          */
/************************************************************/

/*================== End of Type Definitions ===============*/

/************************************************************/
/*                                                          */
/*             FUNCTION PROTOTYPES                          */
/*             -------------------                          */
/*                                                          */
/************************************************************/

/*================== End of Prototypes =====================*/

/************************************************************/
/*                                                          */
/*           GLOBAL VARIABLE DECLARATIONS                   */
/*           ----------------------------                   */
/*                                                          */
/************************************************************/

/*============== End of Variable Declarations ==============*/

/************************************************************/
/*                                                          */
/* Name       - cpcmd_open.                                 */
/*                                                          */
/* Function   - Open the CP command device.                 */
/*                                                          */
/* Parameters - inode -                                     */
/*              filp  -                                     */
/*                                                          */
/************************************************************/

int
cpcmd_open(struct inode *inode, struct file *filp)
{
	int num = MINOR(inode->i_rdev);
	CPInt_Dev *dev;
	CPCmd_Dev *devExt;

	if (num >= cpint_nr_devs) {
		printk(KERN_ERR
		       "cmdmain: device number %d exceeds max of %d\n",
		       num, cpint_nr_devs);
		return -ENODEV;
	}

	dev = &cpint_devices[num];

	devExt = kmalloc(sizeof (CPCmd_Dev), GFP_KERNEL | __GFP_REPEAT);

	if (!devExt)
		return -ENOMEM;

	memset(devExt, 0, sizeof (CPCmd_Dev));
	devExt->dev = dev;

    /*--------------------------------------------------*/
	/* use filp->private_data to point to device data   */
    /*--------------------------------------------------*/
	filp->private_data = devExt;

	return 0;
}

/*===================== End of Function ====================*/

/************************************************************/
/*                                                          */
/* Name       - cpcmd_release.                              */
/*                                                          */
/* Function   - Close a device.                             */
/*                                                          */
/* Parameters - inode -                                     */
/*              filp  -                                     */
/*                                                          */
/************************************************************/

int
cpcmd_release(struct inode *inode, struct file *filp)
{
	CPCmd_Dev *devExt = filp->private_data;

	if (devExt->data)
		kfree(devExt->data);

	kfree(devExt);

	return (0);
}

/*===================== End of Function ====================*/

/************************************************************/
/*                                                          */
/* Name       - cpcmd_read.                                 */
/*                                                          */
/* Function   - Read the results of a CP command.           */
/*                                                          */
/* Parameters - inode -                                     */
/*              filp  -                                     */
/*              buf   - Pointer to read buffer.             */
/*              count - Size of read buffer.                */
/*                                                          */
/************************************************************/

ssize_t
cpcmd_read(struct file * filp, char *buf, size_t count, loff_t * ppos)
{
	CPCmd_Dev *devExt = filp->private_data;
	loff_t f_pos = filp->f_pos;

	if (f_pos > devExt->size)
		return 0;

	if (f_pos + count > devExt->size)
		count = devExt->size - f_pos;

	if (!devExt->data)
		return 0;

	copy_to_user(buf, &devExt->data[f_pos], count);

	*ppos = f_pos + count;
	if (*ppos > devExt->size) {
		*ppos = 0;
		kfree(devExt->data);
		devExt->data = NULL;
	}

	return count;
}

/*===================== End of Function ====================*/

/************************************************************/
/*                                                          */
/* Name       - cpcmd_write.                                */
/*                                                          */
/* Function   - Execute a CP command and place result into  */
/*              a response buffer.                          */
/*                                                          */
/* Parameters - inode -                                     */
/*              filp  -                                     */
/*              buf   - Pointer to read buffer.             */
/*              count - Size of read buffer.                */
/*                                                          */
/************************************************************/

ssize_t
cpcmd_write(struct file * filp, const char *buf, size_t count, loff_t * ppos)
{
	CPCmd_Dev *devExt = filp->private_data;
	const long mask = 0x60000000L;
	int cc = -1, rspSize, l_cmdOp;

	if (count > sizeof (devExt->cmd))
		return -E2BIG;

	devExt->count = count;
	if (!devExt->data) {
		devExt->data = kmalloc(CPRESP, GFP_KERNEL | __GFP_REPEAT);
		if (!devExt->data)
			return -ENOMEM;
		devExt->size = CPRESP;
		memset(devExt->data, 0, CPRESP);
	}

	copy_from_user(devExt->cmd, buf, devExt->count);

	ASCEBC(devExt->cmd, devExt->count);
	for (l_cmdOp = 0; l_cmdOp < devExt->count; l_cmdOp++) {
		if (devExt->cmd[l_cmdOp] != ' ')
			break;
	}
	if (l_cmdOp == devExt->count)
		return -EINVAL;

	for (; l_cmdOp < devExt->count; l_cmdOp++) {
		if (devExt->cmd[l_cmdOp] == ' ')
			break;
	}

    /*------------------------------------------------------*/
	/* Check if we uppercase everything or just the command */
    /*------------------------------------------------------*/
	if (devExt->flag & UPCASE)
		EBC_TOUPPER(devExt->cmd, l_cmdOp);
	else
		EBC_TOUPPER(devExt->cmd, devExt->count);

	while (cc != 0) {
#ifdef __s390x__
		asm volatile ("LRAG  2,0(%3)\t/* Get cmd address */\n\t"
			      "LGR   4,%4\t/* Get length of command */\n\t"
			      "OG    4,%7\t/* Set flags */\n\t"
			      "LRAG  3,0(%5)\t/* Get response address */\n\t"
			      "LGR   5,%6\t/* Get response length */\n\t"
			      "AHI   5,-1\t/* Leave room for eol */\n\t"
			      ".long 0x83240008\t/* Issue command */\n\t"
			      "IPM   %1\t\t/* Get CC */\n\t"
			      "SRL   %1,28\t/* Shuffle down */\n\t"
			      "LGR   %0,4\t/* Keep return code */\n\t"
			      "LGR   %2,5\t/* Get response length */\n\t":"=d"
			      (devExt->rc), "=d"(cc), "=d"(rspSize)
			      :"a"(devExt->cmd), "d"(devExt->count),
			      "a"(devExt->data), "d"(devExt->size), "m"(mask)
			      :"cc", "2", "3", "4", "5");
#else
		asm volatile ("LRA   2,0(%3)\t/* Get cmd address */\n\t"
			      "LR    4,%4\t/* Get length of command */\n\t"
			      "O     4,%7\t/* Set flags */\n\t"
			      "LRA   3,0(%5)\t/* Get response address */\n\t"
			      "LR    5,%6\t/* Get response length */\n\t"
			      "AHI   5,-1\t/* Leave room for eol */\n\t"
			      ".long 0x83240008\t/* Issue command */\n\t"
			      "IPM   %1\t\t/* Get CC */\n\t"
			      "SRL   %1,28\t/* Shuffle down */\n\t"
			      "LR    %0,4\t/* Keep return code */\n\t"
			      "LR    %2,5\t/* Get response length */\n\t":"=d"
			      (devExt->rc), "=d"(cc), "=d"(rspSize)
			      :"a"(devExt->cmd), "d"(devExt->count),
			      "a"(devExt->data), "d"(devExt->size), "m"(mask)
			      :"cc", "2", "3", "4", "5");
#endif
		if (cc != 0) {
			if (rspSize <= CPRSPLIM) {
				kfree(devExt->data);
				devExt->size += rspSize + 1;
				devExt->data =
				    kmalloc(devExt->size,
					    GFP_KERNEL | __GFP_REPEAT);
				if (!devExt->data)
					return -ENOMEM;
				memset(devExt->data, 0, devExt->size);
			} else
				return -ENOMEM;
		} else
			devExt->size = rspSize;
	}
	EBCASC(devExt->data, devExt->size);

	filp->f_pos = 0;
	return count;
}

/*===================== End of Function ====================*/

/************************************************************/
/*                                                          */
/* Name       - cpcmd_ioctl.                                */
/*                                                          */
/* Function   - Perform IOCTL functions for CP interface:   */
/*		1. Retrieve the return code of the last     */
/*                 command issued.			    */
/*                                                          */
/* Parameters - inode -                                     */
/*              filp  -                                     */
/*              cmd   - Command to be issued.               */
/*              arg   - Argument.                           */
/*                                                          */
/************************************************************/

int
cpcmd_ioctl(struct inode *inode, struct file *filp,
	    unsigned int cmd, unsigned long arg)
{
	CPCmd_Dev *devExt = filp->private_data;

	if (devExt != NULL) {
		switch (cmd) {
		case CPIOCRC:	/* Return the return code   */
			if (copy_to_user((char *) arg,
					 &devExt->rc, sizeof (devExt->rc)))
				return -EFAULT;
			return 0;

		case CPIOUPC:	/* Only uppercase command   */
			devExt->flag |= UPCASE;
			return 0;
			break;

		default:
			return -EINVAL;
		}
	} else
		return -ENODEV;

}

/*===================== End of Function ====================*/
