/* 
 * $Id: zh_ioctl32.c,v 1.4.2.2 2004/03/24 11:18:00 aherrman Exp $
 *
 * (C) Copyright IBM Corp. 2003
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version. See the file COPYING for more
 * information.
 *
 * Authors:
 *       Stefan Voelkel <Stefan.Voelkel@millenux.com>
 *       Andreas Herrmann <aherrman@de.ibm.com>
 *
 * No need for special handler functions here since our ioctl() method is 32 BIT
 * aware.
 *
 * For this trick to work we define a 32bit structure (see "zh.h") and a normal
 * one (eg struct zh_scsi_report_luns32 and struct zh_scsi_report_luns). We also
 * define an extra 32 BIT ioctl number, ZH_IOC_SCSI_REPORT_LUNS32 using the
 * zh_scsi_report_luns32 structure.
 *
 * This creates an ioctl command that is the same on 32 and 64 BIT. On a 64 BIT
 * kernel only 32 BIT applications will call the ioctl() with the 32 BIT ioctl
 * command, thus we can react on that and apply our pointer voodoo.
 */

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

#include <linux/fs.h>
#include <linux/ioctl.h>
#include <asm/uaccess.h>
#include <asm/ioctl32.h>

#include "zh.h"

#include "../../../arch/s390x/kernel/linux32.h"

/* void* casts needed because of fourth parameter */
#define ZH_IOC_DEFAULT(cmd) {cmd, (void*) sys_ioctl}
#define ZH_IOC_HANDLER(cmd, handler) {cmd, (void*) handler}

/**
 * zh_ioctl_entry - one entry in the conversion array
 * @cmd: ioctl cmd
 * @handler: the corresponding handler
 */
struct zh_ioctl_entry
{
	unsigned int cmd;
	ioctl_trans_handler_t handler;
};

/**
 * struct zh_send_ct32 - data needed to send out a Generic Service command,
 *	32BIT version
 * @devid: id of HBA via which to send CT
 * @req_length: size the request buffer
 * @req: request buffer
 * @resp_length: size of response buffer
 * @resp: response buffer
 */
struct zh_send_ct32
{
	devid_t devid;
	u32 req_length;
	u32 req;
	u32 resp_length;
	u32 resp;
} __attribute__((packed));

#define ZH_IOC_SEND_CT32 _IOWR(ZH_IOC_MAGIC, 7, struct zh_send_ct32)

/**
 * zh_send_ct32 - ioctl32 conversion function for ZH_IOC_SEND_CT
 * @fd: fd of device file
 * @cmd: command to execute
 * @arg: parameter(s) for the command
 * Return:  0 on success, else -E* code
 * Context: User
 */
static inline int zh_send_ct32(unsigned int fd, unsigned int cmd,
			       unsigned long arg)
{
	int ret;
	struct zh_send_ct ioc_data;
	struct zh_send_ct32 ioc_data32;
	struct zh_send_ct32 *u_ptr = (struct zh_send_ct32 *) A(arg);

	if (copy_from_user(&ioc_data32, u_ptr, sizeof(ioc_data32)))
		return -EFAULT;

	ioc_data.devid = ioc_data32.devid;
	ioc_data.req_length = ioc_data32.req_length;
	ioc_data.resp_length = ioc_data32.resp_length;
	ioc_data.req = (void *) A(ioc_data32.req);
	ioc_data.resp = (void *) A(ioc_data32.resp);

	ret = zh_send_ct_helper(&ioc_data);

	return ret;
}

/**
 * struct zh_scsi_report_luns32 - data needed for an REPORT_LUNS, 32BIT version
 * @devid: of the adapter
 * @wwpn: of the port
 * @*rsp_buffer: pointer to response buffer
 * @rsp_buffer_size: of the response buffer
 * @sense: buffer for sense data
 */
struct zh_scsi_report_luns32
{
	devid_t devid;
	wwn_t wwpn;
	u32 rsp_buffer;
	u32 rsp_buffer_size;
	u8 sense[ZH_SCSI_SENSE_BUFFERSIZE];
} __attribute__((packed));

#define ZH_IOC_SCSI_REPORT_LUNS32 \
_IOW(ZH_IOC_MAGIC, 10, struct zh_scsi_report_luns32)

/**
 * zh_scsi_report_luns32 - ioctl32 conversion function for
 *	ZH_SCSI_REPORT_LUNS32
 * @fd: fd of device file
 * @cmd: command to execute
 * @arg: parameter(s) for the command
 * Return:  0 on success, else -E* code
 * Context: User
 */
static inline int zh_scsi_report_luns32(unsigned int fd, unsigned int cmd,
					unsigned long arg)
{
	int ret;
	struct zh_scsi_report_luns ioc_data = { 0 };
	struct zh_scsi_report_luns32 ioc_data32;
	struct zh_scsi_report_luns32 *u_ptr;

	u_ptr = (struct zh_scsi_report_luns32 *) A(arg);

	if (copy_from_user(&ioc_data32, u_ptr, sizeof(ioc_data32)))
		return -EFAULT;

	ioc_data.devid = ioc_data32.devid;
	ioc_data.wwpn = ioc_data32.wwpn;
	ioc_data.rsp_buffer = (void *) A(ioc_data32.rsp_buffer);
	ioc_data.rsp_buffer_size = ioc_data32.rsp_buffer_size;

	ret = zh_report_luns_helper(&ioc_data);

	if (ret >= 0) {
		memcpy(&ioc_data32.sense, &(ioc_data.sense),
		       ZH_SCSI_SENSE_BUFFERSIZE);
		if (copy_to_user(u_ptr, &ioc_data32, sizeof(ioc_data32))) {
			ret = -EFAULT;
		}
		if (ret > 0) {
			ret = 0;
		}
	}

	return ret;
}

/*
 * All the commands we have to register, may be freed if not compiled as module
 */
static struct zh_ioctl_entry zh_conversion[] __initdata = {
	ZH_IOC_DEFAULT(ZH_IOC_GET_ADAPTERATTRIBUTES),
	ZH_IOC_DEFAULT(ZH_IOC_GET_PORTATTRIBUTES),
	ZH_IOC_DEFAULT(ZH_IOC_GET_PORTSTATISTICS),
	ZH_IOC_DEFAULT(ZH_IOC_GET_DPORTATTRIBUTES),
	ZH_IOC_DEFAULT(ZH_IOC_GET_RNID),
	ZH_IOC_DEFAULT(ZH_IOC_SEND_RNID),
	ZH_IOC_HANDLER(ZH_IOC_SEND_CT32, zh_send_ct32),
	ZH_IOC_DEFAULT(ZH_IOC_SCSI_INQUIRY),
	ZH_IOC_DEFAULT(ZH_IOC_SCSI_READ_CAPACITY),
	ZH_IOC_HANDLER(ZH_IOC_SCSI_REPORT_LUNS32, zh_scsi_report_luns32),
	ZH_IOC_DEFAULT(ZH_IOC_GET_EVENT_BUFFER),
	ZH_IOC_DEFAULT(ZH_IOC_GET_CONFIG),
	ZH_IOC_DEFAULT(ZH_IOC_CLEAR_CONFIG),
	ZH_IOC_DEFAULT(ZH_IOC_EVENT_START),
	ZH_IOC_DEFAULT(ZH_IOC_EVENT_STOP),
	ZH_IOC_DEFAULT(ZH_IOC_EVENT),
	ZH_IOC_DEFAULT(ZH_IOC_EVENT_INSERT),
};

/**
 * do_unregister - unregister previously registered conversion ioctl()s
 * @from: index in the zh_conversion table to start deregistering from
 *
 * All ioctl()s _before_ from will be deregistered. If from is 0 all will be
 * unregistered.
 */
static int do_unregister(int from)
{
	int i;

	if (0 == from) {
		from = sizeof(zh_conversion)/sizeof(zh_conversion[0]);
	}
	
	for (i = from - 1; i >= 0; --i) {
		unregister_ioctl32_conversion(zh_conversion[i].cmd);
	}

	return 0;
}

/**
 * do_register - register ioctl() conversion routines
 */
static int do_register(void)
{
	unsigned int i;
	int ret;

	for (i = 0; i < sizeof(zh_conversion)/sizeof(zh_conversion[0]); ++i) {
		ret = register_ioctl32_conversion(zh_conversion[i].cmd,
				zh_conversion[i].handler);
		if (ret != 0) {
			do_unregister(i - 1);
			break;
		}
	}
	
	return ret;
}

/**
 * zh_unregister_ioctl_conversion - clean up method
 */
int zh_unregister_ioctl_conversion(void)
{
	return do_unregister(0);
}

/**
 * zh_register_ioctl_conversion - initialization method
 */
int zh_register_ioctl_conversion(void)
{
	return do_register();
}
