/* 
 * $Id: zh_main.c,v 1.10.2.2 2004/03/24 11:18:00 aherrman Exp $
 *
 * Module providing an interface for HBA API (FC-HBA) implementation
 * to the zfcp driver.
 *
 * (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>
 */

/*
 * To automatically create the device node (after module loading) use:
 *
 *	minor=`cat /proc/misc | awk "\\$2==\\"zfcp_hbaapi\\" {print \\$1}"`
 *	mknod /dev/zfcp_hbaapi c 10 $minor
 */

#define HBAAPI_REVISION "$Revision: 1.10.2.2 $"

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

#include <linux/kmod.h>

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/miscdevice.h>
#include <linux/stringify.h>

#include <asm/uaccess.h>
#include <asm/current.h>
#include <asm/atomic.h>
#include <asm/div64.h>
#include <asm/semaphore.h>

#include <linux/spinlock.h>

#include "zh.h"
#include "zfcp.h"
#include "zfcp_zh.h"

#ifdef CONFIG_S390_SUPPORT
# include "zh_ioctl32.h"
#endif

MODULE_AUTHOR("Stefan Voelkel <Stefan.Voelkel@millenux.com>, "
	"Andreas Herrmann  <aherrman@de.ibm.com>, "
	"IBM Deutschland Entwicklung GmbH");
MODULE_DESCRIPTION("Interface for HBA API to FCP HBA driver for IBM zSeries, "
	HBAAPI_REVISION);
MODULE_LICENSE("GPL");

EXPORT_NO_SYMBOLS;

/*
 * module and kernel parameters
 */
int maxshared = ZH_EVENTS_MAX;
int maxpolled = ZH_EVENTS_MAX;
int minor = MISC_DYNAMIC_MINOR;

#ifdef MODULE
MODULE_PARM(maxshared,"i");
MODULE_PARM_DESC(maxshared, "Maximum number of events in the shared event"
		" queue, defaults to "__stringify(ZH_EVENTS_MAX));

MODULE_PARM(maxpolled,"i");
MODULE_PARM_DESC(maxpolled, "Maximum number of events in the polled event"
		" queue, defaults to "__stringify(ZH_EVENTS_MAX));

MODULE_PARM(minor, "i");
MODULE_PARM_DESC(minor, "Minor of the misc device to register, defaults to"
		"dynamic registration");
#else
static int __init zh_maxshared_setup(char *str)
{
	maxshared = simple_strtol(str, NULL, 0);
	return 1;
}
__setup("zfcp_hbaapi_maxshared=", zh_maxshared_setup);

static int __init zh_maxpolled_setup(char *str)
{
	maxpolled = simple_strtol(str, NULL, 0);
	return 1;
}
__setup("zfcp_hbaapi_maxpolled=", zh_maxpolled_setup);

static int __init zh_minor_setup(char *str)
{
	minor = simple_strtol(str, NULL, 0);
	return 1;
}
__setup("zfcp_hbaapi_minor=", zh_minor_setup);
#endif

/**
 * struct zh_event_item - An event
 * @event: the event itself, as it is passed to userspace
 * @count: reference counter
 * @list: list handling
 *
 * An item in the kernel event queue.
 */
struct zh_event_item
{
	struct zh_event event;
	atomic_t count;
	struct list_head list;
};

/**
 * struct zh_config - An config entry
 * @event: the event itself
 * @list: list handling
 *
 * A non-counted event. Used for configuration and polled events.
 */
struct zh_config
{
	struct zh_event event;
	struct list_head list;
};

/**
 * struct zh_client - per client data
 * @registered: 1 if the fd is registered for events, else 0
 * @lost: 1 if the fd has lost an event, else 0
 * @*last: pointer to the last delivered event or NULL
 * @config: list of private config events
 * @clients: list handling for list of clients
 *
 * This structure is attached to filp->private_data and used throughout the
 * module to save per client data. 
 */
struct zh_client
{
	struct semaphore sem;
	unsigned int registered:1;
	unsigned int lost:1;
	struct list_head *last;
	struct list_head config;
	struct list_head clients;
};

/* ioctl workers
 * We could inline them since only about two are called from more than one
 * place. OTOH this is no time critical code */
static int zh_ioc_get_adapterattributes(struct zh_get_adapterattributes *);
static int zh_ioc_get_portattributes(struct zh_get_portattributes *);
static int zh_ioc_get_portstatistics(struct zh_get_portstatistics *);
static int zh_ioc_get_dportattributes(struct zh_get_portattributes *);
static int zh_ioc_get_config(struct zh_get_config *, struct file *);
static int zh_ioc_clear_config(struct file *);
static int zh_ioc_get_event_buffer(struct zh_get_event_buffer *);
static int zh_ioc_event_start(struct file *);
static int zh_ioc_event_stop(struct file *);
static int zh_ioc_event(struct zh_event *, struct file *);
static int zh_ioc_event_insert(void);
static int zh_ioc_scsi_inquiry(struct zh_scsi_inquiry *);
static int zh_ioc_scsi_read_capacity(struct zh_scsi_read_capacity *);
static int zh_ioc_scsi_report_luns(struct zh_scsi_report_luns *);
static int zh_ioc_get_rnid(struct zh_get_rnid *);
static int zh_ioc_send_rnid(struct zh_send_rnid *);
static int zh_ioc_send_ct(struct zh_send_ct *);

/* callbacks for asynchronous event generation. called form zfcp.o */
static void zh_cb_adapter_add(struct file *, devno_t, wwn_t, wwn_t);
static void zh_cb_port_add(struct file *, devno_t, wwn_t, wwn_t, fc_id_t);
static void zh_cb_unit_add(struct file *, devno_t, wwn_t, fcp_lun_t,
			   unsigned int, unsigned int, unsigned int,
			   unsigned int);
static void zh_cb_incomming_els(const devno_t, const fc_id_t, const void *);
static void zh_cb_link_down(const fc_id_t);
static void zh_cb_link_up(const fc_id_t);

/* implemented file operations for our device file */
static int zh_ioctl(struct inode *, struct file *, unsigned int, unsigned long);
static int zh_open(struct inode *, struct file *);
static ssize_t zh_read(struct file *, char *, size_t, loff_t *);
static int zh_release(struct inode *, struct file *);

/* auxiliary functions */
static void zh_map_port_speed(u32 *, int);
#define ZH_PORT_OPERATING_SPEED 1
#define ZH_PORT_SUPPORTED_SPEED 0

/*
 * zh_fops - Device file operations
 *
 * Structure describing the possible operations on our device file
 * */
static struct file_operations zh_fops = {
	.ioctl = zh_ioctl,
	.open = zh_open,
	.release = zh_release,
	.read = zh_read,
};

/*
 * struct zh_misc - misc device description
 * @minor: our minor
 * @name: that is what we are called
 * @fops: file operations
 *
 * Passed to register_misc
 */
static struct miscdevice zh_misc = {
	.name = "zfcp_hbaapi",
	.fops = &zh_fops
};

/*
 * struct zh_callbacks - Callbacks for zfcp events
 * @adapter_add: called on adapter add events
 *
 * This is passed to the zfcp_register_callbacks method, to enable
 * zfcp to call our hooks.
 */
static struct zfcp_zh_callbacks zh_callbacks = {
	.adapter_add = zh_cb_adapter_add,
	.port_add = zh_cb_port_add,
	.unit_add = zh_cb_unit_add,
	.incomming_els = zh_cb_incomming_els,
	.link_down = zh_cb_link_down,
	.link_up = zh_cb_link_up
};

/**
 * struct zh_events
 * @lock: spinlock protecting the structure
 * @wq: wait queue to wait for events
 * @registered: number of processes to notify on events
 * @pending: number of events in the queue
 * @queue: list of events
 * @clients: anchor for list of clients
 *
 * This structure contains all data needed for asynchronous event handling
 */
struct zh_shared_events {
	spinlock_t lock;
	wait_queue_head_t wq;
	unsigned short registered;
	unsigned int pending;
	struct list_head queue;
	struct list_head clients;
};

static struct zh_shared_events zh_shared;

/**
 * struct zh_polled_events
 * @lock: spinlock protecting this structure
 * @pending: number of events pending
 * @queue: list of events
 *
 * Polled events must be in an extra queue according to FC-HBA.
 */
struct zh_polled_events
{
	spinlock_t lock;
	zh_count_t pending;
	struct list_head queue;
};

static struct zh_polled_events zh_polled;

/**
 * add_event_to_polled - Add an event to the polled queue
 * @*c: pointer to an event
 *
 * We use the zh_config structure here. It distinguishes itself from the
 * zh_event only through the missing "left" counter. Since a polled event is
 * only delivered once, we do not need a counter here.
 */
static void add_event_to_polled(struct zh_config *c)
{
	struct zh_config *last;
	unsigned long flags;

	spin_lock_irqsave(&zh_polled.lock, flags);

	if (zh_polled.pending == maxpolled) {
		last = list_entry(zh_polled.queue.next, struct zh_config, list);
		list_del(zh_polled.queue.next);
		kfree(last);
	} else {
		++zh_polled.pending;
	}

	list_add_tail(&c->list, &zh_polled.queue);

	spin_unlock_irqrestore(&zh_polled.lock, flags);
}

/**
 * add_event_to_shared - Add an event to the list of pending events
 * @e: The event that should be added
 * Context: irq/user
 *
 * Events will be thrown away if nobody is registered for delivery. If there
 * are already &maxevents events in the list, the oldest is discarded.
 */
static void add_event_to_shared(struct zh_event_item *e)
{
	struct zh_event_item *item;
	struct list_head *go, *first;
	struct zh_client *c;
	unsigned long flags;

	/* atm we can be called from user, and from irq context
	 * so we need the irqsafe thingie here. */
	spin_lock_irqsave(&zh_shared.lock, flags);

	/* do not keep events if we have nobody to deliver it to */
	if (zh_shared.registered == 0) {
		spin_unlock_irqrestore(&zh_shared.lock, flags);
		kfree(e);
		return;
	}

	/* is the queue full? */
	if (zh_shared.pending == maxshared) {
		first = zh_shared.queue.next;

		/* check if we have to flag some clients */
		list_for_each(go, &zh_shared.clients) {
			c = list_entry(go, struct zh_client, clients);

			if (NULL == c->last) {
				ZH_LOG(KERN_INFO, "lost event for client "
				       "with pid %u\n", current->pid);
				c->lost = 1;
			} else {
				if (first == c->last) {
					c->last = NULL;
				}
			}
		}

		item = list_entry(first, struct zh_event_item, list);
		list_del(first);
		kfree(item);
		
		ZH_LOG(KERN_INFO, "event queue full, deleted item %p\n",
		       item);

	} else {
		++zh_shared.pending;
	}

	/* initialize event, add it to the list */
	atomic_set(&e->count, zh_shared.registered);
	
	list_add_tail(&e->list, &zh_shared.queue);

	spin_unlock_irqrestore(&zh_shared.lock, flags);

	/* wake up all processes waiting for events */
	wake_up_interruptible_all(&zh_shared.wq);
}

/**
 * add_event - Enqueue an event
 * @filp: struct file to add the event to, or NULL
 * @e: the event to enqueue
 * @c: the event to enqueue
 *
 * Either add the event privately to the fd directly, or to the
 * global queue of events.
 * FIXME (stage 2) check whether some polled events have to be added to shared
 * queueu, too.
 */
static inline void add_event(struct file *filp, struct zh_event_item *e, struct
		zh_config *c)
{
	if (NULL == filp) {
		add_event_to_shared(e);
	} else {
		struct zh_client *head = (void*) filp->private_data;
		
		list_add_tail(&c->list, &head->config);
	}
}

/**
 * zh_open - Implements the device open call
 * @inode: struct inode
 * @filp: struct file
 * Return: 0 on success, else -ENOMEM 
 * Context: user
 * 
 * Called when the zfcp_hbaapi device file is opened. Iniializes
 * filp->private_data.
 */
static int zh_open(struct inode *inode, struct file *filp)
{
	struct zh_client *data;

	MOD_INC_USE_COUNT;

	data = kmalloc(sizeof(*data), GFP_KERNEL);

	if (data == NULL) {
		MOD_DEC_USE_COUNT;
		return -ENOMEM;
	}

	sema_init(&data->sem, 1);
	data->last = NULL;
	data->registered = 0;
	data->lost = 0;
	INIT_LIST_HEAD(&data->config);
	INIT_LIST_HEAD(&data->clients);
	filp->private_data = data;

	return 0;
}

/**
 * zh_release - Called on file release
 * @inode: struct inode
 * @filp: struct file
 * Return: always 0
 * Context: user
 *
 * Called when all copies of a filedescriptor are closed, thus we
 * can mess around with private_data, and free it.
 */
static int zh_release(struct inode *inode, struct file *filp)
{
	zh_ioc_event_stop(filp);
	zh_ioc_clear_config(filp);

	kfree(filp->private_data);
	filp->private_data = NULL;

	MOD_DEC_USE_COUNT;
	return 0;
}

/**
 * zh_read - The device's read method
 * Context: user
 *
 * Used to read the whole configuration data, eg adapters, ports or units
 * from zfcp.
 *
 * Access is serialized with a semaphore thus cloned file descriptors may block,
 * eg fd = open(); fork(); parent:ioctl(ZH_IOC_EVENT); child:read(); The child
 * will be block until the parent returns from the ioctl(), IF they use *
 * _exactly the same_ file descriptor. Diffrent file descriptors do _not_ block
 * each other.
 */
static ssize_t zh_read(struct file *filp, char* buf, size_t s, loff_t *off)
{
	size_t count, i, ret;
	struct zh_config *c;
	struct list_head *go, *safe;
	struct zh_client *client = (struct zh_client*) filp->private_data;

	down_interruptible(&client->sem);

	if (s < sizeof(c->event)) {
		ret = -ENOSPC;
		goto up;
	}

	if (list_empty(&client->config)) {
		ret = 0;
		goto up;
	}

	count = s / sizeof(c->event);
	i = 0;

	list_for_each_safe(go, safe, &client->config)
	{
		c = list_entry(go, struct zh_config, list);
		
		if (copy_to_user(buf, &c->event, sizeof(c->event))) {
			ret = -EFAULT;
			goto up;
		}
		
		list_del(go);
		kfree(c);

		buf += sizeof(c->event);

		if (++i >= count) {
			break;
		}
	}

	ret = i * sizeof(c->event);

up:
	up(&client->sem);

	return ret;
};

/**
 * zh_ioctl - The device's ioctl() method
 * @inode: struct inode
 * @filp: struct file
 * @cmd: command to execute
 * @arg: parameter(s) for the command
 * Return:  0 on success, else -E* code
 * Context: User
 * 
 * This is the main interaction method between the vendor lib and the
 * kernel. Here we only determine what we should do, and then call the
 * corresponding worker method.
 *
 * Also read zh_read()'s comment.
 */
static int zh_ioctl(struct inode *inode, struct file *filp,
		    unsigned int cmd, unsigned long arg)
{

	int ret;
	struct zh_client *client = (void*) filp->private_data;

	down_interruptible(&client->sem);

	switch (cmd)
	{
	case ZH_IOC_GET_ADAPTERATTRIBUTES:
		ret = zh_ioc_get_adapterattributes(
			(struct zh_get_adapterattributes *) arg);
		break;
	case ZH_IOC_GET_PORTATTRIBUTES:
		ret = zh_ioc_get_portattributes(
			(struct zh_get_portattributes *) arg);
		break;
	case ZH_IOC_GET_DPORTATTRIBUTES:
		ret = zh_ioc_get_dportattributes(
			(struct zh_get_portattributes *) arg);
		break;
	case ZH_IOC_GET_PORTSTATISTICS:
		ret = zh_ioc_get_portstatistics(
			(struct zh_get_portstatistics *) arg);
		break;
	case ZH_IOC_GET_EVENT_BUFFER:
		ret = zh_ioc_get_event_buffer(
			(struct zh_get_event_buffer *) arg);
		break;
	case ZH_IOC_EVENT_START:
		ret = zh_ioc_event_start(filp);
		break;
	case ZH_IOC_EVENT_STOP:
		ret = zh_ioc_event_stop(filp);
		break;
	case ZH_IOC_EVENT:
		ret = zh_ioc_event((struct zh_event*) arg, filp);
		break;
	case ZH_IOC_EVENT_INSERT: /* DEBUG ONLY */
		ret = zh_ioc_event_insert();
		break;
	case ZH_IOC_SCSI_INQUIRY:
		ret = zh_ioc_scsi_inquiry((struct zh_scsi_inquiry*) arg);
		break;
	case ZH_IOC_SCSI_READ_CAPACITY:
		ret = zh_ioc_scsi_read_capacity(
			(struct zh_scsi_read_capacity *) arg);
		break;
	case ZH_IOC_SCSI_REPORT_LUNS:
		ret = zh_ioc_scsi_report_luns(
			(struct zh_scsi_report_luns *) arg);
		break;
	case ZH_IOC_GET_CONFIG:
		ret = zh_ioc_get_config((struct zh_get_config *) arg, filp);
		break;
	case ZH_IOC_CLEAR_CONFIG:
		ret = zh_ioc_clear_config(filp);
		break;
	case ZH_IOC_GET_RNID:
		ret = zh_ioc_get_rnid((struct zh_get_rnid *) arg);
		break;
	case ZH_IOC_SEND_RNID:
		ret = zh_ioc_send_rnid((struct zh_send_rnid *) arg);
		break;
	case ZH_IOC_SEND_CT:
		ret = zh_ioc_send_ct((struct zh_send_ct *) arg);
		break;
	default:
		ret = -ENOTTY;
		break;
	}

	up(&client->sem);

	return ret;
}

/**
 * zh_init - Module initialisation
 * Return: 0 on success, else < 0
 * Context: user
 * 
 * Sets owner, registers with zfcp, registers misc device, initializes
 * global events structure.
 *
 * FIXME Register a misc minor number for zfcp_hbaapi at www.lanana.org.
 */
static int __init zh_init(void)
{
	int ret;
	
	if (0 >= maxshared) {
		ZH_LOG(KERN_ERR, "illegal value for maxshared: %d, "
		       "minimum is 1\n", maxshared);
		return -EINVAL;
	}

#ifdef CONFIG_ARCH_S390X
	ret = zh_register_ioctl_conversion();
	if (ret < 0) {
		return ret;
	}
#endif

	/* register callbacks with zfcp */
	ret = zfcp_zh_callbacks_register(&zh_callbacks);
	if (ret < 0) {
		return ret;
	}

	SET_MODULE_OWNER(&zh_fops);

	/* register a misc char device */
	zh_misc.minor = minor;
	ret = misc_register(&zh_misc);
	if (ret < 0) {
		goto failed_misc;
	}

	/* initialize shared events */
	spin_lock_init(&zh_shared.lock);
	init_waitqueue_head(&zh_shared.wq);
	zh_shared.registered = 0;
	zh_shared.pending = 0;
	INIT_LIST_HEAD(&zh_shared.queue);
	INIT_LIST_HEAD(&zh_shared.clients);

	/* initalize polled events */
	spin_lock_init(&zh_polled.lock);
	zh_polled.pending = 0;
	INIT_LIST_HEAD(&zh_polled.queue);

	ZH_LOG(KERN_NOTICE, "loaded hbaapi.o, version %s, maxshared=%d, "
			"maxpolled=%d\n", HBAAPI_REVISION, maxshared,
			maxpolled);

	if (MISC_DYNAMIC_MINOR == minor) {
		ZH_LOG(KERN_INFO, "registered dynamic minor with misc "
		       "device\n");
	} else {
		ZH_LOG(KERN_INFO, "registered minor %d with misc device\n",
		       minor);
	}

	return 0;

failed_misc:
	zfcp_zh_callbacks_unregister(&zh_callbacks);
	
	return ret;
}

/**
 * zh_exit - Module finalisation
 * Context: user
 *
 * Undoes all work done in zh_init()
 */
static void __exit zh_exit(void)
{
	struct list_head *go, *save;
	struct zh_event_item *e;
	struct zh_config *c;

	zfcp_zh_callbacks_unregister(&zh_callbacks);
	misc_deregister(&zh_misc);

#ifdef CONFIG_ARCH_S390X
	/* FIXME return value? log message? */
	zh_unregister_ioctl_conversion();
#endif

	if (!list_empty(&zh_shared.queue)) {
		ZH_LOG(KERN_ERR, "event queue not empty while unloading "
		       "module\n");

		/* the module can only be unloaded when all file descriptors
		 * have been closed. If there are events left in the queue, we
		 * do have an error in our code. Since nobody else can access
		 * these events, we can free them.
		 */
		list_for_each_safe(go, save, &zh_shared.queue)
		{
			e = list_entry(go, struct zh_event_item, list);
			list_del(go);
			kfree(e);
			--zh_shared.pending;
		}
	}

	if (zh_shared.pending) {
		ZH_LOG(KERN_ERR, "number of pending events not zero but %u\n",
		       zh_shared.pending);
	}

	/* throw away polled events */
	list_for_each_safe(go, save, &zh_polled.queue) {
		c = list_entry(go, struct zh_config, list);
		list_del(go);
		kfree(c);
	}
}

/**
 * zh_ioc_get_adapterattributes - Retrieve attributes of an adapter
 * @u_ptr: userspace pointer to copy data from and to
 * Return: 0 on success, else -E* code
 * Context: user
 */
static int zh_ioc_get_adapterattributes(struct zh_get_adapterattributes *u_ptr)
{
	struct zh_get_adapterattributes ioc_data;
	int ret;

	if (copy_from_user(&ioc_data, u_ptr, sizeof(ioc_data))) {
		return -EFAULT;
	}

	ret = zfcp_zh_get_adapter_attributes(DEVID_TO_DEVNO(ioc_data.devid),
					     &ioc_data.attributes);
	if (0 == ret) {
		if (copy_to_user(u_ptr, &ioc_data, sizeof(*u_ptr))) {
			ret = -EFAULT;
		}
	}

	return ret;
}

/**
 * zh_ioc_get_portattributes - Retrieve attributes of an adapter port
 * @*u_ptr: userspace pointer to copy the data to
 * Return: 0 on success, else -E* code
 * Context: user
 */
static int zh_ioc_get_portattributes(struct zh_get_portattributes *u_ptr)
{
	struct zh_get_portattributes ioc_data;
	int ret;
	
	if (copy_from_user(&ioc_data, u_ptr, sizeof(ioc_data))) {
		return -EFAULT;
	}

	ret = zfcp_zh_get_port_attributes(DEVID_TO_DEVNO(ioc_data.devid),
					  &ioc_data.attributes);

	if ((0 == ret) || (EOPNOTSUPP == ret)) {
		zh_map_port_speed(&ioc_data.attributes.supported_speed,
				  ZH_PORT_SUPPORTED_SPEED);
		zh_map_port_speed(&ioc_data.attributes.speed,
				  ZH_PORT_OPERATING_SPEED);
		if (copy_to_user(u_ptr, &ioc_data, sizeof(*u_ptr))) {
			ret = -EFAULT;
		}
	}

	return ret;
}

/**
 * zh_ioc_get_portstatistics - Retrieve statistics of an adapter port
 * @*u_ptr: userspace pointer to copy data from and to
 * Return: 0 on success, else -E* code
 * Context: user
 */
static int zh_ioc_get_portstatistics(struct zh_get_portstatistics *u_ptr)
{
	struct zh_get_portstatistics ioc_data;
	int ret;
	
	if (copy_from_user(&ioc_data, u_ptr, sizeof(ioc_data))) {
		return -EFAULT;
	}

	ret = zfcp_zh_get_port_statistics(DEVID_TO_DEVNO(ioc_data.devid),
					  &ioc_data.statistics);

	if ((0 == ret) || (EOPNOTSUPP == ret)) {
		if (copy_to_user(u_ptr, &ioc_data, sizeof(*u_ptr))) {
			ret = -EFAULT;
		}
	}

	return ret;
}

/**
 * zh_ioc_get_dportattributes - Retrieve attributes of an target port
 * @*u_ptr: userspace pointer to copy data from and to
 * Return: 0 on success, else -E* code
 * Context: user
 */
static int zh_ioc_get_dportattributes(struct zh_get_portattributes *u_ptr)
{
	struct zh_get_portattributes ioc_data;
	int ret;
	
	if (copy_from_user(&ioc_data, u_ptr, sizeof(ioc_data))) {
		return -EFAULT;
	}

	ret = zfcp_zh_get_dport_attributes(DEVID_TO_DEVNO(ioc_data.devid),
					   ioc_data.wwpn,
					   &ioc_data.attributes);

	if (0 == ret) {
		if (copy_to_user(u_ptr, &ioc_data, sizeof(*u_ptr))) {
			ret = -EFAULT;
		}
	}

	return ret;
}

/**
 * zh_ioc_get_event_buffer - Retrieve events from the polled queue
 * @*u_ptr: userspace pointer to copy data from and to
 * Return: number of return events on success, else -E* code
 * 
 * Copy events belonging to an adapter to userspace and delete them.
 */
static int zh_ioc_get_event_buffer(struct zh_get_event_buffer *u_ptr)
{
	int ret;
	struct zh_get_event_buffer ioc_data;
	struct zh_config *c;
	struct list_head *go, *safe;
	unsigned short i = 0;

	if (copy_from_user(&ioc_data, u_ptr, sizeof(ioc_data))) {
		return -EFAULT;
	}

	if (ioc_data.count > ZH_GET_EVENT_BUFFER_COUNT) {
		ioc_data.count = ZH_GET_EVENT_BUFFER_COUNT;
	}

	spin_lock_irq(&zh_polled.lock);

	list_for_each_safe(go, safe, &zh_polled.queue)
	{
		c = list_entry(go, struct zh_config, list);

		if (i >= ioc_data.count) {
			break;
		}

		if (ioc_data.devid == c->event.data.polled.devid){
			ioc_data.polled[i] = c->event.data.polled;

			list_del(go);
			kfree(c);
			--zh_polled.pending;

			++i;
		}
	}

	spin_unlock_irq(&zh_polled.lock);

	if (copy_to_user(u_ptr, &ioc_data, sizeof(*u_ptr))) {
		ret = -EFAULT;
	} else {
		ret = i;
	}

	return ret;
}

/**
 * zh_ioc_event_start - Called to enable event delivery
 * @*filp: file for which event delivery should be enabled
 * Return: 0 on success, else -E* code
 * Context: user
 *
 * Mark the fd as target for events, increase each events
 * "to-be-delivered-to" counter by 1.
 */
static int zh_ioc_event_start(struct file *filp)
{
	struct zh_client *client = (struct zh_client*) filp->private_data;

	/* registration is only allowed once */
	if (client->registered) {
		return -EINVAL;
	}

	spin_lock_irq(&zh_shared.lock);

	/* remember that there is one more fd events have to be delivered to */
	++zh_shared.registered;

	client->registered = 1;

	/* set number of the next event, 0 means no events available ATM */
	if (list_empty(&zh_shared.queue)) {
		client->last = NULL;
	} else {
		client->last = zh_shared.queue.prev;
	}

	list_add_tail(&client->clients, &zh_shared.clients);

	spin_unlock_irq(&zh_shared.lock);

	return 0;
}

/**
 * count_down_event - Refcount event
 * @list: containded list structure of the event
 * Context: user
 * Return: 1 of the event was deleted, 0 else
 * Locks: zh_shared.lock must be held
 * 
 * Removes an event from the list, if it was delivered to all fd's. Otherwise
 * just refcount it.
 */
static inline int count_down_event(struct list_head *list)
{
	struct zh_event_item *e = list_entry(list, struct zh_event_item, list);

	if (atomic_dec_and_test(&e->count)) {
		--zh_shared.pending;

		list_del(list);
		kfree(e);
			
		return 1;
	} else {
		return 0;
	}
}

/**
 * zh_ioc_event_stop - Stop event delivery
 * @*filp: file for which event delivery should be disabled
 * Return: 0 on success, else -E* code
 * Context: user
 *
 * Decrease total number of fd's which get events, count down all events
 * _after_ the event delivered to this fd.
 */
static int zh_ioc_event_stop(struct file *filp)
{
	struct list_head *go, *safe;
	struct zh_client *client = (struct zh_client*) filp->private_data;

	/* deregistration is only allowed once */
	if (!client->registered) {
		return -EINVAL;
	}

	spin_lock_irq(&zh_shared.lock);

	--zh_shared.registered;

	list_del(&client->clients);

	/* count down all not yet delivered events for this fd */
	list_for_each_safe(go, safe, &zh_shared.queue)
	{
		if (NULL == client->last) {
			count_down_event(go);
		} else {
			if (go == client->last) {
				client->last = NULL;
			}
		}
	}
	
	spin_unlock_irq(&zh_shared.lock);

	client->registered = 0;
	client->lost = 0;
	client->last = NULL;

	return 0;
}

/**
 * has_next_event - Condition for wait_event_interruptible
 * @head: private data of the fd
 * Return: 1 if there is an event waiting, 0 else
 * Locks: lock/unlock of zh_shared.lock
 *
 * This is used as the condition for the wait_event_interruptible()
 * call. It avoids a wakeup, statechange race.
 */
static inline int has_next_event(struct zh_client *client)
{
	int ret = 0;
	unsigned long flags;

	spin_lock_irqsave(&zh_shared.lock, flags);

	if (NULL == client->last) {
		ret = !list_empty(&zh_shared.queue);
	} else {
		ret = (zh_shared.queue.prev != client->last);
	}
	
	spin_unlock_irqrestore(&zh_shared.lock, flags);

	return ret;
}

/**
 * zh_ioc_event - Wait for an event
 * @*u_ptr: userspace pointer to copy data to
 * @filp: descriptor receiving events
 * Return: 0 on success, -E* code else
 * Context: user
 *
 * The heart of the event delivery. Waits for events and delivers the next one
 */
static int zh_ioc_event(struct zh_event *u_ptr, struct file *filp)
{
	struct zh_event_item *event;
	struct list_head *entry;
	int ret;
	struct zh_client *client = (struct zh_client*) filp->private_data;

	if (!client->registered) {
		return -EINVAL;
	}

	/* wait for events */
	ret = wait_event_interruptible(zh_shared.wq, has_next_event(client));
	if (-ERESTARTSYS == ret) {
		/* ERESTARTSYS should never be seen by user programs */
		return -ERESTART;
	}

	spin_lock_irq(&zh_shared.lock);

	/* need to check it lock protected */
	if (client->lost) {
		client->last = NULL;
		client->lost = 0;
		ret = -ENXIO;
		goto release;
	}

	if (NULL == client->last) {
		entry = zh_shared.queue.next;
	} else {
		entry = client->last->next;
	}
	
	event = list_entry(entry, struct zh_event_item, list);

	if (copy_to_user(u_ptr, &event->event, sizeof(*u_ptr))) {
		ret = -EFAULT;
		goto release; /* keep the event in the queue */
	}

	if (count_down_event(entry)) {
		client->last = NULL;
	} else {
		client->last = entry;
	}

	ret = 0;

release:
	spin_unlock_irq(&zh_shared.lock);
	return ret;
}

/**
 * zh_ioc_event_insert - Insert an event into the list
 * @*u_ptr: userspace pointer to copy data from
 * Return: 0 on success, else -E* code
 * Debug: DEBUG ONLY
 *
 * Insert a dummy event into the list of events, used to determine if
 * the event handling code is working. Insert a dummy event into the 
 * polled event buffer, used to test the polled event buffer code.
 */
static int zh_ioc_event_insert(void)
{
	struct zh_event_item *e;
	
	e = (struct zh_event_item*) kmalloc(sizeof(*e), GFP_KERNEL);
	if (e == NULL) {
		return -ENOMEM;
	}

	memset(e, 0, sizeof(*e));
	e->event.event = ZH_EVENT_DUMMY;

	add_event_to_shared(e);

	return 0;
}

/**
 * zh_sg_free - Free an allocated scatterlist
 * @sg: the scatterlist
 */
static void zh_sg_free(struct sg_list *sg)
{
	int i;
	
	for (i = sg->count - 1; i >= 0; --i) {
		free_page((unsigned long) sg->sg[i].address);
	}

	kfree(sg->sg);
	memset(sg, 0, sizeof(*sg));
}

/**
 * zh_sg_alloc - Allocate a scatterlist
 * @*sg: scatterlist
 * Return: 0 on success, else -E* code
 * Context: User
 *
 * Either all pages can be allocated, or none. On error *sg is invalid and
 * has to be reinitialized.
 */
static int zh_sg_alloc(struct sg_list *sg, size_t size)
{
	unsigned int i;
	size_t lastsize, array;

	if (0 == size) {
		return -EINVAL;
	}

	sg->count = size / PAGE_SIZE;
	lastsize = size - (sg->count * PAGE_SIZE);
	if (0 != lastsize) {
		++sg->count;
	} else {
		lastsize = PAGE_SIZE;
	}

	/* allocate the array */
	array = sizeof(struct scatterlist) * sg->count;
	sg->sg = kmalloc(array, GFP_KERNEL);
	if (NULL == sg->sg) {
		return -ENOMEM;
	}

	memset(sg->sg, 0, array);

	/* allocate needed pages */
	for (i = 0; i < sg->count; ++i) {
		sg->sg[i].address = (void*) get_zeroed_page(GFP_KERNEL);
		if (NULL == sg->sg[i].address) {
			sg->count = i;
			zh_sg_free(sg);
			return -ENOMEM;
		}

		sg->sg[i].length = PAGE_SIZE;
	}

	sg->sg[i-1].length = lastsize;

	return 0;
}

/**
 * zh_sg_get_size - calculate size in bytes of a sg
 * @*sg: pointer to a sg structure
 */
static inline size_t zh_sg_get_size(struct sg_list *sg)
{
	return (((sg->count - 1) * PAGE_SIZE) + sg->sg[sg->count - 1].length);
}

/**
 * zh_sg_copy_from_user - copy data from user
 * @*u_ptr: address in userspace to copy from
 * @size: bytes to copy
 * @*sg: describing space to copy to
 * Return: 0 on success, -E* code else
 * 
 * This method refuses to copy data if size is too big (-ENOSPC)
 */
static int zh_sg_copy_from_user(char *u_ptr, size_t size, struct sg_list *sg)
{
	unsigned int i;

	if (size > zh_sg_get_size(sg)) {
		return -ENOSPC;
	}

	for (i = 0; i < sg->count - 1; ++i) {
		if (copy_from_user(sg->sg[i].address, u_ptr, PAGE_SIZE)) {
			return -EFAULT;
		}

		u_ptr += PAGE_SIZE;
	}

	if (copy_from_user(sg->sg[i].address, u_ptr,
			   sg->sg[sg->count - 1].length)) {
		return -EFAULT;
	}

	return 0;
}

/**
 * zh_sg_copy_from_user_alloc - Allocate a scatterlist and copy data from user
 */
static inline int zh_sg_copy_from_user_alloc(char *u_ptr, size_t size,
					     struct sg_list *sg)
{
	int ret;

	ret = zh_sg_alloc(sg, size);
	if (ret < 0) {
		return ret;
	}

	return zh_sg_copy_from_user(u_ptr, size, sg);
}

/**
 * zh_sg_copy_to_user - Copy sg to userspace
 * @*u_ptr: address in userspace to copy to
 * @*size: bytes to copy, contains number of copied bytes on return
 * @*sg: describing data to copy
 * Return: copied bytes on success, -E* code else
 */
static int zh_sg_copy_to_user(char *u_ptr, size_t *size, struct sg_list *sg)
{
	unsigned int i;
	unsigned int copy;
	size_t left;
	char *begin;

	begin = u_ptr;
	left = *size;
	*size = 0;

	for (i = 0; (i < sg->count) && (left > 0); ++i)	{
		/* FIXME: What to check here? I think one assumption is
		   (sg[i].address == 0) => (sg[i].offset == 0) which becomes
		   (sg[i].address != 0) ||
		   ((sg[i].address == 0) && sg[i].offset == 0) */
		if ((NULL == sg->sg[i].address) && (0 != sg->sg[i].offset)) {
			return -EINVAL;
		}

		copy = min(PAGE_SIZE, left);
		copy = min(copy, sg->sg[i].length);

		if (copy_to_user(u_ptr, sg->sg[i].address, copy)) {
			return -EFAULT;
		}

		u_ptr += copy;
		left -= copy;
		*size += copy;
	}

	if ((u_ptr - begin) != zh_sg_get_size(sg)) {
		return -ENOSPC;
	} else {
		return 0;
	}
}

/**
 * zh_sg_copy_to_user_truncate - Copy as much from sg to user as fits
 * @*u_ptr: address in userspace to copy to
 * @*size: bytes to copy, contains number of copied bytes on return
 * @*sg: describing data to copy
 * Return: 0 on success, -E* code else
 *
 * This wrapper to zh_sg_copy_to_user() simply ignores -ENOSPC errors, which is
 * what we want when we use this function.
 */
static inline int zh_sg_copy_to_user_truncate(char *u_ptr, size_t *size,
					      struct sg_list *sg)
{
	long long ret;
	
	ret = zh_sg_copy_to_user(u_ptr, size, sg);

	if (-ENOSPC == ret) {
		ret = 0;
	}
	
	return ret;
}

/**
 * zh_alloc_scsi_cmnd - Allocate and fill a Scsi_Cmnd
 * @cmd: The scsi command as specified in the SCSI standards
 * @cmd_size: size of cmd in bytes
 * @*sg: pointer to scatterlist to retrieve response
 * Return: The created Scsi_Cmnd on succes, else NULL
 */
static Scsi_Cmnd* zh_alloc_scsi_cmnd(void *cmd, size_t cmd_size,
				     struct sg_list *sg)
{
	Scsi_Cmnd *sc;

	sc = kmalloc(sizeof(*sc), GFP_KERNEL);
	if (NULL == sc) {
		return NULL;
	}
	
	memset(sc, 0, sizeof(*sc));
	/* zfcp uses host_scribble, bh_next and scsi_done, don't touch em */

	sc->sc_data_direction = SCSI_DATA_READ;

	sc->use_sg = sg->count;
	sc->sglist_len = sg->count;
	sc->buffer = sg->sg;
	sc->bufflen = zh_sg_get_size(sg);
	sc->request_buffer = sc->buffer;
	sc->request_bufflen = sc->bufflen;

	sc->cmd_len = cmd_size;

	memcpy(sc->cmnd, cmd, cmd_size);
	memcpy(sc->data_cmnd, cmd, cmd_size);

	return sc;
}

/**
 * zh_do_scsi_command - worker for sending scsi commands
 * @devid: of the adpapter to send via
 * @wwpn: of the port to send the command to
 * @lun: to send the command to
 * @cmd: SCSI command to send
 * @cmd_size: size of the command in bytes
 * @rsp: userspace pointer to copy the response to
 * @rsp_size: size of the userspace buffer
 * @sense: userspace pointer to copy sense data to
 */
static int zh_do_scsi_command(devid_t devid, wwn_t wwpn, fcp_lun_t lun,
			      void *cmd, size_t cmd_size, struct sg_list *rsp,
			      void *sense)
{
	Scsi_Cmnd *sc;
	int ret;

	sc = zh_alloc_scsi_cmnd(cmd, cmd_size, rsp);
	if (NULL == sc) {
		return -ENOMEM;
	}

	ret = zfcp_zh_send_scsi(DEVID_TO_DEVNO(devid), wwpn, lun, sc);

	/* the scsi stack sets this, if there was a scsi error */
	if (ret > 0) {
		memcpy(sense, sc->sense_buffer, SCSI_SENSE_BUFFERSIZE);
	}

	kfree(sc);

	return ret;
}

/**
 * zh_ioc_scsi_report_luns - Send SCSI REPORT LUNS
 * @*u_ptr: userspace pointer to copy data from and to
 * Return: 0 on success, < 0 -E* code, > 0 SCSI ERROR
 * Context: user
 */
static int zh_ioc_scsi_report_luns(struct zh_scsi_report_luns *u_ptr)
{
	int ret;
	struct zh_scsi_report_luns ioc_data;

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

	ret = zh_report_luns_helper(&ioc_data);

	if (ret >= 0) {
		if (copy_to_user(u_ptr, &ioc_data, sizeof(ioc_data))) {
			return -EFAULT;
		}
		if (ret > 0) {
			ret = 0;
		}
	}

	return ret;
}

/**
 * zh_report_luns_helper - Send SCSI REPORT LUNS
 * @*u_ptr: userspace pointer to copy data from and to
 * Return: 0 on success, < 0 -E* code, > 0 SCSI ERROR
 * Context: user
 */
int zh_report_luns_helper(struct zh_scsi_report_luns *ioc_data)
{
	int ret;
	size_t copy;
	struct sg_list sg;
	struct scsi_report_luns_cmd cmd = { 0 };

	if (ioc_data->rsp_buffer_size < SCSI_REPORT_LUNS_SIZE_MIN) {
		return -EINVAL;
	}

	ret = zfcp_zh_assert_fclun_zero(DEVID_TO_DEVNO(ioc_data->devid),
					ioc_data->wwpn);
	if (0 != ret) {
		return ret;
	}

	ret = zh_sg_alloc(&sg, ioc_data->rsp_buffer_size);
	if (ret < 0) {
		return ret;
	}
	
	cmd.op = REPORT_LUNS;
	cmd.alloc_length = ioc_data->rsp_buffer_size;

	ret = zh_do_scsi_command(ioc_data->devid, ioc_data->wwpn, 0, &cmd,
				 sizeof(cmd), &sg, ioc_data->sense);

	if (ret >= 0) {
		copy = ioc_data->rsp_buffer_size;
		ret =  zh_sg_copy_to_user_truncate(ioc_data->rsp_buffer, &copy,
						   &sg);
	}

	zh_sg_free(&sg);

	return ret;
}

/**
 * zh_ioc_scsi_read_capacity - send SCSI READ CAPACITY
 * @*u_ptr: userspace pointer to copy data from and to
 * Return: 0 on success, < 0 -E* code, > 0 SCSI ERROR
 * Context: user
 */
static int zh_ioc_scsi_read_capacity(struct zh_scsi_read_capacity *u_ptr)
{
	int ret;
	struct sg_list sg;
	struct zh_scsi_read_capacity ioc_data;
	struct scsi_read_capacity_cmd cmd = { 0 };

	if (copy_from_user(&ioc_data, u_ptr, sizeof(ioc_data))) {
		return -EFAULT;
	}

	ret = zh_sg_alloc(&sg, ZH_SCSI_READ_CAPACITY_SIZE);
	if (ret < 0) {
		return ret;
	}

	cmd.op = READ_CAPACITY;

	ret = zh_do_scsi_command(ioc_data.devid, ioc_data.wwpn,
				 ioc_data.fclun, &cmd, sizeof(cmd), &sg,
				 ioc_data.sense);

	if (ret >= 0) {
		memcpy(ioc_data.read_capacity, sg.sg[0].address,
		       sg.sg[0].length);

		if (copy_to_user(u_ptr, &ioc_data, sizeof(*u_ptr))) {
			ret = -EFAULT;
		}
	}

	zh_sg_free(&sg);

	return ret;
}

/**
 * zh_ioc_scsi_inquiry - send SCSI INQUIRY
 * @*u_ptr: userspace pointer to copy data from and to
 * Return: 0 on success, < 0 -E* code, > 0 SCSI ERROR
 * Context: user
 */
static int zh_ioc_scsi_inquiry(struct zh_scsi_inquiry *u_ptr)
{
	int ret;
	struct sg_list sg;
	struct zh_scsi_inquiry ioc_data;
	struct scsi_inquiry_cmd cmd = { 0 };

	if (copy_from_user(&ioc_data, u_ptr, sizeof(ioc_data))) {
		return -EFAULT;
	}

	ret = zh_sg_alloc(&sg, ZH_SCSI_INQUIRY_SIZE);
	if (ret < 0) {
		return ret;
	}

	/* alloc_lentgh is of type u8, thus can only range to 255 */
	cmd.op  = INQUIRY;
	cmd.alloc_length = 255;

	if (ioc_data.evpd)	{
		cmd.evpd = 1;
		cmd.page_code = ioc_data.page_code;
	}

	ret = zh_do_scsi_command(ioc_data.devid, ioc_data.wwpn,
				 ioc_data.fclun, &cmd, sizeof(cmd), &sg,
				 ioc_data.sense);

	if (ret >= 0) {
		memcpy(ioc_data.inquiry, sg.sg[0].address, sg.sg[0].length);

		if (copy_to_user(u_ptr, &ioc_data, sizeof(*u_ptr))) {
			ret = -EFAULT;
		}
	}

	zh_sg_free(&sg);

	return ret;
}

/**
 * zh_ioc_get_config - create private events for a specfific struct
 * @*u_ptr: userspace pointer to copy data from
 * @*filp: requesting fd
 * Return: no of created events, else -E* code
 * Context: user
 *
 * With this ioctl events are generated and attached to the fd only.  Used to
 * enumerate currently configured adapters/ports/units. Subsequent calls
 * discard prior created events.
 */
static int zh_ioc_get_config(struct zh_get_config *u_ptr, struct file *filp)
{
	struct zh_get_config ioc_data;
	struct zh_client *head = (void*) filp->private_data;

	if (!list_empty(&head->config)) {
		zh_ioc_clear_config(filp);
	}
	
	if (copy_from_user(&ioc_data, u_ptr, sizeof(ioc_data))) {
		return -EFAULT;
	}

	return zfcp_zh_get_config(filp, DEVID_TO_DEVNO(ioc_data.devid),
				  ioc_data.wwpn, ioc_data.flags);
}

/**
 * zh_ioc_clear_config - remove config events from fd
 * @filp: fd requesting to clear its config
 * Return: always 0
 * Context: user
 */
static int zh_ioc_clear_config(struct file *filp)
{
	struct zh_config *c;
	struct list_head *go, *safe;
	struct zh_client *client = (struct zh_client*) filp->private_data;

	list_for_each_safe(go, safe, &client->config)
	{
		c = list_entry(go, struct zh_config, list);
		list_del(go);
		kfree(c);
	}

	INIT_LIST_HEAD(&client->config);

	return 0;
}

/**
 * zh_ioc_get_rnid() - get RNID from adapter
 * @u_ptr: userspace pointer to copy data from and to
 *
 * Note: We set data in zfcp_hbaapi since we can not access the data
 *	the adapter sends out.
 * Note: wwpn and wwnn not set because not used in ZFCP HBA API Library
 */
static int zh_ioc_get_rnid(struct zh_get_rnid *u_ptr)
{
	struct zh_get_rnid ioc_data;

	if (copy_from_user(&ioc_data, u_ptr, sizeof(ioc_data))) {
		return -EFAULT;
	}
	
	memset(&ioc_data.payload, 0, sizeof(ioc_data.payload));

	ioc_data.payload.code = ZFCP_LS_RNID;
	ioc_data.payload.node_id_format = 0xDF;
	ioc_data.payload.common_id_length =
		sizeof(struct zfcp_ls_rnid_common_id);
	ioc_data.payload.specific_id_length =
		sizeof(struct zfcp_ls_rnid_general_topology_id);

	/* all other fields not set */
	ioc_data.payload.specific_id.associated_type = 0x000000a;
	ioc_data.payload.specific_id.physical_port_number = 1;
	ioc_data.payload.specific_id.nr_attached_nodes = 1;

	if (copy_to_user(u_ptr, &ioc_data, sizeof(*u_ptr))) {
		return -EFAULT;
	}

	return 0;
}

/**
 * zh_ioc_send_rnid - send an rnid to a port
 * @*u_ptr: userspace pointer to copy data from and to
 *
 * Send a FC-FS ELS RNID to a discovered port.
 */
static int zh_ioc_send_rnid(struct zh_send_rnid *u_ptr)
{
	int ret;
	struct sg_list sg_send, sg_receive;
	struct zh_send_rnid ioc_data;
	struct zfcp_ls_rnid *rnid;

	if (copy_from_user(&ioc_data, u_ptr, sizeof(ioc_data))) {
		return -EFAULT;
	}

	ret = zh_sg_alloc(&sg_send, sizeof(struct zfcp_ls_rnid));
	if (ret < 0) {
		return ret;
	}

	ret = zh_sg_alloc(&sg_receive, sizeof(struct zfcp_ls_rnid_acc));
	if (ret < 0) {
		goto free_send;
	}

	rnid = (void*) sg_send.sg[0].address;
	rnid->code = ZFCP_LS_RNID;
	rnid->node_id_format = 0xDF;

	ret = zfcp_zh_send_els(DEVID_TO_DEVNO(ioc_data.devid), ioc_data.wwpn,
			       sg_send.sg, sg_send.count, sg_receive.sg,
			       sg_receive.count);
	if (0 == ret) {
		ioc_data.size = sg_receive.sg[0].length;
		memcpy(&ioc_data.payload, sg_receive.sg[0].address,
		       ioc_data.size);
		
		if (copy_to_user(u_ptr, &ioc_data, sizeof(*u_ptr))) {
			ret = -EFAULT;
		}
	}

	zh_sg_free(&sg_receive);
free_send:
	zh_sg_free(&sg_send);
	
	return ret;
}

/**
 * zh_ioc_send_ct - send a Generic Service command
 * @u_ptr: userspace pointer to parameter structure
 * Return: 0 on success, -E* code else
 *
 * Send a FC-GS CT IU
 */
static int zh_ioc_send_ct(struct zh_send_ct *u_ptr)
{
	int ret;
	struct zh_send_ct ioc_data;

	if (copy_from_user(&ioc_data, u_ptr, sizeof(ioc_data))) {
		return -EFAULT;
	}

	ret = zh_send_ct_helper(&ioc_data);

	return ret;
}

/**
 * zh_send_ct_helper - send a Generic Service command
 * @send_ct: userspace pointer to parameter structure
 * Return: 0 on success, -E* code else
 *
 * Send a FC-GS CT IU
 */
int zh_send_ct_helper(struct zh_send_ct *send_ct)
{
	int ret;
	struct sg_list req, resp;

	ret = zh_sg_alloc(&req, send_ct->req_length);
	if (ret < 0) {
		return ret;
	}

	if (zh_sg_copy_from_user(send_ct->req, send_ct->req_length, &req)) {
		ret = -EFAULT;
		goto free_req;
	}

	ret = zh_sg_alloc(&resp, send_ct->resp_length);
	if (ret < 0) {
		goto free_req;
	}

	ret = zfcp_zh_send_ct(DEVID_TO_DEVNO(send_ct->devid),
			      req.sg, req.count, resp.sg, resp.count);

	if (0 == ret) {
		size_t size = send_ct->resp_length;
		ret = zh_sg_copy_to_user_truncate(send_ct->resp, &size, &resp);
	}

	zh_sg_free(&resp);
free_req:
	zh_sg_free(&req);
	
	return ret;
}

/**
 * prepare_event - prepare event structure
 * @filp: fd event to attach
 * @e: countable event
 * @c: non-countable event
 * Return: created event on success, else NULL
 * Context: irq/user
 *
 * To be able to generate private and shared events with the same callbacks,
 * this function creates a private or shared event depending on filp beeing NULL
 * or not.
 */
static inline struct zh_event*
prepare_event(struct file *filp, struct zh_event_item **e, struct zh_config **c)
{
	if (NULL != filp) {
		*c = (struct zh_config*) kmalloc(sizeof(**c), GFP_ATOMIC);
		if (NULL == *c) {
			return NULL;
		}

		*e = NULL;
		return &(*c)->event;
	} else {
		*e = (struct zh_event_item*) kmalloc(sizeof(**e), GFP_ATOMIC);
		if (NULL == *e) {
			return NULL;
		}

		*c = NULL;
		return &(*e)->event;
	}
}

/**
 * zh_cb_adapter_add - Callback for adapter add events
 * @filp: Address of struct file (see below)
 * @devno: of the adapter
 * @wwnn: of the adapter
 * @wwpn: of the adapter
 * Context: irq/user
 * FIXME: check context
 *
 * Called in two situations:
 *   - initial config creation (filp != NULL) (USER-ctx)
 *   - adapter added to running system (filp == NULL) (IRQ-ctx)
 *
 * When called with (filp == NULL) the created event is added to the
 * global list of events, and thus delivered to all registered fd's.
 * When called with (filp != NULL) the created event is private to the
 * specific filp, and is appended only there.
 * */
static void zh_cb_adapter_add(struct file *filp, devno_t devno, wwn_t wwnn,
			      wwn_t wwpn)
{
	struct zh_event *event;
	struct zh_event_item *e = NULL;
	struct zh_config *c = NULL;
	
	ZH_LOG(KERN_DEBUG, "wwpn: 0x%llx\n", (long long) wwpn);

	event = prepare_event(filp, &e, &c);
	if (NULL == event) {
		return;
	}

	event->event = ZH_EVENT_ADAPTER_ADD;
	event->data.adapter_add.devid = ZH_DEVID(0, 0, devno);
	event->data.adapter_add.wwnn = wwnn;
	event->data.adapter_add.wwpn = wwpn;

	add_event(filp, e, c);
}

/**
 * zh_cb_port_add() - Called on port add events
 * @filp: Address of struct file (see zh_cb_adapter_add)
 * @devno: of the adapter
 * @wwnn: of the port
 * @wwpn: of the port
 * @did: of the port
 * Context: irq/user
 * FIXME: check context
 */
static void zh_cb_port_add(struct file *filp, devno_t devno, wwn_t wwpn,
			   wwn_t wwnn, fc_id_t did)
{
	struct zh_event *event;
	struct zh_event_item *e;
	struct zh_config *c;

	ZH_LOG(KERN_DEBUG, "wwpn: 0x%llx\n", (long long) wwpn);

	event = prepare_event(filp, &e, &c);
	if (NULL == event) {
		return;
	}

	event->event = ZH_EVENT_PORT_ADD;
	event->data.port_add.devid = ZH_DEVID(0, 0, devno);
	event->data.port_add.wwpn = wwpn;
	event->data.port_add.wwnn = wwnn;
	event->data.port_add.did = did;

	add_event(filp, e, c);
}

/**
 * zh_cb_unit_add() - Called on unit add events
 * @filp: Address of struct file (see zh_cb_adapter_add)
 * @devno: of the adapter
 * @wwnn: of the port
 * @wwpn: of the port
 * @fclun: of the unit
 * @host: SCSI host id
 * @channel: SCSI channel
 * @target: SCSI target
 * @lun: SCSI lun
 * Context: irq/user
 * FIXME: check context
 */
static void zh_cb_unit_add(struct file *filp, devno_t devno, wwn_t wwpn,
			   fcp_lun_t fclun, unsigned int host,
			   unsigned int channel, unsigned int target,
			   unsigned int lun)
{
	struct zh_event *event;
	struct zh_event_item *e;
	struct zh_config *c;

	ZH_LOG(KERN_DEBUG, "wwpn: 0x%llx lun: 0x%llx\n", (long long) wwpn,
	       (long long) fclun);

	event = prepare_event(filp, &e, &c);
	if (NULL == event) {
		return;
	}

	event->event = ZH_EVENT_UNIT_ADD;
	event->data.unit_add.devid = ZH_DEVID(0, 0, devno);
	event->data.unit_add.wwpn = wwpn;
	event->data.unit_add.fclun = fclun;
	event->data.unit_add.host = host;
	event->data.unit_add.channel = channel;
	event->data.unit_add.id = target;
	event->data.unit_add.lun = lun;

	add_event(filp, e, c);
}

/**
 * zh_cb_incomming_els_rscn - RSCN ELS worker
 * @devno: of the adapter
 * @s_id: sid of the sending port
 * @v: pointer to rscn payload
 * Context: irq
 */
static inline void zh_cb_incomming_els_rscn(const devno_t devno,
					    const fc_id_t s_id, const void *v)
{
	int count, i;
	struct zh_config *c;
	const struct zh_els_rscn *rscn = v;
	struct zh_els_rscn_payload *payload = rscn->payload;

	ZH_LOG(KERN_INFO, "incoming RSCN\n");

	count = rscn->payload_length / sizeof(struct zh_els_rscn_payload);
	
	for (i = 0; i < count; ++i, ++payload) {
		c = kmalloc(sizeof(*c), GFP_ATOMIC);
		if (NULL == c) {
			return;
		}

		c->event.event = ZH_EVENT_POLLED;
		c->event.data.polled.event = ZH_EVENT_POLLED_RSCN;
		c->event.data.polled.devid = ZH_DEVID(0, 0, devno);
		c->event.data.polled.data.rscn.port_fc_id = s_id;
		memcpy(&c->event.data.polled.data.rscn.port_page, payload,
		       sizeof(*payload));

		add_event_to_polled(c);
	}
}

/**
 * zh_cb_incomming_els - hook for incomming els'
 * @devno: of the adapter the els came in
 * @s_id: sid of the port the els came in
 * @*v: pointer to the els payload
 * Context: irq
 */
static void zh_cb_incomming_els(const devno_t devno, const fc_id_t s_id,
				const void *v)
{
	const u8 *op = v;

	switch (*op)
	{
	case ZFCP_LS_RSCN:
		zh_cb_incomming_els_rscn(devno, s_id, v);
		break;
	}
}

/**
 * zh_cb_link_down - hook for link down events
 * @id: s_id this event occured on
 * Context: irq
 *
 * Atm this hook is only called for local link down events.
 */
static void zh_cb_link_down(const fc_id_t id)
{
	struct zh_config *c;

	c = kmalloc(sizeof(*c), GFP_ATOMIC);
	if (NULL == c) {
		return;
	}

	c->event.event = ZH_EVENT_POLLED_LINK_DOWN;
	c->event.data.polled.data.link.port_fc_id = id;

	add_event_to_polled(c);
}

/**
 * zh_cb_link_up - hook for link up events
 * @id: s_id this event occured on
 * Context: irq
 *
 * Atm this hook is only called for local link up events.
 */
static void zh_cb_link_up(const fc_id_t id)
{
	struct zh_config *c;

	c = kmalloc(sizeof(*c), GFP_ATOMIC);
	if (NULL == c) {
		return;
	}

	c->event.event = ZH_EVENT_POLLED_LINK_UP;
	c->event.data.polled.data.link.port_fc_id = id;

	add_event_to_polled(c);
}

/**
 * zh_map_port_speed - maps port speed between FC-FS/FC-GS4 and FC-HBA
 * @speed: address of u32 to be mapped
 */
static void zh_map_port_speed(u32 *speed, int flag_operating_speed)
{
	if (flag_operating_speed == ZH_PORT_OPERATING_SPEED) {
		switch(*speed) {
		case 4:
			*speed = 8;
		case 8:	
			*speed = 4;
		case (1<<14):
			*speed = 0;
		}
	} else {		/* ZH_PORT_SUPPORTED_SPEED */
		switch(*speed) {
		case 4:
			*speed = 8;
		case 8:
			*speed = 4;
		case (1<<15):
			*speed = 0;
		}
	}

	return;
}

module_init(zh_init);
module_exit(zh_exit);
