Linux-2.6.12-rc2

Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.

Let it rip!
diff --git a/drivers/s390/scsi/Makefile b/drivers/s390/scsi/Makefile
new file mode 100644
index 0000000..fc14530
--- /dev/null
+++ b/drivers/s390/scsi/Makefile
@@ -0,0 +1,9 @@
+#
+# Makefile for the S/390 specific device drivers
+#
+
+zfcp-objs := zfcp_aux.o zfcp_ccw.o zfcp_scsi.o zfcp_erp.o zfcp_qdio.o \
+	     zfcp_fsf.o zfcp_sysfs_adapter.o zfcp_sysfs_port.o \
+	     zfcp_sysfs_unit.o zfcp_sysfs_driver.o
+
+obj-$(CONFIG_ZFCP) += zfcp.o
diff --git a/drivers/s390/scsi/zfcp_aux.c b/drivers/s390/scsi/zfcp_aux.c
new file mode 100644
index 0000000..6a43322
--- /dev/null
+++ b/drivers/s390/scsi/zfcp_aux.c
@@ -0,0 +1,1977 @@
+/*
+ *
+ * linux/drivers/s390/scsi/zfcp_aux.c
+ *
+ * FCP adapter driver for IBM eServer zSeries
+ *
+ * (C) Copyright IBM Corp. 2002, 2004
+ *
+ * Author(s): Martin Peschke <mpeschke@de.ibm.com>
+ *            Raimund Schroeder <raimund.schroeder@de.ibm.com>
+ *            Aron Zeh
+ *            Wolfgang Taphorn
+ *            Stefan Bader <stefan.bader@de.ibm.com>
+ *            Heiko Carstens <heiko.carstens@de.ibm.com>
+ *            Andreas Herrmann <aherrman@de.ibm.com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#define ZFCP_AUX_REVISION "$Revision: 1.145 $"
+
+#include "zfcp_ext.h"
+
+/* accumulated log level (module parameter) */
+static u32 loglevel = ZFCP_LOG_LEVEL_DEFAULTS;
+static char *device;
+/*********************** FUNCTION PROTOTYPES *********************************/
+
+/* written against the module interface */
+static int __init  zfcp_module_init(void);
+
+/* FCP related */
+static void zfcp_ns_gid_pn_handler(unsigned long);
+
+/* miscellaneous */
+static inline int zfcp_sg_list_alloc(struct zfcp_sg_list *, size_t);
+static inline void zfcp_sg_list_free(struct zfcp_sg_list *);
+static inline int zfcp_sg_list_copy_from_user(struct zfcp_sg_list *,
+					      void __user *, size_t);
+static inline int zfcp_sg_list_copy_to_user(void __user *,
+					    struct zfcp_sg_list *, size_t);
+
+static int zfcp_cfdc_dev_ioctl(struct inode *, struct file *,
+	unsigned int, unsigned long);
+
+#define ZFCP_CFDC_IOC_MAGIC                     0xDD
+#define ZFCP_CFDC_IOC \
+	_IOWR(ZFCP_CFDC_IOC_MAGIC, 0, struct zfcp_cfdc_sense_data)
+
+#ifdef CONFIG_COMPAT
+static struct ioctl_trans zfcp_ioctl_trans = {ZFCP_CFDC_IOC, (void*) sys_ioctl};
+#endif
+
+static struct file_operations zfcp_cfdc_fops = {
+	.ioctl = zfcp_cfdc_dev_ioctl
+};
+
+static struct miscdevice zfcp_cfdc_misc = {
+	.minor = ZFCP_CFDC_DEV_MINOR,
+	.name = ZFCP_CFDC_DEV_NAME,
+	.fops = &zfcp_cfdc_fops
+};
+
+/*********************** KERNEL/MODULE PARAMETERS  ***************************/
+
+/* declare driver module init/cleanup functions */
+module_init(zfcp_module_init);
+
+MODULE_AUTHOR("Heiko Carstens <heiko.carstens@de.ibm.com>, "
+	      "Andreas Herrman <aherrman@de.ibm.com>, "
+	      "Martin Peschke <mpeschke@de.ibm.com>, "
+	      "Raimund Schroeder <raimund.schroeder@de.ibm.com>, "
+	      "Wolfgang Taphorn <taphorn@de.ibm.com>, "
+	      "Aron Zeh <arzeh@de.ibm.com>, "
+	      "IBM Deutschland Entwicklung GmbH");
+MODULE_DESCRIPTION
+    ("FCP (SCSI over Fibre Channel) HBA driver for IBM eServer zSeries");
+MODULE_LICENSE("GPL");
+
+module_param(device, charp, 0);
+MODULE_PARM_DESC(device, "specify initial device");
+
+module_param(loglevel, uint, 0);
+MODULE_PARM_DESC(loglevel,
+		 "log levels, 8 nibbles: "
+		 "FC ERP QDIO CIO Config FSF SCSI Other, "
+		 "levels: 0=none 1=normal 2=devel 3=trace");
+
+#ifdef ZFCP_PRINT_FLAGS
+u32 flags_dump = 0;
+module_param(flags_dump, uint, 0);
+#endif
+
+/****************************************************************/
+/************** Functions without logging ***********************/
+/****************************************************************/
+
+void
+_zfcp_hex_dump(char *addr, int count)
+{
+	int i;
+	for (i = 0; i < count; i++) {
+		printk("%02x", addr[i]);
+		if ((i % 4) == 3)
+			printk(" ");
+		if ((i % 32) == 31)
+			printk("\n");
+	}
+	if (((i-1) % 32) != 31)
+		printk("\n");
+}
+
+/****************************************************************/
+/************** Uncategorised Functions *************************/
+/****************************************************************/
+
+#define ZFCP_LOG_AREA			ZFCP_LOG_AREA_OTHER
+
+static inline int
+zfcp_fsf_req_is_scsi_cmnd(struct zfcp_fsf_req *fsf_req)
+{
+	return ((fsf_req->fsf_command == FSF_QTCB_FCP_CMND) &&
+		!(fsf_req->status & ZFCP_STATUS_FSFREQ_TASK_MANAGEMENT));
+}
+
+void
+zfcp_cmd_dbf_event_fsf(const char *text, struct zfcp_fsf_req *fsf_req,
+		       void *add_data, int add_length)
+{
+	struct zfcp_adapter *adapter = fsf_req->adapter;
+	struct scsi_cmnd *scsi_cmnd;
+	int level = 3;
+	int i;
+	unsigned long flags;
+
+	spin_lock_irqsave(&adapter->dbf_lock, flags);
+	if (zfcp_fsf_req_is_scsi_cmnd(fsf_req)) {
+		scsi_cmnd = fsf_req->data.send_fcp_command_task.scsi_cmnd;
+		debug_text_event(adapter->cmd_dbf, level, "fsferror");
+		debug_text_event(adapter->cmd_dbf, level, text);
+		debug_event(adapter->cmd_dbf, level, &fsf_req,
+			    sizeof (unsigned long));
+		debug_event(adapter->cmd_dbf, level, &fsf_req->seq_no,
+			    sizeof (u32));
+		debug_event(adapter->cmd_dbf, level, &scsi_cmnd,
+			    sizeof (unsigned long));
+		debug_event(adapter->cmd_dbf, level, &scsi_cmnd->cmnd,
+			    min(ZFCP_CMD_DBF_LENGTH, (int)scsi_cmnd->cmd_len));
+		for (i = 0; i < add_length; i += ZFCP_CMD_DBF_LENGTH)
+			debug_event(adapter->cmd_dbf,
+				    level,
+				    (char *) add_data + i,
+				    min(ZFCP_CMD_DBF_LENGTH, add_length - i));
+	}
+	spin_unlock_irqrestore(&adapter->dbf_lock, flags);
+}
+
+/* XXX additionally log unit if available */
+/* ---> introduce new parameter for unit, see 2.4 code */
+void
+zfcp_cmd_dbf_event_scsi(const char *text, struct scsi_cmnd *scsi_cmnd)
+{
+	struct zfcp_adapter *adapter;
+	union zfcp_req_data *req_data;
+	struct zfcp_fsf_req *fsf_req;
+	int level = ((host_byte(scsi_cmnd->result) != 0) ? 1 : 5);
+	unsigned long flags;
+
+	adapter = (struct zfcp_adapter *) scsi_cmnd->device->host->hostdata[0];
+	req_data = (union zfcp_req_data *) scsi_cmnd->host_scribble;
+	fsf_req = (req_data ? req_data->send_fcp_command_task.fsf_req : NULL);
+	spin_lock_irqsave(&adapter->dbf_lock, flags);
+	debug_text_event(adapter->cmd_dbf, level, "hostbyte");
+	debug_text_event(adapter->cmd_dbf, level, text);
+	debug_event(adapter->cmd_dbf, level, &scsi_cmnd->result, sizeof (u32));
+	debug_event(adapter->cmd_dbf, level, &scsi_cmnd,
+		    sizeof (unsigned long));
+	debug_event(adapter->cmd_dbf, level, &scsi_cmnd->cmnd,
+		    min(ZFCP_CMD_DBF_LENGTH, (int)scsi_cmnd->cmd_len));
+	if (likely(fsf_req)) {
+		debug_event(adapter->cmd_dbf, level, &fsf_req,
+			    sizeof (unsigned long));
+		debug_event(adapter->cmd_dbf, level, &fsf_req->seq_no,
+			    sizeof (u32));
+	} else {
+		debug_text_event(adapter->cmd_dbf, level, "");
+		debug_text_event(adapter->cmd_dbf, level, "");
+	}
+	spin_unlock_irqrestore(&adapter->dbf_lock, flags);
+}
+
+void
+zfcp_in_els_dbf_event(struct zfcp_adapter *adapter, const char *text,
+		      struct fsf_status_read_buffer *status_buffer, int length)
+{
+	int level = 1;
+	int i;
+
+	debug_text_event(adapter->in_els_dbf, level, text);
+	debug_event(adapter->in_els_dbf, level, &status_buffer->d_id, 8);
+	for (i = 0; i < length; i += ZFCP_IN_ELS_DBF_LENGTH)
+		debug_event(adapter->in_els_dbf,
+			    level,
+			    (char *) status_buffer->payload + i,
+			    min(ZFCP_IN_ELS_DBF_LENGTH, length - i));
+}
+
+/**
+ * zfcp_device_setup - setup function
+ * @str: pointer to parameter string
+ *
+ * Parse "device=..." parameter string.
+ */
+static int __init
+zfcp_device_setup(char *str)
+{
+	char *tmp;
+
+	if (!str)
+		return 0;
+
+	tmp = strchr(str, ',');
+	if (!tmp)
+		goto err_out;
+	*tmp++ = '\0';
+	strncpy(zfcp_data.init_busid, str, BUS_ID_SIZE);
+	zfcp_data.init_busid[BUS_ID_SIZE-1] = '\0';
+
+	zfcp_data.init_wwpn = simple_strtoull(tmp, &tmp, 0);
+	if (*tmp++ != ',')
+		goto err_out;
+	if (*tmp == '\0')
+		goto err_out;
+
+	zfcp_data.init_fcp_lun = simple_strtoull(tmp, &tmp, 0);
+	if (*tmp != '\0')
+		goto err_out;
+	return 1;
+
+ err_out:
+	ZFCP_LOG_NORMAL("Parse error for device parameter string %s\n", str);
+	return 0;
+}
+
+static void __init
+zfcp_init_device_configure(void)
+{
+	struct zfcp_adapter *adapter;
+	struct zfcp_port *port;
+	struct zfcp_unit *unit;
+
+	down(&zfcp_data.config_sema);
+	read_lock_irq(&zfcp_data.config_lock);
+	adapter = zfcp_get_adapter_by_busid(zfcp_data.init_busid);
+	if (adapter)
+		zfcp_adapter_get(adapter);
+	read_unlock_irq(&zfcp_data.config_lock);
+
+	if (adapter == NULL)
+		goto out_adapter;
+	port = zfcp_port_enqueue(adapter, zfcp_data.init_wwpn, 0, 0);
+	if (!port)
+		goto out_port;
+	unit = zfcp_unit_enqueue(port, zfcp_data.init_fcp_lun);
+	if (!unit)
+		goto out_unit;
+	up(&zfcp_data.config_sema);
+	ccw_device_set_online(adapter->ccw_device);
+	zfcp_erp_wait(adapter);
+	down(&zfcp_data.config_sema);
+	zfcp_unit_put(unit);
+ out_unit:
+	zfcp_port_put(port);
+ out_port:
+	zfcp_adapter_put(adapter);
+ out_adapter:
+	up(&zfcp_data.config_sema);
+	return;
+}
+
+static int __init
+zfcp_module_init(void)
+{
+
+	int retval = 0;
+
+	atomic_set(&zfcp_data.loglevel, loglevel);
+
+	/* initialize adapter list */
+	INIT_LIST_HEAD(&zfcp_data.adapter_list_head);
+
+	/* initialize adapters to be removed list head */
+	INIT_LIST_HEAD(&zfcp_data.adapter_remove_lh);
+
+	zfcp_transport_template = fc_attach_transport(&zfcp_transport_functions);
+	if (!zfcp_transport_template)
+		return -ENODEV;
+
+	retval = register_ioctl32_conversion(zfcp_ioctl_trans.cmd,
+					     zfcp_ioctl_trans.handler);
+	if (retval != 0) {
+		ZFCP_LOG_INFO("registration of ioctl32 conversion failed\n");
+		goto out;
+	}
+
+	retval = misc_register(&zfcp_cfdc_misc);
+	if (retval != 0) {
+		ZFCP_LOG_INFO("registration of misc device "
+			      "zfcp_cfdc failed\n");
+		goto out_misc_register;
+	} else {
+		ZFCP_LOG_TRACE("major/minor for zfcp_cfdc: %d/%d\n",
+			       ZFCP_CFDC_DEV_MAJOR, zfcp_cfdc_misc.minor);
+	}
+
+	/* Initialise proc semaphores */
+	sema_init(&zfcp_data.config_sema, 1);
+
+	/* initialise configuration rw lock */
+	rwlock_init(&zfcp_data.config_lock);
+
+	/* save address of data structure managing the driver module */
+	zfcp_data.scsi_host_template.module = THIS_MODULE;
+
+	/* setup dynamic I/O */
+	retval = zfcp_ccw_register();
+	if (retval) {
+		ZFCP_LOG_NORMAL("registration with common I/O layer failed\n");
+		goto out_ccw_register;
+	}
+
+	if (zfcp_device_setup(device))
+		zfcp_init_device_configure();
+
+	goto out;
+
+ out_ccw_register:
+	misc_deregister(&zfcp_cfdc_misc);
+ out_misc_register:
+	unregister_ioctl32_conversion(zfcp_ioctl_trans.cmd);
+ out:
+	return retval;
+}
+
+/*
+ * function:    zfcp_cfdc_dev_ioctl
+ *
+ * purpose:     Handle control file upload/download transaction via IOCTL
+ *		interface
+ *
+ * returns:     0           - Operation completed successfuly
+ *              -ENOTTY     - Unknown IOCTL command
+ *              -EINVAL     - Invalid sense data record
+ *              -ENXIO      - The FCP adapter is not available
+ *              -EOPNOTSUPP - The FCP adapter does not have CFDC support
+ *              -ENOMEM     - Insufficient memory
+ *              -EFAULT     - User space memory I/O operation fault
+ *              -EPERM      - Cannot create or queue FSF request or create SBALs
+ *              -ERESTARTSYS- Received signal (is mapped to EAGAIN by VFS)
+ */
+static int
+zfcp_cfdc_dev_ioctl(struct inode *inode, struct file *file,
+                    unsigned int command, unsigned long buffer)
+{
+	struct zfcp_cfdc_sense_data *sense_data, __user *sense_data_user;
+	struct zfcp_adapter *adapter = NULL;
+	struct zfcp_fsf_req *fsf_req = NULL;
+	struct zfcp_sg_list *sg_list = NULL;
+	u32 fsf_command, option;
+	char *bus_id = NULL;
+	int retval = 0;
+
+	sense_data = kmalloc(sizeof(struct zfcp_cfdc_sense_data), GFP_KERNEL);
+	if (sense_data == NULL) {
+		retval = -ENOMEM;
+		goto out;
+	}
+
+	sg_list = kmalloc(sizeof(struct zfcp_sg_list), GFP_KERNEL);
+	if (sg_list == NULL) {
+		retval = -ENOMEM;
+		goto out;
+	}
+	memset(sg_list, 0, sizeof(*sg_list));
+
+	if (command != ZFCP_CFDC_IOC) {
+		ZFCP_LOG_INFO("IOC request code 0x%x invalid\n", command);
+		retval = -ENOTTY;
+		goto out;
+	}
+
+	if ((sense_data_user = (void __user *) buffer) == NULL) {
+		ZFCP_LOG_INFO("sense data record is required\n");
+		retval = -EINVAL;
+		goto out;
+	}
+
+	retval = copy_from_user(sense_data, sense_data_user,
+				sizeof(struct zfcp_cfdc_sense_data));
+	if (retval) {
+		retval = -EFAULT;
+		goto out;
+	}
+
+	if (sense_data->signature != ZFCP_CFDC_SIGNATURE) {
+		ZFCP_LOG_INFO("invalid sense data request signature 0x%08x\n",
+			      ZFCP_CFDC_SIGNATURE);
+		retval = -EINVAL;
+		goto out;
+	}
+
+	switch (sense_data->command) {
+
+	case ZFCP_CFDC_CMND_DOWNLOAD_NORMAL:
+		fsf_command = FSF_QTCB_DOWNLOAD_CONTROL_FILE;
+		option = FSF_CFDC_OPTION_NORMAL_MODE;
+		break;
+
+	case ZFCP_CFDC_CMND_DOWNLOAD_FORCE:
+		fsf_command = FSF_QTCB_DOWNLOAD_CONTROL_FILE;
+		option = FSF_CFDC_OPTION_FORCE;
+		break;
+
+	case ZFCP_CFDC_CMND_FULL_ACCESS:
+		fsf_command = FSF_QTCB_DOWNLOAD_CONTROL_FILE;
+		option = FSF_CFDC_OPTION_FULL_ACCESS;
+		break;
+
+	case ZFCP_CFDC_CMND_RESTRICTED_ACCESS:
+		fsf_command = FSF_QTCB_DOWNLOAD_CONTROL_FILE;
+		option = FSF_CFDC_OPTION_RESTRICTED_ACCESS;
+		break;
+
+	case ZFCP_CFDC_CMND_UPLOAD:
+		fsf_command = FSF_QTCB_UPLOAD_CONTROL_FILE;
+		option = 0;
+		break;
+
+	default:
+		ZFCP_LOG_INFO("invalid command code 0x%08x\n",
+			      sense_data->command);
+		retval = -EINVAL;
+		goto out;
+	}
+
+	bus_id = kmalloc(BUS_ID_SIZE, GFP_KERNEL);
+	if (bus_id == NULL) {
+		retval = -ENOMEM;
+		goto out;
+	}
+	snprintf(bus_id, BUS_ID_SIZE, "%d.%d.%04x",
+		(sense_data->devno >> 24),
+		(sense_data->devno >> 16) & 0xFF,
+		(sense_data->devno & 0xFFFF));
+
+	read_lock_irq(&zfcp_data.config_lock);
+	adapter = zfcp_get_adapter_by_busid(bus_id);
+	if (adapter)
+		zfcp_adapter_get(adapter);
+	read_unlock_irq(&zfcp_data.config_lock);
+
+	kfree(bus_id);
+
+	if (adapter == NULL) {
+		ZFCP_LOG_INFO("invalid adapter\n");
+		retval = -ENXIO;
+		goto out;
+	}
+
+	if (sense_data->command & ZFCP_CFDC_WITH_CONTROL_FILE) {
+		retval = zfcp_sg_list_alloc(sg_list,
+					    ZFCP_CFDC_MAX_CONTROL_FILE_SIZE);
+		if (retval) {
+			retval = -ENOMEM;
+			goto out;
+		}
+	}
+
+	if ((sense_data->command & ZFCP_CFDC_DOWNLOAD) &&
+	    (sense_data->command & ZFCP_CFDC_WITH_CONTROL_FILE)) {
+		retval = zfcp_sg_list_copy_from_user(
+			sg_list, &sense_data_user->control_file,
+			ZFCP_CFDC_MAX_CONTROL_FILE_SIZE);
+		if (retval) {
+			retval = -EFAULT;
+			goto out;
+		}
+	}
+
+	retval = zfcp_fsf_control_file(adapter, &fsf_req, fsf_command,
+				       option, sg_list);
+	if (retval)
+		goto out;
+
+	if ((fsf_req->qtcb->prefix.prot_status != FSF_PROT_GOOD) &&
+	    (fsf_req->qtcb->prefix.prot_status != FSF_PROT_FSF_STATUS_PRESENTED)) {
+		retval = -ENXIO;
+		goto out;
+	}
+
+	sense_data->fsf_status = fsf_req->qtcb->header.fsf_status;
+	memcpy(&sense_data->fsf_status_qual,
+	       &fsf_req->qtcb->header.fsf_status_qual,
+	       sizeof(union fsf_status_qual));
+	memcpy(&sense_data->payloads, &fsf_req->qtcb->bottom.support.els, 256);
+
+	retval = copy_to_user(sense_data_user, sense_data,
+		sizeof(struct zfcp_cfdc_sense_data));
+	if (retval) {
+		retval = -EFAULT;
+		goto out;
+	}
+
+	if (sense_data->command & ZFCP_CFDC_UPLOAD) {
+		retval = zfcp_sg_list_copy_to_user(
+			&sense_data_user->control_file, sg_list,
+			ZFCP_CFDC_MAX_CONTROL_FILE_SIZE);
+		if (retval) {
+			retval = -EFAULT;
+			goto out;
+		}
+	}
+
+ out:
+	if (fsf_req != NULL)
+		zfcp_fsf_req_cleanup(fsf_req);
+
+	if ((adapter != NULL) && (retval != -ENXIO))
+		zfcp_adapter_put(adapter);
+
+	if (sg_list != NULL) {
+		zfcp_sg_list_free(sg_list);
+		kfree(sg_list);
+	}
+
+	if (sense_data != NULL)
+		kfree(sense_data);
+
+	return retval;
+}
+
+
+/**
+ * zfcp_sg_list_alloc - create a scatter-gather list of the specified size
+ * @sg_list: structure describing a scatter gather list
+ * @size: size of scatter-gather list
+ * Return: 0 on success, else -ENOMEM
+ *
+ * In sg_list->sg a pointer to the created scatter-gather list is returned,
+ * or NULL if we run out of memory. sg_list->count specifies the number of
+ * elements of the scatter-gather list. The maximum size of a single element
+ * in the scatter-gather list is PAGE_SIZE.
+ */
+static inline int
+zfcp_sg_list_alloc(struct zfcp_sg_list *sg_list, size_t size)
+{
+	struct scatterlist *sg;
+	unsigned int i;
+	int retval = 0;
+	void *address;
+
+	BUG_ON(sg_list == NULL);
+
+	sg_list->count = size >> PAGE_SHIFT;
+	if (size & ~PAGE_MASK)
+		sg_list->count++;
+	sg_list->sg = kmalloc(sg_list->count * sizeof(struct scatterlist),
+			      GFP_KERNEL);
+	if (sg_list->sg == NULL) {
+		sg_list->count = 0;
+		retval = -ENOMEM;
+		goto out;
+	}
+	memset(sg_list->sg, 0, sg_list->count * sizeof(struct scatterlist));
+
+	for (i = 0, sg = sg_list->sg; i < sg_list->count; i++, sg++) {
+		sg->length = min(size, PAGE_SIZE);
+		sg->offset = 0;
+		address = (void *) get_zeroed_page(GFP_KERNEL);
+		if (address == NULL) {
+			sg_list->count = i;
+			zfcp_sg_list_free(sg_list);
+			retval = -ENOMEM;
+			goto out;
+		}
+		zfcp_address_to_sg(address, sg);
+		size -= sg->length;
+	}
+
+ out:
+	return retval;
+}
+
+
+/**
+ * zfcp_sg_list_free - free memory of a scatter-gather list
+ * @sg_list: structure describing a scatter-gather list
+ *
+ * Memory for each element in the scatter-gather list is freed.
+ * Finally sg_list->sg is freed itself and sg_list->count is reset.
+ */
+static inline void
+zfcp_sg_list_free(struct zfcp_sg_list *sg_list)
+{
+	struct scatterlist *sg;
+	unsigned int i;
+
+	BUG_ON(sg_list == NULL);
+
+	for (i = 0, sg = sg_list->sg; i < sg_list->count; i++, sg++)
+		free_page((unsigned long) zfcp_sg_to_address(sg));
+
+	sg_list->count = 0;
+	kfree(sg_list->sg);
+}
+
+/**
+ * zfcp_sg_size - determine size of a scatter-gather list
+ * @sg: array of (struct scatterlist)
+ * @sg_count: elements in array
+ * Return: size of entire scatter-gather list
+ */
+size_t
+zfcp_sg_size(struct scatterlist *sg, unsigned int sg_count)
+{
+	unsigned int i;
+	struct scatterlist *p;
+	size_t size;
+
+	size = 0;
+	for (i = 0, p = sg; i < sg_count; i++, p++) {
+		BUG_ON(p == NULL);
+		size += p->length;
+	}
+
+	return size;
+}
+
+
+/**
+ * zfcp_sg_list_copy_from_user -copy data from user space to scatter-gather list
+ * @sg_list: structure describing a scatter-gather list
+ * @user_buffer: pointer to buffer in user space
+ * @size: number of bytes to be copied
+ * Return: 0 on success, -EFAULT if copy_from_user fails.
+ */
+static inline int
+zfcp_sg_list_copy_from_user(struct zfcp_sg_list *sg_list,
+			    void __user *user_buffer,
+                            size_t size)
+{
+	struct scatterlist *sg;
+	unsigned int length;
+	void *zfcp_buffer;
+	int retval = 0;
+
+	BUG_ON(sg_list == NULL);
+
+	if (zfcp_sg_size(sg_list->sg, sg_list->count) < size)
+		return -EFAULT;
+
+	for (sg = sg_list->sg; size > 0; sg++) {
+		length = min((unsigned int)size, sg->length);
+		zfcp_buffer = zfcp_sg_to_address(sg);
+		if (copy_from_user(zfcp_buffer, user_buffer, length)) {
+			retval = -EFAULT;
+			goto out;
+		}
+		user_buffer += length;
+		size -= length;
+	}
+
+ out:
+	return retval;
+}
+
+
+/**
+ * zfcp_sg_list_copy_to_user - copy data from scatter-gather list to user space
+ * @user_buffer: pointer to buffer in user space
+ * @sg_list: structure describing a scatter-gather list
+ * @size: number of bytes to be copied
+ * Return: 0 on success, -EFAULT if copy_to_user fails
+ */
+static inline int
+zfcp_sg_list_copy_to_user(void __user  *user_buffer,
+			  struct zfcp_sg_list *sg_list,
+                          size_t size)
+{
+	struct scatterlist *sg;
+	unsigned int length;
+	void *zfcp_buffer;
+	int retval = 0;
+
+	BUG_ON(sg_list == NULL);
+
+	if (zfcp_sg_size(sg_list->sg, sg_list->count) < size)
+		return -EFAULT;
+
+	for (sg = sg_list->sg; size > 0; sg++) {
+		length = min((unsigned int) size, sg->length);
+		zfcp_buffer = zfcp_sg_to_address(sg);
+		if (copy_to_user(user_buffer, zfcp_buffer, length)) {
+			retval = -EFAULT;
+			goto out;
+		}
+		user_buffer += length;
+		size -= length;
+	}
+
+ out:
+	return retval;
+}
+
+
+#undef ZFCP_LOG_AREA
+
+/****************************************************************/
+/****** Functions for configuration/set-up of structures ********/
+/****************************************************************/
+
+#define ZFCP_LOG_AREA			ZFCP_LOG_AREA_CONFIG
+
+/**
+ * zfcp_get_unit_by_lun - find unit in unit list of port by FCP LUN
+ * @port: pointer to port to search for unit
+ * @fcp_lun: FCP LUN to search for
+ * Traverse list of all units of a port and return pointer to a unit
+ * with the given FCP LUN.
+ */
+struct zfcp_unit *
+zfcp_get_unit_by_lun(struct zfcp_port *port, fcp_lun_t fcp_lun)
+{
+	struct zfcp_unit *unit;
+	int found = 0;
+
+	list_for_each_entry(unit, &port->unit_list_head, list) {
+		if ((unit->fcp_lun == fcp_lun) &&
+		    !atomic_test_mask(ZFCP_STATUS_COMMON_REMOVE, &unit->status))
+		{
+			found = 1;
+			break;
+		}
+	}
+	return found ? unit : NULL;
+}
+
+/**
+ * zfcp_get_port_by_wwpn - find port in port list of adapter by wwpn
+ * @adapter: pointer to adapter to search for port
+ * @wwpn: wwpn to search for
+ * Traverse list of all ports of an adapter and return pointer to a port
+ * with the given wwpn.
+ */
+struct zfcp_port *
+zfcp_get_port_by_wwpn(struct zfcp_adapter *adapter, wwn_t wwpn)
+{
+	struct zfcp_port *port;
+	int found = 0;
+
+	list_for_each_entry(port, &adapter->port_list_head, list) {
+		if ((port->wwpn == wwpn) &&
+		    !(atomic_read(&port->status) &
+		      (ZFCP_STATUS_PORT_NO_WWPN | ZFCP_STATUS_COMMON_REMOVE))) {
+			found = 1;
+			break;
+		}
+	}
+	return found ? port : NULL;
+}
+
+/**
+ * zfcp_get_port_by_did - find port in port list of adapter by d_id
+ * @adapter: pointer to adapter to search for port
+ * @d_id: d_id to search for
+ * Traverse list of all ports of an adapter and return pointer to a port
+ * with the given d_id.
+ */
+struct zfcp_port *
+zfcp_get_port_by_did(struct zfcp_adapter *adapter, u32 d_id)
+{
+	struct zfcp_port *port;
+	int found = 0;
+
+	list_for_each_entry(port, &adapter->port_list_head, list) {
+		if ((port->d_id == d_id) &&
+		    !atomic_test_mask(ZFCP_STATUS_COMMON_REMOVE, &port->status))
+		{
+			found = 1;
+			break;
+		}
+	}
+	return found ? port : NULL;
+}
+
+/**
+ * zfcp_get_adapter_by_busid - find adpater in adapter list by bus_id
+ * @bus_id: bus_id to search for
+ * Traverse list of all adapters and return pointer to an adapter
+ * with the given bus_id.
+ */
+struct zfcp_adapter *
+zfcp_get_adapter_by_busid(char *bus_id)
+{
+	struct zfcp_adapter *adapter;
+	int found = 0;
+
+	list_for_each_entry(adapter, &zfcp_data.adapter_list_head, list) {
+		if ((strncmp(bus_id, zfcp_get_busid_by_adapter(adapter),
+			     BUS_ID_SIZE) == 0) &&
+		    !atomic_test_mask(ZFCP_STATUS_COMMON_REMOVE,
+				      &adapter->status)){
+			found = 1;
+			break;
+		}
+	}
+	return found ? adapter : NULL;
+}
+
+/**
+ * zfcp_unit_enqueue - enqueue unit to unit list of a port.
+ * @port: pointer to port where unit is added
+ * @fcp_lun: FCP LUN of unit to be enqueued
+ * Return: pointer to enqueued unit on success, NULL on error
+ * Locks: config_sema must be held to serialize changes to the unit list
+ *
+ * Sets up some unit internal structures and creates sysfs entry.
+ */
+struct zfcp_unit *
+zfcp_unit_enqueue(struct zfcp_port *port, fcp_lun_t fcp_lun)
+{
+	struct zfcp_unit *unit, *tmp_unit;
+	scsi_lun_t scsi_lun;
+	int found;
+
+	/*
+	 * check that there is no unit with this FCP_LUN already in list
+	 * and enqueue it.
+	 * Note: Unlike for the adapter and the port, this is an error
+	 */
+	read_lock_irq(&zfcp_data.config_lock);
+	unit = zfcp_get_unit_by_lun(port, fcp_lun);
+	read_unlock_irq(&zfcp_data.config_lock);
+	if (unit)
+		return NULL;
+
+	unit = kmalloc(sizeof (struct zfcp_unit), GFP_KERNEL);
+	if (!unit)
+		return NULL;
+	memset(unit, 0, sizeof (struct zfcp_unit));
+
+	/* initialise reference count stuff */
+	atomic_set(&unit->refcount, 0);
+	init_waitqueue_head(&unit->remove_wq);
+
+	unit->port = port;
+	unit->fcp_lun = fcp_lun;
+
+	/* setup for sysfs registration */
+	snprintf(unit->sysfs_device.bus_id, BUS_ID_SIZE, "0x%016llx", fcp_lun);
+	unit->sysfs_device.parent = &port->sysfs_device;
+	unit->sysfs_device.release = zfcp_sysfs_unit_release;
+	dev_set_drvdata(&unit->sysfs_device, unit);
+
+	/* mark unit unusable as long as sysfs registration is not complete */
+	atomic_set_mask(ZFCP_STATUS_COMMON_REMOVE, &unit->status);
+
+	if (device_register(&unit->sysfs_device)) {
+		kfree(unit);
+		return NULL;
+	}
+
+	if (zfcp_sysfs_unit_create_files(&unit->sysfs_device)) {
+		device_unregister(&unit->sysfs_device);
+		return NULL;
+	}
+
+	zfcp_unit_get(unit);
+
+	scsi_lun = 0;
+	found = 0;
+	write_lock_irq(&zfcp_data.config_lock);
+	list_for_each_entry(tmp_unit, &port->unit_list_head, list) {
+		if (tmp_unit->scsi_lun != scsi_lun) {
+			found = 1;
+			break;
+		}
+		scsi_lun++;
+	}
+	unit->scsi_lun = scsi_lun;
+	if (found)
+		list_add_tail(&unit->list, &tmp_unit->list);
+	else
+		list_add_tail(&unit->list, &port->unit_list_head);
+	atomic_clear_mask(ZFCP_STATUS_COMMON_REMOVE, &unit->status);
+	atomic_set_mask(ZFCP_STATUS_COMMON_RUNNING, &unit->status);
+	write_unlock_irq(&zfcp_data.config_lock);
+
+	port->units++;
+	zfcp_port_get(port);
+
+	return unit;
+}
+
+void
+zfcp_unit_dequeue(struct zfcp_unit *unit)
+{
+	zfcp_unit_wait(unit);
+	write_lock_irq(&zfcp_data.config_lock);
+	list_del(&unit->list);
+	write_unlock_irq(&zfcp_data.config_lock);
+	unit->port->units--;
+	zfcp_port_put(unit->port);
+	zfcp_sysfs_unit_remove_files(&unit->sysfs_device);
+	device_unregister(&unit->sysfs_device);
+}
+
+static void *
+zfcp_mempool_alloc(unsigned int __nocast gfp_mask, void *size)
+{
+	return kmalloc((size_t) size, gfp_mask);
+}
+
+static void
+zfcp_mempool_free(void *element, void *size)
+{
+	kfree(element);
+}
+
+/*
+ * Allocates a combined QTCB/fsf_req buffer for erp actions and fcp/SCSI
+ * commands.
+ * It also genrates fcp-nameserver request/response buffer and unsolicited 
+ * status read fsf_req buffers.
+ *
+ * locks:       must only be called with zfcp_data.config_sema taken
+ */
+static int
+zfcp_allocate_low_mem_buffers(struct zfcp_adapter *adapter)
+{
+	adapter->pool.fsf_req_erp =
+		mempool_create(ZFCP_POOL_FSF_REQ_ERP_NR,
+			       zfcp_mempool_alloc, zfcp_mempool_free, (void *)
+			       sizeof(struct zfcp_fsf_req_pool_element));
+
+	if (NULL == adapter->pool.fsf_req_erp)
+		return -ENOMEM;
+
+	adapter->pool.fsf_req_scsi =
+		mempool_create(ZFCP_POOL_FSF_REQ_SCSI_NR,
+			       zfcp_mempool_alloc, zfcp_mempool_free, (void *)
+			       sizeof(struct zfcp_fsf_req_pool_element));
+
+	if (NULL == adapter->pool.fsf_req_scsi)
+		return -ENOMEM;
+
+	adapter->pool.fsf_req_abort =
+		mempool_create(ZFCP_POOL_FSF_REQ_ABORT_NR,
+			       zfcp_mempool_alloc, zfcp_mempool_free, (void *)
+			       sizeof(struct zfcp_fsf_req_pool_element));
+
+	if (NULL == adapter->pool.fsf_req_abort)
+		return -ENOMEM;
+
+	adapter->pool.fsf_req_status_read =
+		mempool_create(ZFCP_POOL_STATUS_READ_NR,
+			       zfcp_mempool_alloc, zfcp_mempool_free,
+			       (void *) sizeof(struct zfcp_fsf_req));
+
+	if (NULL == adapter->pool.fsf_req_status_read)
+		return -ENOMEM;
+
+	adapter->pool.data_status_read =
+		mempool_create(ZFCP_POOL_STATUS_READ_NR,
+			       zfcp_mempool_alloc, zfcp_mempool_free,
+			       (void *) sizeof(struct fsf_status_read_buffer));
+
+	if (NULL == adapter->pool.data_status_read)
+		return -ENOMEM;
+
+	adapter->pool.data_gid_pn =
+		mempool_create(ZFCP_POOL_DATA_GID_PN_NR,
+			       zfcp_mempool_alloc, zfcp_mempool_free, (void *)
+			       sizeof(struct zfcp_gid_pn_data));
+
+	if (NULL == adapter->pool.data_gid_pn)
+		return -ENOMEM;
+
+	return 0;
+}
+
+/**
+ * zfcp_free_low_mem_buffers - free memory pools of an adapter
+ * @adapter: pointer to zfcp_adapter for which memory pools should be freed
+ * locking:  zfcp_data.config_sema must be held
+ */
+static void
+zfcp_free_low_mem_buffers(struct zfcp_adapter *adapter)
+{
+	if (adapter->pool.fsf_req_erp)
+		mempool_destroy(adapter->pool.fsf_req_erp);
+	if (adapter->pool.fsf_req_scsi)
+		mempool_destroy(adapter->pool.fsf_req_scsi);
+	if (adapter->pool.fsf_req_abort)
+		mempool_destroy(adapter->pool.fsf_req_abort);
+	if (adapter->pool.fsf_req_status_read)
+		mempool_destroy(adapter->pool.fsf_req_status_read);
+	if (adapter->pool.data_status_read)
+		mempool_destroy(adapter->pool.data_status_read);
+	if (adapter->pool.data_gid_pn)
+		mempool_destroy(adapter->pool.data_gid_pn);
+}
+
+/**
+ * zfcp_adapter_debug_register - registers debug feature for an adapter
+ * @adapter: pointer to adapter for which debug features should be registered
+ * return: -ENOMEM on error, 0 otherwise
+ */
+int
+zfcp_adapter_debug_register(struct zfcp_adapter *adapter)
+{
+	char dbf_name[20];
+
+	/* debug feature area which records SCSI command failures (hostbyte) */
+	spin_lock_init(&adapter->dbf_lock);
+
+	sprintf(dbf_name, ZFCP_CMD_DBF_NAME "%s",
+		zfcp_get_busid_by_adapter(adapter));
+	adapter->cmd_dbf = debug_register(dbf_name, ZFCP_CMD_DBF_INDEX,
+					  ZFCP_CMD_DBF_AREAS,
+					  ZFCP_CMD_DBF_LENGTH);
+	debug_register_view(adapter->cmd_dbf, &debug_hex_ascii_view);
+	debug_set_level(adapter->cmd_dbf, ZFCP_CMD_DBF_LEVEL);
+
+	/* debug feature area which records SCSI command aborts */
+	sprintf(dbf_name, ZFCP_ABORT_DBF_NAME "%s",
+		zfcp_get_busid_by_adapter(adapter));
+	adapter->abort_dbf = debug_register(dbf_name, ZFCP_ABORT_DBF_INDEX,
+					    ZFCP_ABORT_DBF_AREAS,
+					    ZFCP_ABORT_DBF_LENGTH);
+	debug_register_view(adapter->abort_dbf, &debug_hex_ascii_view);
+	debug_set_level(adapter->abort_dbf, ZFCP_ABORT_DBF_LEVEL);
+
+	/* debug feature area which records incoming ELS commands */
+	sprintf(dbf_name, ZFCP_IN_ELS_DBF_NAME "%s",
+		zfcp_get_busid_by_adapter(adapter));
+	adapter->in_els_dbf = debug_register(dbf_name, ZFCP_IN_ELS_DBF_INDEX,
+					     ZFCP_IN_ELS_DBF_AREAS,
+					     ZFCP_IN_ELS_DBF_LENGTH);
+	debug_register_view(adapter->in_els_dbf, &debug_hex_ascii_view);
+	debug_set_level(adapter->in_els_dbf, ZFCP_IN_ELS_DBF_LEVEL);
+
+	/* debug feature area which records erp events */
+	sprintf(dbf_name, ZFCP_ERP_DBF_NAME "%s",
+		zfcp_get_busid_by_adapter(adapter));
+	adapter->erp_dbf = debug_register(dbf_name, ZFCP_ERP_DBF_INDEX,
+					  ZFCP_ERP_DBF_AREAS,
+					  ZFCP_ERP_DBF_LENGTH);
+	debug_register_view(adapter->erp_dbf, &debug_hex_ascii_view);
+	debug_set_level(adapter->erp_dbf, ZFCP_ERP_DBF_LEVEL);
+
+	if (!(adapter->cmd_dbf && adapter->abort_dbf &&
+	      adapter->in_els_dbf && adapter->erp_dbf)) {
+		zfcp_adapter_debug_unregister(adapter);
+		return -ENOMEM;
+	}
+
+	return 0;
+
+}
+
+/**
+ * zfcp_adapter_debug_unregister - unregisters debug feature for an adapter
+ * @adapter: pointer to adapter for which debug features should be unregistered
+ */
+void
+zfcp_adapter_debug_unregister(struct zfcp_adapter *adapter)
+{
+ 	debug_unregister(adapter->abort_dbf);
+ 	debug_unregister(adapter->cmd_dbf);
+ 	debug_unregister(adapter->erp_dbf);
+ 	debug_unregister(adapter->in_els_dbf);
+	adapter->abort_dbf = NULL;
+	adapter->cmd_dbf = NULL;
+	adapter->erp_dbf = NULL;
+	adapter->in_els_dbf = NULL;
+}
+
+void
+zfcp_dummy_release(struct device *dev)
+{
+	return;
+}
+
+/*
+ * Enqueues an adapter at the end of the adapter list in the driver data.
+ * All adapter internal structures are set up.
+ * Proc-fs entries are also created.
+ *
+ * returns:	0             if a new adapter was successfully enqueued
+ *              ZFCP_KNOWN    if an adapter with this devno was already present
+ *		-ENOMEM       if alloc failed
+ * locks:	config_sema must be held to serialise changes to the adapter list
+ */
+struct zfcp_adapter *
+zfcp_adapter_enqueue(struct ccw_device *ccw_device)
+{
+	int retval = 0;
+	struct zfcp_adapter *adapter;
+
+	/*
+	 * Note: It is safe to release the list_lock, as any list changes 
+	 * are protected by the config_sema, which must be held to get here
+	 */
+
+	/* try to allocate new adapter data structure (zeroed) */
+	adapter = kmalloc(sizeof (struct zfcp_adapter), GFP_KERNEL);
+	if (!adapter) {
+		ZFCP_LOG_INFO("error: allocation of base adapter "
+			      "structure failed\n");
+		goto out;
+	}
+	memset(adapter, 0, sizeof (struct zfcp_adapter));
+
+	ccw_device->handler = NULL;
+
+	/* save ccw_device pointer */
+	adapter->ccw_device = ccw_device;
+
+	retval = zfcp_qdio_allocate_queues(adapter);
+	if (retval)
+		goto queues_alloc_failed;
+
+	retval = zfcp_qdio_allocate(adapter);
+	if (retval)
+		goto qdio_allocate_failed;
+
+	retval = zfcp_allocate_low_mem_buffers(adapter);
+	if (retval) {
+		ZFCP_LOG_INFO("error: pool allocation failed\n");
+		goto failed_low_mem_buffers;
+	}
+
+	/* initialise reference count stuff */
+	atomic_set(&adapter->refcount, 0);
+	init_waitqueue_head(&adapter->remove_wq);
+
+	/* initialise list of ports */
+	INIT_LIST_HEAD(&adapter->port_list_head);
+
+	/* initialise list of ports to be removed */
+	INIT_LIST_HEAD(&adapter->port_remove_lh);
+
+	/* initialize list of fsf requests */
+	rwlock_init(&adapter->fsf_req_list_lock);
+	INIT_LIST_HEAD(&adapter->fsf_req_list_head);
+
+	/* initialize abort lock */
+	rwlock_init(&adapter->abort_lock);
+
+	/* initialise some erp stuff */
+	init_waitqueue_head(&adapter->erp_thread_wqh);
+	init_waitqueue_head(&adapter->erp_done_wqh);
+
+	/* initialize lock of associated request queue */
+	rwlock_init(&adapter->request_queue.queue_lock);
+
+	/* intitialise SCSI ER timer */
+	init_timer(&adapter->scsi_er_timer);
+
+	/* set FC service class used per default */
+	adapter->fc_service_class = ZFCP_FC_SERVICE_CLASS_DEFAULT;
+
+	sprintf(adapter->name, "%s", zfcp_get_busid_by_adapter(adapter));
+	ASCEBC(adapter->name, strlen(adapter->name));
+
+	/* mark adapter unusable as long as sysfs registration is not complete */
+	atomic_set_mask(ZFCP_STATUS_COMMON_REMOVE, &adapter->status);
+
+	adapter->ccw_device = ccw_device;
+	dev_set_drvdata(&ccw_device->dev, adapter);
+
+	if (zfcp_sysfs_adapter_create_files(&ccw_device->dev))
+		goto sysfs_failed;
+
+	adapter->generic_services.parent = &adapter->ccw_device->dev;
+	adapter->generic_services.release = zfcp_dummy_release;
+	snprintf(adapter->generic_services.bus_id, BUS_ID_SIZE,
+		 "generic_services");
+
+	if (device_register(&adapter->generic_services))
+		goto generic_services_failed;
+
+	/* put allocated adapter at list tail */
+	write_lock_irq(&zfcp_data.config_lock);
+	atomic_clear_mask(ZFCP_STATUS_COMMON_REMOVE, &adapter->status);
+	list_add_tail(&adapter->list, &zfcp_data.adapter_list_head);
+	write_unlock_irq(&zfcp_data.config_lock);
+
+	zfcp_data.adapters++;
+
+	goto out;
+
+ generic_services_failed:
+	zfcp_sysfs_adapter_remove_files(&adapter->ccw_device->dev);
+ sysfs_failed:
+	dev_set_drvdata(&ccw_device->dev, NULL);
+ failed_low_mem_buffers:
+	zfcp_free_low_mem_buffers(adapter);
+	if (qdio_free(ccw_device) != 0)
+		ZFCP_LOG_NORMAL("bug: qdio_free for adapter %s failed\n",
+				zfcp_get_busid_by_adapter(adapter));
+ qdio_allocate_failed:
+	zfcp_qdio_free_queues(adapter);
+ queues_alloc_failed:
+	kfree(adapter);
+	adapter = NULL;
+ out:
+	return adapter;
+}
+
+/*
+ * returns:	0 - struct zfcp_adapter  data structure successfully removed
+ *		!0 - struct zfcp_adapter  data structure could not be removed
+ *			(e.g. still used)
+ * locks:	adapter list write lock is assumed to be held by caller
+ *              adapter->fsf_req_list_lock is taken and released within this 
+ *              function and must not be held on entry
+ */
+void
+zfcp_adapter_dequeue(struct zfcp_adapter *adapter)
+{
+	int retval = 0;
+	unsigned long flags;
+
+	device_unregister(&adapter->generic_services);
+	zfcp_sysfs_adapter_remove_files(&adapter->ccw_device->dev);
+	dev_set_drvdata(&adapter->ccw_device->dev, NULL);
+	/* sanity check: no pending FSF requests */
+	read_lock_irqsave(&adapter->fsf_req_list_lock, flags);
+	retval = !list_empty(&adapter->fsf_req_list_head);
+	read_unlock_irqrestore(&adapter->fsf_req_list_lock, flags);
+	if (retval) {
+		ZFCP_LOG_NORMAL("bug: adapter %s (%p) still in use, "
+				"%i requests outstanding\n",
+				zfcp_get_busid_by_adapter(adapter), adapter,
+				atomic_read(&adapter->fsf_reqs_active));
+		retval = -EBUSY;
+		goto out;
+	}
+
+	/* remove specified adapter data structure from list */
+	write_lock_irq(&zfcp_data.config_lock);
+	list_del(&adapter->list);
+	write_unlock_irq(&zfcp_data.config_lock);
+
+	/* decrease number of adapters in list */
+	zfcp_data.adapters--;
+
+	ZFCP_LOG_TRACE("adapter %s (%p) removed from list, "
+		       "%i adapters still in list\n",
+		       zfcp_get_busid_by_adapter(adapter),
+		       adapter, zfcp_data.adapters);
+
+	retval = qdio_free(adapter->ccw_device);
+	if (retval)
+		ZFCP_LOG_NORMAL("bug: qdio_free for adapter %s failed\n",
+				zfcp_get_busid_by_adapter(adapter));
+
+	zfcp_free_low_mem_buffers(adapter);
+	/* free memory of adapter data structure and queues */
+	zfcp_qdio_free_queues(adapter);
+	ZFCP_LOG_TRACE("freeing adapter structure\n");
+	kfree(adapter);
+ out:
+	return;
+}
+
+/**
+ * zfcp_port_enqueue - enqueue port to port list of adapter
+ * @adapter: adapter where remote port is added
+ * @wwpn: WWPN of the remote port to be enqueued
+ * @status: initial status for the port
+ * @d_id: destination id of the remote port to be enqueued
+ * Return: pointer to enqueued port on success, NULL on error
+ * Locks: config_sema must be held to serialize changes to the port list
+ *
+ * All port internal structures are set up and the sysfs entry is generated.
+ * d_id is used to enqueue ports with a well known address like the Directory
+ * Service for nameserver lookup.
+ */
+struct zfcp_port *
+zfcp_port_enqueue(struct zfcp_adapter *adapter, wwn_t wwpn, u32 status,
+		  u32 d_id)
+{
+	struct zfcp_port *port, *tmp_port;
+	int check_wwpn;
+	scsi_id_t scsi_id;
+	int found;
+
+	check_wwpn = !(status & ZFCP_STATUS_PORT_NO_WWPN);
+
+	/*
+	 * check that there is no port with this WWPN already in list
+	 */
+	if (check_wwpn) {
+		read_lock_irq(&zfcp_data.config_lock);
+		port = zfcp_get_port_by_wwpn(adapter, wwpn);
+		read_unlock_irq(&zfcp_data.config_lock);
+		if (port)
+			return NULL;
+	}
+
+	port = kmalloc(sizeof (struct zfcp_port), GFP_KERNEL);
+	if (!port)
+		return NULL;
+	memset(port, 0, sizeof (struct zfcp_port));
+
+	/* initialise reference count stuff */
+	atomic_set(&port->refcount, 0);
+	init_waitqueue_head(&port->remove_wq);
+
+	INIT_LIST_HEAD(&port->unit_list_head);
+	INIT_LIST_HEAD(&port->unit_remove_lh);
+
+	port->adapter = adapter;
+
+	if (check_wwpn)
+		port->wwpn = wwpn;
+
+	atomic_set_mask(status, &port->status);
+
+	/* setup for sysfs registration */
+	if (status & ZFCP_STATUS_PORT_WKA) {
+		switch (d_id) {
+		case ZFCP_DID_DIRECTORY_SERVICE:
+			snprintf(port->sysfs_device.bus_id, BUS_ID_SIZE,
+				 "directory");
+			break;
+		case ZFCP_DID_MANAGEMENT_SERVICE:
+			snprintf(port->sysfs_device.bus_id, BUS_ID_SIZE,
+				 "management");
+			break;
+		case ZFCP_DID_KEY_DISTRIBUTION_SERVICE:
+			snprintf(port->sysfs_device.bus_id, BUS_ID_SIZE,
+				 "key_distribution");
+			break;
+		case ZFCP_DID_ALIAS_SERVICE:
+			snprintf(port->sysfs_device.bus_id, BUS_ID_SIZE,
+				 "alias");
+			break;
+		case ZFCP_DID_TIME_SERVICE:
+			snprintf(port->sysfs_device.bus_id, BUS_ID_SIZE,
+				 "time");
+			break;
+		default:
+			kfree(port);
+			return NULL;
+		}
+		port->d_id = d_id;
+		port->sysfs_device.parent = &adapter->generic_services;
+	} else {
+		snprintf(port->sysfs_device.bus_id,
+			 BUS_ID_SIZE, "0x%016llx", wwpn);
+	port->sysfs_device.parent = &adapter->ccw_device->dev;
+	}
+	port->sysfs_device.release = zfcp_sysfs_port_release;
+	dev_set_drvdata(&port->sysfs_device, port);
+
+	/* mark port unusable as long as sysfs registration is not complete */
+	atomic_set_mask(ZFCP_STATUS_COMMON_REMOVE, &port->status);
+
+	if (device_register(&port->sysfs_device)) {
+		kfree(port);
+		return NULL;
+	}
+
+	if (zfcp_sysfs_port_create_files(&port->sysfs_device, status)) {
+		device_unregister(&port->sysfs_device);
+		return NULL;
+	}
+
+	zfcp_port_get(port);
+
+	scsi_id = 1;
+	found = 0;
+	write_lock_irq(&zfcp_data.config_lock);
+	list_for_each_entry(tmp_port, &adapter->port_list_head, list) {
+		if (atomic_test_mask(ZFCP_STATUS_PORT_NO_SCSI_ID,
+				     &tmp_port->status))
+			continue;
+		if (tmp_port->scsi_id != scsi_id) {
+			found = 1;
+			break;
+		}
+		scsi_id++;
+	}
+	port->scsi_id = scsi_id;
+	if (found)
+		list_add_tail(&port->list, &tmp_port->list);
+	else
+		list_add_tail(&port->list, &adapter->port_list_head);
+	atomic_clear_mask(ZFCP_STATUS_COMMON_REMOVE, &port->status);
+	atomic_set_mask(ZFCP_STATUS_COMMON_RUNNING, &port->status);
+	if (d_id == ZFCP_DID_DIRECTORY_SERVICE)
+		if (!adapter->nameserver_port)
+			adapter->nameserver_port = port;
+	adapter->ports++;
+	write_unlock_irq(&zfcp_data.config_lock);
+
+	zfcp_adapter_get(adapter);
+
+	return port;
+}
+
+void
+zfcp_port_dequeue(struct zfcp_port *port)
+{
+	zfcp_port_wait(port);
+	write_lock_irq(&zfcp_data.config_lock);
+	list_del(&port->list);
+	port->adapter->ports--;
+	write_unlock_irq(&zfcp_data.config_lock);
+	zfcp_adapter_put(port->adapter);
+	zfcp_sysfs_port_remove_files(&port->sysfs_device,
+				     atomic_read(&port->status));
+	device_unregister(&port->sysfs_device);
+}
+
+/* Enqueues a nameserver port */
+int
+zfcp_nameserver_enqueue(struct zfcp_adapter *adapter)
+{
+	struct zfcp_port *port;
+
+	port = zfcp_port_enqueue(adapter, 0, ZFCP_STATUS_PORT_WKA,
+				 ZFCP_DID_DIRECTORY_SERVICE);
+	if (!port) {
+		ZFCP_LOG_INFO("error: enqueue of nameserver port for "
+			      "adapter %s failed\n",
+			      zfcp_get_busid_by_adapter(adapter));
+		return -ENXIO;
+	}
+	zfcp_port_put(port);
+
+	return 0;
+}
+
+#undef ZFCP_LOG_AREA
+
+/****************************************************************/
+/******* Fibre Channel Standard related Functions  **************/
+/****************************************************************/
+
+#define ZFCP_LOG_AREA                   ZFCP_LOG_AREA_FC
+
+void
+zfcp_fsf_incoming_els_rscn(struct zfcp_adapter *adapter,
+			   struct fsf_status_read_buffer *status_buffer)
+{
+	struct fcp_rscn_head *fcp_rscn_head;
+	struct fcp_rscn_element *fcp_rscn_element;
+	struct zfcp_port *port;
+	u16 i;
+	u16 no_entries;
+	u32 range_mask;
+	unsigned long flags;
+
+	fcp_rscn_head = (struct fcp_rscn_head *) status_buffer->payload;
+	fcp_rscn_element = (struct fcp_rscn_element *) status_buffer->payload;
+
+	/* see FC-FS */
+	no_entries = (fcp_rscn_head->payload_len / 4);
+
+	zfcp_in_els_dbf_event(adapter, "##rscn", status_buffer,
+			      fcp_rscn_head->payload_len);
+
+	debug_text_event(adapter->erp_dbf, 1, "unsol_els_rscn:");
+	for (i = 1; i < no_entries; i++) {
+		/* skip head and start with 1st element */
+		fcp_rscn_element++;
+		switch (fcp_rscn_element->addr_format) {
+		case ZFCP_PORT_ADDRESS:
+			ZFCP_LOG_FLAGS(1, "ZFCP_PORT_ADDRESS\n");
+			range_mask = ZFCP_PORTS_RANGE_PORT;
+			break;
+		case ZFCP_AREA_ADDRESS:
+			ZFCP_LOG_FLAGS(1, "ZFCP_AREA_ADDRESS\n");
+			range_mask = ZFCP_PORTS_RANGE_AREA;
+			break;
+		case ZFCP_DOMAIN_ADDRESS:
+			ZFCP_LOG_FLAGS(1, "ZFCP_DOMAIN_ADDRESS\n");
+			range_mask = ZFCP_PORTS_RANGE_DOMAIN;
+			break;
+		case ZFCP_FABRIC_ADDRESS:
+			ZFCP_LOG_FLAGS(1, "ZFCP_FABRIC_ADDRESS\n");
+			range_mask = ZFCP_PORTS_RANGE_FABRIC;
+			break;
+		default:
+			ZFCP_LOG_INFO("incoming RSCN with unknown "
+				      "address format\n");
+			continue;
+		}
+		read_lock_irqsave(&zfcp_data.config_lock, flags);
+		list_for_each_entry(port, &adapter->port_list_head, list) {
+			if (atomic_test_mask
+			    (ZFCP_STATUS_PORT_WKA, &port->status))
+				continue;
+			/* Do we know this port? If not skip it. */
+			if (!atomic_test_mask
+			    (ZFCP_STATUS_PORT_DID_DID, &port->status)) {
+				ZFCP_LOG_INFO("incoming RSCN, trying to open "
+					      "port 0x%016Lx\n", port->wwpn);
+				debug_text_event(adapter->erp_dbf, 1,
+						 "unsol_els_rscnu:");
+				zfcp_erp_port_reopen(port,
+						     ZFCP_STATUS_COMMON_ERP_FAILED);
+				continue;
+			}
+
+			/*
+			 * FIXME: race: d_id might being invalidated
+			 * (...DID_DID reset)
+			 */
+			if ((port->d_id & range_mask)
+			    == (fcp_rscn_element->nport_did & range_mask)) {
+				ZFCP_LOG_TRACE("reopen did 0x%08x\n",
+					       fcp_rscn_element->nport_did);
+				/*
+				 * Unfortunately, an RSCN does not specify the
+				 * type of change a target underwent. We assume
+				 * that it makes sense to reopen the link.
+				 * FIXME: Shall we try to find out more about
+				 * the target and link state before closing it?
+				 * How to accomplish this? (nameserver?)
+				 * Where would such code be put in?
+				 * (inside or outside erp)
+				 */
+				ZFCP_LOG_INFO("incoming RSCN, trying to open "
+					      "port 0x%016Lx\n", port->wwpn);
+				debug_text_event(adapter->erp_dbf, 1,
+						 "unsol_els_rscnk:");
+				zfcp_test_link(port);
+			}
+		}
+		read_unlock_irqrestore(&zfcp_data.config_lock, flags);
+	}
+}
+
+static void
+zfcp_fsf_incoming_els_plogi(struct zfcp_adapter *adapter,
+			    struct fsf_status_read_buffer *status_buffer)
+{
+	logi *els_logi = (logi *) status_buffer->payload;
+	struct zfcp_port *port;
+	unsigned long flags;
+
+	zfcp_in_els_dbf_event(adapter, "##plogi", status_buffer, 28);
+
+	read_lock_irqsave(&zfcp_data.config_lock, flags);
+	list_for_each_entry(port, &adapter->port_list_head, list) {
+		if (port->wwpn == (*(wwn_t *) & els_logi->nport_wwn))
+			break;
+	}
+	read_unlock_irqrestore(&zfcp_data.config_lock, flags);
+
+	if (!port || (port->wwpn != (*(wwn_t *) & els_logi->nport_wwn))) {
+		ZFCP_LOG_DEBUG("ignored incoming PLOGI for nonexisting port "
+			       "with d_id 0x%08x on adapter %s\n",
+			       status_buffer->d_id,
+			       zfcp_get_busid_by_adapter(adapter));
+	} else {
+		debug_text_event(adapter->erp_dbf, 1, "unsol_els_plogi:");
+		debug_event(adapter->erp_dbf, 1, &els_logi->nport_wwn, 8);
+		zfcp_erp_port_forced_reopen(port, 0);
+	}
+}
+
+static void
+zfcp_fsf_incoming_els_logo(struct zfcp_adapter *adapter,
+			   struct fsf_status_read_buffer *status_buffer)
+{
+	struct fcp_logo *els_logo = (struct fcp_logo *) status_buffer->payload;
+	struct zfcp_port *port;
+	unsigned long flags;
+
+	zfcp_in_els_dbf_event(adapter, "##logo", status_buffer, 16);
+
+	read_lock_irqsave(&zfcp_data.config_lock, flags);
+	list_for_each_entry(port, &adapter->port_list_head, list) {
+		if (port->wwpn == els_logo->nport_wwpn)
+			break;
+	}
+	read_unlock_irqrestore(&zfcp_data.config_lock, flags);
+
+	if (!port || (port->wwpn != els_logo->nport_wwpn)) {
+		ZFCP_LOG_DEBUG("ignored incoming LOGO for nonexisting port "
+			       "with d_id 0x%08x on adapter %s\n",
+			       status_buffer->d_id,
+			       zfcp_get_busid_by_adapter(adapter));
+	} else {
+		debug_text_event(adapter->erp_dbf, 1, "unsol_els_logo:");
+		debug_event(adapter->erp_dbf, 1, &els_logo->nport_wwpn, 8);
+		zfcp_erp_port_forced_reopen(port, 0);
+	}
+}
+
+static void
+zfcp_fsf_incoming_els_unknown(struct zfcp_adapter *adapter,
+			      struct fsf_status_read_buffer *status_buffer)
+{
+	zfcp_in_els_dbf_event(adapter, "##undef", status_buffer, 24);
+	ZFCP_LOG_NORMAL("warning: unknown incoming ELS 0x%08x "
+			"for adapter %s\n", *(u32 *) (status_buffer->payload),
+			zfcp_get_busid_by_adapter(adapter));
+
+}
+
+void
+zfcp_fsf_incoming_els(struct zfcp_fsf_req *fsf_req)
+{
+	struct fsf_status_read_buffer *status_buffer;
+	u32 els_type;
+	struct zfcp_adapter *adapter;
+
+	status_buffer = fsf_req->data.status_read.buffer;
+	els_type = *(u32 *) (status_buffer->payload);
+	adapter = fsf_req->adapter;
+
+	if (els_type == LS_PLOGI)
+		zfcp_fsf_incoming_els_plogi(adapter, status_buffer);
+	else if (els_type == LS_LOGO)
+		zfcp_fsf_incoming_els_logo(adapter, status_buffer);
+	else if ((els_type & 0xffff0000) == LS_RSCN)
+		/* we are only concerned with the command, not the length */
+		zfcp_fsf_incoming_els_rscn(adapter, status_buffer);
+	else
+		zfcp_fsf_incoming_els_unknown(adapter, status_buffer);
+}
+
+
+/**
+ * zfcp_gid_pn_buffers_alloc - allocate buffers for GID_PN nameserver request
+ * @gid_pn: pointer to return pointer to struct zfcp_gid_pn_data
+ * @pool: pointer to mempool_t if non-null memory pool is used for allocation
+ */
+static int
+zfcp_gid_pn_buffers_alloc(struct zfcp_gid_pn_data **gid_pn, mempool_t *pool)
+{
+	struct zfcp_gid_pn_data *data;
+
+	if (pool != NULL) {
+		data = mempool_alloc(pool, GFP_ATOMIC);
+		if (likely(data != NULL)) {
+			data->ct.pool = pool;
+		}
+	} else {
+		data = kmalloc(sizeof(struct zfcp_gid_pn_data), GFP_ATOMIC);
+	}
+
+        if (NULL == data)
+                return -ENOMEM;
+
+	memset(data, 0, sizeof(*data));
+        data->ct.req = &data->req;
+        data->ct.resp = &data->resp;
+	data->ct.req_count = data->ct.resp_count = 1;
+	zfcp_address_to_sg(&data->ct_iu_req, &data->req);
+        zfcp_address_to_sg(&data->ct_iu_resp, &data->resp);
+        data->req.length = sizeof(struct ct_iu_gid_pn_req);
+        data->resp.length = sizeof(struct ct_iu_gid_pn_resp);
+
+	*gid_pn = data;
+	return 0;
+}
+
+/**
+ * zfcp_gid_pn_buffers_free - free buffers for GID_PN nameserver request
+ * @gid_pn: pointer to struct zfcp_gid_pn_data which has to be freed
+ */
+static void
+zfcp_gid_pn_buffers_free(struct zfcp_gid_pn_data *gid_pn)
+{
+        if ((gid_pn->ct.pool != 0))
+		mempool_free(gid_pn, gid_pn->ct.pool);
+	else
+                kfree(gid_pn);
+
+	return;
+}
+
+/**
+ * zfcp_ns_gid_pn_request - initiate GID_PN nameserver request
+ * @erp_action: pointer to zfcp_erp_action where GID_PN request is needed
+ */
+int
+zfcp_ns_gid_pn_request(struct zfcp_erp_action *erp_action)
+{
+	int ret;
+        struct ct_iu_gid_pn_req *ct_iu_req;
+        struct zfcp_gid_pn_data *gid_pn;
+        struct zfcp_adapter *adapter = erp_action->adapter;
+
+	ret = zfcp_gid_pn_buffers_alloc(&gid_pn, adapter->pool.data_gid_pn);
+	if (ret < 0) {
+		ZFCP_LOG_INFO("error: buffer allocation for gid_pn nameserver "
+			      "request failed for adapter %s\n",
+			      zfcp_get_busid_by_adapter(adapter));
+		goto out;
+	}
+
+	/* setup nameserver request */
+        ct_iu_req = zfcp_sg_to_address(gid_pn->ct.req);
+        ct_iu_req->header.revision = ZFCP_CT_REVISION;
+        ct_iu_req->header.gs_type = ZFCP_CT_DIRECTORY_SERVICE;
+        ct_iu_req->header.gs_subtype = ZFCP_CT_NAME_SERVER;
+        ct_iu_req->header.options = ZFCP_CT_SYNCHRONOUS;
+        ct_iu_req->header.cmd_rsp_code = ZFCP_CT_GID_PN;
+        ct_iu_req->header.max_res_size = ZFCP_CT_MAX_SIZE;
+	ct_iu_req->wwpn = erp_action->port->wwpn;
+
+        /* setup parameters for send generic command */
+        gid_pn->ct.port = adapter->nameserver_port;
+	gid_pn->ct.handler = zfcp_ns_gid_pn_handler;
+	gid_pn->ct.handler_data = (unsigned long) gid_pn;
+        gid_pn->ct.timeout = ZFCP_NS_GID_PN_TIMEOUT;
+        gid_pn->ct.timer = &erp_action->timer;
+	gid_pn->port = erp_action->port;
+
+	ret = zfcp_fsf_send_ct(&gid_pn->ct, adapter->pool.fsf_req_erp,
+			       erp_action);
+	if (ret) {
+		ZFCP_LOG_INFO("error: initiation of gid_pn nameserver request "
+                              "failed for adapter %s\n",
+			      zfcp_get_busid_by_adapter(adapter));
+
+                zfcp_gid_pn_buffers_free(gid_pn);
+	}
+
+ out:
+	return ret;
+}
+
+/**
+ * zfcp_ns_gid_pn_handler - handler for GID_PN nameserver request
+ * @data: unsigned long, contains pointer to struct zfcp_gid_pn_data
+ */
+static void zfcp_ns_gid_pn_handler(unsigned long data)
+{
+	struct zfcp_port *port;
+        struct zfcp_send_ct *ct;
+	struct ct_iu_gid_pn_req *ct_iu_req;
+	struct ct_iu_gid_pn_resp *ct_iu_resp;
+        struct zfcp_gid_pn_data *gid_pn;
+
+
+	gid_pn = (struct zfcp_gid_pn_data *) data;
+	port = gid_pn->port;
+        ct = &gid_pn->ct;
+	ct_iu_req = zfcp_sg_to_address(ct->req);
+	ct_iu_resp = zfcp_sg_to_address(ct->resp);
+
+	if ((ct->status != 0) || zfcp_check_ct_response(&ct_iu_resp->header)) {
+		/* FIXME: do we need some specific erp entry points */
+		atomic_set_mask(ZFCP_STATUS_PORT_INVALID_WWPN, &port->status);
+		goto failed;
+	}
+	/* paranoia */
+	if (ct_iu_req->wwpn != port->wwpn) {
+		ZFCP_LOG_NORMAL("bug: wwpn 0x%016Lx returned by nameserver "
+				"lookup does not match expected wwpn 0x%016Lx "
+				"for adapter %s\n", ct_iu_req->wwpn, port->wwpn,
+				zfcp_get_busid_by_port(port));
+		goto mismatch;
+	}
+
+	/* looks like a valid d_id */
+        port->d_id = ct_iu_resp->d_id & ZFCP_DID_MASK;
+	atomic_set_mask(ZFCP_STATUS_PORT_DID_DID, &port->status);
+	ZFCP_LOG_DEBUG("adapter %s:  wwpn=0x%016Lx ---> d_id=0x%08x\n",
+		       zfcp_get_busid_by_port(port), port->wwpn, port->d_id);
+	goto out;
+
+ mismatch:
+	ZFCP_LOG_DEBUG("CT IUs do not match:\n");
+	ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG, (char *) ct_iu_req,
+		      sizeof(struct ct_iu_gid_pn_req));
+	ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG, (char *) ct_iu_resp,
+		      sizeof(struct ct_iu_gid_pn_resp));
+
+ failed:
+	ZFCP_LOG_NORMAL("warning: failed gid_pn nameserver request for wwpn "
+			"0x%016Lx for adapter %s\n",
+			port->wwpn, zfcp_get_busid_by_port(port));
+ out:
+        zfcp_gid_pn_buffers_free(gid_pn);
+	return;
+}
+
+/* reject CT_IU reason codes acc. to FC-GS-4 */
+static const struct zfcp_rc_entry zfcp_ct_rc[] = {
+	{0x01, "invalid command code"},
+	{0x02, "invalid version level"},
+	{0x03, "logical error"},
+	{0x04, "invalid CT_IU size"},
+	{0x05, "logical busy"},
+	{0x07, "protocol error"},
+	{0x09, "unable to perform command request"},
+	{0x0b, "command not supported"},
+	{0x0d, "server not available"},
+	{0x0e, "session could not be established"},
+	{0xff, "vendor specific error"},
+	{0, NULL},
+};
+
+/* LS_RJT reason codes acc. to FC-FS */
+static const struct zfcp_rc_entry zfcp_ls_rjt_rc[] = {
+	{0x01, "invalid LS_Command code"},
+	{0x03, "logical error"},
+	{0x05, "logical busy"},
+	{0x07, "protocol error"},
+	{0x09, "unable to perform command request"},
+	{0x0b, "command not supported"},
+	{0x0e, "command already in progress"},
+	{0xff, "vendor specific error"},
+	{0, NULL},
+};
+
+/* reject reason codes according to FC-PH/FC-FS */
+static const struct zfcp_rc_entry zfcp_p_rjt_rc[] = {
+	{0x01, "invalid D_ID"},
+	{0x02, "invalid S_ID"},
+	{0x03, "Nx_Port not available, temporary"},
+	{0x04, "Nx_Port not available, permament"},
+	{0x05, "class not supported"},
+	{0x06, "delimiter usage error"},
+	{0x07, "TYPE not supported"},
+	{0x08, "invalid Link_Control"},
+	{0x09, "invalid R_CTL field"},
+	{0x0a, "invalid F_CTL field"},
+	{0x0b, "invalid OX_ID"},
+	{0x0c, "invalid RX_ID"},
+	{0x0d, "invalid SEQ_ID"},
+	{0x0e, "invalid DF_CTL"},
+	{0x0f, "invalid SEQ_CNT"},
+	{0x10, "invalid parameter field"},
+	{0x11, "exchange error"},
+	{0x12, "protocol error"},
+	{0x13, "incorrect length"},
+	{0x14, "unsupported ACK"},
+	{0x15, "class of service not supported by entity at FFFFFE"},
+	{0x16, "login required"},
+	{0x17, "excessive sequences attempted"},
+	{0x18, "unable to establish exchange"},
+	{0x1a, "fabric path not available"},
+	{0x1b, "invalid VC_ID (class 4)"},
+	{0x1c, "invalid CS_CTL field"},
+	{0x1d, "insufficient resources for VC (class 4)"},
+	{0x1f, "invalid class of service"},
+	{0x20, "preemption request rejected"},
+	{0x21, "preemption not enabled"},
+	{0x22, "multicast error"},
+	{0x23, "multicast error terminate"},
+	{0x24, "process login required"},
+	{0xff, "vendor specific reject"},
+	{0, NULL},
+};
+
+/**
+ * zfcp_rc_description - return description for given reaon code
+ * @code: reason code
+ * @rc_table: table of reason codes and descriptions
+ */
+static inline const char *
+zfcp_rc_description(u8 code, const struct zfcp_rc_entry *rc_table)
+{
+	const char *descr = "unknown reason code";
+
+	do {
+		if (code == rc_table->code) {
+			descr = rc_table->description;
+			break;
+		}
+		rc_table++;
+	} while (rc_table->code && rc_table->description);
+
+	return descr;
+}
+
+/**
+ * zfcp_check_ct_response - evaluate reason code for CT_IU
+ * @rjt: response payload to an CT_IU request
+ * Return: 0 for accept CT_IU, 1 for reject CT_IU or invlid response code
+ */
+int
+zfcp_check_ct_response(struct ct_hdr *rjt)
+{
+	if (rjt->cmd_rsp_code == ZFCP_CT_ACCEPT)
+		return 0;
+
+	if (rjt->cmd_rsp_code != ZFCP_CT_REJECT) {
+		ZFCP_LOG_NORMAL("error: invalid Generic Service command/"
+				"response code (0x%04hx)\n",
+				rjt->cmd_rsp_code);
+		return 1;
+	}
+
+	ZFCP_LOG_INFO("Generic Service command rejected\n");
+	ZFCP_LOG_INFO("%s (0x%02x, 0x%02x, 0x%02x)\n",
+		      zfcp_rc_description(rjt->reason_code, zfcp_ct_rc),
+		      (u32) rjt->reason_code, (u32) rjt->reason_code_expl,
+		      (u32) rjt->vendor_unique);
+
+	return 1;
+}
+
+/**
+ * zfcp_print_els_rjt - print reject parameter and description for ELS reject
+ * @rjt_par: reject parameter acc. to FC-PH/FC-FS
+ * @rc_table: table of reason codes and descriptions
+ */
+static inline void
+zfcp_print_els_rjt(struct zfcp_ls_rjt_par *rjt_par,
+		   const struct zfcp_rc_entry *rc_table)
+{
+	ZFCP_LOG_INFO("%s (%02x %02x %02x %02x)\n",
+		      zfcp_rc_description(rjt_par->reason_code, rc_table),
+		      (u32) rjt_par->action, (u32) rjt_par->reason_code,
+		      (u32) rjt_par->reason_expl, (u32) rjt_par->vendor_unique);
+}
+
+/**
+ * zfcp_fsf_handle_els_rjt - evaluate status qualifier/reason code on ELS reject
+ * @sq: status qualifier word
+ * @rjt_par: reject parameter as described in FC-PH and FC-FS
+ * Return: -EROMTEIO for LS_RJT, -EREMCHG for invalid D_ID, -EIO else
+ */
+int
+zfcp_handle_els_rjt(u32 sq, struct zfcp_ls_rjt_par *rjt_par)
+{
+	int ret = -EIO;
+
+	if (sq == FSF_IOSTAT_NPORT_RJT) {
+		ZFCP_LOG_INFO("ELS rejected (P_RJT)\n");
+		zfcp_print_els_rjt(rjt_par, zfcp_p_rjt_rc);
+		/* invalid d_id */
+		if (rjt_par->reason_code == 0x01)
+			ret = -EREMCHG;
+	} else if (sq == FSF_IOSTAT_FABRIC_RJT) {
+		ZFCP_LOG_INFO("ELS rejected (F_RJT)\n");
+		zfcp_print_els_rjt(rjt_par, zfcp_p_rjt_rc);
+		/* invalid d_id */
+		if (rjt_par->reason_code == 0x01)
+			ret = -EREMCHG;
+	} else if (sq == FSF_IOSTAT_LS_RJT) {
+		ZFCP_LOG_INFO("ELS rejected (LS_RJT)\n");
+		zfcp_print_els_rjt(rjt_par, zfcp_ls_rjt_rc);
+		ret = -EREMOTEIO;
+	} else
+		ZFCP_LOG_INFO("unexpected SQ: 0x%02x\n", sq);
+
+	return ret;
+}
+
+#undef ZFCP_LOG_AREA
diff --git a/drivers/s390/scsi/zfcp_ccw.c b/drivers/s390/scsi/zfcp_ccw.c
new file mode 100644
index 0000000..0fc4638
--- /dev/null
+++ b/drivers/s390/scsi/zfcp_ccw.c
@@ -0,0 +1,312 @@
+/*
+ * linux/drivers/s390/scsi/zfcp_ccw.c
+ *
+ * FCP adapter driver for IBM eServer zSeries
+ *
+ * CCW driver related routines
+ *
+ * (C) Copyright IBM Corp. 2003, 2004
+ *
+ * Authors:
+ *      Martin Peschke <mpeschke@de.ibm.com>
+ *	Heiko Carstens <heiko.carstens@de.ibm.com>
+ *      Andreas Herrmann <aherrman@de.ibm.com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#define ZFCP_CCW_C_REVISION "$Revision: 1.58 $"
+
+#include "zfcp_ext.h"
+
+#define ZFCP_LOG_AREA                   ZFCP_LOG_AREA_CONFIG
+
+static int zfcp_ccw_probe(struct ccw_device *);
+static void zfcp_ccw_remove(struct ccw_device *);
+static int zfcp_ccw_set_online(struct ccw_device *);
+static int zfcp_ccw_set_offline(struct ccw_device *);
+static int zfcp_ccw_notify(struct ccw_device *, int);
+static void zfcp_ccw_shutdown(struct device *);
+
+static struct ccw_device_id zfcp_ccw_device_id[] = {
+	{CCW_DEVICE_DEVTYPE(ZFCP_CONTROL_UNIT_TYPE,
+			    ZFCP_CONTROL_UNIT_MODEL,
+			    ZFCP_DEVICE_TYPE,
+			    ZFCP_DEVICE_MODEL)},
+	{CCW_DEVICE_DEVTYPE(ZFCP_CONTROL_UNIT_TYPE,
+			    ZFCP_CONTROL_UNIT_MODEL,
+			    ZFCP_DEVICE_TYPE,
+			    ZFCP_DEVICE_MODEL_PRIV)},
+	{},
+};
+
+static struct ccw_driver zfcp_ccw_driver = {
+	.owner       = THIS_MODULE,
+	.name        = ZFCP_NAME,
+	.ids         = zfcp_ccw_device_id,
+	.probe       = zfcp_ccw_probe,
+	.remove      = zfcp_ccw_remove,
+	.set_online  = zfcp_ccw_set_online,
+	.set_offline = zfcp_ccw_set_offline,
+	.notify      = zfcp_ccw_notify,
+	.driver      = {
+		.shutdown = zfcp_ccw_shutdown,
+	},
+};
+
+MODULE_DEVICE_TABLE(ccw, zfcp_ccw_device_id);
+
+/**
+ * zfcp_ccw_probe - probe function of zfcp driver
+ * @ccw_device: pointer to belonging ccw device
+ *
+ * This function gets called by the common i/o layer and sets up the initial
+ * data structures for each fcp adapter, which was detected by the system.
+ * Also the sysfs files for this adapter will be created by this function.
+ * In addition the nameserver port will be added to the ports of the adapter
+ * and its sysfs representation will be created too.
+ */
+static int
+zfcp_ccw_probe(struct ccw_device *ccw_device)
+{
+	struct zfcp_adapter *adapter;
+	int retval = 0;
+
+	down(&zfcp_data.config_sema);
+	adapter = zfcp_adapter_enqueue(ccw_device);
+	if (!adapter)
+		retval = -EINVAL;
+	else
+		ZFCP_LOG_DEBUG("Probed adapter %s\n",
+			       zfcp_get_busid_by_adapter(adapter));
+	up(&zfcp_data.config_sema);
+	return retval;
+}
+
+/**
+ * zfcp_ccw_remove - remove function of zfcp driver
+ * @ccw_device: pointer to belonging ccw device
+ *
+ * This function gets called by the common i/o layer and removes an adapter
+ * from the system. Task of this function is to get rid of all units and
+ * ports that belong to this adapter. And in addition all resources of this
+ * adapter will be freed too.
+ */
+static void
+zfcp_ccw_remove(struct ccw_device *ccw_device)
+{
+	struct zfcp_adapter *adapter;
+	struct zfcp_port *port, *p;
+	struct zfcp_unit *unit, *u;
+
+	ccw_device_set_offline(ccw_device);
+	down(&zfcp_data.config_sema);
+	adapter = dev_get_drvdata(&ccw_device->dev);
+
+	ZFCP_LOG_DEBUG("Removing adapter %s\n",
+		       zfcp_get_busid_by_adapter(adapter));
+	write_lock_irq(&zfcp_data.config_lock);
+	list_for_each_entry_safe(port, p, &adapter->port_list_head, list) {
+		list_for_each_entry_safe(unit, u, &port->unit_list_head, list) {
+			list_move(&unit->list, &port->unit_remove_lh);
+			atomic_set_mask(ZFCP_STATUS_COMMON_REMOVE,
+					&unit->status);
+		}
+		list_move(&port->list, &adapter->port_remove_lh);
+		atomic_set_mask(ZFCP_STATUS_COMMON_REMOVE, &port->status);
+	}
+	atomic_set_mask(ZFCP_STATUS_COMMON_REMOVE, &adapter->status);
+	write_unlock_irq(&zfcp_data.config_lock);
+
+	list_for_each_entry_safe(port, p, &adapter->port_remove_lh, list) {
+		list_for_each_entry_safe(unit, u, &port->unit_remove_lh, list) {
+			zfcp_unit_dequeue(unit);
+		}
+		zfcp_port_dequeue(port);
+	}
+	zfcp_adapter_wait(adapter);
+	zfcp_adapter_dequeue(adapter);
+
+	up(&zfcp_data.config_sema);
+}
+
+/**
+ * zfcp_ccw_set_online - set_online function of zfcp driver
+ * @ccw_device: pointer to belonging ccw device
+ *
+ * This function gets called by the common i/o layer and sets an adapter
+ * into state online. Setting an fcp device online means that it will be
+ * registered with the SCSI stack, that the QDIO queues will be set up
+ * and that the adapter will be opened (asynchronously).
+ */
+static int
+zfcp_ccw_set_online(struct ccw_device *ccw_device)
+{
+	struct zfcp_adapter *adapter;
+	int retval;
+
+	down(&zfcp_data.config_sema);
+	adapter = dev_get_drvdata(&ccw_device->dev);
+
+	retval = zfcp_adapter_debug_register(adapter);
+	if (retval)
+		goto out;
+	retval = zfcp_erp_thread_setup(adapter);
+	if (retval) {
+		ZFCP_LOG_INFO("error: start of error recovery thread for "
+			      "adapter %s failed\n",
+			      zfcp_get_busid_by_adapter(adapter));
+		goto out_erp_thread;
+	}
+
+	retval = zfcp_adapter_scsi_register(adapter);
+	if (retval)
+		goto out_scsi_register;
+	zfcp_erp_modify_adapter_status(adapter, ZFCP_STATUS_COMMON_RUNNING,
+				       ZFCP_SET);
+	zfcp_erp_adapter_reopen(adapter, ZFCP_STATUS_COMMON_ERP_FAILED);
+	zfcp_erp_wait(adapter);
+	goto out;
+
+ out_scsi_register:
+	zfcp_erp_thread_kill(adapter);
+ out_erp_thread:
+	zfcp_adapter_debug_unregister(adapter);
+ out:
+	up(&zfcp_data.config_sema);
+	return retval;
+}
+
+/**
+ * zfcp_ccw_set_offline - set_offline function of zfcp driver
+ * @ccw_device: pointer to belonging ccw device
+ *
+ * This function gets called by the common i/o layer and sets an adapter
+ * into state offline. Setting an fcp device offline means that it will be
+ * unregistered from the SCSI stack and that the adapter will be shut down
+ * asynchronously.
+ */
+static int
+zfcp_ccw_set_offline(struct ccw_device *ccw_device)
+{
+	struct zfcp_adapter *adapter;
+
+	down(&zfcp_data.config_sema);
+	adapter = dev_get_drvdata(&ccw_device->dev);
+	zfcp_erp_adapter_shutdown(adapter, 0);
+	zfcp_erp_wait(adapter);
+	zfcp_adapter_scsi_unregister(adapter);
+	zfcp_erp_thread_kill(adapter);
+	zfcp_adapter_debug_unregister(adapter);
+	up(&zfcp_data.config_sema);
+	return 0;
+}
+
+/**
+ * zfcp_ccw_notify
+ * @ccw_device: pointer to belonging ccw device
+ * @event: indicates if adapter was detached or attached
+ *
+ * This function gets called by the common i/o layer if an adapter has gone
+ * or reappeared.
+ */
+static int
+zfcp_ccw_notify(struct ccw_device *ccw_device, int event)
+{
+	struct zfcp_adapter *adapter;
+
+	down(&zfcp_data.config_sema);
+	adapter = dev_get_drvdata(&ccw_device->dev);
+	switch (event) {
+	case CIO_GONE:
+		ZFCP_LOG_NORMAL("adapter %s: device gone\n",
+				zfcp_get_busid_by_adapter(adapter));
+		debug_text_event(adapter->erp_dbf,1,"dev_gone");
+		zfcp_erp_adapter_shutdown(adapter, 0);
+		break;
+	case CIO_NO_PATH:
+		ZFCP_LOG_NORMAL("adapter %s: no path\n",
+				zfcp_get_busid_by_adapter(adapter));
+		debug_text_event(adapter->erp_dbf,1,"no_path");
+		zfcp_erp_adapter_shutdown(adapter, 0);
+		break;
+	case CIO_OPER:
+		ZFCP_LOG_NORMAL("adapter %s: operational again\n",
+				zfcp_get_busid_by_adapter(adapter));
+		debug_text_event(adapter->erp_dbf,1,"dev_oper");
+		zfcp_erp_modify_adapter_status(adapter,
+					       ZFCP_STATUS_COMMON_RUNNING,
+					       ZFCP_SET);
+		zfcp_erp_adapter_reopen(adapter,
+					ZFCP_STATUS_COMMON_ERP_FAILED);
+		break;
+	}
+	zfcp_erp_wait(adapter);
+	up(&zfcp_data.config_sema);
+	return 1;
+}
+
+/**
+ * zfcp_ccw_register - ccw register function
+ *
+ * Registers the driver at the common i/o layer. This function will be called
+ * at module load time/system start.
+ */
+int __init
+zfcp_ccw_register(void)
+{
+	int retval;
+
+	retval = ccw_driver_register(&zfcp_ccw_driver);
+	if (retval)
+		goto out;
+	retval = zfcp_sysfs_driver_create_files(&zfcp_ccw_driver.driver);
+	if (retval)
+		ccw_driver_unregister(&zfcp_ccw_driver);
+ out:
+	return retval;
+}
+
+/**
+ * zfcp_ccw_unregister - ccw unregister function
+ *
+ * Unregisters the driver from common i/o layer. Function will be called at
+ * module unload/system shutdown.
+ */
+void __exit
+zfcp_ccw_unregister(void)
+{
+	zfcp_sysfs_driver_remove_files(&zfcp_ccw_driver.driver);
+	ccw_driver_unregister(&zfcp_ccw_driver);
+}
+
+/**
+ * zfcp_ccw_shutdown - gets called on reboot/shutdown
+ *
+ * Makes sure that QDIO queues are down when the system gets stopped.
+ */
+static void
+zfcp_ccw_shutdown(struct device *dev)
+{
+	struct zfcp_adapter *adapter;
+
+	down(&zfcp_data.config_sema);
+	adapter = dev_get_drvdata(dev);
+	zfcp_erp_adapter_shutdown(adapter, 0);
+	zfcp_erp_wait(adapter);
+	up(&zfcp_data.config_sema);
+}
+
+#undef ZFCP_LOG_AREA
diff --git a/drivers/s390/scsi/zfcp_def.h b/drivers/s390/scsi/zfcp_def.h
new file mode 100644
index 0000000..53fcccb
--- /dev/null
+++ b/drivers/s390/scsi/zfcp_def.h
@@ -0,0 +1,1121 @@
+/* 
+ * 
+ * linux/drivers/s390/scsi/zfcp_def.h
+ * 
+ * FCP adapter driver for IBM eServer zSeries 
+ * 
+ * (C) Copyright IBM Corp. 2002, 2004
+ *
+ * Author(s): Martin Peschke <mpeschke@de.ibm.com> 
+ *            Raimund Schroeder <raimund.schroeder@de.ibm.com> 
+ *            Aron Zeh
+ *            Wolfgang Taphorn
+ *            Stefan Bader <stefan.bader@de.ibm.com> 
+ *            Heiko Carstens <heiko.carstens@de.ibm.com> 
+ *            Andreas Herrmann <aherrman@de.ibm.com>
+ *            Volker Sameske <sameske@de.ibm.com>
+ * 
+ * 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, or (at your option) 
+ * any later version. 
+ * 
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA. 
+ */ 
+
+
+#ifndef ZFCP_DEF_H
+#define ZFCP_DEF_H
+
+#define ZFCP_DEF_REVISION "$Revision: 1.111 $"
+
+/*************************** INCLUDES *****************************************/
+
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+#include <linux/miscdevice.h>
+#include <linux/major.h>
+#include <linux/blkdev.h>
+#include <linux/delay.h>
+#include <linux/timer.h>
+#include <scsi/scsi.h>
+#include <scsi/scsi_tcq.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_transport.h>
+#include <scsi/scsi_transport_fc.h>
+#include "../../fc4/fc.h"
+#include "zfcp_fsf.h"
+#include <asm/ccwdev.h>
+#include <asm/qdio.h>
+#include <asm/debug.h>
+#include <asm/ebcdic.h>
+#include <linux/mempool.h>
+#include <linux/syscalls.h>
+#include <linux/ioctl.h>
+#include <linux/ioctl32.h>
+
+/************************ DEBUG FLAGS *****************************************/
+
+#define	ZFCP_PRINT_FLAGS
+
+/********************* GENERAL DEFINES *********************************/
+
+/* zfcp version number, it consists of major, minor, and patch-level number */
+#define ZFCP_VERSION		"4.2.0"
+
+/**
+ * zfcp_sg_to_address - determine kernel address from struct scatterlist
+ * @list: struct scatterlist
+ * Return: kernel address
+ */
+static inline void *
+zfcp_sg_to_address(struct scatterlist *list)
+{
+	return (void *) (page_address(list->page) + list->offset);
+}
+
+/**
+ * zfcp_address_to_sg - set up struct scatterlist from kernel address
+ * @address: kernel address
+ * @list: struct scatterlist
+ */
+static inline void
+zfcp_address_to_sg(void *address, struct scatterlist *list)
+{
+	list->page = virt_to_page(address);
+	list->offset = ((unsigned long) address) & (PAGE_SIZE - 1);
+}
+
+/********************* SCSI SPECIFIC DEFINES *********************************/
+
+/* 32 bit for SCSI ID and LUN as long as the SCSI stack uses this type */
+typedef u32 scsi_id_t;
+typedef u32 scsi_lun_t;
+
+#define ZFCP_ERP_SCSI_LOW_MEM_TIMEOUT           (100*HZ)
+#define ZFCP_SCSI_ER_TIMEOUT                    (100*HZ)
+
+/********************* CIO/QDIO SPECIFIC DEFINES *****************************/
+
+/* Adapter Identification Parameters */
+#define ZFCP_CONTROL_UNIT_TYPE  0x1731
+#define ZFCP_CONTROL_UNIT_MODEL 0x03
+#define ZFCP_DEVICE_TYPE        0x1732
+#define ZFCP_DEVICE_MODEL       0x03
+#define ZFCP_DEVICE_MODEL_PRIV	0x04
+ 
+/* allow as many chained SBALs as are supported by hardware */
+#define ZFCP_MAX_SBALS_PER_REQ		FSF_MAX_SBALS_PER_REQ
+#define ZFCP_MAX_SBALS_PER_CT_REQ	FSF_MAX_SBALS_PER_REQ
+#define ZFCP_MAX_SBALS_PER_ELS_REQ	FSF_MAX_SBALS_PER_ELS_REQ
+
+/* DMQ bug workaround: don't use last SBALE */
+#define ZFCP_MAX_SBALES_PER_SBAL	(QDIO_MAX_ELEMENTS_PER_BUFFER - 1)
+
+/* index of last SBALE (with respect to DMQ bug workaround) */
+#define ZFCP_LAST_SBALE_PER_SBAL	(ZFCP_MAX_SBALES_PER_SBAL - 1)
+
+/* max. number of (data buffer) SBALEs in largest SBAL chain */
+#define ZFCP_MAX_SBALES_PER_REQ		\
+	(ZFCP_MAX_SBALS_PER_REQ * ZFCP_MAX_SBALES_PER_SBAL - 2)
+        /* request ID + QTCB in SBALE 0 + 1 of first SBAL in chain */
+
+/* FIXME(tune): free space should be one max. SBAL chain plus what? */
+#define ZFCP_QDIO_PCI_INTERVAL		(QDIO_MAX_BUFFERS_PER_Q \
+                                         - (ZFCP_MAX_SBALS_PER_REQ + 4))
+
+#define ZFCP_SBAL_TIMEOUT               (5*HZ)
+
+#define ZFCP_TYPE2_RECOVERY_TIME        (8*HZ)
+
+/* queue polling (values in microseconds) */
+#define ZFCP_MAX_INPUT_THRESHOLD 	5000	/* FIXME: tune */
+#define ZFCP_MAX_OUTPUT_THRESHOLD 	1000	/* FIXME: tune */
+#define ZFCP_MIN_INPUT_THRESHOLD 	1	/* ignored by QDIO layer */
+#define ZFCP_MIN_OUTPUT_THRESHOLD 	1	/* ignored by QDIO layer */
+
+#define QDIO_SCSI_QFMT			1	/* 1 for FSF */
+
+/********************* FSF SPECIFIC DEFINES *********************************/
+
+#define ZFCP_ULP_INFO_VERSION                   26
+#define ZFCP_QTCB_VERSION	FSF_QTCB_CURRENT_VERSION
+/* ATTENTION: value must not be used by hardware */
+#define FSF_QTCB_UNSOLICITED_STATUS		0x6305
+#define ZFCP_STATUS_READ_FAILED_THRESHOLD	3
+#define ZFCP_STATUS_READS_RECOM		        FSF_STATUS_READS_RECOM
+#define ZFCP_EXCHANGE_CONFIG_DATA_RETRIES	6
+#define ZFCP_EXCHANGE_CONFIG_DATA_SLEEP		50
+
+/* timeout value for "default timer" for fsf requests */
+#define ZFCP_FSF_REQUEST_TIMEOUT (60*HZ);
+
+/*************** FIBRE CHANNEL PROTOCOL SPECIFIC DEFINES ********************/
+
+typedef unsigned long long wwn_t;
+typedef unsigned int       fc_id_t;
+typedef unsigned long long fcp_lun_t;
+/* data length field may be at variable position in FCP-2 FCP_CMND IU */
+typedef unsigned int       fcp_dl_t;
+
+#define ZFCP_FC_SERVICE_CLASS_DEFAULT	FSF_CLASS_3
+
+/* timeout for name-server lookup (in seconds) */
+#define ZFCP_NS_GID_PN_TIMEOUT		10
+
+/* largest SCSI command we can process */
+/* FCP-2 (FCP_CMND IU) allows up to (255-3+16) */
+#define ZFCP_MAX_SCSI_CMND_LENGTH	255
+/* maximum number of commands in LUN queue (tagged queueing) */
+#define ZFCP_CMND_PER_LUN               32
+
+/* task attribute values in FCP-2 FCP_CMND IU */
+#define SIMPLE_Q	0
+#define HEAD_OF_Q	1
+#define ORDERED_Q	2
+#define ACA_Q		4
+#define UNTAGGED	5
+
+/* task management flags in FCP-2 FCP_CMND IU */
+#define FCP_CLEAR_ACA		0x40
+#define FCP_TARGET_RESET	0x20
+#define FCP_LOGICAL_UNIT_RESET	0x10
+#define FCP_CLEAR_TASK_SET	0x04
+#define FCP_ABORT_TASK_SET	0x02
+
+#define FCP_CDB_LENGTH		16
+
+#define ZFCP_DID_MASK           0x00FFFFFF
+
+/* FCP(-2) FCP_CMND IU */
+struct fcp_cmnd_iu {
+	fcp_lun_t fcp_lun;	   /* FCP logical unit number */
+	u8  crn;	           /* command reference number */
+	u8  reserved0:5;	   /* reserved */
+	u8  task_attribute:3;	   /* task attribute */
+	u8  task_management_flags; /* task management flags */
+	u8  add_fcp_cdb_length:6;  /* additional FCP_CDB length */
+	u8  rddata:1;              /* read data */
+	u8  wddata:1;              /* write data */
+	u8  fcp_cdb[FCP_CDB_LENGTH];
+} __attribute__((packed));
+
+/* FCP(-2) FCP_RSP IU */
+struct fcp_rsp_iu {
+	u8  reserved0[10];
+	union {
+		struct {
+			u8 reserved1:3;
+			u8 fcp_conf_req:1;
+			u8 fcp_resid_under:1;
+			u8 fcp_resid_over:1;
+			u8 fcp_sns_len_valid:1;
+			u8 fcp_rsp_len_valid:1;
+		} bits;
+		u8 value;
+	} validity;
+	u8  scsi_status;
+	u32 fcp_resid;
+	u32 fcp_sns_len;
+	u32 fcp_rsp_len;
+} __attribute__((packed));
+
+
+#define RSP_CODE_GOOD		 0
+#define RSP_CODE_LENGTH_MISMATCH 1
+#define RSP_CODE_FIELD_INVALID	 2
+#define RSP_CODE_RO_MISMATCH	 3
+#define RSP_CODE_TASKMAN_UNSUPP	 4
+#define RSP_CODE_TASKMAN_FAILED	 5
+
+/* see fc-fs */
+#define LS_FAN 0x60000000
+#define LS_RSCN 0x61040000
+
+struct fcp_rscn_head {
+        u8  command;
+        u8  page_length; /* always 0x04 */
+        u16 payload_len;
+} __attribute__((packed));
+
+struct fcp_rscn_element {
+        u8  reserved:2;
+        u8  event_qual:4;
+        u8  addr_format:2;
+        u32 nport_did:24;
+} __attribute__((packed));
+
+#define ZFCP_PORT_ADDRESS   0x0
+#define ZFCP_AREA_ADDRESS   0x1
+#define ZFCP_DOMAIN_ADDRESS 0x2
+#define ZFCP_FABRIC_ADDRESS 0x3
+
+#define ZFCP_PORTS_RANGE_PORT   0xFFFFFF
+#define ZFCP_PORTS_RANGE_AREA   0xFFFF00
+#define ZFCP_PORTS_RANGE_DOMAIN 0xFF0000
+#define ZFCP_PORTS_RANGE_FABRIC 0x000000
+
+#define ZFCP_NO_PORTS_PER_AREA    0x100
+#define ZFCP_NO_PORTS_PER_DOMAIN  0x10000
+#define ZFCP_NO_PORTS_PER_FABRIC  0x1000000
+
+struct fcp_fan {
+        u32 command;
+        u32 fport_did;
+        wwn_t fport_wwpn;
+        wwn_t fport_wwname;
+} __attribute__((packed));
+
+/* see fc-ph */
+struct fcp_logo {
+        u32 command;
+        u32 nport_did;
+        wwn_t nport_wwpn;
+} __attribute__((packed));
+
+/*
+ * FC-FS stuff
+ */
+#define R_A_TOV				10 /* seconds */
+#define ZFCP_ELS_TIMEOUT		(2 * R_A_TOV)
+
+#define ZFCP_LS_RLS			0x0f
+#define ZFCP_LS_ADISC			0x52
+#define ZFCP_LS_RPS			0x56
+#define ZFCP_LS_RSCN			0x61
+#define ZFCP_LS_RNID			0x78
+
+struct zfcp_ls_rjt_par {
+	u8 action;
+ 	u8 reason_code;
+ 	u8 reason_expl;
+ 	u8 vendor_unique;
+} __attribute__ ((packed));
+
+struct zfcp_ls_adisc {
+	u8		code;
+	u8		field[3];
+	u32		hard_nport_id;
+	u64		wwpn;
+	u64		wwnn;
+	u32		nport_id;
+} __attribute__ ((packed));
+
+struct zfcp_ls_adisc_acc {
+	u8		code;
+	u8		field[3];
+	u32		hard_nport_id;
+	u64		wwpn;
+	u64		wwnn;
+	u32		nport_id;
+} __attribute__ ((packed));
+
+struct zfcp_rc_entry {
+	u8 code;
+	const char *description;
+};
+
+/*
+ * FC-GS-2 stuff
+ */
+#define ZFCP_CT_REVISION		0x01
+#define ZFCP_CT_DIRECTORY_SERVICE	0xFC
+#define ZFCP_CT_NAME_SERVER		0x02
+#define ZFCP_CT_SYNCHRONOUS		0x00
+#define ZFCP_CT_GID_PN			0x0121
+#define ZFCP_CT_MAX_SIZE		0x1020
+#define ZFCP_CT_ACCEPT			0x8002
+#define ZFCP_CT_REJECT			0x8001
+
+/*
+ * FC-GS-4 stuff
+ */
+#define ZFCP_CT_TIMEOUT			(3 * R_A_TOV)
+
+
+/***************** S390 DEBUG FEATURE SPECIFIC DEFINES ***********************/
+
+/* debug feature entries per adapter */
+#define ZFCP_ERP_DBF_INDEX     1 
+#define ZFCP_ERP_DBF_AREAS     2
+#define ZFCP_ERP_DBF_LENGTH    16
+#define ZFCP_ERP_DBF_LEVEL     3
+#define ZFCP_ERP_DBF_NAME      "zfcperp"
+
+#define ZFCP_CMD_DBF_INDEX     2
+#define ZFCP_CMD_DBF_AREAS     1
+#define ZFCP_CMD_DBF_LENGTH    8
+#define ZFCP_CMD_DBF_LEVEL     3
+#define ZFCP_CMD_DBF_NAME      "zfcpcmd"
+
+#define ZFCP_ABORT_DBF_INDEX   2
+#define ZFCP_ABORT_DBF_AREAS   1
+#define ZFCP_ABORT_DBF_LENGTH  8
+#define ZFCP_ABORT_DBF_LEVEL   6
+#define ZFCP_ABORT_DBF_NAME    "zfcpabt"
+
+#define ZFCP_IN_ELS_DBF_INDEX  2
+#define ZFCP_IN_ELS_DBF_AREAS  1
+#define ZFCP_IN_ELS_DBF_LENGTH 8
+#define ZFCP_IN_ELS_DBF_LEVEL  6
+#define ZFCP_IN_ELS_DBF_NAME   "zfcpels"
+
+/******************** LOGGING MACROS AND DEFINES *****************************/
+
+/*
+ * Logging may be applied on certain kinds of driver operations
+ * independently. Additionally, different log-levels are supported for
+ * each of these areas.
+ */
+
+#define ZFCP_NAME               "zfcp"
+
+/* read-only LUN sharing switch initial value */
+#define ZFCP_RO_LUN_SHARING_DEFAULTS 0
+
+/* independent log areas */
+#define ZFCP_LOG_AREA_OTHER	0
+#define ZFCP_LOG_AREA_SCSI	1
+#define ZFCP_LOG_AREA_FSF	2
+#define ZFCP_LOG_AREA_CONFIG	3
+#define ZFCP_LOG_AREA_CIO	4
+#define ZFCP_LOG_AREA_QDIO	5
+#define ZFCP_LOG_AREA_ERP	6
+#define ZFCP_LOG_AREA_FC	7
+
+/* log level values*/
+#define ZFCP_LOG_LEVEL_NORMAL	0
+#define ZFCP_LOG_LEVEL_INFO	1
+#define ZFCP_LOG_LEVEL_DEBUG	2
+#define ZFCP_LOG_LEVEL_TRACE	3
+
+/*
+ * this allows removal of logging code by the preprocessor
+ * (the most detailed log level still to be compiled in is specified, 
+ * higher log levels are removed)
+ */
+#define ZFCP_LOG_LEVEL_LIMIT	ZFCP_LOG_LEVEL_TRACE
+
+/* get "loglevel" nibble assignment */
+#define ZFCP_GET_LOG_VALUE(zfcp_lognibble) \
+	       ((atomic_read(&zfcp_data.loglevel) >> (zfcp_lognibble<<2)) & 0xF)
+
+/* set "loglevel" nibble */
+#define ZFCP_SET_LOG_NIBBLE(value, zfcp_lognibble) \
+	       (value << (zfcp_lognibble << 2))
+
+/* all log-level defaults are combined to generate initial log-level */
+#define ZFCP_LOG_LEVEL_DEFAULTS \
+	(ZFCP_SET_LOG_NIBBLE(ZFCP_LOG_LEVEL_NORMAL, ZFCP_LOG_AREA_OTHER) | \
+	 ZFCP_SET_LOG_NIBBLE(ZFCP_LOG_LEVEL_NORMAL, ZFCP_LOG_AREA_SCSI) | \
+	 ZFCP_SET_LOG_NIBBLE(ZFCP_LOG_LEVEL_NORMAL, ZFCP_LOG_AREA_FSF) | \
+	 ZFCP_SET_LOG_NIBBLE(ZFCP_LOG_LEVEL_NORMAL, ZFCP_LOG_AREA_CONFIG) | \
+	 ZFCP_SET_LOG_NIBBLE(ZFCP_LOG_LEVEL_NORMAL, ZFCP_LOG_AREA_CIO) | \
+	 ZFCP_SET_LOG_NIBBLE(ZFCP_LOG_LEVEL_NORMAL, ZFCP_LOG_AREA_QDIO) | \
+	 ZFCP_SET_LOG_NIBBLE(ZFCP_LOG_LEVEL_NORMAL, ZFCP_LOG_AREA_ERP) | \
+	 ZFCP_SET_LOG_NIBBLE(ZFCP_LOG_LEVEL_NORMAL, ZFCP_LOG_AREA_FC))
+
+/* check whether we have the right level for logging */
+#define ZFCP_LOG_CHECK(level) \
+	((ZFCP_GET_LOG_VALUE(ZFCP_LOG_AREA)) >= level)
+
+/* logging routine for zfcp */
+#define _ZFCP_LOG(fmt, args...) \
+	printk(KERN_ERR ZFCP_NAME": %s(%d): " fmt, __FUNCTION__, \
+	       __LINE__ , ##args)
+
+#define ZFCP_LOG(level, fmt, args...) \
+do { \
+	if (ZFCP_LOG_CHECK(level)) \
+		_ZFCP_LOG(fmt, ##args); \
+} while (0)
+	
+#if ZFCP_LOG_LEVEL_LIMIT < ZFCP_LOG_LEVEL_NORMAL
+# define ZFCP_LOG_NORMAL(fmt, args...)
+#else
+# define ZFCP_LOG_NORMAL(fmt, args...) \
+do { \
+	if (ZFCP_LOG_CHECK(ZFCP_LOG_LEVEL_NORMAL)) \
+		printk(KERN_ERR ZFCP_NAME": " fmt, ##args); \
+} while (0)
+#endif
+
+#if ZFCP_LOG_LEVEL_LIMIT < ZFCP_LOG_LEVEL_INFO
+# define ZFCP_LOG_INFO(fmt, args...)
+#else
+# define ZFCP_LOG_INFO(fmt, args...) \
+do { \
+	if (ZFCP_LOG_CHECK(ZFCP_LOG_LEVEL_INFO)) \
+		printk(KERN_ERR ZFCP_NAME": " fmt, ##args); \
+} while (0)
+#endif
+
+#if ZFCP_LOG_LEVEL_LIMIT < ZFCP_LOG_LEVEL_DEBUG
+# define ZFCP_LOG_DEBUG(fmt, args...)
+#else
+# define ZFCP_LOG_DEBUG(fmt, args...) \
+	ZFCP_LOG(ZFCP_LOG_LEVEL_DEBUG, fmt , ##args)
+#endif
+
+#if ZFCP_LOG_LEVEL_LIMIT < ZFCP_LOG_LEVEL_TRACE
+# define ZFCP_LOG_TRACE(fmt, args...)
+#else
+# define ZFCP_LOG_TRACE(fmt, args...) \
+	ZFCP_LOG(ZFCP_LOG_LEVEL_TRACE, fmt , ##args)
+#endif
+
+#ifndef ZFCP_PRINT_FLAGS
+# define ZFCP_LOG_FLAGS(level, fmt, args...)
+#else
+extern u32 flags_dump;
+# define ZFCP_LOG_FLAGS(level, fmt, args...) \
+do { \
+	if (level <= flags_dump) \
+		_ZFCP_LOG(fmt, ##args); \
+} while (0)
+#endif
+
+/*************** ADAPTER/PORT/UNIT AND FSF_REQ STATUS FLAGS ******************/
+
+/* 
+ * Note, the leftmost status byte is common among adapter, port 
+ * and unit
+ */
+#define ZFCP_COMMON_FLAGS			0xfff00000
+#define ZFCP_SPECIFIC_FLAGS			0x000fffff
+
+/* common status bits */
+#define ZFCP_STATUS_COMMON_REMOVE		0x80000000
+#define ZFCP_STATUS_COMMON_RUNNING		0x40000000
+#define ZFCP_STATUS_COMMON_ERP_FAILED		0x20000000
+#define ZFCP_STATUS_COMMON_UNBLOCKED		0x10000000
+#define ZFCP_STATUS_COMMON_OPENING              0x08000000
+#define ZFCP_STATUS_COMMON_OPEN                 0x04000000
+#define ZFCP_STATUS_COMMON_CLOSING              0x02000000
+#define ZFCP_STATUS_COMMON_ERP_INUSE		0x01000000
+#define ZFCP_STATUS_COMMON_ACCESS_DENIED	0x00800000
+
+/* adapter status */
+#define ZFCP_STATUS_ADAPTER_QDIOUP		0x00000002
+#define ZFCP_STATUS_ADAPTER_REGISTERED		0x00000004
+#define ZFCP_STATUS_ADAPTER_XCONFIG_OK		0x00000008
+#define ZFCP_STATUS_ADAPTER_HOST_CON_INIT	0x00000010
+#define ZFCP_STATUS_ADAPTER_ERP_THREAD_UP	0x00000020
+#define ZFCP_STATUS_ADAPTER_ERP_THREAD_KILL	0x00000080
+#define ZFCP_STATUS_ADAPTER_ERP_PENDING		0x00000100
+#define ZFCP_STATUS_ADAPTER_LINK_UNPLUGGED	0x00000200
+
+#define ZFCP_STATUS_ADAPTER_SCSI_UP			\
+		(ZFCP_STATUS_COMMON_UNBLOCKED |	\
+		 ZFCP_STATUS_ADAPTER_REGISTERED)
+
+
+/* FC-PH/FC-GS well-known address identifiers for generic services */
+#define ZFCP_DID_MANAGEMENT_SERVICE		0xFFFFFA
+#define ZFCP_DID_TIME_SERVICE			0xFFFFFB
+#define ZFCP_DID_DIRECTORY_SERVICE		0xFFFFFC
+#define ZFCP_DID_ALIAS_SERVICE			0xFFFFF8
+#define ZFCP_DID_KEY_DISTRIBUTION_SERVICE	0xFFFFF7
+
+/* remote port status */
+#define ZFCP_STATUS_PORT_PHYS_OPEN		0x00000001
+#define ZFCP_STATUS_PORT_DID_DID		0x00000002
+#define ZFCP_STATUS_PORT_PHYS_CLOSING		0x00000004
+#define ZFCP_STATUS_PORT_NO_WWPN		0x00000008
+#define ZFCP_STATUS_PORT_NO_SCSI_ID		0x00000010
+#define ZFCP_STATUS_PORT_INVALID_WWPN		0x00000020
+#define ZFCP_STATUS_PORT_ACCESS_DENIED		0x00000040
+
+/* for ports with well known addresses */
+#define ZFCP_STATUS_PORT_WKA \
+		(ZFCP_STATUS_PORT_NO_WWPN | \
+		 ZFCP_STATUS_PORT_NO_SCSI_ID)
+
+/* logical unit status */
+#define ZFCP_STATUS_UNIT_NOTSUPPUNITRESET	0x00000001
+#define ZFCP_STATUS_UNIT_TEMPORARY		0x00000002
+#define ZFCP_STATUS_UNIT_SHARED			0x00000004
+#define ZFCP_STATUS_UNIT_READONLY		0x00000008
+
+/* FSF request status (this does not have a common part) */
+#define ZFCP_STATUS_FSFREQ_NOT_INIT		0x00000000
+#define ZFCP_STATUS_FSFREQ_POOL  		0x00000001
+#define ZFCP_STATUS_FSFREQ_TASK_MANAGEMENT	0x00000002
+#define ZFCP_STATUS_FSFREQ_COMPLETED		0x00000004
+#define ZFCP_STATUS_FSFREQ_ERROR		0x00000008
+#define ZFCP_STATUS_FSFREQ_CLEANUP		0x00000010
+#define ZFCP_STATUS_FSFREQ_ABORTING		0x00000020
+#define ZFCP_STATUS_FSFREQ_ABORTSUCCEEDED	0x00000040
+#define ZFCP_STATUS_FSFREQ_ABORTNOTNEEDED       0x00000080
+#define ZFCP_STATUS_FSFREQ_ABORTED              0x00000100
+#define ZFCP_STATUS_FSFREQ_TMFUNCFAILED         0x00000200
+#define ZFCP_STATUS_FSFREQ_TMFUNCNOTSUPP        0x00000400
+#define ZFCP_STATUS_FSFREQ_RETRY                0x00000800
+#define ZFCP_STATUS_FSFREQ_DISMISSED            0x00001000
+
+/*********************** ERROR RECOVERY PROCEDURE DEFINES ********************/
+
+#define ZFCP_MAX_ERPS                   3
+
+#define ZFCP_ERP_FSFREQ_TIMEOUT		(30 * HZ)
+#define ZFCP_ERP_MEMWAIT_TIMEOUT	HZ
+
+#define ZFCP_STATUS_ERP_TIMEDOUT	0x10000000
+#define ZFCP_STATUS_ERP_CLOSE_ONLY	0x01000000
+#define ZFCP_STATUS_ERP_DISMISSING	0x00100000
+#define ZFCP_STATUS_ERP_DISMISSED	0x00200000
+#define ZFCP_STATUS_ERP_LOWMEM		0x00400000
+
+#define ZFCP_ERP_STEP_UNINITIALIZED	0x00000000
+#define ZFCP_ERP_STEP_FSF_XCONFIG	0x00000001
+#define ZFCP_ERP_STEP_PHYS_PORT_CLOSING	0x00000010
+#define ZFCP_ERP_STEP_PORT_CLOSING	0x00000100
+#define ZFCP_ERP_STEP_NAMESERVER_OPEN	0x00000200
+#define ZFCP_ERP_STEP_NAMESERVER_LOOKUP	0x00000400
+#define ZFCP_ERP_STEP_PORT_OPENING	0x00000800
+#define ZFCP_ERP_STEP_UNIT_CLOSING	0x00001000
+#define ZFCP_ERP_STEP_UNIT_OPENING	0x00002000
+
+/* Ordered by escalation level (necessary for proper erp-code operation) */
+#define ZFCP_ERP_ACTION_REOPEN_ADAPTER		0x4
+#define ZFCP_ERP_ACTION_REOPEN_PORT_FORCED	0x3
+#define ZFCP_ERP_ACTION_REOPEN_PORT		0x2
+#define ZFCP_ERP_ACTION_REOPEN_UNIT		0x1
+
+#define ZFCP_ERP_ACTION_RUNNING			0x1
+#define ZFCP_ERP_ACTION_READY			0x2
+
+#define ZFCP_ERP_SUCCEEDED	0x0
+#define ZFCP_ERP_FAILED		0x1
+#define ZFCP_ERP_CONTINUES	0x2
+#define ZFCP_ERP_EXIT		0x3
+#define ZFCP_ERP_DISMISSED	0x4
+#define ZFCP_ERP_NOMEM		0x5
+
+
+/******************** CFDC SPECIFIC STUFF *****************************/
+
+/* Firewall data channel sense data record */
+struct zfcp_cfdc_sense_data {
+	u32 signature;           /* Request signature */
+	u32 devno;               /* FCP adapter device number */
+	u32 command;             /* Command code */
+	u32 fsf_status;          /* FSF request status and status qualifier */
+	u8  fsf_status_qual[FSF_STATUS_QUALIFIER_SIZE];
+	u8  payloads[256];       /* Access conflicts list */
+	u8  control_file[0];     /* Access control table */
+};
+
+#define ZFCP_CFDC_SIGNATURE			0xCFDCACDF
+
+#define ZFCP_CFDC_CMND_DOWNLOAD_NORMAL		0x00010001
+#define ZFCP_CFDC_CMND_DOWNLOAD_FORCE		0x00010101
+#define ZFCP_CFDC_CMND_FULL_ACCESS		0x00000201
+#define ZFCP_CFDC_CMND_RESTRICTED_ACCESS	0x00000401
+#define ZFCP_CFDC_CMND_UPLOAD			0x00010002
+
+#define ZFCP_CFDC_DOWNLOAD			0x00000001
+#define ZFCP_CFDC_UPLOAD			0x00000002
+#define ZFCP_CFDC_WITH_CONTROL_FILE		0x00010000
+
+#define ZFCP_CFDC_DEV_NAME			"zfcp_cfdc"
+#define ZFCP_CFDC_DEV_MAJOR			MISC_MAJOR
+#define ZFCP_CFDC_DEV_MINOR			MISC_DYNAMIC_MINOR
+
+#define ZFCP_CFDC_MAX_CONTROL_FILE_SIZE		127 * 1024
+
+/************************* STRUCTURE DEFINITIONS *****************************/
+
+struct zfcp_fsf_req;
+
+/* holds various memory pools of an adapter */
+struct zfcp_adapter_mempool {
+	mempool_t *fsf_req_erp;
+	mempool_t *fsf_req_scsi;
+	mempool_t *fsf_req_abort;
+	mempool_t *fsf_req_status_read;
+	mempool_t *data_status_read;
+	mempool_t *data_gid_pn;
+};
+
+struct  zfcp_exchange_config_data{
+};
+
+struct zfcp_open_port {
+        struct zfcp_port *port;
+};
+
+struct zfcp_close_port {
+	struct zfcp_port *port;
+};
+
+struct zfcp_open_unit {
+	struct zfcp_unit *unit;
+};
+
+struct zfcp_close_unit {
+	struct zfcp_unit *unit;
+};
+
+struct zfcp_close_physical_port {
+        struct zfcp_port *port;
+};
+
+struct zfcp_send_fcp_command_task {
+	struct zfcp_fsf_req *fsf_req;
+	struct zfcp_unit *unit;
+ 	struct scsi_cmnd *scsi_cmnd;
+	unsigned long start_jiffies;
+};
+
+struct zfcp_send_fcp_command_task_management {
+	struct zfcp_unit *unit;
+};
+
+struct zfcp_abort_fcp_command {
+	struct zfcp_fsf_req *fsf_req;
+	struct zfcp_unit *unit;
+};
+
+/*
+ * header for CT_IU
+ */
+struct ct_hdr {
+	u8 revision;		// 0x01
+	u8 in_id[3];		// 0x00
+	u8 gs_type;		// 0xFC	Directory Service
+	u8 gs_subtype;		// 0x02	Name Server
+	u8 options;		// 0x00 single bidirectional exchange
+	u8 reserved0;
+	u16 cmd_rsp_code;	// 0x0121 GID_PN, or 0x0100 GA_NXT
+	u16 max_res_size;	// <= (4096 - 16) / 4
+	u8 reserved1;
+	u8 reason_code;
+	u8 reason_code_expl;
+	u8 vendor_unique;
+} __attribute__ ((packed));
+
+/* nameserver request CT_IU -- for requests where
+ * a port name is required */
+struct ct_iu_gid_pn_req {
+	struct ct_hdr header;
+	wwn_t wwpn;
+} __attribute__ ((packed));
+
+/* FS_ACC IU and data unit for GID_PN nameserver request */
+struct ct_iu_gid_pn_resp {
+	struct ct_hdr header;
+	fc_id_t d_id;
+} __attribute__ ((packed));
+
+typedef void (*zfcp_send_ct_handler_t)(unsigned long);
+
+/**
+ * struct zfcp_send_ct - used to pass parameters to function zfcp_fsf_send_ct
+ * @port: port where the request is sent to
+ * @req: scatter-gather list for request
+ * @resp: scatter-gather list for response
+ * @req_count: number of elements in request scatter-gather list
+ * @resp_count: number of elements in response scatter-gather list
+ * @handler: handler function (called for response to the request)
+ * @handler_data: data passed to handler function
+ * @pool: pointer to memory pool for ct request structure
+ * @timeout: FSF timeout for this request
+ * @timer: timer (e.g. for request initiated by erp)
+ * @completion: completion for synchronization purposes
+ * @status: used to pass error status to calling function
+ */
+struct zfcp_send_ct {
+	struct zfcp_port *port;
+	struct scatterlist *req;
+	struct scatterlist *resp;
+	unsigned int req_count;
+	unsigned int resp_count;
+	zfcp_send_ct_handler_t handler;
+	unsigned long handler_data;
+	mempool_t *pool;
+	int timeout;
+	struct timer_list *timer;
+	struct completion *completion;
+	int status;
+};
+
+/* used for name server requests in error recovery */
+struct zfcp_gid_pn_data {
+	struct zfcp_send_ct ct;
+	struct scatterlist req;
+	struct scatterlist resp;
+	struct ct_iu_gid_pn_req ct_iu_req;
+	struct ct_iu_gid_pn_resp ct_iu_resp;
+        struct zfcp_port *port;
+};
+
+typedef void (*zfcp_send_els_handler_t)(unsigned long);
+
+/**
+ * struct zfcp_send_els - used to pass parameters to function zfcp_fsf_send_els
+ * @adapter: adapter where request is sent from
+ * @d_id: destiniation id of port where request is sent to
+ * @req: scatter-gather list for request
+ * @resp: scatter-gather list for response
+ * @req_count: number of elements in request scatter-gather list
+ * @resp_count: number of elements in response scatter-gather list
+ * @handler: handler function (called for response to the request)
+ * @handler_data: data passed to handler function
+ * @timer: timer (e.g. for request initiated by erp)
+ * @completion: completion for synchronization purposes
+ * @ls_code: hex code of ELS command
+ * @status: used to pass error status to calling function
+ */
+struct zfcp_send_els {
+	struct zfcp_adapter *adapter;
+	fc_id_t d_id;
+	struct scatterlist *req;
+	struct scatterlist *resp;
+	unsigned int req_count;
+	unsigned int resp_count;
+	zfcp_send_els_handler_t handler;
+	unsigned long handler_data;
+	struct timer_list *timer;
+	struct completion *completion;
+	int ls_code;
+	int status;
+};
+
+struct zfcp_status_read {
+	struct fsf_status_read_buffer *buffer;
+};
+
+struct zfcp_fsf_done {
+	struct completion *complete;
+	int status;
+};
+
+/* request specific data */
+union zfcp_req_data {
+	struct zfcp_exchange_config_data exchange_config_data;
+	struct zfcp_open_port		  open_port;
+	struct zfcp_close_port		  close_port;
+	struct zfcp_open_unit		  open_unit;
+	struct zfcp_close_unit		  close_unit;
+	struct zfcp_close_physical_port	  close_physical_port;
+	struct zfcp_send_fcp_command_task send_fcp_command_task;
+        struct zfcp_send_fcp_command_task_management
+					  send_fcp_command_task_management;
+	struct zfcp_abort_fcp_command	  abort_fcp_command;
+	struct zfcp_send_ct *send_ct;
+	struct zfcp_send_els *send_els;
+	struct zfcp_status_read 	  status_read;
+	struct fsf_qtcb_bottom_port *port_data;
+};
+
+struct zfcp_qdio_queue {
+	struct qdio_buffer *buffer[QDIO_MAX_BUFFERS_PER_Q]; /* SBALs */
+	u8		   free_index;	      /* index of next free bfr
+						 in queue (free_count>0) */
+	atomic_t           free_count;	      /* number of free buffers
+						 in queue */
+	rwlock_t	   queue_lock;	      /* lock for operations on queue */
+        int                distance_from_int; /* SBALs used since PCI indication
+						 was last set */
+};
+
+struct zfcp_erp_action {
+	struct list_head list;
+	int action;	              /* requested action code */
+	struct zfcp_adapter *adapter; /* device which should be recovered */
+	struct zfcp_port *port;
+	struct zfcp_unit *unit;
+	volatile u32 status;	      /* recovery status */
+	u32 step;	              /* active step of this erp action */
+	struct zfcp_fsf_req *fsf_req; /* fsf request currently pending
+					 for this action */
+	struct timer_list timer;
+};
+
+
+struct zfcp_adapter {
+	struct list_head	list;              /* list of adapters */
+	atomic_t                refcount;          /* reference count */
+	wait_queue_head_t	remove_wq;         /* can be used to wait for
+						      refcount drop to zero */
+	wwn_t			wwnn;	           /* WWNN */
+	wwn_t			wwpn;	           /* WWPN */
+	fc_id_t			s_id;	           /* N_Port ID */
+	struct ccw_device       *ccw_device;	   /* S/390 ccw device */
+	u8			fc_service_class;
+	u32			fc_topology;	   /* FC topology */
+	u32			fc_link_speed;	   /* FC interface speed */
+	u32			hydra_version;	   /* Hydra version */
+	u32			fsf_lic_version;
+        u32			supported_features;/* of FCP channel */
+        u32			hardware_version;  /* of FCP channel */
+        u8			serial_number[32]; /* of hardware */
+	struct Scsi_Host	*scsi_host;	   /* Pointer to mid-layer */
+	unsigned short          scsi_host_no;      /* Assigned host number */
+	unsigned char		name[9];
+	struct list_head	port_list_head;	   /* remote port list */
+	struct list_head        port_remove_lh;    /* head of ports to be
+						      removed */
+	u32			ports;	           /* number of remote ports */
+        struct timer_list       scsi_er_timer;     /* SCSI err recovery watch */
+	struct list_head	fsf_req_list_head; /* head of FSF req list */
+	rwlock_t		fsf_req_list_lock; /* lock for ops on list of
+						      FSF requests */
+        atomic_t       		fsf_reqs_active;   /* # active FSF reqs */
+	struct zfcp_qdio_queue	request_queue;	   /* request queue */
+	u32			fsf_req_seq_no;	   /* FSF cmnd seq number */
+	wait_queue_head_t	request_wq;	   /* can be used to wait for
+						      more avaliable SBALs */
+	struct zfcp_qdio_queue	response_queue;	   /* response queue */
+	rwlock_t		abort_lock;        /* Protects against SCSI
+						      stack abort/command
+						      completion races */
+	u16			status_read_failed; /* # failed status reads */
+	atomic_t		status;	           /* status of this adapter */
+	struct list_head	erp_ready_head;	   /* error recovery for this
+						      adapter/devices */
+	struct list_head	erp_running_head;
+	rwlock_t		erp_lock;
+	struct semaphore	erp_ready_sem;
+	wait_queue_head_t	erp_thread_wqh;
+	wait_queue_head_t	erp_done_wqh;
+	struct zfcp_erp_action	erp_action;	   /* pending error recovery */
+        atomic_t                erp_counter;
+	u32			erp_total_count;   /* total nr of enqueued erp
+						      actions */
+	u32			erp_low_mem_count; /* nr of erp actions waiting
+						      for memory */
+	struct zfcp_port	*nameserver_port;  /* adapter's nameserver */
+        debug_info_t            *erp_dbf;          /* S/390 debug features */
+	debug_info_t            *abort_dbf;
+	debug_info_t            *in_els_dbf;
+	debug_info_t            *cmd_dbf;
+	spinlock_t              dbf_lock;
+	struct zfcp_adapter_mempool	pool;      /* Adapter memory pools */
+	struct qdio_initialize  qdio_init_data;    /* for qdio_establish */
+	struct device           generic_services;  /* directory for WKA ports */
+};
+
+/*
+ * the struct device sysfs_device must be at the beginning of this structure.
+ * pointer to struct device is used to free port structure in release function
+ * of the device. don't change!
+ */
+struct zfcp_port {
+	struct device          sysfs_device;   /* sysfs device */
+	struct list_head       list;	       /* list of remote ports */
+	atomic_t               refcount;       /* reference count */
+	wait_queue_head_t      remove_wq;      /* can be used to wait for
+						  refcount drop to zero */
+	struct zfcp_adapter    *adapter;       /* adapter used to access port */
+	struct list_head       unit_list_head; /* head of logical unit list */
+	struct list_head       unit_remove_lh; /* head of luns to be removed
+						  list */
+	u32		       units;	       /* # of logical units in list */
+	atomic_t	       status;	       /* status of this remote port */
+	scsi_id_t	       scsi_id;	       /* own SCSI ID */
+	wwn_t		       wwnn;	       /* WWNN if known */
+	wwn_t		       wwpn;	       /* WWPN */
+	fc_id_t		       d_id;	       /* D_ID */
+	u32		       handle;	       /* handle assigned by FSF */
+	struct zfcp_erp_action erp_action;     /* pending error recovery */
+        atomic_t               erp_counter;
+};
+
+/* the struct device sysfs_device must be at the beginning of this structure.
+ * pointer to struct device is used to free unit structure in release function
+ * of the device. don't change!
+ */
+struct zfcp_unit {
+	struct device          sysfs_device;   /* sysfs device */
+	struct list_head       list;	       /* list of logical units */
+	atomic_t               refcount;       /* reference count */
+	wait_queue_head_t      remove_wq;      /* can be used to wait for
+						  refcount drop to zero */
+	struct zfcp_port       *port;	       /* remote port of unit */
+	atomic_t	       status;	       /* status of this logical unit */
+	scsi_lun_t	       scsi_lun;       /* own SCSI LUN */
+	fcp_lun_t	       fcp_lun;	       /* own FCP_LUN */
+	u32		       handle;	       /* handle assigned by FSF */
+        struct scsi_device     *device;        /* scsi device struct pointer */
+	struct zfcp_erp_action erp_action;     /* pending error recovery */
+        atomic_t               erp_counter;
+};
+
+/* FSF request */
+struct zfcp_fsf_req {
+	struct list_head       list;	       /* list of FSF requests */
+	struct zfcp_adapter    *adapter;       /* adapter request belongs to */
+	u8		       sbal_number;    /* nr of SBALs free for use */
+	u8		       sbal_first;     /* first SBAL for this request */
+	u8		       sbal_last;      /* last possible SBAL for
+						  this reuest */
+	u8		       sbal_curr;      /* current SBAL during creation
+						  of request */
+	u8		       sbale_curr;     /* current SBALE during creation
+						  of request */
+	wait_queue_head_t      completion_wq;  /* can be used by a routine
+						  to wait for completion */
+	volatile u32	       status;	       /* status of this request */
+	u32		       fsf_command;    /* FSF Command copy */
+	struct fsf_qtcb	       *qtcb;	       /* address of associated QTCB */
+	u32		       seq_no;         /* Sequence number of request */
+        union zfcp_req_data    data;           /* Info fields of request */ 
+	struct zfcp_erp_action *erp_action;    /* used if this request is
+						  issued on behalf of erp */
+	mempool_t	       *pool;	       /* used if request was alloacted
+						  from emergency pool */
+};
+
+typedef void zfcp_fsf_req_handler_t(struct zfcp_fsf_req*);
+
+/* driver data */
+struct zfcp_data {
+	struct scsi_host_template scsi_host_template;
+        atomic_t                status;             /* Module status flags */
+	struct list_head	adapter_list_head;  /* head of adapter list */
+	struct list_head	adapter_remove_lh;  /* head of adapters to be
+						       removed */
+        rwlock_t                status_read_lock;   /* for status read thread */
+        struct list_head        status_read_receive_head;
+        struct list_head        status_read_send_head;
+        struct semaphore        status_read_sema;
+	wait_queue_head_t	status_read_thread_wqh;
+	u32			adapters;	    /* # of adapters in list */
+	rwlock_t                config_lock;        /* serialises changes
+						       to adapter/port/unit
+						       lists */
+	struct semaphore        config_sema;        /* serialises configuration
+						       changes */
+	atomic_t		loglevel;            /* current loglevel */
+	char                    init_busid[BUS_ID_SIZE];
+	wwn_t                   init_wwpn;
+	fcp_lun_t               init_fcp_lun;
+	char 			*driver_version;
+};
+
+/**
+ * struct zfcp_sg_list - struct describing a scatter-gather list
+ * @sg: pointer to array of (struct scatterlist)
+ * @count: number of elements in scatter-gather list
+ */
+struct zfcp_sg_list {
+	struct scatterlist *sg;
+	unsigned int count;
+};
+
+/* number of elements for various memory pools */
+#define ZFCP_POOL_FSF_REQ_ERP_NR	1
+#define ZFCP_POOL_FSF_REQ_SCSI_NR	1
+#define ZFCP_POOL_FSF_REQ_ABORT_NR	1
+#define ZFCP_POOL_STATUS_READ_NR	ZFCP_STATUS_READS_RECOM
+#define ZFCP_POOL_DATA_GID_PN_NR	1
+
+/* struct used by memory pools for fsf_requests */
+struct zfcp_fsf_req_pool_element {
+	struct zfcp_fsf_req fsf_req;
+	struct fsf_qtcb qtcb;
+};
+
+/********************** ZFCP SPECIFIC DEFINES ********************************/
+
+#define ZFCP_FSFREQ_CLEANUP_TIMEOUT	HZ/10
+
+#define ZFCP_KNOWN              0x00000001
+#define ZFCP_REQ_AUTO_CLEANUP	0x00000002
+#define ZFCP_WAIT_FOR_SBAL	0x00000004
+#define ZFCP_REQ_NO_QTCB	0x00000008
+
+#define ZFCP_SET                0x00000100
+#define ZFCP_CLEAR              0x00000200
+
+#define ZFCP_INTERRUPTIBLE	1
+#define ZFCP_UNINTERRUPTIBLE	0
+
+#ifndef atomic_test_mask
+#define atomic_test_mask(mask, target) \
+           ((atomic_read(target) & mask) == mask)
+#endif
+
+extern void _zfcp_hex_dump(char *, int);
+#define ZFCP_HEX_DUMP(level, addr, count) \
+		if (ZFCP_LOG_CHECK(level)) { \
+			_zfcp_hex_dump(addr, count); \
+		}
+
+#define zfcp_get_busid_by_adapter(adapter) (adapter->ccw_device->dev.bus_id)
+#define zfcp_get_busid_by_port(port) (zfcp_get_busid_by_adapter(port->adapter))
+#define zfcp_get_busid_by_unit(unit) (zfcp_get_busid_by_port(unit->port))
+
+/*
+ *  functions needed for reference/usage counting
+ */
+
+static inline void
+zfcp_unit_get(struct zfcp_unit *unit)
+{
+	atomic_inc(&unit->refcount);
+}
+
+static inline void
+zfcp_unit_put(struct zfcp_unit *unit)
+{
+	if (atomic_dec_return(&unit->refcount) == 0)
+		wake_up(&unit->remove_wq);
+}
+
+static inline void
+zfcp_unit_wait(struct zfcp_unit *unit)
+{
+	wait_event(unit->remove_wq, atomic_read(&unit->refcount) == 0);
+}
+
+static inline void
+zfcp_port_get(struct zfcp_port *port)
+{
+	atomic_inc(&port->refcount);
+}
+
+static inline void
+zfcp_port_put(struct zfcp_port *port)
+{
+	if (atomic_dec_return(&port->refcount) == 0)
+		wake_up(&port->remove_wq);
+}
+
+static inline void
+zfcp_port_wait(struct zfcp_port *port)
+{
+	wait_event(port->remove_wq, atomic_read(&port->refcount) == 0);
+}
+
+static inline void
+zfcp_adapter_get(struct zfcp_adapter *adapter)
+{
+	atomic_inc(&adapter->refcount);
+}
+
+static inline void
+zfcp_adapter_put(struct zfcp_adapter *adapter)
+{
+	if (atomic_dec_return(&adapter->refcount) == 0)
+		wake_up(&adapter->remove_wq);
+}
+
+static inline void
+zfcp_adapter_wait(struct zfcp_adapter *adapter)
+{
+	wait_event(adapter->remove_wq, atomic_read(&adapter->refcount) == 0);
+}
+
+#endif /* ZFCP_DEF_H */
diff --git a/drivers/s390/scsi/zfcp_erp.c b/drivers/s390/scsi/zfcp_erp.c
new file mode 100644
index 0000000..cfc0d8c
--- /dev/null
+++ b/drivers/s390/scsi/zfcp_erp.c
@@ -0,0 +1,3585 @@
+/* 
+ * 
+ * linux/drivers/s390/scsi/zfcp_erp.c 
+ * 
+ * FCP adapter driver for IBM eServer zSeries 
+ * 
+ * (C) Copyright IBM Corp. 2002, 2004
+ *
+ * Author(s): Martin Peschke <mpeschke@de.ibm.com> 
+ *            Raimund Schroeder <raimund.schroeder@de.ibm.com> 
+ *            Aron Zeh
+ *            Wolfgang Taphorn
+ *            Stefan Bader <stefan.bader@de.ibm.com> 
+ *            Heiko Carstens <heiko.carstens@de.ibm.com> 
+ *            Andreas Herrmann <aherrman@de.ibm.com>
+ * 
+ * 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, or (at your option) 
+ * any later version. 
+ * 
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA. 
+ */
+
+#define ZFCP_LOG_AREA			ZFCP_LOG_AREA_ERP
+
+#define ZFCP_ERP_REVISION "$Revision: 1.86 $"
+
+#include "zfcp_ext.h"
+
+static int zfcp_erp_adisc(struct zfcp_adapter *, fc_id_t);
+static void zfcp_erp_adisc_handler(unsigned long);
+
+static int zfcp_erp_adapter_reopen_internal(struct zfcp_adapter *, int);
+static int zfcp_erp_port_forced_reopen_internal(struct zfcp_port *, int);
+static int zfcp_erp_port_reopen_internal(struct zfcp_port *, int);
+static int zfcp_erp_unit_reopen_internal(struct zfcp_unit *, int);
+
+static int zfcp_erp_port_reopen_all_internal(struct zfcp_adapter *, int);
+static int zfcp_erp_unit_reopen_all_internal(struct zfcp_port *, int);
+
+static void zfcp_erp_adapter_block(struct zfcp_adapter *, int);
+static void zfcp_erp_adapter_unblock(struct zfcp_adapter *);
+static void zfcp_erp_port_block(struct zfcp_port *, int);
+static void zfcp_erp_port_unblock(struct zfcp_port *);
+static void zfcp_erp_unit_block(struct zfcp_unit *, int);
+static void zfcp_erp_unit_unblock(struct zfcp_unit *);
+
+static int zfcp_erp_thread(void *);
+
+static int zfcp_erp_strategy(struct zfcp_erp_action *);
+
+static int zfcp_erp_strategy_do_action(struct zfcp_erp_action *);
+static int zfcp_erp_strategy_memwait(struct zfcp_erp_action *);
+static int zfcp_erp_strategy_check_target(struct zfcp_erp_action *, int);
+static int zfcp_erp_strategy_check_unit(struct zfcp_unit *, int);
+static int zfcp_erp_strategy_check_port(struct zfcp_port *, int);
+static int zfcp_erp_strategy_check_adapter(struct zfcp_adapter *, int);
+static int zfcp_erp_strategy_statechange(int, u32, struct zfcp_adapter *,
+					 struct zfcp_port *,
+					 struct zfcp_unit *, int);
+static inline int zfcp_erp_strategy_statechange_detected(atomic_t *, u32);
+static int zfcp_erp_strategy_followup_actions(int, struct zfcp_adapter *,
+					      struct zfcp_port *,
+					      struct zfcp_unit *, int);
+static int zfcp_erp_strategy_check_queues(struct zfcp_adapter *);
+static int zfcp_erp_strategy_check_action(struct zfcp_erp_action *, int);
+
+static int zfcp_erp_adapter_strategy(struct zfcp_erp_action *);
+static int zfcp_erp_adapter_strategy_generic(struct zfcp_erp_action *, int);
+static int zfcp_erp_adapter_strategy_close(struct zfcp_erp_action *);
+static int zfcp_erp_adapter_strategy_close_qdio(struct zfcp_erp_action *);
+static int zfcp_erp_adapter_strategy_close_fsf(struct zfcp_erp_action *);
+static int zfcp_erp_adapter_strategy_open(struct zfcp_erp_action *);
+static int zfcp_erp_adapter_strategy_open_qdio(struct zfcp_erp_action *);
+static int zfcp_erp_adapter_strategy_open_fsf(struct zfcp_erp_action *);
+static int zfcp_erp_adapter_strategy_open_fsf_xconfig(struct zfcp_erp_action *);
+static int zfcp_erp_adapter_strategy_open_fsf_statusread(
+	struct zfcp_erp_action *);
+
+static int zfcp_erp_port_forced_strategy(struct zfcp_erp_action *);
+static int zfcp_erp_port_forced_strategy_close(struct zfcp_erp_action *);
+
+static int zfcp_erp_port_strategy(struct zfcp_erp_action *);
+static int zfcp_erp_port_strategy_clearstati(struct zfcp_port *);
+static int zfcp_erp_port_strategy_close(struct zfcp_erp_action *);
+static int zfcp_erp_port_strategy_open(struct zfcp_erp_action *);
+static int zfcp_erp_port_strategy_open_nameserver(struct zfcp_erp_action *);
+static int zfcp_erp_port_strategy_open_nameserver_wakeup(
+	struct zfcp_erp_action *);
+static int zfcp_erp_port_strategy_open_common(struct zfcp_erp_action *);
+static int zfcp_erp_port_strategy_open_common_lookup(struct zfcp_erp_action *);
+static int zfcp_erp_port_strategy_open_port(struct zfcp_erp_action *);
+
+static int zfcp_erp_unit_strategy(struct zfcp_erp_action *);
+static int zfcp_erp_unit_strategy_clearstati(struct zfcp_unit *);
+static int zfcp_erp_unit_strategy_close(struct zfcp_erp_action *);
+static int zfcp_erp_unit_strategy_open(struct zfcp_erp_action *);
+
+static int zfcp_erp_action_dismiss_adapter(struct zfcp_adapter *);
+static int zfcp_erp_action_dismiss_port(struct zfcp_port *);
+static int zfcp_erp_action_dismiss_unit(struct zfcp_unit *);
+static int zfcp_erp_action_dismiss(struct zfcp_erp_action *);
+
+static int zfcp_erp_action_enqueue(int, struct zfcp_adapter *,
+				   struct zfcp_port *, struct zfcp_unit *);
+static int zfcp_erp_action_dequeue(struct zfcp_erp_action *);
+static void zfcp_erp_action_cleanup(int, struct zfcp_adapter *,
+				    struct zfcp_port *, struct zfcp_unit *,
+				    int);
+
+static void zfcp_erp_action_ready(struct zfcp_erp_action *);
+static int  zfcp_erp_action_exists(struct zfcp_erp_action *);
+
+static inline void zfcp_erp_action_to_ready(struct zfcp_erp_action *);
+static inline void zfcp_erp_action_to_running(struct zfcp_erp_action *);
+
+static void zfcp_erp_memwait_handler(unsigned long);
+static void zfcp_erp_timeout_handler(unsigned long);
+static inline void zfcp_erp_timeout_init(struct zfcp_erp_action *);
+
+/**
+ * zfcp_fsf_request_timeout_handler - called if a request timed out
+ * @data: pointer to adapter for handler function
+ *
+ * This function needs to be called if requests (ELS, Generic Service,
+ * or SCSI commands) exceed a certain time limit. The assumption is
+ * that after the time limit the adapter get stuck. So we trigger a reopen of
+ * the adapter. This should not be used for error recovery, SCSI abort
+ * commands and SCSI requests from SCSI mid-layer.
+ */
+void
+zfcp_fsf_request_timeout_handler(unsigned long data)
+{
+	struct zfcp_adapter *adapter;
+
+	adapter = (struct zfcp_adapter *) data;
+
+	zfcp_erp_adapter_reopen(adapter, 0);
+}
+
+/*
+ * function:	zfcp_fsf_scsi_er_timeout_handler
+ *
+ * purpose:     This function needs to be called whenever a SCSI error recovery
+ *              action (abort/reset) does not return.
+ *              Re-opening the adapter means that the command can be returned
+ *              by zfcp (it is guarranteed that it does not return via the
+ *              adapter anymore). The buffer can then be used again.
+ *    
+ * returns:     sod all
+ */
+void
+zfcp_fsf_scsi_er_timeout_handler(unsigned long data)
+{
+	struct zfcp_adapter *adapter = (struct zfcp_adapter *) data;
+
+	ZFCP_LOG_NORMAL("warning: SCSI error recovery timed out. "
+			"Restarting all operations on the adapter %s\n",
+			zfcp_get_busid_by_adapter(adapter));
+	debug_text_event(adapter->erp_dbf, 1, "eh_lmem_tout");
+	zfcp_erp_adapter_reopen(adapter, 0);
+
+	return;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	called if an adapter failed,
+ *		initiates adapter recovery which is done
+ *		asynchronously
+ *
+ * returns:	0	- initiated action succesfully
+ *		<0	- failed to initiate action
+ */
+int
+zfcp_erp_adapter_reopen_internal(struct zfcp_adapter *adapter, int clear_mask)
+{
+	int retval;
+
+	debug_text_event(adapter->erp_dbf, 5, "a_ro");
+	ZFCP_LOG_DEBUG("reopen adapter %s\n",
+		       zfcp_get_busid_by_adapter(adapter));
+
+	zfcp_erp_adapter_block(adapter, clear_mask);
+
+	if (atomic_test_mask(ZFCP_STATUS_COMMON_ERP_FAILED, &adapter->status)) {
+		ZFCP_LOG_DEBUG("skipped reopen of failed adapter %s\n",
+			       zfcp_get_busid_by_adapter(adapter));
+		debug_text_event(adapter->erp_dbf, 5, "a_ro_f");
+		/* ensure propagation of failed status to new devices */
+		zfcp_erp_adapter_failed(adapter);
+		retval = -EIO;
+		goto out;
+	}
+	retval = zfcp_erp_action_enqueue(ZFCP_ERP_ACTION_REOPEN_ADAPTER,
+					 adapter, NULL, NULL);
+
+ out:
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	Wrappper for zfcp_erp_adapter_reopen_internal
+ *              used to ensure the correct locking
+ *
+ * returns:	0	- initiated action succesfully
+ *		<0	- failed to initiate action
+ */
+int
+zfcp_erp_adapter_reopen(struct zfcp_adapter *adapter, int clear_mask)
+{
+	int retval;
+	unsigned long flags;
+
+	read_lock_irqsave(&zfcp_data.config_lock, flags);
+	write_lock(&adapter->erp_lock);
+	retval = zfcp_erp_adapter_reopen_internal(adapter, clear_mask);
+	write_unlock(&adapter->erp_lock);
+	read_unlock_irqrestore(&zfcp_data.config_lock, flags);
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:
+ */
+int
+zfcp_erp_adapter_shutdown(struct zfcp_adapter *adapter, int clear_mask)
+{
+	int retval;
+
+	retval = zfcp_erp_adapter_reopen(adapter,
+					 ZFCP_STATUS_COMMON_RUNNING |
+					 ZFCP_STATUS_COMMON_ERP_FAILED |
+					 clear_mask);
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:
+ */
+int
+zfcp_erp_port_shutdown(struct zfcp_port *port, int clear_mask)
+{
+	int retval;
+
+	retval = zfcp_erp_port_reopen(port,
+				      ZFCP_STATUS_COMMON_RUNNING |
+				      ZFCP_STATUS_COMMON_ERP_FAILED |
+				      clear_mask);
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:
+ */
+int
+zfcp_erp_unit_shutdown(struct zfcp_unit *unit, int clear_mask)
+{
+	int retval;
+
+	retval = zfcp_erp_unit_reopen(unit,
+				      ZFCP_STATUS_COMMON_RUNNING |
+				      ZFCP_STATUS_COMMON_ERP_FAILED |
+				      clear_mask);
+
+	return retval;
+}
+
+
+/**
+ * zfcp_erp_adisc - send ADISC ELS command
+ * @adapter: adapter structure
+ * @d_id: d_id of port where ADISC is sent to
+ */
+int
+zfcp_erp_adisc(struct zfcp_adapter *adapter, fc_id_t d_id)
+{
+	struct zfcp_send_els *send_els;
+	struct zfcp_ls_adisc *adisc;
+	void *address = NULL;
+	int retval = 0;
+	struct timer_list *timer;
+
+	send_els = kmalloc(sizeof(struct zfcp_send_els), GFP_ATOMIC);
+	if (send_els == NULL)
+		goto nomem;
+	memset(send_els, 0, sizeof(*send_els));
+
+	send_els->req = kmalloc(sizeof(struct scatterlist), GFP_ATOMIC);
+	if (send_els->req == NULL)
+		goto nomem;
+	memset(send_els->req, 0, sizeof(*send_els->req));
+
+	send_els->resp = kmalloc(sizeof(struct scatterlist), GFP_ATOMIC);
+	if (send_els->resp == NULL)
+		goto nomem;
+	memset(send_els->resp, 0, sizeof(*send_els->resp));
+
+	address = (void *) get_zeroed_page(GFP_ATOMIC);
+	if (address == NULL)
+		goto nomem;
+
+	zfcp_address_to_sg(address, send_els->req);
+	address += PAGE_SIZE >> 1;
+	zfcp_address_to_sg(address, send_els->resp);
+	send_els->req_count = send_els->resp_count = 1;
+
+	send_els->adapter = adapter;
+	send_els->d_id = d_id;
+	send_els->handler = zfcp_erp_adisc_handler;
+	send_els->handler_data = (unsigned long) send_els;
+
+	adisc = zfcp_sg_to_address(send_els->req);
+	send_els->ls_code = adisc->code = ZFCP_LS_ADISC;
+
+	send_els->req->length = sizeof(struct zfcp_ls_adisc);
+	send_els->resp->length = sizeof(struct zfcp_ls_adisc_acc);
+
+	/* acc. to FC-FS, hard_nport_id in ADISC should not be set for ports
+	   without FC-AL-2 capability, so we don't set it */
+	adisc->wwpn = adapter->wwpn;
+	adisc->wwnn = adapter->wwnn;
+	adisc->nport_id = adapter->s_id;
+	ZFCP_LOG_INFO("ADISC request from s_id 0x%08x to d_id 0x%08x "
+		      "(wwpn=0x%016Lx, wwnn=0x%016Lx, "
+		      "hard_nport_id=0x%08x, nport_id=0x%08x)\n",
+		      adapter->s_id, d_id, (wwn_t) adisc->wwpn,
+		      (wwn_t) adisc->wwnn, adisc->hard_nport_id,
+		      adisc->nport_id);
+
+	timer = kmalloc(sizeof(struct timer_list), GFP_ATOMIC);
+	if (!timer)
+		goto nomem;
+
+	init_timer(timer);
+	timer->function = zfcp_fsf_request_timeout_handler;
+	timer->data = (unsigned long) adapter;
+	timer->expires = ZFCP_FSF_REQUEST_TIMEOUT;
+	send_els->timer = timer;
+
+	retval = zfcp_fsf_send_els(send_els);
+	if (retval != 0) {
+		ZFCP_LOG_NORMAL("error: initiation of Send ELS failed for port "
+				"0x%08x on adapter %s\n", d_id,
+				zfcp_get_busid_by_adapter(adapter));
+		del_timer(send_els->timer);
+		goto freemem;
+	}
+
+	goto out;
+
+ nomem:
+	retval = -ENOMEM;
+ freemem:
+	if (address != NULL)
+		__free_pages(send_els->req->page, 0);
+	if (send_els != NULL) {
+		kfree(send_els->timer);
+		kfree(send_els->req);
+		kfree(send_els->resp);
+		kfree(send_els);
+	}
+ out:
+	return retval;
+}
+
+
+/**
+ * zfcp_erp_adisc_handler - handler for ADISC ELS command
+ * @data: pointer to struct zfcp_send_els
+ *
+ * If ADISC failed (LS_RJT or timed out) forced reopen of the port is triggered.
+ */
+void
+zfcp_erp_adisc_handler(unsigned long data)
+{
+	struct zfcp_send_els *send_els;
+	struct zfcp_port *port;
+	struct zfcp_adapter *adapter;
+	fc_id_t d_id;
+	struct zfcp_ls_adisc_acc *adisc;
+
+	send_els = (struct zfcp_send_els *) data;
+
+	del_timer(send_els->timer);
+
+	adapter = send_els->adapter;
+	d_id = send_els->d_id;
+
+	read_lock(&zfcp_data.config_lock);
+	port = zfcp_get_port_by_did(send_els->adapter, send_els->d_id);
+	read_unlock(&zfcp_data.config_lock);
+
+	BUG_ON(port == NULL);
+
+	/* request rejected or timed out */
+	if (send_els->status != 0) {
+		ZFCP_LOG_NORMAL("ELS request rejected/timed out, "
+				"force physical port reopen "
+				"(adapter %s, port d_id=0x%08x)\n",
+				zfcp_get_busid_by_adapter(adapter), d_id);
+		debug_text_event(adapter->erp_dbf, 3, "forcreop");
+		if (zfcp_erp_port_forced_reopen(port, 0))
+			ZFCP_LOG_NORMAL("failed reopen of port "
+					"(adapter %s, wwpn=0x%016Lx)\n",
+					zfcp_get_busid_by_port(port),
+					port->wwpn);
+		goto out;
+	}
+
+	adisc = zfcp_sg_to_address(send_els->resp);
+
+	ZFCP_LOG_INFO("ADISC response from d_id 0x%08x to s_id "
+		      "0x%08x (wwpn=0x%016Lx, wwnn=0x%016Lx, "
+		      "hard_nport_id=0x%08x, nport_id=0x%08x)\n",
+		      d_id, adapter->s_id, (wwn_t) adisc->wwpn,
+		      (wwn_t) adisc->wwnn, adisc->hard_nport_id,
+		      adisc->nport_id);
+
+	/* set wwnn for port */
+	if (port->wwnn == 0)
+		port->wwnn = adisc->wwnn;
+
+	if (port->wwpn != adisc->wwpn) {
+		ZFCP_LOG_NORMAL("d_id assignment changed, reopening "
+				"port (adapter %s, wwpn=0x%016Lx, "
+				"adisc_resp_wwpn=0x%016Lx)\n",
+				zfcp_get_busid_by_port(port),
+				port->wwpn, (wwn_t) adisc->wwpn);
+		if (zfcp_erp_port_reopen(port, 0))
+			ZFCP_LOG_NORMAL("failed reopen of port "
+					"(adapter %s, wwpn=0x%016Lx)\n",
+					zfcp_get_busid_by_port(port),
+					port->wwpn);
+	}
+
+ out:
+	zfcp_port_put(port);
+	__free_pages(send_els->req->page, 0);
+	kfree(send_els->timer);
+	kfree(send_els->req);
+	kfree(send_els->resp);
+	kfree(send_els);
+}
+
+
+/**
+ * zfcp_test_link - lightweight link test procedure
+ * @port: port to be tested
+ *
+ * Test status of a link to a remote port using the ELS command ADISC.
+ */
+int
+zfcp_test_link(struct zfcp_port *port)
+{
+	int retval;
+
+	zfcp_port_get(port);
+	retval = zfcp_erp_adisc(port->adapter, port->d_id);
+	if (retval != 0) {
+		zfcp_port_put(port);
+		ZFCP_LOG_NORMAL("reopen needed for port 0x%016Lx "
+				"on adapter %s\n ", port->wwpn,
+				zfcp_get_busid_by_port(port));
+		retval = zfcp_erp_port_forced_reopen(port, 0);
+		if (retval != 0) {
+			ZFCP_LOG_NORMAL("reopen of remote port 0x%016Lx "
+					"on adapter %s failed\n", port->wwpn,
+					zfcp_get_busid_by_port(port));
+			retval = -EPERM;
+		}
+	}
+
+	return retval;
+}
+
+
+/*
+ * function:	
+ *
+ * purpose:	called if a port failed to be opened normally
+ *		initiates Forced Reopen recovery which is done
+ *		asynchronously
+ *
+ * returns:	0	- initiated action succesfully
+ *		<0	- failed to initiate action
+ */
+static int
+zfcp_erp_port_forced_reopen_internal(struct zfcp_port *port, int clear_mask)
+{
+	int retval;
+	struct zfcp_adapter *adapter = port->adapter;
+
+	debug_text_event(adapter->erp_dbf, 5, "pf_ro");
+	debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t));
+
+	ZFCP_LOG_DEBUG("forced reopen of port 0x%016Lx on adapter %s\n",
+		       port->wwpn, zfcp_get_busid_by_port(port));
+
+	zfcp_erp_port_block(port, clear_mask);
+
+	if (atomic_test_mask(ZFCP_STATUS_COMMON_ERP_FAILED, &port->status)) {
+		ZFCP_LOG_DEBUG("skipped forced reopen of failed port 0x%016Lx "
+			       "on adapter %s\n", port->wwpn,
+			       zfcp_get_busid_by_port(port));
+		debug_text_event(adapter->erp_dbf, 5, "pf_ro_f");
+		debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t));
+		retval = -EIO;
+		goto out;
+	}
+
+	retval = zfcp_erp_action_enqueue(ZFCP_ERP_ACTION_REOPEN_PORT_FORCED,
+					 port->adapter, port, NULL);
+
+ out:
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	Wrappper for zfcp_erp_port_forced_reopen_internal
+ *              used to ensure the correct locking
+ *
+ * returns:	0	- initiated action succesfully
+ *		<0	- failed to initiate action
+ */
+int
+zfcp_erp_port_forced_reopen(struct zfcp_port *port, int clear_mask)
+{
+	int retval;
+	unsigned long flags;
+	struct zfcp_adapter *adapter;
+
+	adapter = port->adapter;
+	read_lock_irqsave(&zfcp_data.config_lock, flags);
+	write_lock(&adapter->erp_lock);
+	retval = zfcp_erp_port_forced_reopen_internal(port, clear_mask);
+	write_unlock(&adapter->erp_lock);
+	read_unlock_irqrestore(&zfcp_data.config_lock, flags);
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	called if a port is to be opened
+ *		initiates Reopen recovery which is done
+ *		asynchronously
+ *
+ * returns:	0	- initiated action succesfully
+ *		<0	- failed to initiate action
+ */
+static int
+zfcp_erp_port_reopen_internal(struct zfcp_port *port, int clear_mask)
+{
+	int retval;
+	struct zfcp_adapter *adapter = port->adapter;
+
+	debug_text_event(adapter->erp_dbf, 5, "p_ro");
+	debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t));
+
+	ZFCP_LOG_DEBUG("reopen of port 0x%016Lx on adapter %s\n",
+		       port->wwpn, zfcp_get_busid_by_port(port));
+
+	zfcp_erp_port_block(port, clear_mask);
+
+	if (atomic_test_mask(ZFCP_STATUS_COMMON_ERP_FAILED, &port->status)) {
+		ZFCP_LOG_DEBUG("skipped reopen of failed port 0x%016Lx "
+			       "on adapter %s\n", port->wwpn,
+			       zfcp_get_busid_by_port(port));
+		debug_text_event(adapter->erp_dbf, 5, "p_ro_f");
+		debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t));
+		/* ensure propagation of failed status to new devices */
+		zfcp_erp_port_failed(port);
+		retval = -EIO;
+		goto out;
+	}
+
+	retval = zfcp_erp_action_enqueue(ZFCP_ERP_ACTION_REOPEN_PORT,
+					 port->adapter, port, NULL);
+
+ out:
+	return retval;
+}
+
+/**
+ * zfcp_erp_port_reopen - initiate reopen of a remote port
+ * @port: port to be reopened
+ * @clear_mask: specifies flags in port status to be cleared
+ * Return: 0 on success, < 0 on error
+ *
+ * This is a wrappper function for zfcp_erp_port_reopen_internal. It ensures
+ * correct locking. An error recovery task is initiated to do the reopen.
+ * To wait for the completion of the reopen zfcp_erp_wait should be used.
+ */
+int
+zfcp_erp_port_reopen(struct zfcp_port *port, int clear_mask)
+{
+	int retval;
+	unsigned long flags;
+	struct zfcp_adapter *adapter = port->adapter;
+
+	read_lock_irqsave(&zfcp_data.config_lock, flags);
+	write_lock(&adapter->erp_lock);
+	retval = zfcp_erp_port_reopen_internal(port, clear_mask);
+	write_unlock(&adapter->erp_lock);
+	read_unlock_irqrestore(&zfcp_data.config_lock, flags);
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	called if a unit is to be opened
+ *		initiates Reopen recovery which is done
+ *		asynchronously
+ *
+ * returns:	0	- initiated action succesfully
+ *		<0	- failed to initiate action
+ */
+static int
+zfcp_erp_unit_reopen_internal(struct zfcp_unit *unit, int clear_mask)
+{
+	int retval;
+	struct zfcp_adapter *adapter = unit->port->adapter;
+
+	debug_text_event(adapter->erp_dbf, 5, "u_ro");
+	debug_event(adapter->erp_dbf, 5, &unit->fcp_lun, sizeof (fcp_lun_t));
+	ZFCP_LOG_DEBUG("reopen of unit 0x%016Lx on port 0x%016Lx "
+		       "on adapter %s\n", unit->fcp_lun,
+		       unit->port->wwpn, zfcp_get_busid_by_unit(unit));
+
+	zfcp_erp_unit_block(unit, clear_mask);
+
+	if (atomic_test_mask(ZFCP_STATUS_COMMON_ERP_FAILED, &unit->status)) {
+		ZFCP_LOG_DEBUG("skipped reopen of failed unit 0x%016Lx "
+			       "on port 0x%016Lx on adapter %s\n",
+			       unit->fcp_lun, unit->port->wwpn,
+			       zfcp_get_busid_by_unit(unit));
+		debug_text_event(adapter->erp_dbf, 5, "u_ro_f");
+		debug_event(adapter->erp_dbf, 5, &unit->fcp_lun,
+			    sizeof (fcp_lun_t));
+		retval = -EIO;
+		goto out;
+	}
+
+	retval = zfcp_erp_action_enqueue(ZFCP_ERP_ACTION_REOPEN_UNIT,
+					 unit->port->adapter, unit->port, unit);
+ out:
+	return retval;
+}
+
+/**
+ * zfcp_erp_unit_reopen - initiate reopen of a unit
+ * @unit: unit to be reopened
+ * @clear_mask: specifies flags in unit status to be cleared
+ * Return: 0 on success, < 0 on error
+ *
+ * This is a wrappper for zfcp_erp_unit_reopen_internal. It ensures correct
+ * locking. An error recovery task is initiated to do the reopen.
+ * To wait for the completion of the reopen zfcp_erp_wait should be used.
+ */
+int
+zfcp_erp_unit_reopen(struct zfcp_unit *unit, int clear_mask)
+{
+	int retval;
+	unsigned long flags;
+	struct zfcp_adapter *adapter;
+	struct zfcp_port *port;
+
+	port = unit->port;
+	adapter = port->adapter;
+
+	read_lock_irqsave(&zfcp_data.config_lock, flags);
+	write_lock(&adapter->erp_lock);
+	retval = zfcp_erp_unit_reopen_internal(unit, clear_mask);
+	write_unlock(&adapter->erp_lock);
+	read_unlock_irqrestore(&zfcp_data.config_lock, flags);
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	disable I/O,
+ *		return any open requests and clean them up,
+ *		aim: no pending and incoming I/O
+ *
+ * returns:
+ */
+static void
+zfcp_erp_adapter_block(struct zfcp_adapter *adapter, int clear_mask)
+{
+	debug_text_event(adapter->erp_dbf, 6, "a_bl");
+	zfcp_erp_modify_adapter_status(adapter,
+				       ZFCP_STATUS_COMMON_UNBLOCKED |
+				       clear_mask, ZFCP_CLEAR);
+}
+
+/*
+ * function:	
+ *
+ * purpose:	enable I/O
+ *
+ * returns:
+ */
+static void
+zfcp_erp_adapter_unblock(struct zfcp_adapter *adapter)
+{
+	debug_text_event(adapter->erp_dbf, 6, "a_ubl");
+	atomic_set_mask(ZFCP_STATUS_COMMON_UNBLOCKED, &adapter->status);
+}
+
+/*
+ * function:	
+ *
+ * purpose:	disable I/O,
+ *		return any open requests and clean them up,
+ *		aim: no pending and incoming I/O
+ *
+ * returns:
+ */
+static void
+zfcp_erp_port_block(struct zfcp_port *port, int clear_mask)
+{
+	struct zfcp_adapter *adapter = port->adapter;
+
+	debug_text_event(adapter->erp_dbf, 6, "p_bl");
+	debug_event(adapter->erp_dbf, 6, &port->wwpn, sizeof (wwn_t));
+	zfcp_erp_modify_port_status(port,
+				    ZFCP_STATUS_COMMON_UNBLOCKED | clear_mask,
+				    ZFCP_CLEAR);
+}
+
+/*
+ * function:	
+ *
+ * purpose:	enable I/O
+ *
+ * returns:
+ */
+static void
+zfcp_erp_port_unblock(struct zfcp_port *port)
+{
+	struct zfcp_adapter *adapter = port->adapter;
+
+	debug_text_event(adapter->erp_dbf, 6, "p_ubl");
+	debug_event(adapter->erp_dbf, 6, &port->wwpn, sizeof (wwn_t));
+	atomic_set_mask(ZFCP_STATUS_COMMON_UNBLOCKED, &port->status);
+}
+
+/*
+ * function:	
+ *
+ * purpose:	disable I/O,
+ *		return any open requests and clean them up,
+ *		aim: no pending and incoming I/O
+ *
+ * returns:
+ */
+static void
+zfcp_erp_unit_block(struct zfcp_unit *unit, int clear_mask)
+{
+	struct zfcp_adapter *adapter = unit->port->adapter;
+
+	debug_text_event(adapter->erp_dbf, 6, "u_bl");
+	debug_event(adapter->erp_dbf, 6, &unit->fcp_lun, sizeof (fcp_lun_t));
+	zfcp_erp_modify_unit_status(unit,
+				    ZFCP_STATUS_COMMON_UNBLOCKED | clear_mask,
+				    ZFCP_CLEAR);
+}
+
+/*
+ * function:	
+ *
+ * purpose:	enable I/O
+ *
+ * returns:
+ */
+static void
+zfcp_erp_unit_unblock(struct zfcp_unit *unit)
+{
+	struct zfcp_adapter *adapter = unit->port->adapter;
+
+	debug_text_event(adapter->erp_dbf, 6, "u_ubl");
+	debug_event(adapter->erp_dbf, 6, &unit->fcp_lun, sizeof (fcp_lun_t));
+	atomic_set_mask(ZFCP_STATUS_COMMON_UNBLOCKED, &unit->status);
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:
+ */
+static void
+zfcp_erp_action_ready(struct zfcp_erp_action *erp_action)
+{
+	struct zfcp_adapter *adapter = erp_action->adapter;
+
+	debug_text_event(adapter->erp_dbf, 4, "a_ar");
+	debug_event(adapter->erp_dbf, 4, &erp_action->action, sizeof (int));
+
+	zfcp_erp_action_to_ready(erp_action);
+	up(&adapter->erp_ready_sem);
+}
+
+/*
+ * function:	
+ *
+ * purpose:
+ *
+ * returns:	<0			erp_action not found in any list
+ *		ZFCP_ERP_ACTION_READY	erp_action is in ready list
+ *		ZFCP_ERP_ACTION_RUNNING	erp_action is in running list
+ *
+ * locks:	erp_lock must be held
+ */
+static int
+zfcp_erp_action_exists(struct zfcp_erp_action *erp_action)
+{
+	int retval = -EINVAL;
+	struct list_head *entry;
+	struct zfcp_erp_action *entry_erp_action;
+	struct zfcp_adapter *adapter = erp_action->adapter;
+
+	/* search in running list */
+	list_for_each(entry, &adapter->erp_running_head) {
+		entry_erp_action =
+		    list_entry(entry, struct zfcp_erp_action, list);
+		if (entry_erp_action == erp_action) {
+			retval = ZFCP_ERP_ACTION_RUNNING;
+			goto out;
+		}
+	}
+	/* search in ready list */
+	list_for_each(entry, &adapter->erp_ready_head) {
+		entry_erp_action =
+		    list_entry(entry, struct zfcp_erp_action, list);
+		if (entry_erp_action == erp_action) {
+			retval = ZFCP_ERP_ACTION_READY;
+			goto out;
+		}
+	}
+
+ out:
+	return retval;
+}
+
+/*
+ * purpose:	checks current status of action (timed out, dismissed, ...)
+ *		and does appropriate preparations (dismiss fsf request, ...)
+ *
+ * locks:	called under erp_lock (disabled interrupts)
+ *
+ * returns:	0
+ */
+static int
+zfcp_erp_strategy_check_fsfreq(struct zfcp_erp_action *erp_action)
+{
+	int retval = 0;
+	struct zfcp_fsf_req *fsf_req;
+	struct zfcp_adapter *adapter = erp_action->adapter;
+
+	if (erp_action->fsf_req) {
+		/* take lock to ensure that request is not being deleted meanwhile */
+		write_lock(&adapter->fsf_req_list_lock);
+		/* check whether fsf req does still exist */
+		list_for_each_entry(fsf_req, &adapter->fsf_req_list_head, list)
+		    if (fsf_req == erp_action->fsf_req)
+			break;
+		if (fsf_req == erp_action->fsf_req) {
+			/* fsf_req still exists */
+			debug_text_event(adapter->erp_dbf, 3, "a_ca_req");
+			debug_event(adapter->erp_dbf, 3, &fsf_req,
+				    sizeof (unsigned long));
+			/* dismiss fsf_req of timed out or dismissed erp_action */
+			if (erp_action->status & (ZFCP_STATUS_ERP_DISMISSED |
+						  ZFCP_STATUS_ERP_TIMEDOUT)) {
+				debug_text_event(adapter->erp_dbf, 3,
+						 "a_ca_disreq");
+				fsf_req->status |= ZFCP_STATUS_FSFREQ_DISMISSED;
+			}
+			if (erp_action->status & ZFCP_STATUS_ERP_TIMEDOUT) {
+				ZFCP_LOG_NORMAL("error: erp step timed out "
+						"(action=%d, fsf_req=%p)\n ",
+						erp_action->action,
+						erp_action->fsf_req);
+			}
+			/*
+			 * If fsf_req is neither dismissed nor completed
+			 * then keep it running asynchronously and don't mess
+			 * with the association of erp_action and fsf_req.
+			 */
+			if (fsf_req->status & (ZFCP_STATUS_FSFREQ_COMPLETED |
+					       ZFCP_STATUS_FSFREQ_DISMISSED)) {
+				/* forget about association between fsf_req
+				   and erp_action */
+				fsf_req->erp_action = NULL;
+				erp_action->fsf_req = NULL;
+			}
+		} else {
+			debug_text_event(adapter->erp_dbf, 3, "a_ca_gonereq");
+			/*
+			 * even if this fsf_req has gone, forget about
+			 * association between erp_action and fsf_req
+			 */
+			erp_action->fsf_req = NULL;
+		}
+		write_unlock(&adapter->fsf_req_list_lock);
+	} else
+		debug_text_event(adapter->erp_dbf, 3, "a_ca_noreq");
+
+	return retval;
+}
+
+/*
+ * purpose:	generic handler for asynchronous events related to erp_action events
+ *		(normal completion, time-out, dismissing, retry after
+ *		low memory condition)
+ *
+ * note:	deletion of timer is not required (e.g. in case of a time-out),
+ *		but a second try does no harm,
+ *		we leave it in here to allow for greater simplification
+ *
+ * returns:	0 - there was an action to handle
+ *		!0 - otherwise
+ */
+static int
+zfcp_erp_async_handler_nolock(struct zfcp_erp_action *erp_action,
+			      unsigned long set_mask)
+{
+	int retval;
+	struct zfcp_adapter *adapter = erp_action->adapter;
+
+	if (zfcp_erp_action_exists(erp_action) == ZFCP_ERP_ACTION_RUNNING) {
+		debug_text_event(adapter->erp_dbf, 2, "a_asyh_ex");
+		debug_event(adapter->erp_dbf, 2, &erp_action->action,
+			    sizeof (int));
+		if (!(set_mask & ZFCP_STATUS_ERP_TIMEDOUT))
+			del_timer(&erp_action->timer);
+		erp_action->status |= set_mask;
+		zfcp_erp_action_ready(erp_action);
+		retval = 0;
+	} else {
+		/* action is ready or gone - nothing to do */
+		debug_text_event(adapter->erp_dbf, 3, "a_asyh_gone");
+		debug_event(adapter->erp_dbf, 3, &erp_action->action,
+			    sizeof (int));
+		retval = 1;
+	}
+
+	return retval;
+}
+
+/*
+ * purpose:	generic handler for asynchronous events related to erp_action
+ *               events	(normal completion, time-out, dismissing, retry after
+ *		low memory condition)
+ *
+ * note:	deletion of timer is not required (e.g. in case of a time-out),
+ *		but a second try does no harm,
+ *		we leave it in here to allow for greater simplification
+ *
+ * returns:	0 - there was an action to handle
+ *		!0 - otherwise
+ */
+int
+zfcp_erp_async_handler(struct zfcp_erp_action *erp_action,
+		       unsigned long set_mask)
+{
+	struct zfcp_adapter *adapter = erp_action->adapter;
+	unsigned long flags;
+	int retval;
+
+	write_lock_irqsave(&adapter->erp_lock, flags);
+	retval = zfcp_erp_async_handler_nolock(erp_action, set_mask);
+	write_unlock_irqrestore(&adapter->erp_lock, flags);
+
+	return retval;
+}
+
+/*
+ * purpose:	is called for erp_action which was slept waiting for
+ *		memory becoming avaliable,
+ *		will trigger that this action will be continued
+ */
+static void
+zfcp_erp_memwait_handler(unsigned long data)
+{
+	struct zfcp_erp_action *erp_action = (struct zfcp_erp_action *) data;
+	struct zfcp_adapter *adapter = erp_action->adapter;
+
+	debug_text_event(adapter->erp_dbf, 2, "a_mwh");
+	debug_event(adapter->erp_dbf, 2, &erp_action->action, sizeof (int));
+
+	zfcp_erp_async_handler(erp_action, 0);
+}
+
+/*
+ * purpose:	is called if an asynchronous erp step timed out,
+ *		action gets an appropriate flag and will be processed
+ *		accordingly
+ */
+static void
+zfcp_erp_timeout_handler(unsigned long data)
+{
+	struct zfcp_erp_action *erp_action = (struct zfcp_erp_action *) data;
+	struct zfcp_adapter *adapter = erp_action->adapter;
+
+	debug_text_event(adapter->erp_dbf, 2, "a_th");
+	debug_event(adapter->erp_dbf, 2, &erp_action->action, sizeof (int));
+
+	zfcp_erp_async_handler(erp_action, ZFCP_STATUS_ERP_TIMEDOUT);
+}
+
+/*
+ * purpose:	is called for an erp_action which needs to be ended
+ *		though not being done,
+ *		this is usually required if an higher is generated,
+ *		action gets an appropriate flag and will be processed
+ *		accordingly
+ *
+ * locks:	erp_lock held (thus we need to call another handler variant)
+ */
+static int
+zfcp_erp_action_dismiss(struct zfcp_erp_action *erp_action)
+{
+	struct zfcp_adapter *adapter = erp_action->adapter;
+
+	debug_text_event(adapter->erp_dbf, 2, "a_adis");
+	debug_event(adapter->erp_dbf, 2, &erp_action->action, sizeof (int));
+
+	zfcp_erp_async_handler_nolock(erp_action, ZFCP_STATUS_ERP_DISMISSED);
+
+	return 0;
+}
+
+int
+zfcp_erp_thread_setup(struct zfcp_adapter *adapter)
+{
+	int retval = 0;
+
+	atomic_clear_mask(ZFCP_STATUS_ADAPTER_ERP_THREAD_UP, &adapter->status);
+
+	rwlock_init(&adapter->erp_lock);
+	INIT_LIST_HEAD(&adapter->erp_ready_head);
+	INIT_LIST_HEAD(&adapter->erp_running_head);
+	sema_init(&adapter->erp_ready_sem, 0);
+
+	retval = kernel_thread(zfcp_erp_thread, adapter, SIGCHLD);
+	if (retval < 0) {
+		ZFCP_LOG_NORMAL("error: creation of erp thread failed for "
+				"adapter %s\n",
+				zfcp_get_busid_by_adapter(adapter));
+		debug_text_event(adapter->erp_dbf, 5, "a_thset_fail");
+	} else {
+		wait_event(adapter->erp_thread_wqh,
+			   atomic_test_mask(ZFCP_STATUS_ADAPTER_ERP_THREAD_UP,
+					    &adapter->status));
+		debug_text_event(adapter->erp_dbf, 5, "a_thset_ok");
+	}
+
+	return (retval < 0);
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:
+ *
+ * context:	process (i.e. proc-fs or rmmod/insmod)
+ *
+ * note:	The caller of this routine ensures that the specified
+ *		adapter has been shut down and that this operation
+ *		has been completed. Thus, there are no pending erp_actions
+ *		which would need to be handled here.
+ */
+int
+zfcp_erp_thread_kill(struct zfcp_adapter *adapter)
+{
+	int retval = 0;
+
+	atomic_set_mask(ZFCP_STATUS_ADAPTER_ERP_THREAD_KILL, &adapter->status);
+	up(&adapter->erp_ready_sem);
+
+	wait_event(adapter->erp_thread_wqh,
+		   !atomic_test_mask(ZFCP_STATUS_ADAPTER_ERP_THREAD_UP,
+				     &adapter->status));
+
+	atomic_clear_mask(ZFCP_STATUS_ADAPTER_ERP_THREAD_KILL,
+			  &adapter->status);
+
+	debug_text_event(adapter->erp_dbf, 5, "a_thki_ok");
+
+	return retval;
+}
+
+/*
+ * purpose:	is run as a kernel thread,
+ *		goes through list of error recovery actions of associated adapter
+ *		and delegates single action to execution
+ *
+ * returns:	0
+ */
+static int
+zfcp_erp_thread(void *data)
+{
+	struct zfcp_adapter *adapter = (struct zfcp_adapter *) data;
+	struct list_head *next;
+	struct zfcp_erp_action *erp_action;
+	unsigned long flags;
+
+	daemonize("zfcperp%s", zfcp_get_busid_by_adapter(adapter));
+	/* Block all signals */
+	siginitsetinv(&current->blocked, 0);
+	atomic_set_mask(ZFCP_STATUS_ADAPTER_ERP_THREAD_UP, &adapter->status);
+	debug_text_event(adapter->erp_dbf, 5, "a_th_run");
+	wake_up(&adapter->erp_thread_wqh);
+
+	while (!atomic_test_mask(ZFCP_STATUS_ADAPTER_ERP_THREAD_KILL,
+				 &adapter->status)) {
+
+		write_lock_irqsave(&adapter->erp_lock, flags);
+		next = adapter->erp_ready_head.prev;
+		write_unlock_irqrestore(&adapter->erp_lock, flags);
+
+		if (next != &adapter->erp_ready_head) {
+			erp_action =
+			    list_entry(next, struct zfcp_erp_action, list);
+			/*
+			 * process action (incl. [re]moving it
+			 * from 'ready' queue)
+			 */
+			zfcp_erp_strategy(erp_action);
+		}
+
+		/*
+		 * sleep as long as there is nothing to do, i.e.
+		 * no action in 'ready' queue to be processed and
+		 * thread is not to be killed
+		 */
+		down_interruptible(&adapter->erp_ready_sem);
+		debug_text_event(adapter->erp_dbf, 5, "a_th_woken");
+	}
+
+	atomic_clear_mask(ZFCP_STATUS_ADAPTER_ERP_THREAD_UP, &adapter->status);
+	debug_text_event(adapter->erp_dbf, 5, "a_th_stop");
+	wake_up(&adapter->erp_thread_wqh);
+
+	return 0;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	drives single error recovery action and schedules higher and
+ *		subordinate actions, if necessary
+ *
+ * returns:	ZFCP_ERP_CONTINUES	- action continues (asynchronously)
+ *		ZFCP_ERP_SUCCEEDED	- action finished successfully (deqd)
+ *		ZFCP_ERP_FAILED		- action finished unsuccessfully (deqd)
+ *		ZFCP_ERP_EXIT		- action finished (dequeued), offline
+ *		ZFCP_ERP_DISMISSED	- action canceled (dequeued)
+ */
+static int
+zfcp_erp_strategy(struct zfcp_erp_action *erp_action)
+{
+	int retval = 0;
+	struct zfcp_adapter *adapter = erp_action->adapter;
+	struct zfcp_port *port = erp_action->port;
+	struct zfcp_unit *unit = erp_action->unit;
+	int action = erp_action->action;
+	u32 status = erp_action->status;
+	unsigned long flags;
+
+	/* serialise dismissing, timing out, moving, enqueueing */
+	read_lock_irqsave(&zfcp_data.config_lock, flags);
+	write_lock(&adapter->erp_lock);
+
+	/* dequeue dismissed action and leave, if required */
+	retval = zfcp_erp_strategy_check_action(erp_action, retval);
+	if (retval == ZFCP_ERP_DISMISSED) {
+		debug_text_event(adapter->erp_dbf, 4, "a_st_dis1");
+		goto unlock;
+	}
+
+	/*
+	 * move action to 'running' queue before processing it
+	 * (to avoid a race condition regarding moving the
+	 * action to the 'running' queue and back)
+	 */
+	zfcp_erp_action_to_running(erp_action);
+
+	/*
+	 * try to process action as far as possible,
+	 * no lock to allow for blocking operations (kmalloc, qdio, ...),
+	 * afterwards the lock is required again for the following reasons:
+	 * - dequeueing of finished action and enqueueing of
+	 *   follow-up actions must be atomic so that any other
+	 *   reopen-routine does not believe there is nothing to do
+	 *   and that it is safe to enqueue something else,
+	 * - we want to force any control thread which is dismissing
+	 *   actions to finish this before we decide about
+	 *   necessary steps to be taken here further
+	 */
+	write_unlock(&adapter->erp_lock);
+	read_unlock_irqrestore(&zfcp_data.config_lock, flags);
+	retval = zfcp_erp_strategy_do_action(erp_action);
+	read_lock_irqsave(&zfcp_data.config_lock, flags);
+	write_lock(&adapter->erp_lock);
+
+	/*
+	 * check for dismissed status again to avoid follow-up actions,
+	 * failing of targets and so on for dismissed actions
+	 */
+	retval = zfcp_erp_strategy_check_action(erp_action, retval);
+
+	switch (retval) {
+	case ZFCP_ERP_DISMISSED:
+		/* leave since this action has ridden to its ancestors */
+		debug_text_event(adapter->erp_dbf, 6, "a_st_dis2");
+		goto unlock;
+	case ZFCP_ERP_NOMEM:
+		/* no memory to continue immediately, let it sleep */
+		if (!(erp_action->status & ZFCP_STATUS_ERP_LOWMEM)) {
+			++adapter->erp_low_mem_count;
+			erp_action->status |= ZFCP_STATUS_ERP_LOWMEM;
+		}
+		/* This condition is true if there is no memory available
+		   for any erp_action on this adapter. This implies that there
+		   are no elements in the memory pool(s) left for erp_actions.
+		   This might happen if an erp_action that used a memory pool
+		   element was timed out.
+		 */
+		if (adapter->erp_total_count == adapter->erp_low_mem_count) {
+			debug_text_event(adapter->erp_dbf, 3, "a_st_lowmem");
+			ZFCP_LOG_NORMAL("error: no mempool elements available, "
+					"restarting I/O on adapter %s "
+					"to free mempool\n",
+					zfcp_get_busid_by_adapter(adapter));
+			zfcp_erp_adapter_reopen_internal(adapter, 0);
+		} else {
+		debug_text_event(adapter->erp_dbf, 2, "a_st_memw");
+		retval = zfcp_erp_strategy_memwait(erp_action);
+		}
+		goto unlock;
+	case ZFCP_ERP_CONTINUES:
+		/* leave since this action runs asynchronously */
+		debug_text_event(adapter->erp_dbf, 6, "a_st_cont");
+		if (erp_action->status & ZFCP_STATUS_ERP_LOWMEM) {
+			--adapter->erp_low_mem_count;
+			erp_action->status &= ~ZFCP_STATUS_ERP_LOWMEM;
+		}
+		goto unlock;
+	}
+	/* ok, finished action (whatever its result is) */
+
+	/* check for unrecoverable targets */
+	retval = zfcp_erp_strategy_check_target(erp_action, retval);
+
+	/* action must be dequeued (here to allow for further ones) */
+	zfcp_erp_action_dequeue(erp_action);
+
+	/*
+	 * put this target through the erp mill again if someone has
+	 * requested to change the status of a target being online 
+	 * to offline or the other way around
+	 * (old retval is preserved if nothing has to be done here)
+	 */
+	retval = zfcp_erp_strategy_statechange(action, status, adapter,
+					       port, unit, retval);
+
+	/*
+	 * leave if target is in permanent error state or if
+	 * action is repeated in order to process state change
+	 */
+	if (retval == ZFCP_ERP_EXIT) {
+		debug_text_event(adapter->erp_dbf, 2, "a_st_exit");
+		goto unlock;
+	}
+
+	/* trigger follow up actions */
+	zfcp_erp_strategy_followup_actions(action, adapter, port, unit, retval);
+
+ unlock:
+	write_unlock(&adapter->erp_lock);
+	read_unlock_irqrestore(&zfcp_data.config_lock, flags);
+	
+	if (retval != ZFCP_ERP_CONTINUES)
+		zfcp_erp_action_cleanup(action, adapter, port, unit, retval);
+
+	/*
+	 * a few tasks remain when the erp queues are empty
+	 * (don't do that if the last action evaluated was dismissed
+	 * since this clearly indicates that there is more to come) :
+	 * - close the name server port if it is open yet
+	 *   (enqueues another [probably] final action)
+	 * - otherwise, wake up whoever wants to be woken when we are
+	 *   done with erp
+	 */
+	if (retval != ZFCP_ERP_DISMISSED)
+		zfcp_erp_strategy_check_queues(adapter);
+
+	debug_text_event(adapter->erp_dbf, 6, "a_st_done");
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:	ZFCP_ERP_DISMISSED	- if action has been dismissed
+ *		retval			- otherwise
+ */
+static int
+zfcp_erp_strategy_check_action(struct zfcp_erp_action *erp_action, int retval)
+{
+	struct zfcp_adapter *adapter = erp_action->adapter;
+
+	zfcp_erp_strategy_check_fsfreq(erp_action);
+
+	debug_event(adapter->erp_dbf, 5, &erp_action->action, sizeof (int));
+	if (erp_action->status & ZFCP_STATUS_ERP_DISMISSED) {
+		debug_text_event(adapter->erp_dbf, 3, "a_stcd_dis");
+		zfcp_erp_action_dequeue(erp_action);
+		retval = ZFCP_ERP_DISMISSED;
+	} else
+		debug_text_event(adapter->erp_dbf, 5, "a_stcd_nodis");
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:
+ */
+static int
+zfcp_erp_strategy_do_action(struct zfcp_erp_action *erp_action)
+{
+	int retval = ZFCP_ERP_FAILED;
+	struct zfcp_adapter *adapter = erp_action->adapter;
+
+	/*
+	 * try to execute/continue action as far as possible,
+	 * note: no lock in subsequent strategy routines
+	 * (this allows these routine to call schedule, e.g.
+	 * kmalloc with such flags or qdio_initialize & friends)
+	 * Note: in case of timeout, the seperate strategies will fail
+	 * anyhow. No need for a special action. Even worse, a nameserver
+	 * failure would not wake up waiting ports without the call.
+	 */
+	switch (erp_action->action) {
+
+	case ZFCP_ERP_ACTION_REOPEN_ADAPTER:
+		retval = zfcp_erp_adapter_strategy(erp_action);
+		break;
+
+	case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED:
+		retval = zfcp_erp_port_forced_strategy(erp_action);
+		break;
+
+	case ZFCP_ERP_ACTION_REOPEN_PORT:
+		retval = zfcp_erp_port_strategy(erp_action);
+		break;
+
+	case ZFCP_ERP_ACTION_REOPEN_UNIT:
+		retval = zfcp_erp_unit_strategy(erp_action);
+		break;
+
+	default:
+		debug_text_exception(adapter->erp_dbf, 1, "a_stda_bug");
+		debug_event(adapter->erp_dbf, 1, &erp_action->action,
+			    sizeof (int));
+		ZFCP_LOG_NORMAL("bug: unknown erp action requested on "
+				"adapter %s (action=%d)\n",
+				zfcp_get_busid_by_adapter(erp_action->adapter),
+				erp_action->action);
+	}
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	triggers retry of this action after a certain amount of time
+ *		by means of timer provided by erp_action
+ *
+ * returns:	ZFCP_ERP_CONTINUES - erp_action sleeps in erp running queue
+ */
+static int
+zfcp_erp_strategy_memwait(struct zfcp_erp_action *erp_action)
+{
+	int retval = ZFCP_ERP_CONTINUES;
+	struct zfcp_adapter *adapter = erp_action->adapter;
+
+	debug_text_event(adapter->erp_dbf, 6, "a_mwinit");
+	debug_event(adapter->erp_dbf, 6, &erp_action->action, sizeof (int));
+	init_timer(&erp_action->timer);
+	erp_action->timer.function = zfcp_erp_memwait_handler;
+	erp_action->timer.data = (unsigned long) erp_action;
+	erp_action->timer.expires = jiffies + ZFCP_ERP_MEMWAIT_TIMEOUT;
+	add_timer(&erp_action->timer);
+
+	return retval;
+}
+
+/* 
+ * function:    zfcp_erp_adapter_failed
+ *
+ * purpose:     sets the adapter and all underlying devices to ERP_FAILED
+ *
+ */
+void
+zfcp_erp_adapter_failed(struct zfcp_adapter *adapter)
+{
+	zfcp_erp_modify_adapter_status(adapter,
+				       ZFCP_STATUS_COMMON_ERP_FAILED, ZFCP_SET);
+	ZFCP_LOG_NORMAL("adapter erp failed on adapter %s\n",
+			zfcp_get_busid_by_adapter(adapter));
+	debug_text_event(adapter->erp_dbf, 2, "a_afail");
+}
+
+/* 
+ * function:    zfcp_erp_port_failed
+ *
+ * purpose:     sets the port and all underlying devices to ERP_FAILED
+ *
+ */
+void
+zfcp_erp_port_failed(struct zfcp_port *port)
+{
+	zfcp_erp_modify_port_status(port,
+				    ZFCP_STATUS_COMMON_ERP_FAILED, ZFCP_SET);
+
+	if (atomic_test_mask(ZFCP_STATUS_PORT_WKA, &port->status))
+		ZFCP_LOG_NORMAL("port erp failed (adapter %s, "
+				"port d_id=0x%08x)\n",
+				zfcp_get_busid_by_port(port), port->d_id);
+	else
+		ZFCP_LOG_NORMAL("port erp failed (adapter %s, wwpn=0x%016Lx)\n",
+				zfcp_get_busid_by_port(port), port->wwpn);
+
+	debug_text_event(port->adapter->erp_dbf, 2, "p_pfail");
+	debug_event(port->adapter->erp_dbf, 2, &port->wwpn, sizeof (wwn_t));
+}
+
+/* 
+ * function:    zfcp_erp_unit_failed
+ *
+ * purpose:     sets the unit to ERP_FAILED
+ *
+ */
+void
+zfcp_erp_unit_failed(struct zfcp_unit *unit)
+{
+	zfcp_erp_modify_unit_status(unit,
+				    ZFCP_STATUS_COMMON_ERP_FAILED, ZFCP_SET);
+
+	ZFCP_LOG_NORMAL("unit erp failed on unit 0x%016Lx on port 0x%016Lx "
+			" on adapter %s\n", unit->fcp_lun,
+			unit->port->wwpn, zfcp_get_busid_by_unit(unit));
+	debug_text_event(unit->port->adapter->erp_dbf, 2, "u_ufail");
+	debug_event(unit->port->adapter->erp_dbf, 2,
+		    &unit->fcp_lun, sizeof (fcp_lun_t));
+}
+
+/*
+ * function:	zfcp_erp_strategy_check_target
+ *
+ * purpose:	increments the erp action count on the device currently in
+ *              recovery if the action failed or resets the count in case of
+ *              success. If a maximum count is exceeded the device is marked
+ *              as ERP_FAILED.
+ *		The 'blocked' state of a target which has been recovered
+ *              successfully is reset.
+ *
+ * returns:	ZFCP_ERP_CONTINUES	- action continues (not considered)
+ *		ZFCP_ERP_SUCCEEDED	- action finished successfully 
+ *		ZFCP_ERP_EXIT		- action failed and will not continue
+ */
+static int
+zfcp_erp_strategy_check_target(struct zfcp_erp_action *erp_action, int result)
+{
+	struct zfcp_adapter *adapter = erp_action->adapter;
+	struct zfcp_port *port = erp_action->port;
+	struct zfcp_unit *unit = erp_action->unit;
+
+	debug_text_event(adapter->erp_dbf, 5, "a_stct_norm");
+	debug_event(adapter->erp_dbf, 5, &erp_action->action, sizeof (int));
+	debug_event(adapter->erp_dbf, 5, &result, sizeof (int));
+
+	switch (erp_action->action) {
+
+	case ZFCP_ERP_ACTION_REOPEN_UNIT:
+		result = zfcp_erp_strategy_check_unit(unit, result);
+		break;
+
+	case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED:
+	case ZFCP_ERP_ACTION_REOPEN_PORT:
+		result = zfcp_erp_strategy_check_port(port, result);
+		break;
+
+	case ZFCP_ERP_ACTION_REOPEN_ADAPTER:
+		result = zfcp_erp_strategy_check_adapter(adapter, result);
+		break;
+	}
+
+	return result;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:
+ */
+static int
+zfcp_erp_strategy_statechange(int action,
+			      u32 status,
+			      struct zfcp_adapter *adapter,
+			      struct zfcp_port *port,
+			      struct zfcp_unit *unit, int retval)
+{
+	debug_text_event(adapter->erp_dbf, 3, "a_stsc");
+	debug_event(adapter->erp_dbf, 3, &action, sizeof (int));
+
+	switch (action) {
+
+	case ZFCP_ERP_ACTION_REOPEN_ADAPTER:
+		if (zfcp_erp_strategy_statechange_detected(&adapter->status,
+							   status)) {
+			zfcp_erp_adapter_reopen_internal(adapter, ZFCP_STATUS_COMMON_ERP_FAILED);
+			retval = ZFCP_ERP_EXIT;
+		}
+		break;
+
+	case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED:
+	case ZFCP_ERP_ACTION_REOPEN_PORT:
+		if (zfcp_erp_strategy_statechange_detected(&port->status,
+							   status)) {
+			zfcp_erp_port_reopen_internal(port, ZFCP_STATUS_COMMON_ERP_FAILED);
+			retval = ZFCP_ERP_EXIT;
+		}
+		break;
+
+	case ZFCP_ERP_ACTION_REOPEN_UNIT:
+		if (zfcp_erp_strategy_statechange_detected(&unit->status,
+							   status)) {
+			zfcp_erp_unit_reopen_internal(unit, ZFCP_STATUS_COMMON_ERP_FAILED);
+			retval = ZFCP_ERP_EXIT;
+		}
+		break;
+	}
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:
+ */
+static inline int
+zfcp_erp_strategy_statechange_detected(atomic_t * target_status, u32 erp_status)
+{
+	return
+	    /* take it online */
+	    (atomic_test_mask(ZFCP_STATUS_COMMON_RUNNING, target_status) &&
+	     (ZFCP_STATUS_ERP_CLOSE_ONLY & erp_status)) ||
+	    /* take it offline */
+	    (!atomic_test_mask(ZFCP_STATUS_COMMON_RUNNING, target_status) &&
+	     !(ZFCP_STATUS_ERP_CLOSE_ONLY & erp_status));
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:
+ */
+static int
+zfcp_erp_strategy_check_unit(struct zfcp_unit *unit, int result)
+{
+	debug_text_event(unit->port->adapter->erp_dbf, 5, "u_stct");
+	debug_event(unit->port->adapter->erp_dbf, 5, &unit->fcp_lun,
+		    sizeof (fcp_lun_t));
+
+	switch (result) {
+	case ZFCP_ERP_SUCCEEDED :
+		atomic_set(&unit->erp_counter, 0);
+		zfcp_erp_unit_unblock(unit);
+		break;
+	case ZFCP_ERP_FAILED :
+		atomic_inc(&unit->erp_counter);
+		if (atomic_read(&unit->erp_counter) > ZFCP_MAX_ERPS)
+			zfcp_erp_unit_failed(unit);
+		break;
+	case ZFCP_ERP_EXIT :
+		/* nothing */
+		break;
+	}
+
+	if (atomic_test_mask(ZFCP_STATUS_COMMON_ERP_FAILED, &unit->status)) {
+		zfcp_erp_unit_block(unit, 0); /* for ZFCP_ERP_SUCCEEDED */
+		result = ZFCP_ERP_EXIT;
+	}
+
+	return result;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:
+ */
+static int
+zfcp_erp_strategy_check_port(struct zfcp_port *port, int result)
+{
+	debug_text_event(port->adapter->erp_dbf, 5, "p_stct");
+	debug_event(port->adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t));
+
+	switch (result) {
+	case ZFCP_ERP_SUCCEEDED :
+		atomic_set(&port->erp_counter, 0);
+		zfcp_erp_port_unblock(port);
+		break;
+	case ZFCP_ERP_FAILED :
+		atomic_inc(&port->erp_counter);
+		if (atomic_read(&port->erp_counter) > ZFCP_MAX_ERPS)
+			zfcp_erp_port_failed(port);
+		break;
+	case ZFCP_ERP_EXIT :
+		/* nothing */
+		break;
+	}
+
+	if (atomic_test_mask(ZFCP_STATUS_COMMON_ERP_FAILED, &port->status)) {
+		zfcp_erp_port_block(port, 0); /* for ZFCP_ERP_SUCCEEDED */
+		result = ZFCP_ERP_EXIT;
+	}
+
+	return result;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:
+ */
+static int
+zfcp_erp_strategy_check_adapter(struct zfcp_adapter *adapter, int result)
+{
+	debug_text_event(adapter->erp_dbf, 5, "a_stct");
+
+	switch (result) {
+	case ZFCP_ERP_SUCCEEDED :
+		atomic_set(&adapter->erp_counter, 0);
+		zfcp_erp_adapter_unblock(adapter);
+		break;
+	case ZFCP_ERP_FAILED :
+		atomic_inc(&adapter->erp_counter);
+		if (atomic_read(&adapter->erp_counter) > ZFCP_MAX_ERPS)
+			zfcp_erp_adapter_failed(adapter);
+		break;
+	case ZFCP_ERP_EXIT :
+		/* nothing */
+		break;
+	}
+
+	if (atomic_test_mask(ZFCP_STATUS_COMMON_ERP_FAILED, &adapter->status)) {
+		zfcp_erp_adapter_block(adapter, 0); /* for ZFCP_ERP_SUCCEEDED */
+		result = ZFCP_ERP_EXIT;
+	}
+
+	return result;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	remaining things in good cases,
+ *		escalation in bad cases
+ *
+ * returns:
+ */
+static int
+zfcp_erp_strategy_followup_actions(int action,
+				   struct zfcp_adapter *adapter,
+				   struct zfcp_port *port,
+				   struct zfcp_unit *unit, int status)
+{
+	debug_text_event(adapter->erp_dbf, 5, "a_stfol");
+	debug_event(adapter->erp_dbf, 5, &action, sizeof (int));
+
+	/* initiate follow-up actions depending on success of finished action */
+	switch (action) {
+
+	case ZFCP_ERP_ACTION_REOPEN_ADAPTER:
+		if (status == ZFCP_ERP_SUCCEEDED)
+			zfcp_erp_port_reopen_all_internal(adapter, 0);
+		else
+			zfcp_erp_adapter_reopen_internal(adapter, 0);
+		break;
+
+	case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED:
+		if (status == ZFCP_ERP_SUCCEEDED)
+			zfcp_erp_port_reopen_internal(port, 0);
+		else
+			zfcp_erp_adapter_reopen_internal(adapter, 0);
+		break;
+
+	case ZFCP_ERP_ACTION_REOPEN_PORT:
+		if (status == ZFCP_ERP_SUCCEEDED)
+			zfcp_erp_unit_reopen_all_internal(port, 0);
+		else
+			zfcp_erp_port_forced_reopen_internal(port, 0);
+		break;
+
+	case ZFCP_ERP_ACTION_REOPEN_UNIT:
+		if (status == ZFCP_ERP_SUCCEEDED) ;	/* no further action */
+		else
+			zfcp_erp_port_reopen_internal(unit->port, 0);
+		break;
+	}
+
+	return 0;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:
+ */
+static int
+zfcp_erp_strategy_check_queues(struct zfcp_adapter *adapter)
+{
+	unsigned long flags;
+
+	read_lock_irqsave(&zfcp_data.config_lock, flags);
+	read_lock(&adapter->erp_lock);
+	if (list_empty(&adapter->erp_ready_head) &&
+	    list_empty(&adapter->erp_running_head)) {
+			debug_text_event(adapter->erp_dbf, 4, "a_cq_wake");
+			atomic_clear_mask(ZFCP_STATUS_ADAPTER_ERP_PENDING,
+					  &adapter->status);
+			wake_up(&adapter->erp_done_wqh);
+	} else
+		debug_text_event(adapter->erp_dbf, 5, "a_cq_notempty");
+	read_unlock(&adapter->erp_lock);
+	read_unlock_irqrestore(&zfcp_data.config_lock, flags);
+
+	return 0;
+}
+
+/**
+ * zfcp_erp_wait - wait for completion of error recovery on an adapter
+ * @adapter: adapter for which to wait for completion of its error recovery
+ * Return: 0
+ */
+int
+zfcp_erp_wait(struct zfcp_adapter *adapter)
+{
+	int retval = 0;
+
+	wait_event(adapter->erp_done_wqh,
+		   !atomic_test_mask(ZFCP_STATUS_ADAPTER_ERP_PENDING,
+				     &adapter->status));
+
+	return retval;
+}
+
+/*
+ * function:	zfcp_erp_modify_adapter_status
+ *
+ * purpose:	
+ *
+ */
+void
+zfcp_erp_modify_adapter_status(struct zfcp_adapter *adapter,
+			       u32 mask, int set_or_clear)
+{
+	struct zfcp_port *port;
+	u32 common_mask = mask & ZFCP_COMMON_FLAGS;
+
+	if (set_or_clear == ZFCP_SET) {
+		atomic_set_mask(mask, &adapter->status);
+		debug_text_event(adapter->erp_dbf, 3, "a_mod_as_s");
+	} else {
+		atomic_clear_mask(mask, &adapter->status);
+		if (mask & ZFCP_STATUS_COMMON_ERP_FAILED)
+			atomic_set(&adapter->erp_counter, 0);
+		debug_text_event(adapter->erp_dbf, 3, "a_mod_as_c");
+	}
+	debug_event(adapter->erp_dbf, 3, &mask, sizeof (u32));
+
+	/* Deal with all underlying devices, only pass common_mask */
+	if (common_mask)
+		list_for_each_entry(port, &adapter->port_list_head, list)
+		    zfcp_erp_modify_port_status(port, common_mask,
+						set_or_clear);
+}
+
+/*
+ * function:	zfcp_erp_modify_port_status
+ *
+ * purpose:	sets the port and all underlying devices to ERP_FAILED
+ *
+ */
+void
+zfcp_erp_modify_port_status(struct zfcp_port *port, u32 mask, int set_or_clear)
+{
+	struct zfcp_unit *unit;
+	u32 common_mask = mask & ZFCP_COMMON_FLAGS;
+
+	if (set_or_clear == ZFCP_SET) {
+		atomic_set_mask(mask, &port->status);
+		debug_text_event(port->adapter->erp_dbf, 3, "p_mod_ps_s");
+	} else {
+		atomic_clear_mask(mask, &port->status);
+		if (mask & ZFCP_STATUS_COMMON_ERP_FAILED)
+			atomic_set(&port->erp_counter, 0);
+		debug_text_event(port->adapter->erp_dbf, 3, "p_mod_ps_c");
+	}
+	debug_event(port->adapter->erp_dbf, 3, &port->wwpn, sizeof (wwn_t));
+	debug_event(port->adapter->erp_dbf, 3, &mask, sizeof (u32));
+
+	/* Modify status of all underlying devices, only pass common mask */
+	if (common_mask)
+		list_for_each_entry(unit, &port->unit_list_head, list)
+		    zfcp_erp_modify_unit_status(unit, common_mask,
+						set_or_clear);
+}
+
+/*
+ * function:	zfcp_erp_modify_unit_status
+ *
+ * purpose:	sets the unit to ERP_FAILED
+ *
+ */
+void
+zfcp_erp_modify_unit_status(struct zfcp_unit *unit, u32 mask, int set_or_clear)
+{
+	if (set_or_clear == ZFCP_SET) {
+		atomic_set_mask(mask, &unit->status);
+		debug_text_event(unit->port->adapter->erp_dbf, 3, "u_mod_us_s");
+	} else {
+		atomic_clear_mask(mask, &unit->status);
+		if (mask & ZFCP_STATUS_COMMON_ERP_FAILED) {
+			atomic_set(&unit->erp_counter, 0);
+		}
+		debug_text_event(unit->port->adapter->erp_dbf, 3, "u_mod_us_c");
+	}
+	debug_event(unit->port->adapter->erp_dbf, 3, &unit->fcp_lun,
+		    sizeof (fcp_lun_t));
+	debug_event(unit->port->adapter->erp_dbf, 3, &mask, sizeof (u32));
+}
+
+/*
+ * function:	
+ *
+ * purpose:	Wrappper for zfcp_erp_port_reopen_all_internal
+ *              used to ensure the correct locking
+ *
+ * returns:	0	- initiated action succesfully
+ *		<0	- failed to initiate action
+ */
+int
+zfcp_erp_port_reopen_all(struct zfcp_adapter *adapter, int clear_mask)
+{
+	int retval;
+	unsigned long flags;
+
+	read_lock_irqsave(&zfcp_data.config_lock, flags);
+	write_lock(&adapter->erp_lock);
+	retval = zfcp_erp_port_reopen_all_internal(adapter, clear_mask);
+	write_unlock(&adapter->erp_lock);
+	read_unlock_irqrestore(&zfcp_data.config_lock, flags);
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:	FIXME
+ */
+static int
+zfcp_erp_port_reopen_all_internal(struct zfcp_adapter *adapter, int clear_mask)
+{
+	int retval = 0;
+	struct zfcp_port *port;
+
+	list_for_each_entry(port, &adapter->port_list_head, list)
+		if (!atomic_test_mask(ZFCP_STATUS_PORT_WKA, &port->status))
+			zfcp_erp_port_reopen_internal(port, clear_mask);
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:	FIXME
+ */
+static int
+zfcp_erp_unit_reopen_all_internal(struct zfcp_port *port, int clear_mask)
+{
+	int retval = 0;
+	struct zfcp_unit *unit;
+
+	list_for_each_entry(unit, &port->unit_list_head, list)
+	    zfcp_erp_unit_reopen_internal(unit, clear_mask);
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	this routine executes the 'Reopen Adapter' action
+ *		(the entire action is processed synchronously, since
+ *		there are no actions which might be run concurrently
+ *		per definition)
+ *
+ * returns:	ZFCP_ERP_SUCCEEDED	- action finished successfully
+ *		ZFCP_ERP_FAILED		- action finished unsuccessfully
+ */
+static int
+zfcp_erp_adapter_strategy(struct zfcp_erp_action *erp_action)
+{
+	int retval;
+	struct zfcp_adapter *adapter = erp_action->adapter;
+
+	retval = zfcp_erp_adapter_strategy_close(erp_action);
+	if (erp_action->status & ZFCP_STATUS_ERP_CLOSE_ONLY)
+		retval = ZFCP_ERP_EXIT;
+	else
+		retval = zfcp_erp_adapter_strategy_open(erp_action);
+
+	debug_text_event(adapter->erp_dbf, 3, "a_ast/ret");
+	debug_event(adapter->erp_dbf, 3, &erp_action->action, sizeof (int));
+	debug_event(adapter->erp_dbf, 3, &retval, sizeof (int));
+
+	if (retval == ZFCP_ERP_FAILED) {
+		ZFCP_LOG_INFO("Waiting to allow the adapter %s "
+			      "to recover itself\n",
+			      zfcp_get_busid_by_adapter(adapter));
+		msleep(jiffies_to_msecs(ZFCP_TYPE2_RECOVERY_TIME));
+	}
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:	ZFCP_ERP_SUCCEEDED      - action finished successfully
+ *              ZFCP_ERP_FAILED         - action finished unsuccessfully
+ */
+static int
+zfcp_erp_adapter_strategy_close(struct zfcp_erp_action *erp_action)
+{
+	int retval;
+
+	atomic_set_mask(ZFCP_STATUS_COMMON_CLOSING,
+			&erp_action->adapter->status);
+	retval = zfcp_erp_adapter_strategy_generic(erp_action, 1);
+	atomic_clear_mask(ZFCP_STATUS_COMMON_CLOSING,
+			  &erp_action->adapter->status);
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:	ZFCP_ERP_SUCCEEDED      - action finished successfully
+ *              ZFCP_ERP_FAILED         - action finished unsuccessfully
+ */
+static int
+zfcp_erp_adapter_strategy_open(struct zfcp_erp_action *erp_action)
+{
+	int retval;
+
+	atomic_set_mask(ZFCP_STATUS_COMMON_OPENING,
+			&erp_action->adapter->status);
+	retval = zfcp_erp_adapter_strategy_generic(erp_action, 0);
+	atomic_clear_mask(ZFCP_STATUS_COMMON_OPENING,
+			  &erp_action->adapter->status);
+
+	return retval;
+}
+
+/*
+ * function:    zfcp_register_adapter
+ *
+ * purpose:	allocate the irq associated with this devno and register
+ *		the FSF adapter with the SCSI stack
+ *
+ * returns:	
+ */
+static int
+zfcp_erp_adapter_strategy_generic(struct zfcp_erp_action *erp_action, int close)
+{
+	int retval = ZFCP_ERP_SUCCEEDED;
+
+	if (close)
+		goto close_only;
+
+	retval = zfcp_erp_adapter_strategy_open_qdio(erp_action);
+	if (retval != ZFCP_ERP_SUCCEEDED)
+		goto failed_qdio;
+
+	retval = zfcp_erp_adapter_strategy_open_fsf(erp_action);
+	if (retval != ZFCP_ERP_SUCCEEDED)
+		goto failed_openfcp;
+
+	atomic_set_mask(ZFCP_STATUS_COMMON_OPEN, &erp_action->adapter->status);
+	goto out;
+
+ close_only:
+	atomic_clear_mask(ZFCP_STATUS_COMMON_OPEN,
+			  &erp_action->adapter->status);
+
+ failed_openfcp:
+	zfcp_erp_adapter_strategy_close_qdio(erp_action);
+	zfcp_erp_adapter_strategy_close_fsf(erp_action);
+ failed_qdio:
+ out:
+	return retval;
+}
+
+/*
+ * function:    zfcp_qdio_init
+ *
+ * purpose:	setup QDIO operation for specified adapter
+ *
+ * returns:	0 - successful setup
+ *		!0 - failed setup
+ */
+int
+zfcp_erp_adapter_strategy_open_qdio(struct zfcp_erp_action *erp_action)
+{
+	int retval;
+	int i;
+	volatile struct qdio_buffer_element *sbale;
+	struct zfcp_adapter *adapter = erp_action->adapter;
+
+	if (atomic_test_mask(ZFCP_STATUS_ADAPTER_QDIOUP, &adapter->status)) {
+		ZFCP_LOG_NORMAL("bug: second attempt to set up QDIO on "
+				"adapter %s\n",
+				zfcp_get_busid_by_adapter(adapter));
+		goto failed_sanity;
+	}
+
+	if (qdio_establish(&adapter->qdio_init_data) != 0) {
+		ZFCP_LOG_INFO("error: establishment of QDIO queues failed "
+			      "on adapter %s\n",
+			      zfcp_get_busid_by_adapter(adapter));
+		goto failed_qdio_establish;
+	}
+	debug_text_event(adapter->erp_dbf, 3, "qdio_est");
+
+	if (qdio_activate(adapter->ccw_device, 0) != 0) {
+		ZFCP_LOG_INFO("error: activation of QDIO queues failed "
+			      "on adapter %s\n",
+			      zfcp_get_busid_by_adapter(adapter));
+		goto failed_qdio_activate;
+	}
+	debug_text_event(adapter->erp_dbf, 3, "qdio_act");
+
+	/*
+	 * put buffers into response queue,
+	 */
+	for (i = 0; i < QDIO_MAX_BUFFERS_PER_Q; i++) {
+		sbale = &(adapter->response_queue.buffer[i]->element[0]);
+		sbale->length = 0;
+		sbale->flags = SBAL_FLAGS_LAST_ENTRY;
+		sbale->addr = 0;
+	}
+
+	ZFCP_LOG_TRACE("calling do_QDIO on adapter %s (flags=0x%x, "
+		       "queue_no=%i, index_in_queue=%i, count=%i)\n",
+		       zfcp_get_busid_by_adapter(adapter),
+		       QDIO_FLAG_SYNC_INPUT, 0, 0, QDIO_MAX_BUFFERS_PER_Q);
+
+	retval = do_QDIO(adapter->ccw_device,
+			 QDIO_FLAG_SYNC_INPUT,
+			 0, 0, QDIO_MAX_BUFFERS_PER_Q, NULL);
+
+	if (retval) {
+		ZFCP_LOG_NORMAL("bug: setup of QDIO failed (retval=%d)\n",
+				retval);
+		goto failed_do_qdio;
+	} else {
+		adapter->response_queue.free_index = 0;
+		atomic_set(&adapter->response_queue.free_count, 0);
+		ZFCP_LOG_DEBUG("%i buffers successfully enqueued to "
+			       "response queue\n", QDIO_MAX_BUFFERS_PER_Q);
+	}
+	/* set index of first avalable SBALS / number of available SBALS */
+	adapter->request_queue.free_index = 0;
+	atomic_set(&adapter->request_queue.free_count, QDIO_MAX_BUFFERS_PER_Q);
+	adapter->request_queue.distance_from_int = 0;
+
+	/* initialize waitqueue used to wait for free SBALs in requests queue */
+	init_waitqueue_head(&adapter->request_wq);
+
+	/* ok, we did it - skip all cleanups for different failures */
+	atomic_set_mask(ZFCP_STATUS_ADAPTER_QDIOUP, &adapter->status);
+	retval = ZFCP_ERP_SUCCEEDED;
+	goto out;
+
+ failed_do_qdio:
+	/* NOP */
+
+ failed_qdio_activate:
+	debug_text_event(adapter->erp_dbf, 3, "qdio_down1a");
+	while (qdio_shutdown(adapter->ccw_device,
+			     QDIO_FLAG_CLEANUP_USING_CLEAR) == -EINPROGRESS)
+		msleep(1000);
+	debug_text_event(adapter->erp_dbf, 3, "qdio_down1b");
+
+ failed_qdio_establish:
+ failed_sanity:
+	retval = ZFCP_ERP_FAILED;
+
+ out:
+	return retval;
+}
+
+/*
+ * function:    zfcp_qdio_cleanup
+ *
+ * purpose:	cleans up QDIO operation for the specified adapter
+ *
+ * returns:	0 - successful cleanup
+ *		!0 - failed cleanup
+ */
+int
+zfcp_erp_adapter_strategy_close_qdio(struct zfcp_erp_action *erp_action)
+{
+	int retval = ZFCP_ERP_SUCCEEDED;
+	int first_used;
+	int used_count;
+	struct zfcp_adapter *adapter = erp_action->adapter;
+
+	if (!atomic_test_mask(ZFCP_STATUS_ADAPTER_QDIOUP, &adapter->status)) {
+		ZFCP_LOG_DEBUG("error: attempt to shut down inactive QDIO "
+			       "queues on adapter %s\n",
+			       zfcp_get_busid_by_adapter(adapter));
+		retval = ZFCP_ERP_FAILED;
+		goto out;
+	}
+
+	/*
+	 * Get queue_lock and clear QDIOUP flag. Thus it's guaranteed that
+	 * do_QDIO won't be called while qdio_shutdown is in progress.
+	 */
+
+	write_lock_irq(&adapter->request_queue.queue_lock);
+	atomic_clear_mask(ZFCP_STATUS_ADAPTER_QDIOUP, &adapter->status);
+	write_unlock_irq(&adapter->request_queue.queue_lock);
+
+	debug_text_event(adapter->erp_dbf, 3, "qdio_down2a");
+	while (qdio_shutdown(adapter->ccw_device,
+			     QDIO_FLAG_CLEANUP_USING_CLEAR) == -EINPROGRESS)
+		msleep(1000);
+	debug_text_event(adapter->erp_dbf, 3, "qdio_down2b");
+
+	/*
+	 * First we had to stop QDIO operation.
+	 * Now it is safe to take the following actions.
+	 */
+
+	/* Cleanup only necessary when there are unacknowledged buffers */
+	if (atomic_read(&adapter->request_queue.free_count)
+	    < QDIO_MAX_BUFFERS_PER_Q) {
+		first_used = (adapter->request_queue.free_index +
+			      atomic_read(&adapter->request_queue.free_count))
+			% QDIO_MAX_BUFFERS_PER_Q;
+		used_count = QDIO_MAX_BUFFERS_PER_Q -
+			atomic_read(&adapter->request_queue.free_count);
+		zfcp_qdio_zero_sbals(adapter->request_queue.buffer,
+				     first_used, used_count);
+	}
+	adapter->response_queue.free_index = 0;
+	atomic_set(&adapter->response_queue.free_count, 0);
+	adapter->request_queue.free_index = 0;
+	atomic_set(&adapter->request_queue.free_count, 0);
+	adapter->request_queue.distance_from_int = 0;
+ out:
+	return retval;
+}
+
+/*
+ * function:    zfcp_fsf_init
+ *
+ * purpose:	initializes FSF operation for the specified adapter
+ *
+ * returns:	0 - succesful initialization of FSF operation
+ *		!0 - failed to initialize FSF operation
+ */
+static int
+zfcp_erp_adapter_strategy_open_fsf(struct zfcp_erp_action *erp_action)
+{
+	int retval;
+
+	/* do 'exchange configuration data' */
+	retval = zfcp_erp_adapter_strategy_open_fsf_xconfig(erp_action);
+	if (retval == ZFCP_ERP_FAILED)
+		return retval;
+
+	/* start the desired number of Status Reads */
+	retval = zfcp_erp_adapter_strategy_open_fsf_statusread(erp_action);
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:
+ */
+static int
+zfcp_erp_adapter_strategy_open_fsf_xconfig(struct zfcp_erp_action *erp_action)
+{
+	int retval = ZFCP_ERP_SUCCEEDED;
+	int retries;
+	struct zfcp_adapter *adapter = erp_action->adapter;
+
+	atomic_clear_mask(ZFCP_STATUS_ADAPTER_XCONFIG_OK, &adapter->status);
+	retries = ZFCP_EXCHANGE_CONFIG_DATA_RETRIES;
+
+	do {
+		atomic_clear_mask(ZFCP_STATUS_ADAPTER_HOST_CON_INIT,
+				  &adapter->status);
+		ZFCP_LOG_DEBUG("Doing exchange config data\n");
+		zfcp_erp_action_to_running(erp_action);
+		zfcp_erp_timeout_init(erp_action);
+		if (zfcp_fsf_exchange_config_data(erp_action)) {
+			retval = ZFCP_ERP_FAILED;
+			debug_text_event(adapter->erp_dbf, 5, "a_fstx_xf");
+			ZFCP_LOG_INFO("error:  initiation of exchange of "
+				      "configuration data failed for "
+				      "adapter %s\n",
+				      zfcp_get_busid_by_adapter(adapter));
+			break;
+		}
+		debug_text_event(adapter->erp_dbf, 6, "a_fstx_xok");
+		ZFCP_LOG_DEBUG("Xchange underway\n");
+
+		/*
+		 * Why this works:
+		 * Both the normal completion handler as well as the timeout
+		 * handler will do an 'up' when the 'exchange config data'
+		 * request completes or times out. Thus, the signal to go on
+		 * won't be lost utilizing this semaphore.
+		 * Furthermore, this 'adapter_reopen' action is
+		 * guaranteed to be the only action being there (highest action
+		 * which prevents other actions from being created).
+		 * Resulting from that, the wake signal recognized here
+		 * _must_ be the one belonging to the 'exchange config
+		 * data' request.
+		 */
+		down(&adapter->erp_ready_sem);
+		if (erp_action->status & ZFCP_STATUS_ERP_TIMEDOUT) {
+			ZFCP_LOG_INFO("error: exchange of configuration data "
+				      "for adapter %s timed out\n",
+				      zfcp_get_busid_by_adapter(adapter));
+			break;
+		}
+		if (atomic_test_mask(ZFCP_STATUS_ADAPTER_HOST_CON_INIT,
+				     &adapter->status)) {
+			ZFCP_LOG_DEBUG("host connection still initialising... "
+				       "waiting and retrying...\n");
+			/* sleep a little bit before retry */
+			msleep(jiffies_to_msecs(ZFCP_EXCHANGE_CONFIG_DATA_SLEEP));
+		}
+	} while ((retries--) &&
+		 atomic_test_mask(ZFCP_STATUS_ADAPTER_HOST_CON_INIT,
+				  &adapter->status));
+
+	if (!atomic_test_mask(ZFCP_STATUS_ADAPTER_XCONFIG_OK,
+			      &adapter->status)) {
+		ZFCP_LOG_INFO("error: exchange of configuration data for "
+			      "adapter %s failed\n",
+			      zfcp_get_busid_by_adapter(adapter));
+		retval = ZFCP_ERP_FAILED;
+	}
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:
+ */
+static int
+zfcp_erp_adapter_strategy_open_fsf_statusread(struct zfcp_erp_action
+					      *erp_action)
+{
+	int retval = ZFCP_ERP_SUCCEEDED;
+	int temp_ret;
+	struct zfcp_adapter *adapter = erp_action->adapter;
+	int i;
+
+	adapter->status_read_failed = 0;
+	for (i = 0; i < ZFCP_STATUS_READS_RECOM; i++) {
+		temp_ret = zfcp_fsf_status_read(adapter, ZFCP_WAIT_FOR_SBAL);
+		if (temp_ret < 0) {
+			ZFCP_LOG_INFO("error: set-up of unsolicited status "
+				      "notification failed on adapter %s\n",
+				      zfcp_get_busid_by_adapter(adapter));
+			retval = ZFCP_ERP_FAILED;
+			i--;
+			break;
+		}
+	}
+
+	return retval;
+}
+
+/*
+ * function:    zfcp_fsf_cleanup
+ *
+ * purpose:	cleanup FSF operation for specified adapter
+ *
+ * returns:	0 - FSF operation successfully cleaned up
+ *		!0 - failed to cleanup FSF operation for this adapter
+ */
+static int
+zfcp_erp_adapter_strategy_close_fsf(struct zfcp_erp_action *erp_action)
+{
+	int retval = ZFCP_ERP_SUCCEEDED;
+	struct zfcp_adapter *adapter = erp_action->adapter;
+
+	/*
+	 * wake waiting initiators of requests,
+	 * return SCSI commands (with error status),
+	 * clean up all requests (synchronously)
+	 */
+	zfcp_fsf_req_dismiss_all(adapter);
+	/* reset FSF request sequence number */
+	adapter->fsf_req_seq_no = 0;
+	/* all ports and units are closed */
+	zfcp_erp_modify_adapter_status(adapter,
+				       ZFCP_STATUS_COMMON_OPEN, ZFCP_CLEAR);
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	this routine executes the 'Reopen Physical Port' action
+ *
+ * returns:	ZFCP_ERP_CONTINUES	- action continues (asynchronously)
+ *		ZFCP_ERP_SUCCEEDED	- action finished successfully
+ *		ZFCP_ERP_FAILED		- action finished unsuccessfully
+ */
+static int
+zfcp_erp_port_forced_strategy(struct zfcp_erp_action *erp_action)
+{
+	int retval = ZFCP_ERP_FAILED;
+	struct zfcp_port *port = erp_action->port;
+	struct zfcp_adapter *adapter = erp_action->adapter;
+
+	switch (erp_action->step) {
+
+		/*
+		 * FIXME:
+		 * the ULP spec. begs for waiting for oustanding commands
+		 */
+	case ZFCP_ERP_STEP_UNINITIALIZED:
+		zfcp_erp_port_strategy_clearstati(port);
+		/*
+		 * it would be sufficient to test only the normal open flag
+		 * since the phys. open flag cannot be set if the normal
+		 * open flag is unset - however, this is for readabilty ...
+		 */
+		if (atomic_test_mask((ZFCP_STATUS_PORT_PHYS_OPEN |
+				      ZFCP_STATUS_COMMON_OPEN),
+			             &port->status)) {
+			ZFCP_LOG_DEBUG("port 0x%016Lx is open -> trying "
+				       "close physical\n", port->wwpn);
+			retval =
+			    zfcp_erp_port_forced_strategy_close(erp_action);
+		} else
+			retval = ZFCP_ERP_FAILED;
+		break;
+
+	case ZFCP_ERP_STEP_PHYS_PORT_CLOSING:
+		if (atomic_test_mask(ZFCP_STATUS_PORT_PHYS_OPEN,
+				     &port->status)) {
+			ZFCP_LOG_DEBUG("close physical failed for port "
+				       "0x%016Lx\n", port->wwpn);
+			retval = ZFCP_ERP_FAILED;
+		} else
+			retval = ZFCP_ERP_SUCCEEDED;
+		break;
+	}
+
+	debug_text_event(adapter->erp_dbf, 3, "p_pfst/ret");
+	debug_event(adapter->erp_dbf, 3, &port->wwpn, sizeof (wwn_t));
+	debug_event(adapter->erp_dbf, 3, &erp_action->action, sizeof (int));
+	debug_event(adapter->erp_dbf, 3, &retval, sizeof (int));
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	this routine executes the 'Reopen Port' action
+ *
+ * returns:	ZFCP_ERP_CONTINUES	- action continues (asynchronously)
+ *		ZFCP_ERP_SUCCEEDED	- action finished successfully
+ *		ZFCP_ERP_FAILED		- action finished unsuccessfully
+ */
+static int
+zfcp_erp_port_strategy(struct zfcp_erp_action *erp_action)
+{
+	int retval = ZFCP_ERP_FAILED;
+	struct zfcp_port *port = erp_action->port;
+	struct zfcp_adapter *adapter = erp_action->adapter;
+
+	switch (erp_action->step) {
+
+		/*
+		 * FIXME:
+		 * the ULP spec. begs for waiting for oustanding commands
+		 */
+	case ZFCP_ERP_STEP_UNINITIALIZED:
+		zfcp_erp_port_strategy_clearstati(port);
+		if (atomic_test_mask(ZFCP_STATUS_COMMON_OPEN, &port->status)) {
+			ZFCP_LOG_DEBUG("port 0x%016Lx is open -> trying "
+				       "close\n", port->wwpn);
+			retval = zfcp_erp_port_strategy_close(erp_action);
+			goto out;
+		}		/* else it's already closed, open it */
+		break;
+
+	case ZFCP_ERP_STEP_PORT_CLOSING:
+		if (atomic_test_mask(ZFCP_STATUS_COMMON_OPEN, &port->status)) {
+			ZFCP_LOG_DEBUG("close failed for port 0x%016Lx\n",
+				       port->wwpn);
+			retval = ZFCP_ERP_FAILED;
+			goto out;
+		}		/* else it's closed now, open it */
+		break;
+	}
+	if (erp_action->status & ZFCP_STATUS_ERP_CLOSE_ONLY)
+		retval = ZFCP_ERP_EXIT;
+	else
+		retval = zfcp_erp_port_strategy_open(erp_action);
+
+ out:
+	debug_text_event(adapter->erp_dbf, 3, "p_pst/ret");
+	debug_event(adapter->erp_dbf, 3, &port->wwpn, sizeof (wwn_t));
+	debug_event(adapter->erp_dbf, 3, &erp_action->action, sizeof (int));
+	debug_event(adapter->erp_dbf, 3, &retval, sizeof (int));
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:
+ */
+static int
+zfcp_erp_port_strategy_open(struct zfcp_erp_action *erp_action)
+{
+	int retval;
+
+	if (atomic_test_mask(ZFCP_STATUS_PORT_WKA,
+			     &erp_action->port->status))
+		retval = zfcp_erp_port_strategy_open_nameserver(erp_action);
+	else
+		retval = zfcp_erp_port_strategy_open_common(erp_action);
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:
+ *
+ * FIXME(design):	currently only prepared for fabric (nameserver!)
+ */
+static int
+zfcp_erp_port_strategy_open_common(struct zfcp_erp_action *erp_action)
+{
+	int retval = 0;
+	struct zfcp_adapter *adapter = erp_action->adapter;
+	struct zfcp_port *port = erp_action->port;
+
+	switch (erp_action->step) {
+
+	case ZFCP_ERP_STEP_UNINITIALIZED:
+	case ZFCP_ERP_STEP_PHYS_PORT_CLOSING:
+	case ZFCP_ERP_STEP_PORT_CLOSING:
+		if (!(adapter->nameserver_port)) {
+			retval = zfcp_nameserver_enqueue(adapter);
+			if (retval != 0) {
+				ZFCP_LOG_NORMAL("error: nameserver port "
+						"unavailable for adapter %s\n",
+						zfcp_get_busid_by_adapter(adapter));
+				retval = ZFCP_ERP_FAILED;
+				break;
+			}
+		}
+		if (!atomic_test_mask(ZFCP_STATUS_COMMON_UNBLOCKED,
+				      &adapter->nameserver_port->status)) {
+			ZFCP_LOG_DEBUG("nameserver port is not open -> open "
+				       "nameserver port\n");
+			/* nameserver port may live again */
+			atomic_set_mask(ZFCP_STATUS_COMMON_RUNNING,
+					&adapter->nameserver_port->status);
+			if (zfcp_erp_port_reopen(adapter->nameserver_port, 0)
+			    >= 0) {
+				erp_action->step =
+					ZFCP_ERP_STEP_NAMESERVER_OPEN;
+				retval = ZFCP_ERP_CONTINUES;
+			} else
+				retval = ZFCP_ERP_FAILED;
+			break;
+		}
+		/* else nameserver port is already open, fall through */
+	case ZFCP_ERP_STEP_NAMESERVER_OPEN:
+		if (!atomic_test_mask(ZFCP_STATUS_COMMON_OPEN,
+				      &adapter->nameserver_port->status)) {
+			ZFCP_LOG_DEBUG("open failed for nameserver port\n");
+			retval = ZFCP_ERP_FAILED;
+		} else {
+			ZFCP_LOG_DEBUG("nameserver port is open -> "
+				       "nameserver look-up for port 0x%016Lx\n",
+				       port->wwpn);
+			retval = zfcp_erp_port_strategy_open_common_lookup
+				(erp_action);
+		}
+		break;
+
+	case ZFCP_ERP_STEP_NAMESERVER_LOOKUP:
+		if (!atomic_test_mask(ZFCP_STATUS_PORT_DID_DID, &port->status)) {
+			if (atomic_test_mask
+			    (ZFCP_STATUS_PORT_INVALID_WWPN, &port->status)) {
+				ZFCP_LOG_DEBUG("nameserver look-up failed "
+					       "for port 0x%016Lx "
+					       "(misconfigured WWPN?)\n",
+					       port->wwpn);
+				zfcp_erp_port_failed(port);
+				retval = ZFCP_ERP_EXIT;
+			} else {
+				ZFCP_LOG_DEBUG("nameserver look-up failed for "
+					       "port 0x%016Lx\n", port->wwpn);
+				retval = ZFCP_ERP_FAILED;
+			}
+		} else {
+			ZFCP_LOG_DEBUG("port 0x%016Lx has d_id=0x%08x -> "
+				       "trying open\n", port->wwpn, port->d_id);
+			retval = zfcp_erp_port_strategy_open_port(erp_action);
+		}
+		break;
+
+	case ZFCP_ERP_STEP_PORT_OPENING:
+		/* D_ID might have changed during open */
+		if (atomic_test_mask((ZFCP_STATUS_COMMON_OPEN |
+				      ZFCP_STATUS_PORT_DID_DID),
+				     &port->status)) {
+			ZFCP_LOG_DEBUG("port 0x%016Lx is open\n", port->wwpn);
+			retval = ZFCP_ERP_SUCCEEDED;
+		} else {
+			ZFCP_LOG_DEBUG("open failed for port 0x%016Lx\n",
+				       port->wwpn);
+			retval = ZFCP_ERP_FAILED;
+		}
+		break;
+
+	default:
+		ZFCP_LOG_NORMAL("bug: unknown erp step 0x%08x\n",
+				erp_action->step);
+		retval = ZFCP_ERP_FAILED;
+	}
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:
+ */
+static int
+zfcp_erp_port_strategy_open_nameserver(struct zfcp_erp_action *erp_action)
+{
+	int retval;
+	struct zfcp_port *port = erp_action->port;
+
+	switch (erp_action->step) {
+
+	case ZFCP_ERP_STEP_UNINITIALIZED:
+	case ZFCP_ERP_STEP_PHYS_PORT_CLOSING:
+	case ZFCP_ERP_STEP_PORT_CLOSING:
+		ZFCP_LOG_DEBUG("port 0x%016Lx has d_id=0x%08x -> trying open\n",
+			       port->wwpn, port->d_id);
+		retval = zfcp_erp_port_strategy_open_port(erp_action);
+		break;
+
+	case ZFCP_ERP_STEP_PORT_OPENING:
+		if (atomic_test_mask(ZFCP_STATUS_COMMON_OPEN, &port->status)) {
+			ZFCP_LOG_DEBUG("WKA port is open\n");
+			retval = ZFCP_ERP_SUCCEEDED;
+		} else {
+			ZFCP_LOG_DEBUG("open failed for WKA port\n");
+			retval = ZFCP_ERP_FAILED;
+		}
+		/* this is needed anyway (dont care for retval of wakeup) */
+		ZFCP_LOG_DEBUG("continue other open port operations\n");
+		zfcp_erp_port_strategy_open_nameserver_wakeup(erp_action);
+		break;
+
+	default:
+		ZFCP_LOG_NORMAL("bug: unknown erp step 0x%08x\n",
+				erp_action->step);
+		retval = ZFCP_ERP_FAILED;
+	}
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	makes the erp thread continue with reopen (physical) port
+ *		actions which have been paused until the name server port
+ *		is opened (or failed)
+ *
+ * returns:	0	(a kind of void retval, its not used)
+ */
+static int
+zfcp_erp_port_strategy_open_nameserver_wakeup(struct zfcp_erp_action
+					      *ns_erp_action)
+{
+	int retval = 0;
+	unsigned long flags;
+	struct zfcp_adapter *adapter = ns_erp_action->adapter;
+	struct zfcp_erp_action *erp_action, *tmp;
+
+	read_lock_irqsave(&adapter->erp_lock, flags);
+	list_for_each_entry_safe(erp_action, tmp, &adapter->erp_running_head,
+				 list) {
+		debug_text_event(adapter->erp_dbf, 4, "p_pstnsw_n");
+		debug_event(adapter->erp_dbf, 4, &erp_action->port->wwpn,
+			    sizeof (wwn_t));
+		if (erp_action->step == ZFCP_ERP_STEP_NAMESERVER_OPEN) {
+			debug_text_event(adapter->erp_dbf, 3, "p_pstnsw_w");
+			debug_event(adapter->erp_dbf, 3,
+				    &erp_action->port->wwpn, sizeof (wwn_t));
+			if (atomic_test_mask(
+				    ZFCP_STATUS_COMMON_ERP_FAILED,
+				    &adapter->nameserver_port->status))
+				zfcp_erp_port_failed(erp_action->port);
+			zfcp_erp_action_ready(erp_action);
+		}
+	}
+	read_unlock_irqrestore(&adapter->erp_lock, flags);
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:	ZFCP_ERP_CONTINUES	- action continues (asynchronously)
+ *		ZFCP_ERP_FAILED		- action finished unsuccessfully
+ */
+static int
+zfcp_erp_port_forced_strategy_close(struct zfcp_erp_action *erp_action)
+{
+	int retval;
+	struct zfcp_adapter *adapter = erp_action->adapter;
+	struct zfcp_port *port = erp_action->port;
+
+	zfcp_erp_timeout_init(erp_action);
+	retval = zfcp_fsf_close_physical_port(erp_action);
+	if (retval == -ENOMEM) {
+		debug_text_event(adapter->erp_dbf, 5, "o_pfstc_nomem");
+		debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t));
+		retval = ZFCP_ERP_NOMEM;
+		goto out;
+	}
+	erp_action->step = ZFCP_ERP_STEP_PHYS_PORT_CLOSING;
+	if (retval != 0) {
+		debug_text_event(adapter->erp_dbf, 5, "o_pfstc_cpf");
+		debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t));
+		/* could not send 'open', fail */
+		retval = ZFCP_ERP_FAILED;
+		goto out;
+	}
+	debug_text_event(adapter->erp_dbf, 6, "o_pfstc_cpok");
+	debug_event(adapter->erp_dbf, 6, &port->wwpn, sizeof (wwn_t));
+	retval = ZFCP_ERP_CONTINUES;
+ out:
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:
+ */
+static int
+zfcp_erp_port_strategy_clearstati(struct zfcp_port *port)
+{
+	int retval = 0;
+	struct zfcp_adapter *adapter = port->adapter;
+
+	debug_text_event(adapter->erp_dbf, 5, "p_pstclst");
+	debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t));
+
+	atomic_clear_mask(ZFCP_STATUS_COMMON_OPENING |
+			  ZFCP_STATUS_COMMON_CLOSING |
+			  ZFCP_STATUS_COMMON_ACCESS_DENIED |
+			  ZFCP_STATUS_PORT_DID_DID |
+			  ZFCP_STATUS_PORT_PHYS_CLOSING |
+			  ZFCP_STATUS_PORT_INVALID_WWPN,
+			  &port->status);
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:	ZFCP_ERP_CONTINUES	- action continues (asynchronously)
+ *		ZFCP_ERP_FAILED		- action finished unsuccessfully
+ */
+static int
+zfcp_erp_port_strategy_close(struct zfcp_erp_action *erp_action)
+{
+	int retval;
+	struct zfcp_adapter *adapter = erp_action->adapter;
+	struct zfcp_port *port = erp_action->port;
+
+	zfcp_erp_timeout_init(erp_action);
+	retval = zfcp_fsf_close_port(erp_action);
+	if (retval == -ENOMEM) {
+		debug_text_event(adapter->erp_dbf, 5, "p_pstc_nomem");
+		debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t));
+		retval = ZFCP_ERP_NOMEM;
+		goto out;
+	}
+	erp_action->step = ZFCP_ERP_STEP_PORT_CLOSING;
+	if (retval != 0) {
+		debug_text_event(adapter->erp_dbf, 5, "p_pstc_cpf");
+		debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t));
+		/* could not send 'close', fail */
+		retval = ZFCP_ERP_FAILED;
+		goto out;
+	}
+	debug_text_event(adapter->erp_dbf, 6, "p_pstc_cpok");
+	debug_event(adapter->erp_dbf, 6, &port->wwpn, sizeof (wwn_t));
+	retval = ZFCP_ERP_CONTINUES;
+ out:
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:	ZFCP_ERP_CONTINUES	- action continues (asynchronously)
+ *		ZFCP_ERP_FAILED		- action finished unsuccessfully
+ */
+static int
+zfcp_erp_port_strategy_open_port(struct zfcp_erp_action *erp_action)
+{
+	int retval;
+	struct zfcp_adapter *adapter = erp_action->adapter;
+	struct zfcp_port *port = erp_action->port;
+
+	zfcp_erp_timeout_init(erp_action);
+	retval = zfcp_fsf_open_port(erp_action);
+	if (retval == -ENOMEM) {
+		debug_text_event(adapter->erp_dbf, 5, "p_psto_nomem");
+		debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t));
+		retval = ZFCP_ERP_NOMEM;
+		goto out;
+	}
+	erp_action->step = ZFCP_ERP_STEP_PORT_OPENING;
+	if (retval != 0) {
+		debug_text_event(adapter->erp_dbf, 5, "p_psto_opf");
+		debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t));
+		/* could not send 'open', fail */
+		retval = ZFCP_ERP_FAILED;
+		goto out;
+	}
+	debug_text_event(adapter->erp_dbf, 6, "p_psto_opok");
+	debug_event(adapter->erp_dbf, 6, &port->wwpn, sizeof (wwn_t));
+	retval = ZFCP_ERP_CONTINUES;
+ out:
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:	ZFCP_ERP_CONTINUES	- action continues (asynchronously)
+ *		ZFCP_ERP_FAILED		- action finished unsuccessfully
+ */
+static int
+zfcp_erp_port_strategy_open_common_lookup(struct zfcp_erp_action *erp_action)
+{
+	int retval;
+	struct zfcp_adapter *adapter = erp_action->adapter;
+	struct zfcp_port *port = erp_action->port;
+
+	zfcp_erp_timeout_init(erp_action);
+	retval = zfcp_ns_gid_pn_request(erp_action);
+	if (retval == -ENOMEM) {
+		debug_text_event(adapter->erp_dbf, 5, "p_pstn_nomem");
+		debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t));
+		retval = ZFCP_ERP_NOMEM;
+		goto out;
+	}
+	erp_action->step = ZFCP_ERP_STEP_NAMESERVER_LOOKUP;
+	if (retval != 0) {
+		debug_text_event(adapter->erp_dbf, 5, "p_pstn_ref");
+		debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t));
+		/* could not send nameserver request, fail */
+		retval = ZFCP_ERP_FAILED;
+		goto out;
+	}
+	debug_text_event(adapter->erp_dbf, 6, "p_pstn_reok");
+	debug_event(adapter->erp_dbf, 6, &port->wwpn, sizeof (wwn_t));
+	retval = ZFCP_ERP_CONTINUES;
+ out:
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	this routine executes the 'Reopen Unit' action
+ *		currently no retries
+ *
+ * returns:	ZFCP_ERP_CONTINUES	- action continues (asynchronously)
+ *		ZFCP_ERP_SUCCEEDED	- action finished successfully
+ *		ZFCP_ERP_FAILED		- action finished unsuccessfully
+ */
+static int
+zfcp_erp_unit_strategy(struct zfcp_erp_action *erp_action)
+{
+	int retval = ZFCP_ERP_FAILED;
+	struct zfcp_unit *unit = erp_action->unit;
+	struct zfcp_adapter *adapter = erp_action->adapter;
+
+	switch (erp_action->step) {
+
+		/*
+		 * FIXME:
+		 * the ULP spec. begs for waiting for oustanding commands
+		 */
+	case ZFCP_ERP_STEP_UNINITIALIZED:
+		zfcp_erp_unit_strategy_clearstati(unit);
+		if (atomic_test_mask(ZFCP_STATUS_COMMON_OPEN, &unit->status)) {
+			ZFCP_LOG_DEBUG("unit 0x%016Lx is open -> "
+				       "trying close\n", unit->fcp_lun);
+			retval = zfcp_erp_unit_strategy_close(erp_action);
+			break;
+		}
+		/* else it's already closed, fall through */
+	case ZFCP_ERP_STEP_UNIT_CLOSING:
+		if (atomic_test_mask(ZFCP_STATUS_COMMON_OPEN, &unit->status)) {
+			ZFCP_LOG_DEBUG("close failed for unit 0x%016Lx\n",
+				       unit->fcp_lun);
+			retval = ZFCP_ERP_FAILED;
+		} else {
+			if (erp_action->status & ZFCP_STATUS_ERP_CLOSE_ONLY)
+				retval = ZFCP_ERP_EXIT;
+			else {
+				ZFCP_LOG_DEBUG("unit 0x%016Lx is not open -> "
+					       "trying open\n", unit->fcp_lun);
+				retval =
+				    zfcp_erp_unit_strategy_open(erp_action);
+			}
+		}
+		break;
+
+	case ZFCP_ERP_STEP_UNIT_OPENING:
+		if (atomic_test_mask(ZFCP_STATUS_COMMON_OPEN, &unit->status)) {
+			ZFCP_LOG_DEBUG("unit 0x%016Lx is open\n",
+				       unit->fcp_lun);
+			retval = ZFCP_ERP_SUCCEEDED;
+		} else {
+			ZFCP_LOG_DEBUG("open failed for unit 0x%016Lx\n",
+				       unit->fcp_lun);
+			retval = ZFCP_ERP_FAILED;
+		}
+		break;
+	}
+
+	debug_text_event(adapter->erp_dbf, 3, "u_ust/ret");
+	debug_event(adapter->erp_dbf, 3, &unit->fcp_lun, sizeof (fcp_lun_t));
+	debug_event(adapter->erp_dbf, 3, &erp_action->action, sizeof (int));
+	debug_event(adapter->erp_dbf, 3, &retval, sizeof (int));
+	return retval;
+}
+
+/*
+ * function:
+ *
+ * purpose:
+ *
+ * returns:
+ */
+static int
+zfcp_erp_unit_strategy_clearstati(struct zfcp_unit *unit)
+{
+	int retval = 0;
+	struct zfcp_adapter *adapter = unit->port->adapter;
+
+	debug_text_event(adapter->erp_dbf, 5, "u_ustclst");
+	debug_event(adapter->erp_dbf, 5, &unit->fcp_lun, sizeof (fcp_lun_t));
+
+	atomic_clear_mask(ZFCP_STATUS_COMMON_OPENING |
+			  ZFCP_STATUS_COMMON_CLOSING |
+			  ZFCP_STATUS_COMMON_ACCESS_DENIED |
+			  ZFCP_STATUS_UNIT_SHARED |
+			  ZFCP_STATUS_UNIT_READONLY,
+			  &unit->status);
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:	ZFCP_ERP_CONTINUES	- action continues (asynchronously)
+ *		ZFCP_ERP_FAILED		- action finished unsuccessfully
+ */
+static int
+zfcp_erp_unit_strategy_close(struct zfcp_erp_action *erp_action)
+{
+	int retval;
+	struct zfcp_adapter *adapter = erp_action->adapter;
+	struct zfcp_unit *unit = erp_action->unit;
+
+	zfcp_erp_timeout_init(erp_action);
+	retval = zfcp_fsf_close_unit(erp_action);
+	if (retval == -ENOMEM) {
+		debug_text_event(adapter->erp_dbf, 5, "u_ustc_nomem");
+		debug_event(adapter->erp_dbf, 5, &unit->fcp_lun,
+			    sizeof (fcp_lun_t));
+		retval = ZFCP_ERP_NOMEM;
+		goto out;
+	}
+	erp_action->step = ZFCP_ERP_STEP_UNIT_CLOSING;
+	if (retval != 0) {
+		debug_text_event(adapter->erp_dbf, 5, "u_ustc_cuf");
+		debug_event(adapter->erp_dbf, 5, &unit->fcp_lun,
+			    sizeof (fcp_lun_t));
+		/* could not send 'close', fail */
+		retval = ZFCP_ERP_FAILED;
+		goto out;
+	}
+	debug_text_event(adapter->erp_dbf, 6, "u_ustc_cuok");
+	debug_event(adapter->erp_dbf, 6, &unit->fcp_lun, sizeof (fcp_lun_t));
+	retval = ZFCP_ERP_CONTINUES;
+
+ out:
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:	ZFCP_ERP_CONTINUES	- action continues (asynchronously)
+ *		ZFCP_ERP_FAILED		- action finished unsuccessfully
+ */
+static int
+zfcp_erp_unit_strategy_open(struct zfcp_erp_action *erp_action)
+{
+	int retval;
+	struct zfcp_adapter *adapter = erp_action->adapter;
+	struct zfcp_unit *unit = erp_action->unit;
+
+	zfcp_erp_timeout_init(erp_action);
+	retval = zfcp_fsf_open_unit(erp_action);
+	if (retval == -ENOMEM) {
+		debug_text_event(adapter->erp_dbf, 5, "u_usto_nomem");
+		debug_event(adapter->erp_dbf, 5, &unit->fcp_lun,
+			    sizeof (fcp_lun_t));
+		retval = ZFCP_ERP_NOMEM;
+		goto out;
+	}
+	erp_action->step = ZFCP_ERP_STEP_UNIT_OPENING;
+	if (retval != 0) {
+		debug_text_event(adapter->erp_dbf, 5, "u_usto_ouf");
+		debug_event(adapter->erp_dbf, 5, &unit->fcp_lun,
+			    sizeof (fcp_lun_t));
+		/* could not send 'open', fail */
+		retval = ZFCP_ERP_FAILED;
+		goto out;
+	}
+	debug_text_event(adapter->erp_dbf, 6, "u_usto_ouok");
+	debug_event(adapter->erp_dbf, 6, &unit->fcp_lun, sizeof (fcp_lun_t));
+	retval = ZFCP_ERP_CONTINUES;
+ out:
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:
+ */
+static inline void
+zfcp_erp_timeout_init(struct zfcp_erp_action *erp_action)
+{
+	init_timer(&erp_action->timer);
+	erp_action->timer.function = zfcp_erp_timeout_handler;
+	erp_action->timer.data = (unsigned long) erp_action;
+	/* jiffies will be added in zfcp_fsf_req_send */
+	erp_action->timer.expires = ZFCP_ERP_FSFREQ_TIMEOUT;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	enqueue the specified error recovery action, if needed
+ *
+ * returns:
+ */
+static int
+zfcp_erp_action_enqueue(int action,
+			struct zfcp_adapter *adapter,
+			struct zfcp_port *port, struct zfcp_unit *unit)
+{
+	int retval = 1;
+	struct zfcp_erp_action *erp_action = NULL;
+	int stronger_action = 0;
+	u32 status = 0;
+
+	/*
+	 * We need some rules here which check whether we really need
+	 * this action or whether we should just drop it.
+	 * E.g. if there is a unfinished 'Reopen Port' request then we drop a
+	 * 'Reopen Unit' request for an associated unit since we can't
+	 * satisfy this request now. A 'Reopen Port' action will trigger
+	 * 'Reopen Unit' actions when it completes.
+	 * Thus, there are only actions in the queue which can immediately be
+	 * executed. This makes the processing of the action queue more
+	 * efficient.
+	 */
+
+	if (!atomic_test_mask(ZFCP_STATUS_ADAPTER_ERP_THREAD_UP,
+			      &adapter->status))
+		return -EIO;
+
+	debug_event(adapter->erp_dbf, 4, &action, sizeof (int));
+	/* check whether we really need this */
+	switch (action) {
+	case ZFCP_ERP_ACTION_REOPEN_UNIT:
+		if (atomic_test_mask
+		    (ZFCP_STATUS_COMMON_ERP_INUSE, &unit->status)) {
+			debug_text_event(adapter->erp_dbf, 4, "u_actenq_drp");
+			debug_event(adapter->erp_dbf, 4, &port->wwpn,
+				    sizeof (wwn_t));
+			debug_event(adapter->erp_dbf, 4, &unit->fcp_lun,
+				    sizeof (fcp_lun_t));
+			goto out;
+		}
+		if (!atomic_test_mask
+		    (ZFCP_STATUS_COMMON_RUNNING, &port->status) ||
+		    atomic_test_mask
+		    (ZFCP_STATUS_COMMON_ERP_FAILED, &port->status)) {
+			goto out;
+		}
+		if (!atomic_test_mask
+		    (ZFCP_STATUS_COMMON_UNBLOCKED, &port->status)) {
+			stronger_action = ZFCP_ERP_ACTION_REOPEN_PORT;
+			unit = NULL;
+		}
+		/* fall through !!! */
+
+	case ZFCP_ERP_ACTION_REOPEN_PORT:
+		if (atomic_test_mask
+		    (ZFCP_STATUS_COMMON_ERP_INUSE, &port->status)) {
+			debug_text_event(adapter->erp_dbf, 4, "p_actenq_drp");
+			debug_event(adapter->erp_dbf, 4, &port->wwpn,
+				    sizeof (wwn_t));
+			goto out;
+		}
+		/* fall through !!! */
+
+	case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED:
+		if (atomic_test_mask
+		    (ZFCP_STATUS_COMMON_ERP_INUSE, &port->status)
+		    && port->erp_action.action ==
+		    ZFCP_ERP_ACTION_REOPEN_PORT_FORCED) {
+			debug_text_event(adapter->erp_dbf, 4, "pf_actenq_drp");
+			debug_event(adapter->erp_dbf, 4, &port->wwpn,
+				    sizeof (wwn_t));
+			goto out;
+		}
+		if (!atomic_test_mask
+		    (ZFCP_STATUS_COMMON_RUNNING, &adapter->status) ||
+		    atomic_test_mask
+		    (ZFCP_STATUS_COMMON_ERP_FAILED, &adapter->status)) {
+			goto out;
+		}
+		if (!atomic_test_mask
+		    (ZFCP_STATUS_COMMON_UNBLOCKED, &adapter->status)) {
+			stronger_action = ZFCP_ERP_ACTION_REOPEN_ADAPTER;
+			port = NULL;
+		}
+		/* fall through !!! */
+
+	case ZFCP_ERP_ACTION_REOPEN_ADAPTER:
+		if (atomic_test_mask
+		    (ZFCP_STATUS_COMMON_ERP_INUSE, &adapter->status)) {
+			debug_text_event(adapter->erp_dbf, 4, "a_actenq_drp");
+			goto out;
+		}
+		break;
+
+	default:
+		debug_text_exception(adapter->erp_dbf, 1, "a_actenq_bug");
+		debug_event(adapter->erp_dbf, 1, &action, sizeof (int));
+		ZFCP_LOG_NORMAL("bug: unknown erp action requested "
+				"on adapter %s (action=%d)\n",
+				zfcp_get_busid_by_adapter(adapter), action);
+		goto out;
+	}
+
+	/* check whether we need something stronger first */
+	if (stronger_action) {
+		debug_text_event(adapter->erp_dbf, 4, "a_actenq_str");
+		debug_event(adapter->erp_dbf, 4, &stronger_action,
+			    sizeof (int));
+		ZFCP_LOG_DEBUG("stronger erp action %d needed before "
+			       "erp action %d on adapter %s\n",
+			       stronger_action, action,
+			       zfcp_get_busid_by_adapter(adapter));
+		action = stronger_action;
+	}
+
+	/* mark adapter to have some error recovery pending */
+	atomic_set_mask(ZFCP_STATUS_ADAPTER_ERP_PENDING, &adapter->status);
+
+	/* setup error recovery action */
+	switch (action) {
+
+	case ZFCP_ERP_ACTION_REOPEN_UNIT:
+		zfcp_unit_get(unit);
+		atomic_set_mask(ZFCP_STATUS_COMMON_ERP_INUSE, &unit->status);
+		erp_action = &unit->erp_action;
+		if (!atomic_test_mask
+		    (ZFCP_STATUS_COMMON_RUNNING, &unit->status))
+			status = ZFCP_STATUS_ERP_CLOSE_ONLY;
+		break;
+
+	case ZFCP_ERP_ACTION_REOPEN_PORT:
+	case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED:
+		zfcp_port_get(port);
+		zfcp_erp_action_dismiss_port(port);
+		atomic_set_mask(ZFCP_STATUS_COMMON_ERP_INUSE, &port->status);
+		erp_action = &port->erp_action;
+		if (!atomic_test_mask
+		    (ZFCP_STATUS_COMMON_RUNNING, &port->status))
+			status = ZFCP_STATUS_ERP_CLOSE_ONLY;
+		break;
+
+	case ZFCP_ERP_ACTION_REOPEN_ADAPTER:
+		zfcp_adapter_get(adapter);
+		zfcp_erp_action_dismiss_adapter(adapter);
+		atomic_set_mask(ZFCP_STATUS_COMMON_ERP_INUSE, &adapter->status);
+		erp_action = &adapter->erp_action;
+		if (!atomic_test_mask
+		    (ZFCP_STATUS_COMMON_RUNNING, &adapter->status))
+			status = ZFCP_STATUS_ERP_CLOSE_ONLY;
+		break;
+	}
+
+	debug_text_event(adapter->erp_dbf, 4, "a_actenq");
+
+	memset(erp_action, 0, sizeof (struct zfcp_erp_action));
+	erp_action->adapter = adapter;
+	erp_action->port = port;
+	erp_action->unit = unit;
+	erp_action->action = action;
+	erp_action->status = status;
+
+	++adapter->erp_total_count;
+
+	/* finally put it into 'ready' queue and kick erp thread */
+	list_add(&erp_action->list, &adapter->erp_ready_head);
+	up(&adapter->erp_ready_sem);
+	retval = 0;
+ out:
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:
+ */
+static int
+zfcp_erp_action_dequeue(struct zfcp_erp_action *erp_action)
+{
+	int retval = 0;
+	struct zfcp_adapter *adapter = erp_action->adapter;
+
+	--adapter->erp_total_count;
+	if (erp_action->status & ZFCP_STATUS_ERP_LOWMEM) {
+		--adapter->erp_low_mem_count;
+		erp_action->status &= ~ZFCP_STATUS_ERP_LOWMEM;
+	}
+
+	debug_text_event(adapter->erp_dbf, 4, "a_actdeq");
+	debug_event(adapter->erp_dbf, 4, &erp_action->action, sizeof (int));
+	list_del(&erp_action->list);
+	switch (erp_action->action) {
+	case ZFCP_ERP_ACTION_REOPEN_UNIT:
+		atomic_clear_mask(ZFCP_STATUS_COMMON_ERP_INUSE,
+				  &erp_action->unit->status);
+		break;
+	case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED:
+	case ZFCP_ERP_ACTION_REOPEN_PORT:
+		atomic_clear_mask(ZFCP_STATUS_COMMON_ERP_INUSE,
+				  &erp_action->port->status);
+		break;
+	case ZFCP_ERP_ACTION_REOPEN_ADAPTER:
+		atomic_clear_mask(ZFCP_STATUS_COMMON_ERP_INUSE,
+				  &erp_action->adapter->status);
+		break;
+	default:
+		/* bug */
+		break;
+	}
+	return retval;
+}
+
+/**
+ * zfcp_erp_action_cleanup
+ *
+ * Register unit with scsi stack if appropiate and fix reference counts.
+ * Note: Temporary units are not registered with scsi stack.
+ */
+static void
+zfcp_erp_action_cleanup(int action, struct zfcp_adapter *adapter,
+			struct zfcp_port *port, struct zfcp_unit *unit,
+			int result)
+{
+	switch (action) {
+	case ZFCP_ERP_ACTION_REOPEN_UNIT:
+		if ((result == ZFCP_ERP_SUCCEEDED)
+		    && (!atomic_test_mask(ZFCP_STATUS_UNIT_TEMPORARY,
+					  &unit->status))
+		    && (!unit->device))
+ 			scsi_add_device(unit->port->adapter->scsi_host, 0,
+ 					unit->port->scsi_id, unit->scsi_lun);
+		zfcp_unit_put(unit);
+		break;
+	case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED:
+	case ZFCP_ERP_ACTION_REOPEN_PORT:
+		zfcp_port_put(port);
+		break;
+	case ZFCP_ERP_ACTION_REOPEN_ADAPTER:
+		zfcp_adapter_put(adapter);
+		break;
+	default:
+		break;
+	}
+}
+
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:	FIXME
+ */
+static int
+zfcp_erp_action_dismiss_adapter(struct zfcp_adapter *adapter)
+{
+	int retval = 0;
+	struct zfcp_port *port;
+
+	debug_text_event(adapter->erp_dbf, 5, "a_actab");
+	if (atomic_test_mask(ZFCP_STATUS_COMMON_ERP_INUSE, &adapter->status))
+		zfcp_erp_action_dismiss(&adapter->erp_action);
+	else
+		list_for_each_entry(port, &adapter->port_list_head, list)
+		    zfcp_erp_action_dismiss_port(port);
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:	FIXME
+ */
+static int
+zfcp_erp_action_dismiss_port(struct zfcp_port *port)
+{
+	int retval = 0;
+	struct zfcp_unit *unit;
+	struct zfcp_adapter *adapter = port->adapter;
+
+	debug_text_event(adapter->erp_dbf, 5, "p_actab");
+	debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t));
+	if (atomic_test_mask(ZFCP_STATUS_COMMON_ERP_INUSE, &port->status))
+		zfcp_erp_action_dismiss(&port->erp_action);
+	else
+		list_for_each_entry(unit, &port->unit_list_head, list)
+		    zfcp_erp_action_dismiss_unit(unit);
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:	FIXME
+ */
+static int
+zfcp_erp_action_dismiss_unit(struct zfcp_unit *unit)
+{
+	int retval = 0;
+	struct zfcp_adapter *adapter = unit->port->adapter;
+
+	debug_text_event(adapter->erp_dbf, 5, "u_actab");
+	debug_event(adapter->erp_dbf, 5, &unit->fcp_lun, sizeof (fcp_lun_t));
+	if (atomic_test_mask(ZFCP_STATUS_COMMON_ERP_INUSE, &unit->status))
+		zfcp_erp_action_dismiss(&unit->erp_action);
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	moves erp_action to 'erp running list'
+ *
+ * returns:
+ */
+static inline void
+zfcp_erp_action_to_running(struct zfcp_erp_action *erp_action)
+{
+	struct zfcp_adapter *adapter = erp_action->adapter;
+
+	debug_text_event(adapter->erp_dbf, 6, "a_toru");
+	debug_event(adapter->erp_dbf, 6, &erp_action->action, sizeof (int));
+	list_move(&erp_action->list, &erp_action->adapter->erp_running_head);
+}
+
+/*
+ * function:	
+ *
+ * purpose:	moves erp_action to 'erp ready list'
+ *
+ * returns:
+ */
+static inline void
+zfcp_erp_action_to_ready(struct zfcp_erp_action *erp_action)
+{
+	struct zfcp_adapter *adapter = erp_action->adapter;
+
+	debug_text_event(adapter->erp_dbf, 6, "a_tore");
+	debug_event(adapter->erp_dbf, 6, &erp_action->action, sizeof (int));
+	list_move(&erp_action->list, &erp_action->adapter->erp_ready_head);
+}
+
+/*
+ * function:	zfcp_erp_port_access_denied
+ *
+ * purpose:
+ */
+void
+zfcp_erp_port_access_denied(struct zfcp_port *port)
+{
+	struct zfcp_adapter *adapter = port->adapter;
+	unsigned long flags;
+
+	debug_text_event(adapter->erp_dbf, 3, "p_access_block");
+	debug_event(adapter->erp_dbf, 3, &port->wwpn, sizeof(wwn_t));
+	read_lock_irqsave(&zfcp_data.config_lock, flags);
+	zfcp_erp_modify_port_status(port, ZFCP_STATUS_COMMON_ERP_FAILED |
+				    ZFCP_STATUS_COMMON_ACCESS_DENIED, ZFCP_SET);
+	read_unlock_irqrestore(&zfcp_data.config_lock, flags);
+}
+
+/*
+ * function:	zfcp_erp_unit_access_denied
+ *
+ * purpose:
+ */
+void
+zfcp_erp_unit_access_denied(struct zfcp_unit *unit)
+{
+	struct zfcp_adapter *adapter = unit->port->adapter;
+
+	debug_text_event(adapter->erp_dbf, 3, "u_access_block");
+	debug_event(adapter->erp_dbf, 3, &unit->fcp_lun, sizeof(fcp_lun_t));
+	zfcp_erp_modify_unit_status(unit, ZFCP_STATUS_COMMON_ERP_FAILED |
+				    ZFCP_STATUS_COMMON_ACCESS_DENIED, ZFCP_SET);
+}
+
+/*
+ * function:	zfcp_erp_adapter_access_changed
+ *
+ * purpose:
+ */
+void
+zfcp_erp_adapter_access_changed(struct zfcp_adapter *adapter)
+{
+	struct zfcp_port *port;
+	unsigned long flags;
+
+	debug_text_event(adapter->erp_dbf, 3, "a_access_unblock");
+	debug_event(adapter->erp_dbf, 3, &adapter->name, 8);
+
+	zfcp_erp_port_access_changed(adapter->nameserver_port);
+	read_lock_irqsave(&zfcp_data.config_lock, flags);
+	list_for_each_entry(port, &adapter->port_list_head, list)
+		if (port != adapter->nameserver_port)
+			zfcp_erp_port_access_changed(port);
+	read_unlock_irqrestore(&zfcp_data.config_lock, flags);
+}
+
+/*
+ * function:	zfcp_erp_port_access_changed
+ *
+ * purpose:
+ */
+void
+zfcp_erp_port_access_changed(struct zfcp_port *port)
+{
+	struct zfcp_adapter *adapter = port->adapter;
+	struct zfcp_unit *unit;
+
+	debug_text_event(adapter->erp_dbf, 3, "p_access_unblock");
+	debug_event(adapter->erp_dbf, 3, &port->wwpn, sizeof(wwn_t));
+
+	if (!atomic_test_mask(ZFCP_STATUS_COMMON_ACCESS_DENIED,
+			      &port->status)) {
+		if (!atomic_test_mask(ZFCP_STATUS_PORT_WKA, &port->status))
+			list_for_each_entry(unit, &port->unit_list_head, list)
+				zfcp_erp_unit_access_changed(unit);
+		return;
+	}
+
+	ZFCP_LOG_NORMAL("reopen of port 0x%016Lx on adapter %s "
+			"(due to ACT update)\n",
+			port->wwpn, zfcp_get_busid_by_adapter(adapter));
+	if (zfcp_erp_port_reopen(port, ZFCP_STATUS_COMMON_ERP_FAILED) != 0)
+		ZFCP_LOG_NORMAL("failed reopen of port"
+				"(adapter %s, wwpn=0x%016Lx)\n",
+				zfcp_get_busid_by_adapter(adapter), port->wwpn);
+}
+
+/*
+ * function:	zfcp_erp_unit_access_changed
+ *
+ * purpose:
+ */
+void
+zfcp_erp_unit_access_changed(struct zfcp_unit *unit)
+{
+	struct zfcp_adapter *adapter = unit->port->adapter;
+
+	debug_text_event(adapter->erp_dbf, 3, "u_access_unblock");
+	debug_event(adapter->erp_dbf, 3, &unit->fcp_lun, sizeof(fcp_lun_t));
+
+	if (!atomic_test_mask(ZFCP_STATUS_COMMON_ACCESS_DENIED, &unit->status))
+		return;
+
+	ZFCP_LOG_NORMAL("reopen of unit 0x%016Lx on port 0x%016Lx "
+			" on adapter %s (due to ACT update)\n",
+			unit->fcp_lun, unit->port->wwpn,
+			zfcp_get_busid_by_adapter(adapter));
+	if (zfcp_erp_unit_reopen(unit, ZFCP_STATUS_COMMON_ERP_FAILED) != 0)
+		ZFCP_LOG_NORMAL("failed reopen of unit (adapter %s, "
+				"wwpn=0x%016Lx, fcp_lun=0x%016Lx)\n",
+				zfcp_get_busid_by_adapter(adapter),
+				unit->port->wwpn, unit->fcp_lun);
+}
+
+#undef ZFCP_LOG_AREA
diff --git a/drivers/s390/scsi/zfcp_ext.h b/drivers/s390/scsi/zfcp_ext.h
new file mode 100644
index 0000000..d5fd433
--- /dev/null
+++ b/drivers/s390/scsi/zfcp_ext.h
@@ -0,0 +1,186 @@
+/* 
+ * 
+ * linux/drivers/s390/scsi/zfcp_ext.h
+ * 
+ * FCP adapter driver for IBM eServer zSeries 
+ * 
+ * (C) Copyright IBM Corp. 2002, 2004
+ *
+ * Author(s): Martin Peschke <mpeschke@de.ibm.com> 
+ *            Raimund Schroeder <raimund.schroeder@de.ibm.com> 
+ *            Aron Zeh
+ *            Wolfgang Taphorn
+ *            Stefan Bader <stefan.bader@de.ibm.com> 
+ *            Heiko Carstens <heiko.carstens@de.ibm.com> 
+ *            Andreas Herrmann <aherrman@de.ibm.com>
+ * 
+ * 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, or (at your option) 
+ * any later version. 
+ * 
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA. 
+ */
+
+#ifndef ZFCP_EXT_H
+#define ZFCP_EXT_H
+
+#define ZFCP_EXT_REVISION "$Revision: 1.62 $"
+
+#include "zfcp_def.h"
+
+extern struct zfcp_data zfcp_data;
+
+/******************************** SYSFS  *************************************/
+extern int  zfcp_sysfs_driver_create_files(struct device_driver *);
+extern void zfcp_sysfs_driver_remove_files(struct device_driver *);
+extern int  zfcp_sysfs_adapter_create_files(struct device *);
+extern void zfcp_sysfs_adapter_remove_files(struct device *);
+extern int  zfcp_sysfs_port_create_files(struct device *, u32);
+extern void zfcp_sysfs_port_remove_files(struct device *, u32);
+extern int  zfcp_sysfs_unit_create_files(struct device *);
+extern void zfcp_sysfs_unit_remove_files(struct device *);
+extern void zfcp_sysfs_port_release(struct device *);
+extern void zfcp_sysfs_unit_release(struct device *);
+
+/**************************** CONFIGURATION  *********************************/
+extern struct zfcp_unit *zfcp_get_unit_by_lun(struct zfcp_port *, fcp_lun_t);
+extern struct zfcp_port *zfcp_get_port_by_wwpn(struct zfcp_adapter *, wwn_t);
+extern struct zfcp_port *zfcp_get_port_by_did(struct zfcp_adapter *, u32);
+struct zfcp_adapter *zfcp_get_adapter_by_busid(char *);
+extern struct zfcp_adapter *zfcp_adapter_enqueue(struct ccw_device *);
+extern int    zfcp_adapter_debug_register(struct zfcp_adapter *);
+extern void   zfcp_adapter_dequeue(struct zfcp_adapter *);
+extern void   zfcp_adapter_debug_unregister(struct zfcp_adapter *);
+extern struct zfcp_port *zfcp_port_enqueue(struct zfcp_adapter *, wwn_t,
+					   u32, u32);
+extern void   zfcp_port_dequeue(struct zfcp_port *);
+extern struct zfcp_unit *zfcp_unit_enqueue(struct zfcp_port *, fcp_lun_t);
+extern void   zfcp_unit_dequeue(struct zfcp_unit *);
+
+/******************************* S/390 IO ************************************/
+extern int  zfcp_ccw_register(void);
+extern void zfcp_ccw_unregister(void);
+
+extern void zfcp_qdio_zero_sbals(struct qdio_buffer **, int, int);
+extern int  zfcp_qdio_allocate(struct zfcp_adapter *);
+extern int  zfcp_qdio_allocate_queues(struct zfcp_adapter *);
+extern void zfcp_qdio_free_queues(struct zfcp_adapter *);
+extern int  zfcp_qdio_determine_pci(struct zfcp_qdio_queue *,
+				    struct zfcp_fsf_req *);
+extern int  zfcp_qdio_reqid_check(struct zfcp_adapter *, void *);
+
+extern volatile struct qdio_buffer_element *zfcp_qdio_sbale_req
+	(struct zfcp_fsf_req *, int, int);
+extern volatile struct qdio_buffer_element *zfcp_qdio_sbale_curr
+	(struct zfcp_fsf_req *);
+extern int zfcp_qdio_sbals_from_sg
+	(struct zfcp_fsf_req *, unsigned long, struct scatterlist *, int, int);
+extern int zfcp_qdio_sbals_from_scsicmnd
+	(struct zfcp_fsf_req *, unsigned long, struct scsi_cmnd *);
+
+
+/******************************** FSF ****************************************/
+extern int  zfcp_fsf_open_port(struct zfcp_erp_action *);
+extern int  zfcp_fsf_close_port(struct zfcp_erp_action *);
+extern int  zfcp_fsf_close_physical_port(struct zfcp_erp_action *);
+
+extern int  zfcp_fsf_open_unit(struct zfcp_erp_action *);
+extern int  zfcp_fsf_close_unit(struct zfcp_erp_action *);
+
+extern int  zfcp_fsf_exchange_config_data(struct zfcp_erp_action *);
+extern int  zfcp_fsf_exchange_port_data(struct zfcp_adapter *,
+					struct fsf_qtcb_bottom_port *);
+extern int  zfcp_fsf_control_file(struct zfcp_adapter *, struct zfcp_fsf_req **,
+				  u32, u32, struct zfcp_sg_list *);
+extern void zfcp_fsf_request_timeout_handler(unsigned long);
+extern void zfcp_fsf_scsi_er_timeout_handler(unsigned long);
+extern int  zfcp_fsf_req_dismiss_all(struct zfcp_adapter *);
+extern int  zfcp_fsf_status_read(struct zfcp_adapter *, int);
+extern int zfcp_fsf_req_create(struct zfcp_adapter *, u32, int, mempool_t *,
+			       unsigned long *, struct zfcp_fsf_req **);
+extern int zfcp_fsf_send_ct(struct zfcp_send_ct *, mempool_t *,
+			    struct zfcp_erp_action *);
+extern int zfcp_fsf_send_els(struct zfcp_send_els *);
+extern int  zfcp_fsf_req_wait_and_cleanup(struct zfcp_fsf_req *, int, u32 *);
+extern int  zfcp_fsf_send_fcp_command_task(struct zfcp_adapter *,
+					   struct zfcp_unit *,
+					   struct scsi_cmnd *,
+					   struct timer_list*, int);
+extern int  zfcp_fsf_req_complete(struct zfcp_fsf_req *);
+extern void zfcp_fsf_incoming_els(struct zfcp_fsf_req *);
+extern void zfcp_fsf_req_cleanup(struct zfcp_fsf_req *);
+extern struct zfcp_fsf_req *zfcp_fsf_send_fcp_command_task_management(
+	struct zfcp_adapter *, struct zfcp_unit *, u8, int);
+extern struct zfcp_fsf_req *zfcp_fsf_abort_fcp_command(
+	unsigned long, struct zfcp_adapter *, struct zfcp_unit *, int);
+
+/******************************* FC/FCP **************************************/
+extern int  zfcp_nameserver_enqueue(struct zfcp_adapter *);
+extern int  zfcp_ns_gid_pn_request(struct zfcp_erp_action *);
+extern int  zfcp_check_ct_response(struct ct_hdr *);
+extern int  zfcp_handle_els_rjt(u32, struct zfcp_ls_rjt_par *);
+
+/******************************* SCSI ****************************************/
+extern int  zfcp_adapter_scsi_register(struct zfcp_adapter *);
+extern void zfcp_adapter_scsi_unregister(struct zfcp_adapter *);
+extern void zfcp_set_fcp_dl(struct fcp_cmnd_iu *, fcp_dl_t);
+extern char *zfcp_get_fcp_rsp_info_ptr(struct fcp_rsp_iu *);
+extern void set_host_byte(u32 *, char);
+extern void set_driver_byte(u32 *, char);
+extern char *zfcp_get_fcp_sns_info_ptr(struct fcp_rsp_iu *);
+extern void zfcp_fsf_start_scsi_er_timer(struct zfcp_adapter *);
+extern fcp_dl_t zfcp_get_fcp_dl(struct fcp_cmnd_iu *);
+
+extern int zfcp_scsi_command_async(struct zfcp_adapter *,struct zfcp_unit *,
+				   struct scsi_cmnd *, struct timer_list *);
+extern int zfcp_scsi_command_sync(struct zfcp_unit *, struct scsi_cmnd *,
+				  struct timer_list *);
+extern struct scsi_transport_template *zfcp_transport_template;
+extern struct fc_function_template zfcp_transport_functions;
+
+/******************************** ERP ****************************************/
+extern void zfcp_erp_modify_adapter_status(struct zfcp_adapter *, u32, int);
+extern int  zfcp_erp_adapter_reopen(struct zfcp_adapter *, int);
+extern int  zfcp_erp_adapter_shutdown(struct zfcp_adapter *, int);
+extern void zfcp_erp_adapter_failed(struct zfcp_adapter *);
+
+extern void zfcp_erp_modify_port_status(struct zfcp_port *, u32, int);
+extern int  zfcp_erp_port_reopen(struct zfcp_port *, int);
+extern int  zfcp_erp_port_shutdown(struct zfcp_port *, int);
+extern int  zfcp_erp_port_forced_reopen(struct zfcp_port *, int);
+extern void zfcp_erp_port_failed(struct zfcp_port *);
+extern int  zfcp_erp_port_reopen_all(struct zfcp_adapter *, int);
+
+extern void zfcp_erp_modify_unit_status(struct zfcp_unit *, u32, int);
+extern int  zfcp_erp_unit_reopen(struct zfcp_unit *, int);
+extern int  zfcp_erp_unit_shutdown(struct zfcp_unit *, int);
+extern void zfcp_erp_unit_failed(struct zfcp_unit *);
+
+extern int  zfcp_erp_thread_setup(struct zfcp_adapter *);
+extern int  zfcp_erp_thread_kill(struct zfcp_adapter *);
+extern int  zfcp_erp_wait(struct zfcp_adapter *);
+extern int  zfcp_erp_async_handler(struct zfcp_erp_action *, unsigned long);
+
+extern int  zfcp_test_link(struct zfcp_port *);
+
+extern void zfcp_erp_port_access_denied(struct zfcp_port *);
+extern void zfcp_erp_unit_access_denied(struct zfcp_unit *);
+extern void zfcp_erp_adapter_access_changed(struct zfcp_adapter *);
+extern void zfcp_erp_port_access_changed(struct zfcp_port *);
+extern void zfcp_erp_unit_access_changed(struct zfcp_unit *);
+
+/******************************** AUX ****************************************/
+extern void zfcp_cmd_dbf_event_fsf(const char *, struct zfcp_fsf_req *,
+				   void *, int);
+extern void zfcp_cmd_dbf_event_scsi(const char *, struct scsi_cmnd *);
+extern void zfcp_in_els_dbf_event(struct zfcp_adapter *, const char *,
+				  struct fsf_status_read_buffer *, int);
+#endif	/* ZFCP_EXT_H */
diff --git a/drivers/s390/scsi/zfcp_fsf.c b/drivers/s390/scsi/zfcp_fsf.c
new file mode 100644
index 0000000..578b9fb
--- /dev/null
+++ b/drivers/s390/scsi/zfcp_fsf.c
@@ -0,0 +1,5087 @@
+/*
+ *
+ * linux/drivers/s390/scsi/zfcp_fsf.c
+ *
+ * FCP adapter driver for IBM eServer zSeries
+ *
+ * (C) Copyright IBM Corp. 2002, 2004
+ *
+ * Author(s): Martin Peschke <mpeschke@de.ibm.com>
+ *            Raimund Schroeder <raimund.schroeder@de.ibm.com>
+ *            Aron Zeh
+ *            Wolfgang Taphorn
+ *            Stefan Bader <stefan.bader@de.ibm.com>
+ *            Heiko Carstens <heiko.carstens@de.ibm.com>
+ *            Andreas Herrmann <aherrman@de.ibm.com>
+ *            Volker Sameske <sameske@de.ibm.com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#define ZFCP_FSF_C_REVISION "$Revision: 1.92 $"
+
+#include "zfcp_ext.h"
+
+static int zfcp_fsf_exchange_config_data_handler(struct zfcp_fsf_req *);
+static void zfcp_fsf_exchange_port_data_handler(struct zfcp_fsf_req *);
+static int zfcp_fsf_open_port_handler(struct zfcp_fsf_req *);
+static int zfcp_fsf_close_port_handler(struct zfcp_fsf_req *);
+static int zfcp_fsf_close_physical_port_handler(struct zfcp_fsf_req *);
+static int zfcp_fsf_open_unit_handler(struct zfcp_fsf_req *);
+static int zfcp_fsf_close_unit_handler(struct zfcp_fsf_req *);
+static int zfcp_fsf_send_fcp_command_handler(struct zfcp_fsf_req *);
+static int zfcp_fsf_send_fcp_command_task_handler(struct zfcp_fsf_req *);
+static int zfcp_fsf_send_fcp_command_task_management_handler(
+	struct zfcp_fsf_req *);
+static int zfcp_fsf_abort_fcp_command_handler(struct zfcp_fsf_req *);
+static int zfcp_fsf_status_read_handler(struct zfcp_fsf_req *);
+static int zfcp_fsf_send_ct_handler(struct zfcp_fsf_req *);
+static int zfcp_fsf_send_els_handler(struct zfcp_fsf_req *);
+static int zfcp_fsf_control_file_handler(struct zfcp_fsf_req *);
+static inline int zfcp_fsf_req_sbal_check(
+	unsigned long *, struct zfcp_qdio_queue *, int);
+static inline int zfcp_use_one_sbal(
+	struct scatterlist *, int, struct scatterlist *, int);
+static struct zfcp_fsf_req *zfcp_fsf_req_alloc(mempool_t *, int);
+static int zfcp_fsf_req_send(struct zfcp_fsf_req *, struct timer_list *);
+static int zfcp_fsf_protstatus_eval(struct zfcp_fsf_req *);
+static int zfcp_fsf_fsfstatus_eval(struct zfcp_fsf_req *);
+static int zfcp_fsf_fsfstatus_qual_eval(struct zfcp_fsf_req *);
+static int zfcp_fsf_req_dispatch(struct zfcp_fsf_req *);
+static void zfcp_fsf_req_dismiss(struct zfcp_fsf_req *);
+static void zfcp_fsf_req_free(struct zfcp_fsf_req *);
+
+/* association between FSF command and FSF QTCB type */
+static u32 fsf_qtcb_type[] = {
+	[FSF_QTCB_FCP_CMND] =             FSF_IO_COMMAND,
+	[FSF_QTCB_ABORT_FCP_CMND] =       FSF_SUPPORT_COMMAND,
+	[FSF_QTCB_OPEN_PORT_WITH_DID] =   FSF_SUPPORT_COMMAND,
+	[FSF_QTCB_OPEN_LUN] =             FSF_SUPPORT_COMMAND,
+	[FSF_QTCB_CLOSE_LUN] =            FSF_SUPPORT_COMMAND,
+	[FSF_QTCB_CLOSE_PORT] =           FSF_SUPPORT_COMMAND,
+	[FSF_QTCB_CLOSE_PHYSICAL_PORT] =  FSF_SUPPORT_COMMAND,
+	[FSF_QTCB_SEND_ELS] =             FSF_SUPPORT_COMMAND,
+	[FSF_QTCB_SEND_GENERIC] =         FSF_SUPPORT_COMMAND,
+	[FSF_QTCB_EXCHANGE_CONFIG_DATA] = FSF_CONFIG_COMMAND,
+	[FSF_QTCB_EXCHANGE_PORT_DATA] =   FSF_PORT_COMMAND,
+	[FSF_QTCB_DOWNLOAD_CONTROL_FILE] = FSF_SUPPORT_COMMAND,
+	[FSF_QTCB_UPLOAD_CONTROL_FILE] =  FSF_SUPPORT_COMMAND
+};
+
+static const char zfcp_act_subtable_type[5][8] = {
+	"unknown", "OS", "WWPN", "DID", "LUN"
+};
+
+/****************************************************************/
+/*************** FSF related Functions  *************************/
+/****************************************************************/
+
+#define ZFCP_LOG_AREA			ZFCP_LOG_AREA_FSF
+
+/*
+ * function:	zfcp_fsf_req_alloc
+ *
+ * purpose:     Obtains an fsf_req and potentially a qtcb (for all but 
+ *              unsolicited requests) via helper functions
+ *              Does some initial fsf request set-up.
+ *              
+ * returns:	pointer to allocated fsf_req if successfull
+ *              NULL otherwise
+ *
+ * locks:       none
+ *
+ */
+static struct zfcp_fsf_req *
+zfcp_fsf_req_alloc(mempool_t *pool, int req_flags)
+{
+	size_t size;
+	void *ptr;
+	struct zfcp_fsf_req *fsf_req = NULL;
+
+	if (req_flags & ZFCP_REQ_NO_QTCB)
+		size = sizeof(struct zfcp_fsf_req);
+	else
+		size = sizeof(struct zfcp_fsf_req_pool_element);
+
+	if (likely(pool != NULL))
+		ptr = mempool_alloc(pool, GFP_ATOMIC);
+	else
+		ptr = kmalloc(size, GFP_ATOMIC);
+
+	if (unlikely(NULL == ptr))
+		goto out;
+
+	memset(ptr, 0, size);
+
+	if (req_flags & ZFCP_REQ_NO_QTCB) {
+		fsf_req = (struct zfcp_fsf_req *) ptr;
+	} else {
+		fsf_req = &((struct zfcp_fsf_req_pool_element *) ptr)->fsf_req;
+		fsf_req->qtcb =
+			&((struct zfcp_fsf_req_pool_element *) ptr)->qtcb;
+	}
+
+	fsf_req->pool = pool;
+
+ out:
+	return fsf_req;
+}
+
+/*
+ * function:	zfcp_fsf_req_free
+ *
+ * purpose:     Frees the memory of an fsf_req (and potentially a qtcb) or
+ *              returns it into the pool via helper functions.
+ *
+ * returns:     sod all
+ *
+ * locks:       none
+ */
+static void
+zfcp_fsf_req_free(struct zfcp_fsf_req *fsf_req)
+{
+	if (likely(fsf_req->pool != NULL))
+		mempool_free(fsf_req, fsf_req->pool);
+		else
+			kfree(fsf_req);
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:
+ *
+ * note: qdio queues shall be down (no ongoing inbound processing)
+ */
+int
+zfcp_fsf_req_dismiss_all(struct zfcp_adapter *adapter)
+{
+	int retval = 0;
+	struct zfcp_fsf_req *fsf_req, *tmp;
+
+	list_for_each_entry_safe(fsf_req, tmp, &adapter->fsf_req_list_head,
+				 list)
+	    zfcp_fsf_req_dismiss(fsf_req);
+	/* wait_event_timeout? */
+	while (!list_empty(&adapter->fsf_req_list_head)) {
+		ZFCP_LOG_DEBUG("fsf req list of adapter %s not yet empty\n",
+			       zfcp_get_busid_by_adapter(adapter));
+		/* wait for woken intiators to clean up their requests */
+		msleep(jiffies_to_msecs(ZFCP_FSFREQ_CLEANUP_TIMEOUT));
+	}
+
+	/* consistency check */
+	if (atomic_read(&adapter->fsf_reqs_active)) {
+		ZFCP_LOG_NORMAL("bug: There are still %d FSF requests pending "
+				"on adapter %s after cleanup.\n",
+				atomic_read(&adapter->fsf_reqs_active),
+				zfcp_get_busid_by_adapter(adapter));
+		atomic_set(&adapter->fsf_reqs_active, 0);
+	}
+
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:
+ */
+static void
+zfcp_fsf_req_dismiss(struct zfcp_fsf_req *fsf_req)
+{
+	fsf_req->status |= ZFCP_STATUS_FSFREQ_DISMISSED;
+	zfcp_fsf_req_complete(fsf_req);
+}
+
+/*
+ * function:    zfcp_fsf_req_complete
+ *
+ * purpose:	Updates active counts and timers for openfcp-reqs
+ *              May cleanup request after req_eval returns
+ *
+ * returns:	0 - success
+ *		!0 - failure
+ *
+ * context:	
+ */
+int
+zfcp_fsf_req_complete(struct zfcp_fsf_req *fsf_req)
+{
+	int retval = 0;
+	int cleanup;
+	struct zfcp_adapter *adapter = fsf_req->adapter;
+
+	/* do some statistics */
+	atomic_dec(&adapter->fsf_reqs_active);
+
+	if (unlikely(fsf_req->fsf_command == FSF_QTCB_UNSOLICITED_STATUS)) {
+		ZFCP_LOG_DEBUG("Status read response received\n");
+		/*
+		 * Note: all cleanup handling is done in the callchain of
+		 * the function call-chain below.
+		 */
+		zfcp_fsf_status_read_handler(fsf_req);
+		goto out;
+	} else
+		zfcp_fsf_protstatus_eval(fsf_req);
+
+	/*
+	 * fsf_req may be deleted due to waking up functions, so 
+	 * cleanup is saved here and used later 
+	 */
+	if (likely(fsf_req->status & ZFCP_STATUS_FSFREQ_CLEANUP))
+		cleanup = 1;
+	else
+		cleanup = 0;
+
+	fsf_req->status |= ZFCP_STATUS_FSFREQ_COMPLETED;
+
+	/* cleanup request if requested by initiator */
+	if (likely(cleanup)) {
+		ZFCP_LOG_TRACE("removing FSF request %p\n", fsf_req);
+		/*
+		 * lock must not be held here since it will be
+		 * grabed by the called routine, too
+		 */
+		zfcp_fsf_req_cleanup(fsf_req);
+	} else {
+		/* notify initiator waiting for the requests completion */
+		ZFCP_LOG_TRACE("waking initiator of FSF request %p\n",fsf_req);
+		/*
+		 * FIXME: Race! We must not access fsf_req here as it might have been
+		 * cleaned up already due to the set ZFCP_STATUS_FSFREQ_COMPLETED
+		 * flag. It's an improbable case. But, we have the same paranoia for
+		 * the cleanup flag already.
+		 * Might better be handled using complete()?
+		 * (setting the flag and doing wakeup ought to be atomic
+		 *  with regard to checking the flag as long as waitqueue is
+		 *  part of the to be released structure)
+		 */
+		wake_up(&fsf_req->completion_wq);
+	}
+
+ out:
+	return retval;
+}
+
+/*
+ * function:    zfcp_fsf_protstatus_eval
+ *
+ * purpose:	evaluates the QTCB of the finished FSF request
+ *		and initiates appropriate actions
+ *		(usually calling FSF command specific handlers)
+ *
+ * returns:	
+ *
+ * context:	
+ *
+ * locks:
+ */
+static int
+zfcp_fsf_protstatus_eval(struct zfcp_fsf_req *fsf_req)
+{
+	int retval = 0;
+	struct zfcp_adapter *adapter = fsf_req->adapter;
+
+	ZFCP_LOG_DEBUG("QTCB is at %p\n", fsf_req->qtcb);
+
+	if (fsf_req->status & ZFCP_STATUS_FSFREQ_DISMISSED) {
+		ZFCP_LOG_DEBUG("fsf_req 0x%lx has been dismissed\n",
+			       (unsigned long) fsf_req);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR |
+			ZFCP_STATUS_FSFREQ_RETRY; /* only for SCSI cmnds. */
+		zfcp_cmd_dbf_event_fsf("dismiss", fsf_req, NULL, 0);
+		goto skip_protstatus;
+	}
+
+	/* log additional information provided by FSF (if any) */
+	if (unlikely(fsf_req->qtcb->header.log_length)) {
+		/* do not trust them ;-) */
+		if (fsf_req->qtcb->header.log_start > sizeof(struct fsf_qtcb)) {
+			ZFCP_LOG_NORMAL
+			    ("bug: ULP (FSF logging) log data starts "
+			     "beyond end of packet header. Ignored. "
+			     "(start=%i, size=%li)\n",
+			     fsf_req->qtcb->header.log_start,
+			     sizeof(struct fsf_qtcb));
+			goto forget_log;
+		}
+		if ((size_t) (fsf_req->qtcb->header.log_start +
+		     fsf_req->qtcb->header.log_length)
+		    > sizeof(struct fsf_qtcb)) {
+			ZFCP_LOG_NORMAL("bug: ULP (FSF logging) log data ends "
+					"beyond end of packet header. Ignored. "
+					"(start=%i, length=%i, size=%li)\n",
+					fsf_req->qtcb->header.log_start,
+					fsf_req->qtcb->header.log_length,
+					sizeof(struct fsf_qtcb));
+			goto forget_log;
+		}
+		ZFCP_LOG_TRACE("ULP log data: \n");
+		ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_TRACE,
+			      (char *) fsf_req->qtcb +
+			      fsf_req->qtcb->header.log_start,
+			      fsf_req->qtcb->header.log_length);
+	}
+ forget_log:
+
+	/* evaluate FSF Protocol Status */
+	switch (fsf_req->qtcb->prefix.prot_status) {
+
+	case FSF_PROT_GOOD:
+		ZFCP_LOG_TRACE("FSF_PROT_GOOD\n");
+		break;
+
+	case FSF_PROT_FSF_STATUS_PRESENTED:
+		ZFCP_LOG_TRACE("FSF_PROT_FSF_STATUS_PRESENTED\n");
+		break;
+
+	case FSF_PROT_QTCB_VERSION_ERROR:
+		ZFCP_LOG_FLAGS(0, "FSF_PROT_QTCB_VERSION_ERROR\n");
+		ZFCP_LOG_NORMAL("error: The adapter %s contains "
+				"microcode of version 0x%x, the device driver "
+				"only supports 0x%x. Aborting.\n",
+				zfcp_get_busid_by_adapter(adapter),
+				fsf_req->qtcb->prefix.prot_status_qual.
+				version_error.fsf_version, ZFCP_QTCB_VERSION);
+		/* stop operation for this adapter */
+		debug_text_exception(adapter->erp_dbf, 0, "prot_ver_err");
+		zfcp_erp_adapter_shutdown(adapter, 0);
+		zfcp_cmd_dbf_event_fsf("qverserr", fsf_req,
+				       &fsf_req->qtcb->prefix.prot_status_qual,
+				       sizeof (union fsf_prot_status_qual));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_PROT_SEQ_NUMB_ERROR:
+		ZFCP_LOG_FLAGS(0, "FSF_PROT_SEQ_NUMB_ERROR\n");
+		ZFCP_LOG_NORMAL("bug: Sequence number mismatch between "
+				"driver (0x%x) and adapter %s (0x%x). "
+				"Restarting all operations on this adapter.\n",
+				fsf_req->qtcb->prefix.req_seq_no,
+				zfcp_get_busid_by_adapter(adapter),
+				fsf_req->qtcb->prefix.prot_status_qual.
+				sequence_error.exp_req_seq_no);
+		debug_text_exception(adapter->erp_dbf, 0, "prot_seq_err");
+		/* restart operation on this adapter */
+		zfcp_erp_adapter_reopen(adapter, 0);
+		zfcp_cmd_dbf_event_fsf("seqnoerr", fsf_req,
+				       &fsf_req->qtcb->prefix.prot_status_qual,
+				       sizeof (union fsf_prot_status_qual));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_RETRY;
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_PROT_UNSUPP_QTCB_TYPE:
+		ZFCP_LOG_FLAGS(0, "FSF_PROT_UNSUP_QTCB_TYPE\n");
+		ZFCP_LOG_NORMAL("error: Packet header type used by the "
+				"device driver is incompatible with "
+				"that used on adapter %s. "
+				"Stopping all operations on this adapter.\n",
+				zfcp_get_busid_by_adapter(adapter));
+		debug_text_exception(adapter->erp_dbf, 0, "prot_unsup_qtcb");
+		zfcp_erp_adapter_shutdown(adapter, 0);
+		zfcp_cmd_dbf_event_fsf("unsqtcbt", fsf_req,
+				       &fsf_req->qtcb->prefix.prot_status_qual,
+				       sizeof (union fsf_prot_status_qual));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_PROT_HOST_CONNECTION_INITIALIZING:
+		ZFCP_LOG_FLAGS(1, "FSF_PROT_HOST_CONNECTION_INITIALIZING\n");
+		zfcp_cmd_dbf_event_fsf("hconinit", fsf_req,
+				       &fsf_req->qtcb->prefix.prot_status_qual,
+				       sizeof (union fsf_prot_status_qual));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		atomic_set_mask(ZFCP_STATUS_ADAPTER_HOST_CON_INIT,
+				&(adapter->status));
+		debug_text_event(adapter->erp_dbf, 3, "prot_con_init");
+		break;
+
+	case FSF_PROT_DUPLICATE_REQUEST_ID:
+		ZFCP_LOG_FLAGS(0, "FSF_PROT_DUPLICATE_REQUEST_IDS\n");
+		if (fsf_req->qtcb) {
+			ZFCP_LOG_NORMAL("bug: The request identifier 0x%Lx "
+					"to the adapter %s is ambiguous. "
+					"Stopping all operations on this "
+					"adapter.\n",
+					*(unsigned long long *)
+					(&fsf_req->qtcb->bottom.support.
+					 req_handle),
+					zfcp_get_busid_by_adapter(adapter));
+		} else {
+			ZFCP_LOG_NORMAL("bug: The request identifier %p "
+					"to the adapter %s is ambiguous. "
+					"Stopping all operations on this "
+					"adapter. "
+					"(bug: got this for an unsolicited "
+					"status read request)\n",
+					fsf_req,
+					zfcp_get_busid_by_adapter(adapter));
+		}
+		debug_text_exception(adapter->erp_dbf, 0, "prot_dup_id");
+		zfcp_erp_adapter_shutdown(adapter, 0);
+		zfcp_cmd_dbf_event_fsf("dupreqid", fsf_req,
+				       &fsf_req->qtcb->prefix.prot_status_qual,
+				       sizeof (union fsf_prot_status_qual));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_PROT_LINK_DOWN:
+		ZFCP_LOG_FLAGS(1, "FSF_PROT_LINK_DOWN\n");
+		/*
+		 * 'test and set' is not atomic here -
+		 * it's ok as long as calls to our response queue handler
+		 * (and thus execution of this code here) are serialized
+		 * by the qdio module
+		 */
+		if (!atomic_test_mask(ZFCP_STATUS_ADAPTER_LINK_UNPLUGGED,
+				      &adapter->status)) {
+			switch (fsf_req->qtcb->prefix.prot_status_qual.
+				locallink_error.code) {
+			case FSF_PSQ_LINK_NOLIGHT:
+				ZFCP_LOG_INFO("The local link to adapter %s "
+					      "is down (no light detected).\n",
+					      zfcp_get_busid_by_adapter(
+						      adapter));
+				break;
+			case FSF_PSQ_LINK_WRAPPLUG:
+				ZFCP_LOG_INFO("The local link to adapter %s "
+					      "is down (wrap plug detected).\n",
+					      zfcp_get_busid_by_adapter(
+						      adapter));
+				break;
+			case FSF_PSQ_LINK_NOFCP:
+				ZFCP_LOG_INFO("The local link to adapter %s "
+					      "is down (adjacent node on "
+					      "link does not support FCP).\n",
+					      zfcp_get_busid_by_adapter(
+						      adapter));
+				break;
+			default:
+				ZFCP_LOG_INFO("The local link to adapter %s "
+					      "is down "
+					      "(warning: unknown reason "
+					      "code).\n",
+					      zfcp_get_busid_by_adapter(
+						      adapter));
+				break;
+
+			}
+			/*
+			 * Due to the 'erp failed' flag the adapter won't
+			 * be recovered but will be just set to 'blocked'
+			 * state. All subordinary devices will have state
+			 * 'blocked' and 'erp failed', too.
+			 * Thus the adapter is still able to provide
+			 * 'link up' status without being flooded with
+			 * requests.
+			 * (note: even 'close port' is not permitted)
+			 */
+			ZFCP_LOG_INFO("Stopping all operations for adapter "
+				      "%s.\n",
+				      zfcp_get_busid_by_adapter(adapter));
+			atomic_set_mask(ZFCP_STATUS_ADAPTER_LINK_UNPLUGGED |
+					ZFCP_STATUS_COMMON_ERP_FAILED,
+					&adapter->status);
+			zfcp_erp_adapter_reopen(adapter, 0);
+			debug_text_event(adapter->erp_dbf, 1, "prot_link_down");
+		}
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_PROT_REEST_QUEUE:
+		ZFCP_LOG_FLAGS(1, "FSF_PROT_REEST_QUEUE\n");
+		debug_text_event(adapter->erp_dbf, 1, "prot_reest_queue");
+		ZFCP_LOG_INFO("The local link to adapter with "
+			      "%s was re-plugged. "
+			      "Re-starting operations on this adapter.\n",
+			      zfcp_get_busid_by_adapter(adapter));
+		/* All ports should be marked as ready to run again */
+		zfcp_erp_modify_adapter_status(adapter,
+					       ZFCP_STATUS_COMMON_RUNNING,
+					       ZFCP_SET);
+		zfcp_erp_adapter_reopen(adapter,
+					ZFCP_STATUS_ADAPTER_LINK_UNPLUGGED
+					| ZFCP_STATUS_COMMON_ERP_FAILED);
+		zfcp_cmd_dbf_event_fsf("reestque", fsf_req,
+				       &fsf_req->qtcb->prefix.prot_status_qual,
+				       sizeof (union fsf_prot_status_qual));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_PROT_ERROR_STATE:
+		ZFCP_LOG_FLAGS(0, "FSF_PROT_ERROR_STATE\n");
+		ZFCP_LOG_NORMAL("error: The adapter %s "
+				"has entered the error state. "
+				"Restarting all operations on this "
+				"adapter.\n",
+				zfcp_get_busid_by_adapter(adapter));
+		debug_text_event(adapter->erp_dbf, 0, "prot_err_sta");
+		/* restart operation on this adapter */
+		zfcp_erp_adapter_reopen(adapter, 0);
+		zfcp_cmd_dbf_event_fsf("proterrs", fsf_req,
+				       &fsf_req->qtcb->prefix.prot_status_qual,
+				       sizeof (union fsf_prot_status_qual));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_RETRY;
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	default:
+		ZFCP_LOG_NORMAL("bug: Transfer protocol status information "
+				"provided by the adapter %s "
+				"is not compatible with the device driver. "
+				"Stopping all operations on this adapter. "
+				"(debug info 0x%x).\n",
+				zfcp_get_busid_by_adapter(adapter),
+				fsf_req->qtcb->prefix.prot_status);
+		debug_text_event(adapter->erp_dbf, 0, "prot_inval:");
+		debug_exception(adapter->erp_dbf, 0,
+				&fsf_req->qtcb->prefix.prot_status,
+				sizeof (u32));
+		zfcp_erp_adapter_shutdown(adapter, 0);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+	}
+
+ skip_protstatus:
+	/*
+	 * always call specific handlers to give them a chance to do
+	 * something meaningful even in error cases
+	 */
+	zfcp_fsf_fsfstatus_eval(fsf_req);
+	return retval;
+}
+
+/*
+ * function:	zfcp_fsf_fsfstatus_eval
+ *
+ * purpose:	evaluates FSF status of completed FSF request
+ *		and acts accordingly
+ *
+ * returns:
+ */
+static int
+zfcp_fsf_fsfstatus_eval(struct zfcp_fsf_req *fsf_req)
+{
+	int retval = 0;
+
+	if (unlikely(fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR)) {
+		goto skip_fsfstatus;
+	}
+
+	/* evaluate FSF Status */
+	switch (fsf_req->qtcb->header.fsf_status) {
+	case FSF_UNKNOWN_COMMAND:
+		ZFCP_LOG_FLAGS(0, "FSF_UNKNOWN_COMMAND\n");
+		ZFCP_LOG_NORMAL("bug: Command issued by the device driver is "
+				"not known by the adapter %s "
+				"Stopping all operations on this adapter. "
+				"(debug info 0x%x).\n",
+				zfcp_get_busid_by_adapter(fsf_req->adapter),
+				fsf_req->qtcb->header.fsf_command);
+		debug_text_exception(fsf_req->adapter->erp_dbf, 0,
+				     "fsf_s_unknown");
+		zfcp_erp_adapter_shutdown(fsf_req->adapter, 0);
+		zfcp_cmd_dbf_event_fsf("unknownc", fsf_req,
+				       &fsf_req->qtcb->header.fsf_status_qual,
+				       sizeof (union fsf_status_qual));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_FCP_RSP_AVAILABLE:
+		ZFCP_LOG_FLAGS(2, "FSF_FCP_RSP_AVAILABLE\n");
+		ZFCP_LOG_DEBUG("FCP Sense data will be presented to the "
+			       "SCSI stack.\n");
+		debug_text_event(fsf_req->adapter->erp_dbf, 3, "fsf_s_rsp");
+		break;
+
+	case FSF_ADAPTER_STATUS_AVAILABLE:
+		ZFCP_LOG_FLAGS(2, "FSF_ADAPTER_STATUS_AVAILABLE\n");
+		debug_text_event(fsf_req->adapter->erp_dbf, 2, "fsf_s_astatus");
+		zfcp_fsf_fsfstatus_qual_eval(fsf_req);
+		break;
+
+	default:
+		break;
+	}
+
+ skip_fsfstatus:
+	/*
+	 * always call specific handlers to give them a chance to do
+	 * something meaningful even in error cases
+	 */
+	zfcp_fsf_req_dispatch(fsf_req);
+
+	return retval;
+}
+
+/*
+ * function:	zfcp_fsf_fsfstatus_qual_eval
+ *
+ * purpose:	evaluates FSF status-qualifier of completed FSF request
+ *		and acts accordingly
+ *
+ * returns:
+ */
+static int
+zfcp_fsf_fsfstatus_qual_eval(struct zfcp_fsf_req *fsf_req)
+{
+	int retval = 0;
+
+	switch (fsf_req->qtcb->header.fsf_status_qual.word[0]) {
+	case FSF_SQ_FCP_RSP_AVAILABLE:
+		ZFCP_LOG_FLAGS(2, "FSF_SQ_FCP_RSP_AVAILABLE\n");
+		debug_text_event(fsf_req->adapter->erp_dbf, 4, "fsf_sq_rsp");
+		break;
+	case FSF_SQ_RETRY_IF_POSSIBLE:
+		ZFCP_LOG_FLAGS(2, "FSF_SQ_RETRY_IF_POSSIBLE\n");
+		/* The SCSI-stack may now issue retries or escalate */
+		debug_text_event(fsf_req->adapter->erp_dbf, 2, "fsf_sq_retry");
+		zfcp_cmd_dbf_event_fsf("sqretry", fsf_req,
+				       &fsf_req->qtcb->header.fsf_status_qual,
+				       sizeof (union fsf_status_qual));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+	case FSF_SQ_COMMAND_ABORTED:
+		ZFCP_LOG_FLAGS(2, "FSF_SQ_COMMAND_ABORTED\n");
+		/* Carry the aborted state on to upper layer */
+		debug_text_event(fsf_req->adapter->erp_dbf, 2, "fsf_sq_abort");
+		zfcp_cmd_dbf_event_fsf("sqabort", fsf_req,
+				       &fsf_req->qtcb->header.fsf_status_qual,
+				       sizeof (union fsf_status_qual));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ABORTED;
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+	case FSF_SQ_NO_RECOM:
+		ZFCP_LOG_FLAGS(0, "FSF_SQ_NO_RECOM\n");
+		debug_text_exception(fsf_req->adapter->erp_dbf, 0,
+				     "fsf_sq_no_rec");
+		ZFCP_LOG_NORMAL("bug: No recommendation could be given for a"
+				"problem on the adapter %s "
+				"Stopping all operations on this adapter. ",
+				zfcp_get_busid_by_adapter(fsf_req->adapter));
+		zfcp_erp_adapter_shutdown(fsf_req->adapter, 0);
+		zfcp_cmd_dbf_event_fsf("sqnrecom", fsf_req,
+				       &fsf_req->qtcb->header.fsf_status_qual,
+				       sizeof (union fsf_status_qual));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+	case FSF_SQ_ULP_PROGRAMMING_ERROR:
+		ZFCP_LOG_FLAGS(0, "FSF_SQ_ULP_PROGRAMMING_ERROR\n");
+		ZFCP_LOG_NORMAL("error: not enough SBALs for data transfer "
+				"(adapter %s)\n",
+				zfcp_get_busid_by_adapter(fsf_req->adapter));
+		debug_text_exception(fsf_req->adapter->erp_dbf, 0,
+				     "fsf_sq_ulp_err");
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+	case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE:
+	case FSF_SQ_NO_RETRY_POSSIBLE:
+	case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED:
+		/* dealt with in the respective functions */
+		break;
+	default:
+		ZFCP_LOG_NORMAL("bug: Additional status info could "
+				"not be interpreted properly.\n");
+		ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_NORMAL,
+			      (char *) &fsf_req->qtcb->header.fsf_status_qual,
+			      sizeof (union fsf_status_qual));
+		debug_text_event(fsf_req->adapter->erp_dbf, 0, "fsf_sq_inval:");
+		debug_exception(fsf_req->adapter->erp_dbf, 0,
+				&fsf_req->qtcb->header.fsf_status_qual.word[0],
+				sizeof (u32));
+		zfcp_cmd_dbf_event_fsf("squndef", fsf_req,
+				       &fsf_req->qtcb->header.fsf_status_qual,
+				       sizeof (union fsf_status_qual));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+	}
+
+	return retval;
+}
+
+/*
+ * function:	zfcp_fsf_req_dispatch
+ *
+ * purpose:	calls the appropriate command specific handler
+ *
+ * returns:	
+ */
+static int
+zfcp_fsf_req_dispatch(struct zfcp_fsf_req *fsf_req)
+{
+	struct zfcp_erp_action *erp_action = fsf_req->erp_action;
+	struct zfcp_adapter *adapter = fsf_req->adapter;
+	int retval = 0;
+
+	if (unlikely(fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR)) {
+		ZFCP_LOG_TRACE("fsf_req=%p, QTCB=%p\n", fsf_req, fsf_req->qtcb);
+		ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_TRACE,
+			      (char *) fsf_req->qtcb, sizeof(struct fsf_qtcb));
+	}
+
+	switch (fsf_req->fsf_command) {
+
+	case FSF_QTCB_FCP_CMND:
+		ZFCP_LOG_FLAGS(3, "FSF_QTCB_FCP_CMND\n");
+		zfcp_fsf_send_fcp_command_handler(fsf_req);
+		break;
+
+	case FSF_QTCB_ABORT_FCP_CMND:
+		ZFCP_LOG_FLAGS(2, "FSF_QTCB_ABORT_FCP_CMND\n");
+		zfcp_fsf_abort_fcp_command_handler(fsf_req);
+		break;
+
+	case FSF_QTCB_SEND_GENERIC:
+		ZFCP_LOG_FLAGS(2, "FSF_QTCB_SEND_GENERIC\n");
+		zfcp_fsf_send_ct_handler(fsf_req);
+		break;
+
+	case FSF_QTCB_OPEN_PORT_WITH_DID:
+		ZFCP_LOG_FLAGS(2, "FSF_QTCB_OPEN_PORT_WITH_DID\n");
+		zfcp_fsf_open_port_handler(fsf_req);
+		break;
+
+	case FSF_QTCB_OPEN_LUN:
+		ZFCP_LOG_FLAGS(2, "FSF_QTCB_OPEN_LUN\n");
+		zfcp_fsf_open_unit_handler(fsf_req);
+		break;
+
+	case FSF_QTCB_CLOSE_LUN:
+		ZFCP_LOG_FLAGS(2, "FSF_QTCB_CLOSE_LUN\n");
+		zfcp_fsf_close_unit_handler(fsf_req);
+		break;
+
+	case FSF_QTCB_CLOSE_PORT:
+		ZFCP_LOG_FLAGS(2, "FSF_QTCB_CLOSE_PORT\n");
+		zfcp_fsf_close_port_handler(fsf_req);
+		break;
+
+	case FSF_QTCB_CLOSE_PHYSICAL_PORT:
+		ZFCP_LOG_FLAGS(2, "FSF_QTCB_CLOSE_PHYSICAL_PORT\n");
+		zfcp_fsf_close_physical_port_handler(fsf_req);
+		break;
+
+	case FSF_QTCB_EXCHANGE_CONFIG_DATA:
+		ZFCP_LOG_FLAGS(2, "FSF_QTCB_EXCHANGE_CONFIG_DATA\n");
+		zfcp_fsf_exchange_config_data_handler(fsf_req);
+		break;
+
+	case FSF_QTCB_EXCHANGE_PORT_DATA:
+		ZFCP_LOG_FLAGS(2, "FSF_QTCB_EXCHANGE_PORT_DATA\n");
+		zfcp_fsf_exchange_port_data_handler(fsf_req);
+		break;
+
+	case FSF_QTCB_SEND_ELS:
+		ZFCP_LOG_FLAGS(2, "FSF_QTCB_SEND_ELS\n");
+		zfcp_fsf_send_els_handler(fsf_req);
+		break;
+
+	case FSF_QTCB_DOWNLOAD_CONTROL_FILE:
+		ZFCP_LOG_FLAGS(2, "FSF_QTCB_DOWNLOAD_CONTROL_FILE\n");
+		zfcp_fsf_control_file_handler(fsf_req);
+		break;
+
+	case FSF_QTCB_UPLOAD_CONTROL_FILE:
+		ZFCP_LOG_FLAGS(2, "FSF_QTCB_UPLOAD_CONTROL_FILE\n");
+		zfcp_fsf_control_file_handler(fsf_req);
+		break;
+
+	default:
+		ZFCP_LOG_FLAGS(2, "FSF_QTCB_UNKNOWN\n");
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		ZFCP_LOG_NORMAL("bug: Command issued by the device driver is "
+				"not supported by the adapter %s\n",
+				zfcp_get_busid_by_adapter(fsf_req->adapter));
+		if (fsf_req->fsf_command != fsf_req->qtcb->header.fsf_command)
+			ZFCP_LOG_NORMAL
+			    ("bug: Command issued by the device driver differs "
+			     "from the command returned by the adapter %s "
+			     "(debug info 0x%x, 0x%x).\n",
+			     zfcp_get_busid_by_adapter(fsf_req->adapter),
+			     fsf_req->fsf_command,
+			     fsf_req->qtcb->header.fsf_command);
+	}
+
+	if (!erp_action)
+		return retval;
+
+	debug_text_event(adapter->erp_dbf, 3, "a_frh");
+	debug_event(adapter->erp_dbf, 3, &erp_action->action, sizeof (int));
+	zfcp_erp_async_handler(erp_action, 0);
+
+	return retval;
+}
+
+/*
+ * function:    zfcp_fsf_status_read
+ *
+ * purpose:	initiates a Status Read command at the specified adapter
+ *
+ * returns:
+ */
+int
+zfcp_fsf_status_read(struct zfcp_adapter *adapter, int req_flags)
+{
+	struct zfcp_fsf_req *fsf_req;
+	struct fsf_status_read_buffer *status_buffer;
+	unsigned long lock_flags;
+	volatile struct qdio_buffer_element *sbale;
+	int retval = 0;
+
+	/* setup new FSF request */
+	retval = zfcp_fsf_req_create(adapter, FSF_QTCB_UNSOLICITED_STATUS,
+				     req_flags | ZFCP_REQ_NO_QTCB,
+				     adapter->pool.fsf_req_status_read,
+				     &lock_flags, &fsf_req);
+	if (retval < 0) {
+		ZFCP_LOG_INFO("error: Could not create unsolicited status "
+			      "buffer for adapter %s.\n",
+			      zfcp_get_busid_by_adapter(adapter));
+		goto failed_req_create;
+	}
+
+	sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_curr, 0);
+        sbale[0].flags |= SBAL_FLAGS0_TYPE_STATUS;
+        sbale[2].flags |= SBAL_FLAGS_LAST_ENTRY;
+        fsf_req->sbale_curr = 2;
+
+	status_buffer =
+		mempool_alloc(adapter->pool.data_status_read, GFP_ATOMIC);
+	if (!status_buffer) {
+		ZFCP_LOG_NORMAL("bug: could not get some buffer\n");
+		goto failed_buf;
+	}
+	memset(status_buffer, 0, sizeof (struct fsf_status_read_buffer));
+	fsf_req->data.status_read.buffer = status_buffer;
+
+	/* insert pointer to respective buffer */
+	sbale = zfcp_qdio_sbale_curr(fsf_req);
+	sbale->addr = (void *) status_buffer;
+	sbale->length = sizeof(struct fsf_status_read_buffer);
+
+	/* start QDIO request for this FSF request */
+	retval = zfcp_fsf_req_send(fsf_req, NULL);
+	if (retval) {
+		ZFCP_LOG_DEBUG("error: Could not set-up unsolicited status "
+			       "environment.\n");
+		goto failed_req_send;
+	}
+
+	ZFCP_LOG_TRACE("Status Read request initiated (adapter%s)\n",
+		       zfcp_get_busid_by_adapter(adapter));
+	goto out;
+
+ failed_req_send:
+	mempool_free(status_buffer, adapter->pool.data_status_read);
+
+ failed_buf:
+	zfcp_fsf_req_free(fsf_req);
+ failed_req_create:
+ out:
+	write_unlock_irqrestore(&adapter->request_queue.queue_lock, lock_flags);
+	return retval;
+}
+
+static int
+zfcp_fsf_status_read_port_closed(struct zfcp_fsf_req *fsf_req)
+{
+	struct fsf_status_read_buffer *status_buffer;
+	struct zfcp_adapter *adapter;
+	struct zfcp_port *port;
+	unsigned long flags;
+
+	status_buffer = fsf_req->data.status_read.buffer;
+	adapter = fsf_req->adapter;
+
+	read_lock_irqsave(&zfcp_data.config_lock, flags);
+	list_for_each_entry(port, &adapter->port_list_head, list)
+	    if (port->d_id == (status_buffer->d_id & ZFCP_DID_MASK))
+		break;
+	read_unlock_irqrestore(&zfcp_data.config_lock, flags);
+
+	if (!port || (port->d_id != (status_buffer->d_id & ZFCP_DID_MASK))) {
+		ZFCP_LOG_NORMAL("bug: Reopen port indication received for"
+				"nonexisting port with d_id 0x%08x on "
+				"adapter %s. Ignored.\n",
+				status_buffer->d_id & ZFCP_DID_MASK,
+				zfcp_get_busid_by_adapter(adapter));
+		goto out;
+	}
+
+	switch (status_buffer->status_subtype) {
+
+	case FSF_STATUS_READ_SUB_CLOSE_PHYS_PORT:
+		ZFCP_LOG_FLAGS(2, "FSF_STATUS_READ_SUB_CLOSE_PHYS_PORT\n");
+		debug_text_event(adapter->erp_dbf, 3, "unsol_pc_phys:");
+		zfcp_erp_port_reopen(port, 0);
+		break;
+
+	case FSF_STATUS_READ_SUB_ERROR_PORT:
+		ZFCP_LOG_FLAGS(1, "FSF_STATUS_READ_SUB_ERROR_PORT\n");
+		debug_text_event(adapter->erp_dbf, 1, "unsol_pc_err:");
+		zfcp_erp_port_shutdown(port, 0);
+		break;
+
+	default:
+		debug_text_event(adapter->erp_dbf, 0, "unsol_unk_sub:");
+		debug_exception(adapter->erp_dbf, 0,
+				&status_buffer->status_subtype, sizeof (u32));
+		ZFCP_LOG_NORMAL("bug: Undefined status subtype received "
+				"for a reopen indication on port with "
+				"d_id 0x%08x on the adapter %s. "
+				"Ignored. (debug info 0x%x)\n",
+				status_buffer->d_id,
+				zfcp_get_busid_by_adapter(adapter),
+				status_buffer->status_subtype);
+	}
+ out:
+	return 0;
+}
+
+/*
+ * function:    zfcp_fsf_status_read_handler
+ *
+ * purpose:	is called for finished Open Port command
+ *
+ * returns:	
+ */
+static int
+zfcp_fsf_status_read_handler(struct zfcp_fsf_req *fsf_req)
+{
+	int retval = 0;
+	struct zfcp_adapter *adapter = fsf_req->adapter;
+	struct fsf_status_read_buffer *status_buffer =
+	    fsf_req->data.status_read.buffer;
+
+	if (fsf_req->status & ZFCP_STATUS_FSFREQ_DISMISSED) {
+		mempool_free(status_buffer, adapter->pool.data_status_read);
+		zfcp_fsf_req_cleanup(fsf_req);
+		goto out;
+	}
+
+	switch (status_buffer->status_type) {
+
+	case FSF_STATUS_READ_PORT_CLOSED:
+		ZFCP_LOG_FLAGS(1, "FSF_STATUS_READ_PORT_CLOSED\n");
+		debug_text_event(adapter->erp_dbf, 3, "unsol_pclosed:");
+		debug_event(adapter->erp_dbf, 3,
+			    &status_buffer->d_id, sizeof (u32));
+		zfcp_fsf_status_read_port_closed(fsf_req);
+		break;
+
+	case FSF_STATUS_READ_INCOMING_ELS:
+		ZFCP_LOG_FLAGS(1, "FSF_STATUS_READ_INCOMING_ELS\n");
+		debug_text_event(adapter->erp_dbf, 3, "unsol_els:");
+		zfcp_fsf_incoming_els(fsf_req);
+		break;
+
+	case FSF_STATUS_READ_SENSE_DATA_AVAIL:
+		ZFCP_LOG_FLAGS(1, "FSF_STATUS_READ_SENSE_DATA_AVAIL\n");
+		debug_text_event(adapter->erp_dbf, 3, "unsol_sense:");
+		ZFCP_LOG_INFO("unsolicited sense data received (adapter %s)\n",
+			      zfcp_get_busid_by_adapter(adapter));
+                ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_NORMAL, (char *) status_buffer,
+                              sizeof(struct fsf_status_read_buffer));
+		break;
+
+	case FSF_STATUS_READ_BIT_ERROR_THRESHOLD:
+		ZFCP_LOG_FLAGS(1, "FSF_STATUS_READ_BIT_ERROR_THRESHOLD\n");
+		debug_text_event(adapter->erp_dbf, 3, "unsol_bit_err:");
+		ZFCP_LOG_NORMAL("Bit error threshold data received:\n");
+		ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_NORMAL,
+			      (char *) status_buffer,
+			      sizeof (struct fsf_status_read_buffer));
+		break;
+
+	case FSF_STATUS_READ_LINK_DOWN:
+		ZFCP_LOG_FLAGS(1, "FSF_STATUS_READ_LINK_DOWN\n");
+		debug_text_event(adapter->erp_dbf, 0, "unsol_link_down:");
+		ZFCP_LOG_INFO("Local link to adapter %s is down\n",
+			      zfcp_get_busid_by_adapter(adapter));
+		atomic_set_mask(ZFCP_STATUS_ADAPTER_LINK_UNPLUGGED,
+				&adapter->status);
+		zfcp_erp_adapter_failed(adapter);
+		break;
+
+	case FSF_STATUS_READ_LINK_UP:
+		ZFCP_LOG_FLAGS(1, "FSF_STATUS_READ_LINK_UP\n");
+		debug_text_event(adapter->erp_dbf, 2, "unsol_link_up:");
+		ZFCP_LOG_INFO("Local link to adapter %s was replugged. "
+			      "Restarting operations on this adapter\n",
+			      zfcp_get_busid_by_adapter(adapter));
+		/* All ports should be marked as ready to run again */
+		zfcp_erp_modify_adapter_status(adapter,
+					       ZFCP_STATUS_COMMON_RUNNING,
+					       ZFCP_SET);
+		zfcp_erp_adapter_reopen(adapter,
+					ZFCP_STATUS_ADAPTER_LINK_UNPLUGGED
+					| ZFCP_STATUS_COMMON_ERP_FAILED);
+		break;
+
+	case FSF_STATUS_READ_CFDC_UPDATED:
+		ZFCP_LOG_FLAGS(1, "FSF_STATUS_READ_CFDC_UPDATED\n");
+		debug_text_event(adapter->erp_dbf, 2, "unsol_cfdc_update:");
+		ZFCP_LOG_INFO("CFDC has been updated on the adapter %s\n",
+			      zfcp_get_busid_by_adapter(adapter));
+		zfcp_erp_adapter_access_changed(adapter);
+		break;
+
+	case FSF_STATUS_READ_CFDC_HARDENED:
+		ZFCP_LOG_FLAGS(1, "FSF_STATUS_READ_CFDC_HARDENED\n");
+		debug_text_event(adapter->erp_dbf, 2, "unsol_cfdc_harden:");
+		switch (status_buffer->status_subtype) {
+		case FSF_STATUS_READ_SUB_CFDC_HARDENED_ON_SE:
+			ZFCP_LOG_INFO("CFDC of adapter %s saved on SE\n",
+				      zfcp_get_busid_by_adapter(adapter));
+			break;
+		case FSF_STATUS_READ_SUB_CFDC_HARDENED_ON_SE2:
+			ZFCP_LOG_INFO("CFDC of adapter %s has been copied "
+				      "to the secondary SE\n",
+				zfcp_get_busid_by_adapter(adapter));
+			break;
+		default:
+			ZFCP_LOG_INFO("CFDC of adapter %s has been hardened\n",
+				      zfcp_get_busid_by_adapter(adapter));
+		}
+		break;
+
+	default:
+		debug_text_event(adapter->erp_dbf, 0, "unsol_unknown:");
+		debug_exception(adapter->erp_dbf, 0,
+				&status_buffer->status_type, sizeof (u32));
+		ZFCP_LOG_NORMAL("bug: An unsolicited status packet of unknown "
+				"type was received (debug info 0x%x)\n",
+				status_buffer->status_type);
+		ZFCP_LOG_DEBUG("Dump of status_read_buffer %p:\n",
+			       status_buffer);
+		ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
+			      (char *) status_buffer,
+			      sizeof (struct fsf_status_read_buffer));
+		break;
+	}
+	mempool_free(status_buffer, adapter->pool.data_status_read);
+	zfcp_fsf_req_cleanup(fsf_req);
+	/*
+	 * recycle buffer and start new request repeat until outbound
+	 * queue is empty or adapter shutdown is requested
+	 */
+	/*
+	 * FIXME(qdio):
+	 * we may wait in the req_create for 5s during shutdown, so
+	 * qdio_cleanup will have to wait at least that long before returning
+	 * with failure to allow us a proper cleanup under all circumstances
+	 */
+	/*
+	 * FIXME:
+	 * allocation failure possible? (Is this code needed?)
+	 */
+	retval = zfcp_fsf_status_read(adapter, 0);
+	if (retval < 0) {
+		ZFCP_LOG_INFO("Failed to create unsolicited status read "
+			      "request for the adapter %s.\n",
+			      zfcp_get_busid_by_adapter(adapter));
+		/* temporary fix to avoid status read buffer shortage */
+		adapter->status_read_failed++;
+		if ((ZFCP_STATUS_READS_RECOM - adapter->status_read_failed)
+		    < ZFCP_STATUS_READ_FAILED_THRESHOLD) {
+			ZFCP_LOG_INFO("restart adapter %s due to status read "
+				      "buffer shortage\n",
+				      zfcp_get_busid_by_adapter(adapter));
+			zfcp_erp_adapter_reopen(adapter, 0);
+		}
+	}
+ out:
+	return retval;
+}
+
+/*
+ * function:    zfcp_fsf_abort_fcp_command
+ *
+ * purpose:	tells FSF to abort a running SCSI command
+ *
+ * returns:	address of initiated FSF request
+ *		NULL - request could not be initiated
+ *
+ * FIXME(design): should be watched by a timeout !!! 
+ * FIXME(design) shouldn't this be modified to return an int
+ *               also...don't know how though
+ */
+struct zfcp_fsf_req *
+zfcp_fsf_abort_fcp_command(unsigned long old_req_id,
+			   struct zfcp_adapter *adapter,
+			   struct zfcp_unit *unit, int req_flags)
+{
+	volatile struct qdio_buffer_element *sbale;
+	unsigned long lock_flags;
+	struct zfcp_fsf_req *fsf_req = NULL;
+	int retval = 0;
+
+	/* setup new FSF request */
+	retval = zfcp_fsf_req_create(adapter, FSF_QTCB_ABORT_FCP_CMND,
+				     req_flags, adapter->pool.fsf_req_abort,
+				     &lock_flags, &fsf_req);
+	if (retval < 0) {
+		ZFCP_LOG_INFO("error: Failed to create an abort command "
+			      "request for lun 0x%016Lx on port 0x%016Lx "
+			      "on adapter %s.\n",
+			      unit->fcp_lun,
+			      unit->port->wwpn,
+			      zfcp_get_busid_by_adapter(adapter));
+		goto out;
+	}
+
+	sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_curr, 0);
+        sbale[0].flags |= SBAL_FLAGS0_TYPE_READ;
+        sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY;
+
+	fsf_req->data.abort_fcp_command.unit = unit;
+
+	/* set handles of unit and its parent port in QTCB */
+	fsf_req->qtcb->header.lun_handle = unit->handle;
+	fsf_req->qtcb->header.port_handle = unit->port->handle;
+
+	/* set handle of request which should be aborted */
+	fsf_req->qtcb->bottom.support.req_handle = (u64) old_req_id;
+
+	/* start QDIO request for this FSF request */
+
+	zfcp_fsf_start_scsi_er_timer(adapter);
+	retval = zfcp_fsf_req_send(fsf_req, NULL);
+	if (retval) {
+		del_timer(&adapter->scsi_er_timer);
+		ZFCP_LOG_INFO("error: Failed to send abort command request "
+			      "on adapter %s, port 0x%016Lx, unit 0x%016Lx\n",
+			      zfcp_get_busid_by_adapter(adapter),
+			      unit->port->wwpn, unit->fcp_lun);
+		zfcp_fsf_req_free(fsf_req);
+		fsf_req = NULL;
+		goto out;
+	}
+
+	ZFCP_LOG_DEBUG("Abort FCP Command request initiated "
+		       "(adapter%s, port d_id=0x%08x, "
+		       "unit x%016Lx, old_req_id=0x%lx)\n",
+		       zfcp_get_busid_by_adapter(adapter),
+		       unit->port->d_id,
+		       unit->fcp_lun, old_req_id);
+ out:
+	write_unlock_irqrestore(&adapter->request_queue.queue_lock, lock_flags);
+	return fsf_req;
+}
+
+/*
+ * function:    zfcp_fsf_abort_fcp_command_handler
+ *
+ * purpose:	is called for finished Abort FCP Command request
+ *
+ * returns:	
+ */
+static int
+zfcp_fsf_abort_fcp_command_handler(struct zfcp_fsf_req *new_fsf_req)
+{
+	int retval = -EINVAL;
+	struct zfcp_unit *unit = new_fsf_req->data.abort_fcp_command.unit;
+	unsigned char status_qual =
+	    new_fsf_req->qtcb->header.fsf_status_qual.word[0];
+
+	del_timer(&new_fsf_req->adapter->scsi_er_timer);
+
+	if (new_fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) {
+		/* do not set ZFCP_STATUS_FSFREQ_ABORTSUCCEEDED */
+		goto skip_fsfstatus;
+	}
+
+	/* evaluate FSF status in QTCB */
+	switch (new_fsf_req->qtcb->header.fsf_status) {
+
+	case FSF_PORT_HANDLE_NOT_VALID:
+		if (status_qual >> 4 != status_qual % 0xf) {
+			ZFCP_LOG_FLAGS(2, "FSF_PORT_HANDLE_NOT_VALID\n");
+			debug_text_event(new_fsf_req->adapter->erp_dbf, 3,
+					 "fsf_s_phand_nv0");
+			/*
+			 * In this case a command that was sent prior to a port
+			 * reopen was aborted (handles are different). This is
+			 * fine.
+			 */
+		} else {
+			ZFCP_LOG_FLAGS(1, "FSF_PORT_HANDLE_NOT_VALID\n");
+			ZFCP_LOG_INFO("Temporary port identifier 0x%x for "
+				      "port 0x%016Lx on adapter %s invalid. "
+				      "This may happen occasionally.\n",
+				      unit->port->handle,
+				      unit->port->wwpn,
+				      zfcp_get_busid_by_unit(unit));
+			ZFCP_LOG_INFO("status qualifier:\n");
+			ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_INFO,
+				      (char *) &new_fsf_req->qtcb->header.
+				      fsf_status_qual,
+				      sizeof (union fsf_status_qual));
+			/* Let's hope this sorts out the mess */
+			debug_text_event(new_fsf_req->adapter->erp_dbf, 1,
+					 "fsf_s_phand_nv1");
+			zfcp_erp_adapter_reopen(unit->port->adapter, 0);
+			new_fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		}
+		break;
+
+	case FSF_LUN_HANDLE_NOT_VALID:
+		if (status_qual >> 4 != status_qual % 0xf) {
+			/* 2 */
+			ZFCP_LOG_FLAGS(0, "FSF_LUN_HANDLE_NOT_VALID\n");
+			debug_text_event(new_fsf_req->adapter->erp_dbf, 3,
+					 "fsf_s_lhand_nv0");
+			/*
+			 * In this case a command that was sent prior to a unit
+			 * reopen was aborted (handles are different).
+			 * This is fine.
+			 */
+		} else {
+			ZFCP_LOG_FLAGS(1, "FSF_LUN_HANDLE_NOT_VALID\n");
+			ZFCP_LOG_INFO
+			    ("Warning: Temporary LUN identifier 0x%x of LUN "
+			     "0x%016Lx on port 0x%016Lx on adapter %s is "
+			     "invalid. This may happen in rare cases. "
+			     "Trying to re-establish link.\n",
+			     unit->handle,
+			     unit->fcp_lun,
+			     unit->port->wwpn,
+			     zfcp_get_busid_by_unit(unit));
+			ZFCP_LOG_DEBUG("Status qualifier data:\n");
+			ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
+				      (char *) &new_fsf_req->qtcb->header.
+				      fsf_status_qual,
+				      sizeof (union fsf_status_qual));
+			/* Let's hope this sorts out the mess */
+			debug_text_event(new_fsf_req->adapter->erp_dbf, 1,
+					 "fsf_s_lhand_nv1");
+			zfcp_erp_port_reopen(unit->port, 0);
+			new_fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		}
+		break;
+
+	case FSF_FCP_COMMAND_DOES_NOT_EXIST:
+		ZFCP_LOG_FLAGS(2, "FSF_FCP_COMMAND_DOES_NOT_EXIST\n");
+		retval = 0;
+		debug_text_event(new_fsf_req->adapter->erp_dbf, 3,
+				 "fsf_s_no_exist");
+		new_fsf_req->status |= ZFCP_STATUS_FSFREQ_ABORTNOTNEEDED;
+		break;
+
+	case FSF_PORT_BOXED:
+		/* 2 */
+		ZFCP_LOG_FLAGS(0, "FSF_PORT_BOXED\n");
+		ZFCP_LOG_INFO("Remote port 0x%016Lx on adapter %s needs to "
+			      "be reopened\n", unit->port->wwpn,
+			      zfcp_get_busid_by_unit(unit));
+		debug_text_event(new_fsf_req->adapter->erp_dbf, 2,
+				 "fsf_s_pboxed");
+		zfcp_erp_port_reopen(unit->port, 0);
+		new_fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR
+		    | ZFCP_STATUS_FSFREQ_RETRY;
+		break;
+
+	case FSF_LUN_BOXED:
+                ZFCP_LOG_FLAGS(0, "FSF_LUN_BOXED\n");
+                ZFCP_LOG_INFO(
+                        "unit 0x%016Lx on port 0x%016Lx on adapter %s needs "
+                        "to be reopened\n",
+                        unit->fcp_lun, unit->port->wwpn,
+                        zfcp_get_busid_by_unit(unit));
+                debug_text_event(new_fsf_req->adapter->erp_dbf, 1, "fsf_s_lboxed");
+                zfcp_erp_unit_reopen(unit, 0);
+                zfcp_cmd_dbf_event_fsf("unitbox", new_fsf_req,
+                        &new_fsf_req->qtcb->header.fsf_status_qual,
+                        sizeof(union fsf_status_qual));
+                new_fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR
+                        | ZFCP_STATUS_FSFREQ_RETRY;
+                break;
+
+	case FSF_ADAPTER_STATUS_AVAILABLE:
+		/* 2 */
+		ZFCP_LOG_FLAGS(0, "FSF_ADAPTER_STATUS_AVAILABLE\n");
+		switch (new_fsf_req->qtcb->header.fsf_status_qual.word[0]) {
+		case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE:
+			ZFCP_LOG_FLAGS(2,
+				       "FSF_SQ_INVOKE_LINK_TEST_PROCEDURE\n");
+			debug_text_event(new_fsf_req->adapter->erp_dbf, 1,
+					 "fsf_sq_ltest");
+			/* reopening link to port */
+			zfcp_erp_port_reopen(unit->port, 0);
+			new_fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+			break;
+		case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED:
+			ZFCP_LOG_FLAGS(2,
+				       "FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED\n");
+			/* SCSI stack will escalate */
+			debug_text_event(new_fsf_req->adapter->erp_dbf, 1,
+					 "fsf_sq_ulp");
+			new_fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+			break;
+		default:
+			ZFCP_LOG_NORMAL
+			    ("bug: Wrong status qualifier 0x%x arrived.\n",
+			     new_fsf_req->qtcb->header.fsf_status_qual.word[0]);
+			debug_text_event(new_fsf_req->adapter->erp_dbf, 0,
+					 "fsf_sq_inval:");
+			debug_exception(new_fsf_req->adapter->erp_dbf, 0,
+					&new_fsf_req->qtcb->header.
+					fsf_status_qual.word[0], sizeof (u32));
+			break;
+		}
+		break;
+
+	case FSF_GOOD:
+		/* 3 */
+		ZFCP_LOG_FLAGS(0, "FSF_GOOD\n");
+		retval = 0;
+		new_fsf_req->status |= ZFCP_STATUS_FSFREQ_ABORTSUCCEEDED;
+		break;
+
+	default:
+		ZFCP_LOG_NORMAL("bug: An unknown FSF Status was presented "
+				"(debug info 0x%x)\n",
+				new_fsf_req->qtcb->header.fsf_status);
+		debug_text_event(new_fsf_req->adapter->erp_dbf, 0,
+				 "fsf_s_inval:");
+		debug_exception(new_fsf_req->adapter->erp_dbf, 0,
+				&new_fsf_req->qtcb->header.fsf_status,
+				sizeof (u32));
+		break;
+	}
+ skip_fsfstatus:
+	return retval;
+}
+
+/**
+ * zfcp_use_one_sbal - checks whether req buffer and resp bother each fit into
+ *	one SBALE
+ * Two scatter-gather lists are passed, one for the reqeust and one for the
+ * response.
+ */
+static inline int
+zfcp_use_one_sbal(struct scatterlist *req, int req_count,
+                  struct scatterlist *resp, int resp_count)
+{
+        return ((req_count == 1) &&
+		(resp_count == 1) &&
+                (((unsigned long) zfcp_sg_to_address(&req[0]) &
+		  PAGE_MASK) ==
+		 ((unsigned long) (zfcp_sg_to_address(&req[0]) +
+				   req[0].length - 1) & PAGE_MASK)) &&
+                (((unsigned long) zfcp_sg_to_address(&resp[0]) &
+		  PAGE_MASK) ==
+                 ((unsigned long) (zfcp_sg_to_address(&resp[0]) +
+				   resp[0].length - 1) & PAGE_MASK)));
+}
+
+/**
+ * zfcp_fsf_send_ct - initiate a Generic Service request (FC-GS)
+ * @ct: pointer to struct zfcp_send_ct which conatins all needed data for
+ *	the request
+ * @pool: pointer to memory pool, if non-null this pool is used to allocate
+ *	a struct zfcp_fsf_req
+ * @erp_action: pointer to erp_action, if non-null the Generic Service request
+ *	is sent within error recovery
+ */
+int
+zfcp_fsf_send_ct(struct zfcp_send_ct *ct, mempool_t *pool,
+		 struct zfcp_erp_action *erp_action)
+{
+	volatile struct qdio_buffer_element *sbale;
+	struct zfcp_port *port;
+	struct zfcp_adapter *adapter;
+        struct zfcp_fsf_req *fsf_req;
+        unsigned long lock_flags;
+        int bytes;
+	int ret = 0;
+
+	port = ct->port;
+	adapter = port->adapter;
+
+	ret = zfcp_fsf_req_create(adapter, FSF_QTCB_SEND_GENERIC,
+				  ZFCP_WAIT_FOR_SBAL | ZFCP_REQ_AUTO_CLEANUP,
+				  pool, &lock_flags, &fsf_req);
+	if (ret < 0) {
+                ZFCP_LOG_INFO("error: Could not create CT request (FC-GS) for "
+			      "adapter: %s\n",
+			      zfcp_get_busid_by_adapter(adapter));
+		goto failed_req;
+	}
+
+        if (erp_action != NULL) {
+                erp_action->fsf_req = fsf_req;
+                fsf_req->erp_action = erp_action;
+        }
+
+	sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_curr, 0);
+        if (zfcp_use_one_sbal(ct->req, ct->req_count,
+                              ct->resp, ct->resp_count)){
+                /* both request buffer and response buffer
+                   fit into one sbale each */
+                sbale[0].flags |= SBAL_FLAGS0_TYPE_WRITE_READ;
+                sbale[2].addr = zfcp_sg_to_address(&ct->req[0]);
+                sbale[2].length = ct->req[0].length;
+                sbale[3].addr = zfcp_sg_to_address(&ct->resp[0]);
+                sbale[3].length = ct->resp[0].length;
+                sbale[3].flags |= SBAL_FLAGS_LAST_ENTRY;
+        } else if (adapter->supported_features &
+                   FSF_FEATURE_ELS_CT_CHAINED_SBALS) {
+                /* try to use chained SBALs */
+                bytes = zfcp_qdio_sbals_from_sg(fsf_req,
+                                                SBAL_FLAGS0_TYPE_WRITE_READ,
+                                                ct->req, ct->req_count,
+                                                ZFCP_MAX_SBALS_PER_CT_REQ);
+                if (bytes <= 0) {
+                        ZFCP_LOG_INFO("error: creation of CT request failed "
+				      "on adapter %s\n",
+				      zfcp_get_busid_by_adapter(adapter));
+                        if (bytes == 0)
+                                ret = -ENOMEM;
+                        else
+                                ret = bytes;
+
+                        goto failed_send;
+                }
+                fsf_req->qtcb->bottom.support.req_buf_length = bytes;
+                fsf_req->sbale_curr = ZFCP_LAST_SBALE_PER_SBAL;
+                bytes = zfcp_qdio_sbals_from_sg(fsf_req,
+                                                SBAL_FLAGS0_TYPE_WRITE_READ,
+                                                ct->resp, ct->resp_count,
+                                                ZFCP_MAX_SBALS_PER_CT_REQ);
+                if (bytes <= 0) {
+                        ZFCP_LOG_INFO("error: creation of CT request failed "
+				      "on adapter %s\n",
+				      zfcp_get_busid_by_adapter(adapter));
+                        if (bytes == 0)
+                                ret = -ENOMEM;
+                        else
+                                ret = bytes;
+
+                        goto failed_send;
+                }
+                fsf_req->qtcb->bottom.support.resp_buf_length = bytes;
+        } else {
+                /* reject send generic request */
+		ZFCP_LOG_INFO(
+			"error: microcode does not support chained SBALs,"
+                        "CT request too big (adapter %s)\n",
+			zfcp_get_busid_by_adapter(adapter));
+                ret = -EOPNOTSUPP;
+                goto failed_send;
+        }
+
+	/* settings in QTCB */
+	fsf_req->qtcb->header.port_handle = port->handle;
+	fsf_req->qtcb->bottom.support.service_class = adapter->fc_service_class;
+	fsf_req->qtcb->bottom.support.timeout = ct->timeout;
+        fsf_req->data.send_ct = ct;
+
+	/* start QDIO request for this FSF request */
+	ret = zfcp_fsf_req_send(fsf_req, ct->timer);
+	if (ret) {
+		ZFCP_LOG_DEBUG("error: initiation of CT request failed "
+			       "(adapter %s, port 0x%016Lx)\n",
+			       zfcp_get_busid_by_adapter(adapter), port->wwpn);
+		goto failed_send;
+	}
+
+	ZFCP_LOG_DEBUG("CT request initiated (adapter %s, port 0x%016Lx)\n",
+		       zfcp_get_busid_by_adapter(adapter), port->wwpn);
+	goto out;
+
+ failed_send:
+	zfcp_fsf_req_free(fsf_req);
+        if (erp_action != NULL) {
+                erp_action->fsf_req = NULL;
+        }
+ failed_req:
+ out:
+        write_unlock_irqrestore(&adapter->request_queue.queue_lock,
+				lock_flags);
+	return ret;
+}
+
+/**
+ * zfcp_fsf_send_ct_handler - handler for Generic Service requests
+ * @fsf_req: pointer to struct zfcp_fsf_req
+ *
+ * Data specific for the Generic Service request is passed by
+ * fsf_req->data.send_ct
+ * Usually a specific handler for the request is called via
+ * fsf_req->data.send_ct->handler at end of this function.
+ */
+static int
+zfcp_fsf_send_ct_handler(struct zfcp_fsf_req *fsf_req)
+{
+	struct zfcp_port *port;
+	struct zfcp_adapter *adapter;
+	struct zfcp_send_ct *send_ct;
+	struct fsf_qtcb_header *header;
+	struct fsf_qtcb_bottom_support *bottom;
+	int retval = -EINVAL;
+	u16 subtable, rule, counter;
+
+	adapter = fsf_req->adapter;
+	send_ct = fsf_req->data.send_ct;
+	port = send_ct->port;
+	header = &fsf_req->qtcb->header;
+	bottom = &fsf_req->qtcb->bottom.support;
+
+	if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR)
+		goto skip_fsfstatus;
+
+	/* evaluate FSF status in QTCB */
+	switch (header->fsf_status) {
+
+        case FSF_GOOD:
+                ZFCP_LOG_FLAGS(2,"FSF_GOOD\n");
+                retval = 0;
+		break;
+
+        case FSF_SERVICE_CLASS_NOT_SUPPORTED:
+		ZFCP_LOG_FLAGS(2, "FSF_SERVICE_CLASS_NOT_SUPPORTED\n");
+		if (adapter->fc_service_class <= 3) {
+			ZFCP_LOG_INFO("error: adapter %s does not support fc "
+				      "class %d.\n",
+				      zfcp_get_busid_by_port(port),
+				      adapter->fc_service_class);
+		} else {
+			ZFCP_LOG_INFO("bug: The fibre channel class at the "
+				      "adapter %s is invalid. "
+				      "(debug info %d)\n",
+				      zfcp_get_busid_by_port(port),
+				      adapter->fc_service_class);
+		}
+		/* stop operation for this adapter */
+		debug_text_exception(adapter->erp_dbf, 0, "fsf_s_class_nsup");
+		zfcp_erp_adapter_shutdown(adapter, 0);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+        case FSF_ADAPTER_STATUS_AVAILABLE:
+                ZFCP_LOG_FLAGS(2, "FSF_ADAPTER_STATUS_AVAILABLE\n");
+                switch (header->fsf_status_qual.word[0]){
+                case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE:
+			ZFCP_LOG_FLAGS(2,"FSF_SQ_INVOKE_LINK_TEST_PROCEDURE\n");
+			/* reopening link to port */
+			debug_text_event(adapter->erp_dbf, 1, "fsf_sq_ltest");
+			zfcp_test_link(port);
+			fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+			break;
+                case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED:
+			ZFCP_LOG_FLAGS(2,"FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED\n");
+			/* ERP strategy will escalate */
+			debug_text_event(adapter->erp_dbf, 1, "fsf_sq_ulp");
+			fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+			break;
+                default:
+			ZFCP_LOG_INFO("bug: Wrong status qualifier 0x%x "
+				      "arrived.\n",
+				      header->fsf_status_qual.word[0]);
+			break;
+                }
+                break;
+
+	case FSF_ACCESS_DENIED:
+		ZFCP_LOG_FLAGS(2, "FSF_ACCESS_DENIED\n");
+		ZFCP_LOG_NORMAL("access denied, cannot send generic service "
+				"command (adapter %s, port d_id=0x%08x)\n",
+				zfcp_get_busid_by_port(port), port->d_id);
+		for (counter = 0; counter < 2; counter++) {
+			subtable = header->fsf_status_qual.halfword[counter * 2];
+			rule = header->fsf_status_qual.halfword[counter * 2 + 1];
+			switch (subtable) {
+			case FSF_SQ_CFDC_SUBTABLE_OS:
+			case FSF_SQ_CFDC_SUBTABLE_PORT_WWPN:
+			case FSF_SQ_CFDC_SUBTABLE_PORT_DID:
+			case FSF_SQ_CFDC_SUBTABLE_LUN:
+       				ZFCP_LOG_INFO("Access denied (%s rule %d)\n",
+					zfcp_act_subtable_type[subtable], rule);
+				break;
+			}
+		}
+		debug_text_event(adapter->erp_dbf, 1, "fsf_s_access");
+		zfcp_erp_port_access_denied(port);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+        case FSF_GENERIC_COMMAND_REJECTED:
+		ZFCP_LOG_FLAGS(2, "FSF_GENERIC_COMMAND_REJECTED\n");
+		ZFCP_LOG_INFO("generic service command rejected "
+			      "(adapter %s, port d_id=0x%08x)\n",
+			      zfcp_get_busid_by_port(port), port->d_id);
+		ZFCP_LOG_INFO("status qualifier:\n");
+		ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_INFO,
+			      (char *) &header->fsf_status_qual,
+			      sizeof (union fsf_status_qual));
+		debug_text_event(adapter->erp_dbf, 1, "fsf_s_gcom_rej");
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+        case FSF_PORT_HANDLE_NOT_VALID:
+		ZFCP_LOG_FLAGS(2, "FSF_PORT_HANDLE_NOT_VALID\n");
+		ZFCP_LOG_DEBUG("Temporary port identifier 0x%x for port "
+			       "0x%016Lx on adapter %s invalid. This may "
+			       "happen occasionally.\n", port->handle,
+			       port->wwpn, zfcp_get_busid_by_port(port));
+		ZFCP_LOG_INFO("status qualifier:\n");
+		ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_INFO,
+			      (char *) &header->fsf_status_qual,
+			      sizeof (union fsf_status_qual));
+		debug_text_event(adapter->erp_dbf, 1, "fsf_s_phandle_nv");
+		zfcp_erp_adapter_reopen(adapter, 0);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+        case FSF_PORT_BOXED:
+		ZFCP_LOG_FLAGS(2, "FSF_PORT_BOXED\n");
+		ZFCP_LOG_INFO("port needs to be reopened "
+			      "(adapter %s, port d_id=0x%08x)\n",
+			      zfcp_get_busid_by_port(port), port->d_id);
+		debug_text_event(adapter->erp_dbf, 2, "fsf_s_pboxed");
+		zfcp_erp_port_reopen(port, 0);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR
+		    | ZFCP_STATUS_FSFREQ_RETRY;
+		break;
+
+	/* following states should never occure, all cases avoided
+	   in zfcp_fsf_send_ct - but who knows ... */
+	case FSF_PAYLOAD_SIZE_MISMATCH:
+		ZFCP_LOG_FLAGS(2, "FSF_PAYLOAD_SIZE_MISMATCH\n");
+		ZFCP_LOG_INFO("payload size mismatch (adapter: %s, "
+			      "req_buf_length=%d, resp_buf_length=%d)\n",
+			      zfcp_get_busid_by_adapter(adapter),
+			      bottom->req_buf_length, bottom->resp_buf_length);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+	case FSF_REQUEST_SIZE_TOO_LARGE:
+		ZFCP_LOG_FLAGS(2, "FSF_REQUEST_SIZE_TOO_LARGE\n");
+		ZFCP_LOG_INFO("request size too large (adapter: %s, "
+			      "req_buf_length=%d)\n",
+			      zfcp_get_busid_by_adapter(adapter),
+			      bottom->req_buf_length);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+	case FSF_RESPONSE_SIZE_TOO_LARGE:
+		ZFCP_LOG_FLAGS(2, "FSF_RESPONSE_SIZE_TOO_LARGE\n");
+		ZFCP_LOG_INFO("response size too large (adapter: %s, "
+			      "resp_buf_length=%d)\n",
+			      zfcp_get_busid_by_adapter(adapter),
+			      bottom->resp_buf_length);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+	case FSF_SBAL_MISMATCH:
+		ZFCP_LOG_FLAGS(2, "FSF_SBAL_MISMATCH\n");
+		ZFCP_LOG_INFO("SBAL mismatch (adapter: %s, req_buf_length=%d, "
+			      "resp_buf_length=%d)\n",
+			      zfcp_get_busid_by_adapter(adapter),
+			      bottom->req_buf_length, bottom->resp_buf_length);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+       default:
+		ZFCP_LOG_NORMAL("bug: An unknown FSF Status was presented "
+				"(debug info 0x%x)\n", header->fsf_status);
+		debug_text_event(adapter->erp_dbf, 0, "fsf_sq_inval:");
+		debug_exception(adapter->erp_dbf, 0,
+				&header->fsf_status_qual.word[0], sizeof (u32));
+		break;
+	}
+
+skip_fsfstatus:
+	send_ct->status = retval;
+
+	if (send_ct->handler != NULL)
+		send_ct->handler(send_ct->handler_data);
+
+	return retval;
+}
+
+/**
+ * zfcp_fsf_send_els - initiate an ELS command (FC-FS)
+ * @els: pointer to struct zfcp_send_els which contains all needed data for
+ *	the command.
+ */
+int
+zfcp_fsf_send_els(struct zfcp_send_els *els)
+{
+	volatile struct qdio_buffer_element *sbale;
+	struct zfcp_fsf_req *fsf_req;
+	fc_id_t d_id;
+	struct zfcp_adapter *adapter;
+	unsigned long lock_flags;
+        int bytes;
+	int ret = 0;
+
+	d_id = els->d_id;
+	adapter = els->adapter;
+
+        ret = zfcp_fsf_req_create(adapter, FSF_QTCB_SEND_ELS,
+				  ZFCP_REQ_AUTO_CLEANUP,
+				  NULL, &lock_flags, &fsf_req);
+	if (ret < 0) {
+                ZFCP_LOG_INFO("error: creation of ELS request failed "
+			      "(adapter %s, port d_id: 0x%08x)\n",
+                              zfcp_get_busid_by_adapter(adapter), d_id);
+                goto failed_req;
+	}
+
+	sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_curr, 0);
+        if (zfcp_use_one_sbal(els->req, els->req_count,
+                              els->resp, els->resp_count)){
+                /* both request buffer and response buffer
+                   fit into one sbale each */
+                sbale[0].flags |= SBAL_FLAGS0_TYPE_WRITE_READ;
+                sbale[2].addr = zfcp_sg_to_address(&els->req[0]);
+                sbale[2].length = els->req[0].length;
+                sbale[3].addr = zfcp_sg_to_address(&els->resp[0]);
+                sbale[3].length = els->resp[0].length;
+                sbale[3].flags |= SBAL_FLAGS_LAST_ENTRY;
+        } else if (adapter->supported_features &
+                   FSF_FEATURE_ELS_CT_CHAINED_SBALS) {
+                /* try to use chained SBALs */
+                bytes = zfcp_qdio_sbals_from_sg(fsf_req,
+                                                SBAL_FLAGS0_TYPE_WRITE_READ,
+                                                els->req, els->req_count,
+                                                ZFCP_MAX_SBALS_PER_ELS_REQ);
+                if (bytes <= 0) {
+                        ZFCP_LOG_INFO("error: creation of ELS request failed "
+				      "(adapter %s, port d_id: 0x%08x)\n",
+				      zfcp_get_busid_by_adapter(adapter), d_id);
+                        if (bytes == 0) {
+                                ret = -ENOMEM;
+                        } else {
+                                ret = bytes;
+                        }
+                        goto failed_send;
+                }
+                fsf_req->qtcb->bottom.support.req_buf_length = bytes;
+                fsf_req->sbale_curr = ZFCP_LAST_SBALE_PER_SBAL;
+                bytes = zfcp_qdio_sbals_from_sg(fsf_req,
+                                                SBAL_FLAGS0_TYPE_WRITE_READ,
+                                                els->resp, els->resp_count,
+                                                ZFCP_MAX_SBALS_PER_ELS_REQ);
+                if (bytes <= 0) {
+                        ZFCP_LOG_INFO("error: creation of ELS request failed "
+				      "(adapter %s, port d_id: 0x%08x)\n",
+				      zfcp_get_busid_by_adapter(adapter), d_id);
+                        if (bytes == 0) {
+                                ret = -ENOMEM;
+                        } else {
+                                ret = bytes;
+                        }
+                        goto failed_send;
+                }
+                fsf_req->qtcb->bottom.support.resp_buf_length = bytes;
+        } else {
+                /* reject request */
+		ZFCP_LOG_INFO("error: microcode does not support chained SBALs"
+                              ", ELS request too big (adapter %s, "
+			      "port d_id: 0x%08x)\n",
+			      zfcp_get_busid_by_adapter(adapter), d_id);
+                ret = -EOPNOTSUPP;
+                goto failed_send;
+        }
+
+	/* settings in QTCB */
+	fsf_req->qtcb->bottom.support.d_id = d_id;
+	fsf_req->qtcb->bottom.support.service_class = adapter->fc_service_class;
+	fsf_req->qtcb->bottom.support.timeout = ZFCP_ELS_TIMEOUT;
+	fsf_req->data.send_els = els;
+
+	sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_curr, 0);
+
+	/* start QDIO request for this FSF request */
+	ret = zfcp_fsf_req_send(fsf_req, els->timer);
+	if (ret) {
+		ZFCP_LOG_DEBUG("error: initiation of ELS request failed "
+			       "(adapter %s, port d_id: 0x%08x)\n",
+			       zfcp_get_busid_by_adapter(adapter), d_id);
+		goto failed_send;
+	}
+
+	ZFCP_LOG_DEBUG("ELS request initiated (adapter %s, port d_id: "
+		       "0x%08x)\n", zfcp_get_busid_by_adapter(adapter), d_id);
+	goto out;
+
+ failed_send:
+	zfcp_fsf_req_free(fsf_req);
+
+ failed_req:
+ out:
+	write_unlock_irqrestore(&adapter->request_queue.queue_lock,
+				lock_flags);
+
+        return ret;
+}
+
+/**
+ * zfcp_fsf_send_els_handler - handler for ELS commands
+ * @fsf_req: pointer to struct zfcp_fsf_req
+ *
+ * Data specific for the ELS command is passed by
+ * fsf_req->data.send_els
+ * Usually a specific handler for the command is called via
+ * fsf_req->data.send_els->handler at end of this function.
+ */
+static int zfcp_fsf_send_els_handler(struct zfcp_fsf_req *fsf_req)
+{
+	struct zfcp_adapter *adapter;
+	fc_id_t d_id;
+	struct zfcp_port *port;
+	struct fsf_qtcb_header *header;
+	struct fsf_qtcb_bottom_support *bottom;
+	struct zfcp_send_els *send_els;
+	int retval = -EINVAL;
+	u16 subtable, rule, counter;
+
+	send_els = fsf_req->data.send_els;
+	adapter = send_els->adapter;
+	d_id = send_els->d_id;
+	header = &fsf_req->qtcb->header;
+	bottom = &fsf_req->qtcb->bottom.support;
+
+	if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR)
+		goto skip_fsfstatus;
+
+	switch (header->fsf_status) {
+
+	case FSF_GOOD:
+		ZFCP_LOG_FLAGS(2, "FSF_GOOD\n");
+		retval = 0;
+		break;
+
+	case FSF_SERVICE_CLASS_NOT_SUPPORTED:
+		ZFCP_LOG_FLAGS(2, "FSF_SERVICE_CLASS_NOT_SUPPORTED\n");
+		if (adapter->fc_service_class <= 3) {
+			ZFCP_LOG_INFO("error: adapter %s does "
+				      "not support fibrechannel class %d.\n",
+				      zfcp_get_busid_by_adapter(adapter),
+				      adapter->fc_service_class);
+		} else {
+			ZFCP_LOG_INFO("bug: The fibrechannel class at "
+				      "adapter %s is invalid. "
+				      "(debug info %d)\n",
+				      zfcp_get_busid_by_adapter(adapter),
+				      adapter->fc_service_class);
+		}
+		/* stop operation for this adapter */
+		debug_text_exception(adapter->erp_dbf, 0, "fsf_s_class_nsup");
+		zfcp_erp_adapter_shutdown(adapter, 0);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_ADAPTER_STATUS_AVAILABLE:
+		ZFCP_LOG_FLAGS(2, "FSF_ADAPTER_STATUS_AVAILABLE\n");
+		switch (header->fsf_status_qual.word[0]){
+		case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE:
+			ZFCP_LOG_FLAGS(2,"FSF_SQ_INVOKE_LINK_TEST_PROCEDURE\n");
+			debug_text_event(adapter->erp_dbf, 1, "fsf_sq_ltest");
+			if (send_els->ls_code != ZFCP_LS_ADISC) {
+				read_lock(&zfcp_data.config_lock);
+				port = zfcp_get_port_by_did(adapter, d_id);
+				if (port)
+					zfcp_test_link(port);
+				read_unlock(&zfcp_data.config_lock);
+			}
+			fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+			break;
+		case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED:
+			ZFCP_LOG_FLAGS(2,"FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED\n");
+			debug_text_event(adapter->erp_dbf, 1, "fsf_sq_ulp");
+			fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+			retval =
+			  zfcp_handle_els_rjt(header->fsf_status_qual.word[1],
+					      (struct zfcp_ls_rjt_par *)
+					      &header->fsf_status_qual.word[2]);
+			break;
+		case FSF_SQ_RETRY_IF_POSSIBLE:
+			ZFCP_LOG_FLAGS(2, "FSF_SQ_RETRY_IF_POSSIBLE\n");
+			debug_text_event(adapter->erp_dbf, 1, "fsf_sq_retry");
+			fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+			break;
+		default:
+			ZFCP_LOG_INFO("bug: Wrong status qualifier 0x%x\n",
+				      header->fsf_status_qual.word[0]);
+			ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_INFO,
+				(char*)header->fsf_status_qual.word, 16);
+		}
+		break;
+
+	case FSF_ELS_COMMAND_REJECTED:
+		ZFCP_LOG_FLAGS(2, "FSF_ELS_COMMAND_REJECTED\n");
+		ZFCP_LOG_INFO("ELS has been rejected because command filter "
+			      "prohibited sending "
+			      "(adapter: %s, port d_id: 0x%08x)\n",
+			      zfcp_get_busid_by_adapter(adapter), d_id);
+
+		break;
+
+	case FSF_PAYLOAD_SIZE_MISMATCH:
+		ZFCP_LOG_FLAGS(2, "FSF_PAYLOAD_SIZE_MISMATCH\n");
+		ZFCP_LOG_INFO(
+			"ELS request size and ELS response size must be either "
+			"both 0, or both greater than 0 "
+			"(adapter: %s, req_buf_length=%d resp_buf_length=%d)\n",
+			zfcp_get_busid_by_adapter(adapter),
+			bottom->req_buf_length,
+			bottom->resp_buf_length);
+		break;
+
+	case FSF_REQUEST_SIZE_TOO_LARGE:
+		ZFCP_LOG_FLAGS(2, "FSF_REQUEST_SIZE_TOO_LARGE\n");
+		ZFCP_LOG_INFO(
+			"Length of the ELS request buffer, "
+			"specified in QTCB bottom, "
+			"exceeds the size of the buffers "
+			"that have been allocated for ELS request data "
+			"(adapter: %s, req_buf_length=%d)\n",
+			zfcp_get_busid_by_adapter(adapter),
+			bottom->req_buf_length);
+		break;
+
+	case FSF_RESPONSE_SIZE_TOO_LARGE:
+		ZFCP_LOG_FLAGS(2, "FSF_RESPONSE_SIZE_TOO_LARGE\n");
+		ZFCP_LOG_INFO(
+			"Length of the ELS response buffer, "
+			"specified in QTCB bottom, "
+			"exceeds the size of the buffers "
+			"that have been allocated for ELS response data "
+			"(adapter: %s, resp_buf_length=%d)\n",
+			zfcp_get_busid_by_adapter(adapter),
+			bottom->resp_buf_length);
+		break;
+
+	case FSF_SBAL_MISMATCH:
+		/* should never occure, avoided in zfcp_fsf_send_els */
+		ZFCP_LOG_FLAGS(2, "FSF_SBAL_MISMATCH\n");
+		ZFCP_LOG_INFO("SBAL mismatch (adapter: %s, req_buf_length=%d, "
+			      "resp_buf_length=%d)\n",
+			      zfcp_get_busid_by_adapter(adapter),
+			      bottom->req_buf_length, bottom->resp_buf_length);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_ACCESS_DENIED:
+		ZFCP_LOG_FLAGS(2, "FSF_ACCESS_DENIED\n");
+		ZFCP_LOG_NORMAL("access denied, cannot send ELS command "
+				"(adapter %s, port d_id=0x%08x)\n",
+				zfcp_get_busid_by_adapter(adapter), d_id);
+		for (counter = 0; counter < 2; counter++) {
+			subtable = header->fsf_status_qual.halfword[counter * 2];
+			rule = header->fsf_status_qual.halfword[counter * 2 + 1];
+			switch (subtable) {
+			case FSF_SQ_CFDC_SUBTABLE_OS:
+			case FSF_SQ_CFDC_SUBTABLE_PORT_WWPN:
+			case FSF_SQ_CFDC_SUBTABLE_PORT_DID:
+			case FSF_SQ_CFDC_SUBTABLE_LUN:
+				ZFCP_LOG_INFO("Access denied (%s rule %d)\n",
+					zfcp_act_subtable_type[subtable], rule);
+				break;
+			}
+		}
+		debug_text_event(adapter->erp_dbf, 1, "fsf_s_access");
+		read_lock(&zfcp_data.config_lock);
+		port = zfcp_get_port_by_did(adapter, d_id);
+		if (port != NULL)
+			zfcp_erp_port_access_denied(port);
+		read_unlock(&zfcp_data.config_lock);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	default:
+		ZFCP_LOG_NORMAL(
+			"bug: An unknown FSF Status was presented "
+			"(adapter: %s, fsf_status=0x%08x)\n",
+			zfcp_get_busid_by_adapter(adapter),
+			header->fsf_status);
+		debug_text_event(adapter->erp_dbf, 0, "fsf_sq_inval");
+		debug_exception(adapter->erp_dbf, 0,
+			&header->fsf_status_qual.word[0], sizeof(u32));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+	}
+
+skip_fsfstatus:
+	send_els->status = retval;
+
+	if (send_els->handler != 0)
+		send_els->handler(send_els->handler_data);
+
+	return retval;
+}
+
+/*
+ * function:
+ *
+ * purpose:
+ *
+ * returns:	address of initiated FSF request
+ *		NULL - request could not be initiated
+ */
+int
+zfcp_fsf_exchange_config_data(struct zfcp_erp_action *erp_action)
+{
+	volatile struct qdio_buffer_element *sbale;
+	unsigned long lock_flags;
+	int retval = 0;
+
+	/* setup new FSF request */
+	retval = zfcp_fsf_req_create(erp_action->adapter,
+				     FSF_QTCB_EXCHANGE_CONFIG_DATA,
+				     ZFCP_REQ_AUTO_CLEANUP,
+				     erp_action->adapter->pool.fsf_req_erp,
+				     &lock_flags, &(erp_action->fsf_req));
+	if (retval < 0) {
+		ZFCP_LOG_INFO("error: Could not create exchange configuration "
+			      "data request for adapter %s.\n",
+			      zfcp_get_busid_by_adapter(erp_action->adapter));
+		goto out;
+	}
+
+	sbale = zfcp_qdio_sbale_req(erp_action->fsf_req,
+                                    erp_action->fsf_req->sbal_curr, 0);
+        sbale[0].flags |= SBAL_FLAGS0_TYPE_READ;
+        sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY;
+
+	erp_action->fsf_req->erp_action = erp_action;
+	erp_action->fsf_req->qtcb->bottom.config.feature_selection =
+		(FSF_FEATURE_CFDC | FSF_FEATURE_LUN_SHARING);
+
+	/* start QDIO request for this FSF request */
+	retval = zfcp_fsf_req_send(erp_action->fsf_req, &erp_action->timer);
+	if (retval) {
+		ZFCP_LOG_INFO
+		    ("error: Could not send exchange configuration data "
+		     "command on the adapter %s\n",
+		     zfcp_get_busid_by_adapter(erp_action->adapter));
+		zfcp_fsf_req_free(erp_action->fsf_req);
+		erp_action->fsf_req = NULL;
+		goto out;
+	}
+
+	ZFCP_LOG_DEBUG("exchange configuration data request initiated "
+		       "(adapter %s)\n",
+		       zfcp_get_busid_by_adapter(erp_action->adapter));
+
+ out:
+	write_unlock_irqrestore(&erp_action->adapter->request_queue.queue_lock,
+				lock_flags);
+	return retval;
+}
+
+/**
+ * zfcp_fsf_exchange_config_evaluate
+ * @fsf_req: fsf_req which belongs to xchg config data request
+ * @xchg_ok: specifies if xchg config data was incomplete or complete (0/1)
+ *
+ * returns: -EIO on error, 0 otherwise
+ */
+static int
+zfcp_fsf_exchange_config_evaluate(struct zfcp_fsf_req *fsf_req, int xchg_ok)
+{
+	struct fsf_qtcb_bottom_config *bottom;
+	struct zfcp_adapter *adapter = fsf_req->adapter;
+
+	bottom = &fsf_req->qtcb->bottom.config;
+	ZFCP_LOG_DEBUG("low/high QTCB version 0x%x/0x%x of FSF\n",
+		       bottom->low_qtcb_version, bottom->high_qtcb_version);
+	adapter->fsf_lic_version = bottom->lic_version;
+	adapter->supported_features = bottom->supported_features;
+
+	if (xchg_ok) {
+		adapter->wwnn = bottom->nport_serv_param.wwnn;
+		adapter->wwpn = bottom->nport_serv_param.wwpn;
+		adapter->s_id = bottom->s_id & ZFCP_DID_MASK;
+		adapter->fc_topology = bottom->fc_topology;
+		adapter->fc_link_speed = bottom->fc_link_speed;
+		adapter->hydra_version = bottom->adapter_type;
+	} else {
+		adapter->wwnn = 0;
+		adapter->wwpn = 0;
+		adapter->s_id = 0;
+		adapter->fc_topology = 0;
+		adapter->fc_link_speed = 0;
+		adapter->hydra_version = 0;
+	}
+
+	if(adapter->supported_features & FSF_FEATURE_HBAAPI_MANAGEMENT){
+		adapter->hardware_version = bottom->hardware_version;
+		memcpy(adapter->serial_number, bottom->serial_number, 17);
+		EBCASC(adapter->serial_number, sizeof(adapter->serial_number));
+	}
+
+	ZFCP_LOG_INFO("The adapter %s reported the following characteristics:\n"
+		      "WWNN 0x%016Lx, "
+		      "WWPN 0x%016Lx, "
+		      "S_ID 0x%08x,\n"
+		      "adapter version 0x%x, "
+		      "LIC version 0x%x, "
+		      "FC link speed %d Gb/s\n",
+		      zfcp_get_busid_by_adapter(adapter),
+		      adapter->wwnn,
+		      adapter->wwpn,
+		      (unsigned int) adapter->s_id,
+		      adapter->hydra_version,
+		      adapter->fsf_lic_version,
+		      adapter->fc_link_speed);
+	if (ZFCP_QTCB_VERSION < bottom->low_qtcb_version) {
+		ZFCP_LOG_NORMAL("error: the adapter %s "
+				"only supports newer control block "
+				"versions in comparison to this device "
+				"driver (try updated device driver)\n",
+				zfcp_get_busid_by_adapter(adapter));
+		debug_text_event(adapter->erp_dbf, 0, "low_qtcb_ver");
+		zfcp_erp_adapter_shutdown(adapter, 0);
+		return -EIO;
+	}
+	if (ZFCP_QTCB_VERSION > bottom->high_qtcb_version) {
+		ZFCP_LOG_NORMAL("error: the adapter %s "
+				"only supports older control block "
+				"versions than this device driver uses"
+				"(consider a microcode upgrade)\n",
+				zfcp_get_busid_by_adapter(adapter));
+		debug_text_event(adapter->erp_dbf, 0, "high_qtcb_ver");
+		zfcp_erp_adapter_shutdown(adapter, 0);
+		return -EIO;
+	}
+	return 0;
+}
+
+/*
+ * function:    zfcp_fsf_exchange_config_data_handler
+ *
+ * purpose:     is called for finished Exchange Configuration Data command
+ *
+ * returns:
+ */
+static int
+zfcp_fsf_exchange_config_data_handler(struct zfcp_fsf_req *fsf_req)
+{
+	struct fsf_qtcb_bottom_config *bottom;
+	struct zfcp_adapter *adapter = fsf_req->adapter;
+
+	if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR)
+		return -EIO;
+
+	switch (fsf_req->qtcb->header.fsf_status) {
+
+	case FSF_GOOD:
+		ZFCP_LOG_FLAGS(2, "FSF_GOOD\n");
+
+		if (zfcp_fsf_exchange_config_evaluate(fsf_req, 1))
+			return -EIO;
+
+		switch (adapter->fc_topology) {
+		case FSF_TOPO_P2P:
+			ZFCP_LOG_FLAGS(1, "FSF_TOPO_P2P\n");
+			ZFCP_LOG_NORMAL("error: Point-to-point fibrechannel "
+					"configuration detected at adapter %s "
+					"unsupported, shutting down adapter\n",
+					zfcp_get_busid_by_adapter(adapter));
+			debug_text_event(fsf_req->adapter->erp_dbf, 0,
+					 "top-p-to-p");
+			zfcp_erp_adapter_shutdown(adapter, 0);
+			return -EIO;
+		case FSF_TOPO_AL:
+			ZFCP_LOG_FLAGS(1, "FSF_TOPO_AL\n");
+			ZFCP_LOG_NORMAL("error: Arbitrated loop fibrechannel "
+					"topology detected at adapter %s "
+					"unsupported, shutting down adapter\n",
+					zfcp_get_busid_by_adapter(adapter));
+			debug_text_event(fsf_req->adapter->erp_dbf, 0,
+					 "top-al");
+			zfcp_erp_adapter_shutdown(adapter, 0);
+			return -EIO;
+		case FSF_TOPO_FABRIC:
+			ZFCP_LOG_FLAGS(1, "FSF_TOPO_FABRIC\n");
+			ZFCP_LOG_INFO("Switched fabric fibrechannel "
+				      "network detected at adapter %s.\n",
+				      zfcp_get_busid_by_adapter(adapter));
+			break;
+		default:
+			ZFCP_LOG_NORMAL("bug: The fibrechannel topology "
+					"reported by the exchange "
+					"configuration command for "
+					"the adapter %s is not "
+					"of a type known to the zfcp "
+					"driver, shutting down adapter\n",
+					zfcp_get_busid_by_adapter(adapter));
+			debug_text_exception(fsf_req->adapter->erp_dbf, 0,
+					     "unknown-topo");
+			zfcp_erp_adapter_shutdown(adapter, 0);
+			return -EIO;
+		}
+		bottom = &fsf_req->qtcb->bottom.config;
+		if (bottom->max_qtcb_size < sizeof(struct fsf_qtcb)) {
+			ZFCP_LOG_NORMAL("bug: Maximum QTCB size (%d bytes) "
+					"allowed by the adapter %s "
+					"is lower than the minimum "
+					"required by the driver (%ld bytes).\n",
+					bottom->max_qtcb_size,
+					zfcp_get_busid_by_adapter(adapter),
+					sizeof(struct fsf_qtcb));
+			debug_text_event(fsf_req->adapter->erp_dbf, 0,
+					 "qtcb-size");
+			debug_event(fsf_req->adapter->erp_dbf, 0,
+				    &bottom->max_qtcb_size, sizeof (u32));
+			zfcp_erp_adapter_shutdown(adapter, 0);
+			return -EIO;
+		}
+		atomic_set_mask(ZFCP_STATUS_ADAPTER_XCONFIG_OK,
+				&adapter->status);
+		break;
+	case FSF_EXCHANGE_CONFIG_DATA_INCOMPLETE:
+		debug_text_event(adapter->erp_dbf, 0, "xchg-inco");
+
+		if (zfcp_fsf_exchange_config_evaluate(fsf_req, 0))
+			return -EIO;
+
+		ZFCP_LOG_INFO("Local link to adapter %s is down\n",
+			      zfcp_get_busid_by_adapter(adapter));
+		atomic_set_mask(ZFCP_STATUS_ADAPTER_XCONFIG_OK |
+				ZFCP_STATUS_ADAPTER_LINK_UNPLUGGED,
+				&adapter->status);
+		zfcp_erp_adapter_failed(adapter);
+		break;
+	default:
+		debug_text_event(fsf_req->adapter->erp_dbf, 0, "fsf-stat-ng");
+		debug_event(fsf_req->adapter->erp_dbf, 0,
+			    &fsf_req->qtcb->header.fsf_status, sizeof (u32));
+		zfcp_erp_adapter_shutdown(adapter, 0);
+		return -EIO;
+	}
+	return 0;
+}
+
+/**
+ * zfcp_fsf_exchange_port_data - request information about local port
+ * @adapter: for which port data is requested
+ * @data: response to exchange port data request
+ */
+int
+zfcp_fsf_exchange_port_data(struct zfcp_adapter *adapter,
+			    struct fsf_qtcb_bottom_port *data)
+{
+	volatile struct qdio_buffer_element *sbale;
+	int retval = 0;
+	unsigned long lock_flags;
+        struct zfcp_fsf_req *fsf_req;
+	struct timer_list *timer;
+
+        if(!(adapter->supported_features & FSF_FEATURE_HBAAPI_MANAGEMENT)){
+		ZFCP_LOG_INFO("error: exchange port data "
+                              "command not supported by adapter %s\n",
+			      zfcp_get_busid_by_adapter(adapter));
+                return -EOPNOTSUPP;
+        }
+
+	timer = kmalloc(sizeof(struct timer_list), GFP_KERNEL);
+	if (!timer)
+		return -ENOMEM;
+
+	/* setup new FSF request */
+	retval = zfcp_fsf_req_create(adapter, FSF_QTCB_EXCHANGE_PORT_DATA,
+                                     0, 0, &lock_flags, &fsf_req);
+	if (retval < 0) {
+		ZFCP_LOG_INFO("error: Out of resources. Could not create an "
+                              "exchange port data request for"
+                              "the adapter %s.\n",
+			      zfcp_get_busid_by_adapter(adapter));
+		write_unlock_irqrestore(&adapter->request_queue.queue_lock,
+					lock_flags);
+		goto out;
+	}
+
+	sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_curr, 0);
+        sbale[0].flags |= SBAL_FLAGS0_TYPE_READ;
+        sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY;
+
+        fsf_req->data.port_data = data;
+
+	init_timer(timer);
+	timer->function = zfcp_fsf_request_timeout_handler;
+	timer->data = (unsigned long) adapter;
+	timer->expires = ZFCP_FSF_REQUEST_TIMEOUT;
+
+	retval = zfcp_fsf_req_send(fsf_req, timer);
+	if (retval) {
+		ZFCP_LOG_INFO("error: Could not send an exchange port data "
+                              "command on the adapter %s\n",
+			      zfcp_get_busid_by_adapter(adapter));
+		zfcp_fsf_req_free(fsf_req);
+		write_unlock_irqrestore(&adapter->request_queue.queue_lock,
+					lock_flags);
+		goto out;
+	}
+
+	ZFCP_LOG_DEBUG("Exchange Port Data request initiated (adapter %s)\n",
+		       zfcp_get_busid_by_adapter(adapter));
+
+	write_unlock_irqrestore(&adapter->request_queue.queue_lock,
+				lock_flags);
+
+	wait_event(fsf_req->completion_wq,
+		   fsf_req->status & ZFCP_STATUS_FSFREQ_COMPLETED);
+	del_timer_sync(timer);
+	zfcp_fsf_req_cleanup(fsf_req);
+ out:
+	kfree(timer);
+	return retval;
+}
+
+
+/**
+ * zfcp_fsf_exchange_port_data_handler - handler for exchange_port_data request
+ * @fsf_req: pointer to struct zfcp_fsf_req
+ */
+static void
+zfcp_fsf_exchange_port_data_handler(struct zfcp_fsf_req *fsf_req)
+{
+	struct fsf_qtcb_bottom_port *bottom;
+	struct fsf_qtcb_bottom_port *data = fsf_req->data.port_data;
+
+	if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR)
+		return;
+
+	switch (fsf_req->qtcb->header.fsf_status) {
+        case FSF_GOOD:
+                ZFCP_LOG_FLAGS(2,"FSF_GOOD\n");
+                bottom = &fsf_req->qtcb->bottom.port;
+                memcpy(data, bottom, sizeof(*data));
+                break;
+
+        default:
+		debug_text_event(fsf_req->adapter->erp_dbf, 0, "xchg-port-ng");
+                debug_event(fsf_req->adapter->erp_dbf, 0,
+			    &fsf_req->qtcb->header.fsf_status, sizeof(u32));
+	}
+}
+
+
+/*
+ * function:    zfcp_fsf_open_port
+ *
+ * purpose:	
+ *
+ * returns:	address of initiated FSF request
+ *		NULL - request could not be initiated 
+ */
+int
+zfcp_fsf_open_port(struct zfcp_erp_action *erp_action)
+{
+	volatile struct qdio_buffer_element *sbale;
+	unsigned long lock_flags;
+	int retval = 0;
+
+	/* setup new FSF request */
+	retval = zfcp_fsf_req_create(erp_action->adapter,
+				     FSF_QTCB_OPEN_PORT_WITH_DID,
+				     ZFCP_WAIT_FOR_SBAL | ZFCP_REQ_AUTO_CLEANUP,
+				     erp_action->adapter->pool.fsf_req_erp,
+				     &lock_flags, &(erp_action->fsf_req));
+	if (retval < 0) {
+		ZFCP_LOG_INFO("error: Could not create open port request "
+			      "for port 0x%016Lx on adapter %s.\n",
+			      erp_action->port->wwpn,
+			      zfcp_get_busid_by_adapter(erp_action->adapter));
+		goto out;
+	}
+
+	sbale = zfcp_qdio_sbale_req(erp_action->fsf_req,
+                                    erp_action->fsf_req->sbal_curr, 0);
+        sbale[0].flags |= SBAL_FLAGS0_TYPE_READ;
+        sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY;
+
+	erp_action->fsf_req->qtcb->bottom.support.d_id = erp_action->port->d_id;
+	atomic_set_mask(ZFCP_STATUS_COMMON_OPENING, &erp_action->port->status);
+	erp_action->fsf_req->data.open_port.port = erp_action->port;
+	erp_action->fsf_req->erp_action = erp_action;
+
+	/* start QDIO request for this FSF request */
+	retval = zfcp_fsf_req_send(erp_action->fsf_req, &erp_action->timer);
+	if (retval) {
+		ZFCP_LOG_INFO("error: Could not send open port request for "
+			      "port 0x%016Lx on adapter %s.\n",
+			      erp_action->port->wwpn,
+			      zfcp_get_busid_by_adapter(erp_action->adapter));
+		zfcp_fsf_req_free(erp_action->fsf_req);
+		erp_action->fsf_req = NULL;
+		goto out;
+	}
+
+	ZFCP_LOG_DEBUG("open port request initiated "
+		       "(adapter %s,  port 0x%016Lx)\n",
+		       zfcp_get_busid_by_adapter(erp_action->adapter),
+		       erp_action->port->wwpn);
+ out:
+	write_unlock_irqrestore(&erp_action->adapter->request_queue.queue_lock,
+				lock_flags);
+	return retval;
+}
+
+/*
+ * function:    zfcp_fsf_open_port_handler
+ *
+ * purpose:	is called for finished Open Port command
+ *
+ * returns:	
+ */
+static int
+zfcp_fsf_open_port_handler(struct zfcp_fsf_req *fsf_req)
+{
+	int retval = -EINVAL;
+	struct zfcp_port *port;
+	struct fsf_plogi *plogi;
+	struct fsf_qtcb_header *header;
+	u16 subtable, rule, counter;
+
+	port = fsf_req->data.open_port.port;
+	header = &fsf_req->qtcb->header;
+
+	if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) {
+		/* don't change port status in our bookkeeping */
+		goto skip_fsfstatus;
+	}
+
+	/* evaluate FSF status in QTCB */
+	switch (header->fsf_status) {
+
+	case FSF_PORT_ALREADY_OPEN:
+		ZFCP_LOG_FLAGS(0, "FSF_PORT_ALREADY_OPEN\n");
+		ZFCP_LOG_NORMAL("bug: remote port 0x%016Lx on adapter %s "
+				"is already open.\n",
+				port->wwpn, zfcp_get_busid_by_port(port));
+		debug_text_exception(fsf_req->adapter->erp_dbf, 0,
+				     "fsf_s_popen");
+		/*
+		 * This is a bug, however operation should continue normally
+		 * if it is simply ignored
+		 */
+		break;
+
+	case FSF_ACCESS_DENIED:
+		ZFCP_LOG_FLAGS(2, "FSF_ACCESS_DENIED\n");
+		ZFCP_LOG_NORMAL("Access denied, cannot open port 0x%016Lx "
+				"on adapter %s\n",
+				port->wwpn, zfcp_get_busid_by_port(port));
+		for (counter = 0; counter < 2; counter++) {
+			subtable = header->fsf_status_qual.halfword[counter * 2];
+			rule = header->fsf_status_qual.halfword[counter * 2 + 1];
+			switch (subtable) {
+			case FSF_SQ_CFDC_SUBTABLE_OS:
+			case FSF_SQ_CFDC_SUBTABLE_PORT_WWPN:
+			case FSF_SQ_CFDC_SUBTABLE_PORT_DID:
+			case FSF_SQ_CFDC_SUBTABLE_LUN:
+				ZFCP_LOG_INFO("Access denied (%s rule %d)\n",
+					zfcp_act_subtable_type[subtable], rule);
+				break;
+			}
+		}
+		debug_text_event(fsf_req->adapter->erp_dbf, 1, "fsf_s_access");
+		zfcp_erp_port_access_denied(port);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_MAXIMUM_NUMBER_OF_PORTS_EXCEEDED:
+		ZFCP_LOG_FLAGS(1, "FSF_MAXIMUM_NUMBER_OF_PORTS_EXCEEDED\n");
+		ZFCP_LOG_INFO("error: The FSF adapter is out of resources. "
+			      "The remote port 0x%016Lx on adapter %s "
+			      "could not be opened. Disabling it.\n",
+			      port->wwpn, zfcp_get_busid_by_port(port));
+		debug_text_event(fsf_req->adapter->erp_dbf, 1,
+				 "fsf_s_max_ports");
+		zfcp_erp_port_failed(port);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_ADAPTER_STATUS_AVAILABLE:
+		ZFCP_LOG_FLAGS(2, "FSF_ADAPTER_STATUS_AVAILABLE\n");
+		switch (header->fsf_status_qual.word[0]) {
+		case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE:
+			ZFCP_LOG_FLAGS(2,
+				       "FSF_SQ_INVOKE_LINK_TEST_PROCEDURE\n");
+			debug_text_event(fsf_req->adapter->erp_dbf, 1,
+					 "fsf_sq_ltest");
+			/* ERP strategy will escalate */
+			fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+			break;
+		case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED:
+			/* ERP strategy will escalate */
+			debug_text_event(fsf_req->adapter->erp_dbf, 1,
+					 "fsf_sq_ulp");
+			fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+			break;
+		case FSF_SQ_NO_RETRY_POSSIBLE:
+			ZFCP_LOG_FLAGS(0, "FSF_SQ_NO_RETRY_POSSIBLE\n");
+			ZFCP_LOG_NORMAL("The remote port 0x%016Lx on "
+					"adapter %s could not be opened. "
+					"Disabling it.\n",
+					port->wwpn,
+					zfcp_get_busid_by_port(port));
+			debug_text_exception(fsf_req->adapter->erp_dbf, 0,
+					     "fsf_sq_no_retry");
+			zfcp_erp_port_failed(port);
+			fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+			break;
+		default:
+			ZFCP_LOG_NORMAL
+			    ("bug: Wrong status qualifier 0x%x arrived.\n",
+			     header->fsf_status_qual.word[0]);
+			debug_text_event(fsf_req->adapter->erp_dbf, 0,
+					 "fsf_sq_inval:");
+			debug_exception(
+				fsf_req->adapter->erp_dbf, 0,
+				&header->fsf_status_qual.word[0],
+				sizeof (u32));
+			break;
+		}
+		break;
+
+	case FSF_GOOD:
+		ZFCP_LOG_FLAGS(3, "FSF_GOOD\n");
+		/* save port handle assigned by FSF */
+		port->handle = header->port_handle;
+		ZFCP_LOG_INFO("The remote port 0x%016Lx via adapter %s "
+			      "was opened, it's port handle is 0x%x\n",
+			      port->wwpn, zfcp_get_busid_by_port(port),
+			      port->handle);
+		/* mark port as open */
+		atomic_set_mask(ZFCP_STATUS_COMMON_OPEN |
+				ZFCP_STATUS_PORT_PHYS_OPEN, &port->status);
+		retval = 0;
+		/* check whether D_ID has changed during open */
+		/*
+		 * FIXME: This check is not airtight, as the FCP channel does
+		 * not monitor closures of target port connections caused on
+		 * the remote side. Thus, they might miss out on invalidating
+		 * locally cached WWPNs (and other N_Port parameters) of gone
+		 * target ports. So, our heroic attempt to make things safe
+		 * could be undermined by 'open port' response data tagged with
+		 * obsolete WWPNs. Another reason to monitor potential
+		 * connection closures ourself at least (by interpreting
+		 * incoming ELS' and unsolicited status). It just crosses my
+		 * mind that one should be able to cross-check by means of
+		 * another GID_PN straight after a port has been opened.
+		 * Alternately, an ADISC/PDISC ELS should suffice, as well.
+		 */
+		plogi = (struct fsf_plogi *) fsf_req->qtcb->bottom.support.els;
+		if (!atomic_test_mask(ZFCP_STATUS_PORT_NO_WWPN, &port->status))
+		{
+			if (fsf_req->qtcb->bottom.support.els1_length <
+			    ((((unsigned long) &plogi->serv_param.wwpn) -
+			      ((unsigned long) plogi)) + sizeof (u64))) {
+				ZFCP_LOG_INFO(
+					"warning: insufficient length of "
+					"PLOGI payload (%i)\n",
+					fsf_req->qtcb->bottom.support.els1_length);
+				debug_text_event(fsf_req->adapter->erp_dbf, 0,
+						 "fsf_s_short_plogi:");
+				/* skip sanity check and assume wwpn is ok */
+			} else {
+				if (plogi->serv_param.wwpn != port->wwpn) {
+					ZFCP_LOG_INFO("warning: d_id of port "
+						      "0x%016Lx changed during "
+						      "open\n", port->wwpn);
+					debug_text_event(
+						fsf_req->adapter->erp_dbf, 0,
+						"fsf_s_did_change:");
+					atomic_clear_mask(
+						ZFCP_STATUS_PORT_DID_DID,
+						&port->status);
+				} else
+					port->wwnn = plogi->serv_param.wwnn;
+			}
+		}
+		break;
+
+	case FSF_UNKNOWN_OP_SUBTYPE:
+		/* should never occure, subtype not set in zfcp_fsf_open_port */
+		ZFCP_LOG_FLAGS(2, "FSF_UNKNOWN_OP_SUBTYPE\n");
+		ZFCP_LOG_INFO("unknown operation subtype (adapter: %s, "
+			      "op_subtype=0x%x)\n",
+			      zfcp_get_busid_by_port(port),
+			      fsf_req->qtcb->bottom.support.operation_subtype);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	default:
+		ZFCP_LOG_NORMAL("bug: An unknown FSF Status was presented "
+				"(debug info 0x%x)\n",
+				header->fsf_status);
+		debug_text_event(fsf_req->adapter->erp_dbf, 0, "fsf_s_inval:");
+		debug_exception(fsf_req->adapter->erp_dbf, 0,
+				&header->fsf_status, sizeof (u32));
+		break;
+	}
+
+ skip_fsfstatus:
+	atomic_clear_mask(ZFCP_STATUS_COMMON_OPENING, &port->status);
+	return retval;
+}
+
+/*
+ * function:    zfcp_fsf_close_port
+ *
+ * purpose:     submit FSF command "close port"
+ *
+ * returns:     address of initiated FSF request
+ *              NULL - request could not be initiated
+ */
+int
+zfcp_fsf_close_port(struct zfcp_erp_action *erp_action)
+{
+	volatile struct qdio_buffer_element *sbale;
+	unsigned long lock_flags;
+	int retval = 0;
+
+	/* setup new FSF request */
+	retval = zfcp_fsf_req_create(erp_action->adapter,
+				     FSF_QTCB_CLOSE_PORT,
+				     ZFCP_WAIT_FOR_SBAL | ZFCP_REQ_AUTO_CLEANUP,
+				     erp_action->adapter->pool.fsf_req_erp,
+				     &lock_flags, &(erp_action->fsf_req));
+	if (retval < 0) {
+		ZFCP_LOG_INFO("error: Could not create a close port request "
+			      "for port 0x%016Lx on adapter %s.\n",
+			      erp_action->port->wwpn,
+			      zfcp_get_busid_by_adapter(erp_action->adapter));
+		goto out;
+	}
+
+	sbale = zfcp_qdio_sbale_req(erp_action->fsf_req,
+                                    erp_action->fsf_req->sbal_curr, 0);
+        sbale[0].flags |= SBAL_FLAGS0_TYPE_READ;
+        sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY;
+
+	atomic_set_mask(ZFCP_STATUS_COMMON_CLOSING, &erp_action->port->status);
+	erp_action->fsf_req->data.close_port.port = erp_action->port;
+	erp_action->fsf_req->erp_action = erp_action;
+	erp_action->fsf_req->qtcb->header.port_handle =
+	    erp_action->port->handle;
+
+	/* start QDIO request for this FSF request */
+	retval = zfcp_fsf_req_send(erp_action->fsf_req, &erp_action->timer);
+	if (retval) {
+		ZFCP_LOG_INFO("error: Could not send a close port request for "
+			      "port 0x%016Lx on adapter %s.\n",
+			      erp_action->port->wwpn,
+			      zfcp_get_busid_by_adapter(erp_action->adapter));
+		zfcp_fsf_req_free(erp_action->fsf_req);
+		erp_action->fsf_req = NULL;
+		goto out;
+	}
+
+	ZFCP_LOG_TRACE("close port request initiated "
+		       "(adapter %s, port 0x%016Lx)\n",
+		       zfcp_get_busid_by_adapter(erp_action->adapter),
+		       erp_action->port->wwpn);
+ out:
+	write_unlock_irqrestore(&erp_action->adapter->request_queue.queue_lock,
+				lock_flags);
+	return retval;
+}
+
+/*
+ * function:    zfcp_fsf_close_port_handler
+ *
+ * purpose:     is called for finished Close Port FSF command
+ *
+ * returns:
+ */
+static int
+zfcp_fsf_close_port_handler(struct zfcp_fsf_req *fsf_req)
+{
+	int retval = -EINVAL;
+	struct zfcp_port *port;
+
+	port = fsf_req->data.close_port.port;
+
+	if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) {
+		/* don't change port status in our bookkeeping */
+		goto skip_fsfstatus;
+	}
+
+	/* evaluate FSF status in QTCB */
+	switch (fsf_req->qtcb->header.fsf_status) {
+
+	case FSF_PORT_HANDLE_NOT_VALID:
+		ZFCP_LOG_FLAGS(1, "FSF_PORT_HANDLE_NOT_VALID\n");
+		ZFCP_LOG_INFO("Temporary port identifier 0x%x for port "
+			      "0x%016Lx on adapter %s invalid. This may happen "
+			      "occasionally.\n", port->handle,
+			      port->wwpn, zfcp_get_busid_by_port(port));
+		ZFCP_LOG_DEBUG("status qualifier:\n");
+		ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
+			      (char *) &fsf_req->qtcb->header.fsf_status_qual,
+			      sizeof (union fsf_status_qual));
+		debug_text_event(fsf_req->adapter->erp_dbf, 1,
+				 "fsf_s_phand_nv");
+		zfcp_erp_adapter_reopen(port->adapter, 0);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_ADAPTER_STATUS_AVAILABLE:
+		ZFCP_LOG_FLAGS(2, "FSF_ADAPTER_STATUS_AVAILABLE\n");
+		/* Note: FSF has actually closed the port in this case.
+		 * The status code is just daft. Fingers crossed for a change
+		 */
+		retval = 0;
+		break;
+
+	case FSF_GOOD:
+		ZFCP_LOG_FLAGS(3, "FSF_GOOD\n");
+		ZFCP_LOG_TRACE("remote port 0x016%Lx on adapter %s closed, "
+			       "port handle 0x%x\n", port->wwpn,
+			       zfcp_get_busid_by_port(port), port->handle);
+		zfcp_erp_modify_port_status(port,
+					    ZFCP_STATUS_COMMON_OPEN,
+					    ZFCP_CLEAR);
+		retval = 0;
+		break;
+
+	default:
+		ZFCP_LOG_NORMAL("bug: An unknown FSF Status was presented "
+				"(debug info 0x%x)\n",
+				fsf_req->qtcb->header.fsf_status);
+		debug_text_event(fsf_req->adapter->erp_dbf, 0, "fsf_s_inval:");
+		debug_exception(fsf_req->adapter->erp_dbf, 0,
+				&fsf_req->qtcb->header.fsf_status,
+				sizeof (u32));
+		break;
+	}
+
+ skip_fsfstatus:
+	atomic_clear_mask(ZFCP_STATUS_COMMON_CLOSING, &port->status);
+	return retval;
+}
+
+/*
+ * function:    zfcp_fsf_close_physical_port
+ *
+ * purpose:     submit FSF command "close physical port"
+ *
+ * returns:     address of initiated FSF request
+ *              NULL - request could not be initiated
+ */
+int
+zfcp_fsf_close_physical_port(struct zfcp_erp_action *erp_action)
+{
+	int retval = 0;
+	unsigned long lock_flags;
+	volatile struct qdio_buffer_element *sbale;
+
+	/* setup new FSF request */
+	retval = zfcp_fsf_req_create(erp_action->adapter,
+				     FSF_QTCB_CLOSE_PHYSICAL_PORT,
+				     ZFCP_WAIT_FOR_SBAL | ZFCP_REQ_AUTO_CLEANUP,
+				     erp_action->adapter->pool.fsf_req_erp,
+				     &lock_flags, &erp_action->fsf_req);
+	if (retval < 0) {
+		ZFCP_LOG_INFO("error: Could not create close physical port "
+			      "request (adapter %s, port 0x%016Lx)\n",
+			      zfcp_get_busid_by_adapter(erp_action->adapter),
+			      erp_action->port->wwpn);
+
+		goto out;
+	}
+
+	sbale = zfcp_qdio_sbale_req(erp_action->fsf_req,
+				    erp_action->fsf_req->sbal_curr, 0);
+	sbale[0].flags |= SBAL_FLAGS0_TYPE_READ;
+	sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY;
+
+	/* mark port as being closed */
+	atomic_set_mask(ZFCP_STATUS_PORT_PHYS_CLOSING,
+			&erp_action->port->status);
+	/* save a pointer to this port */
+	erp_action->fsf_req->data.close_physical_port.port = erp_action->port;
+	/* port to be closeed */
+	erp_action->fsf_req->qtcb->header.port_handle =
+	    erp_action->port->handle;
+	erp_action->fsf_req->erp_action = erp_action;
+
+	/* start QDIO request for this FSF request */
+	retval = zfcp_fsf_req_send(erp_action->fsf_req, &erp_action->timer);
+	if (retval) {
+		ZFCP_LOG_INFO("error: Could not send close physical port "
+			      "request (adapter %s, port 0x%016Lx)\n",
+			      zfcp_get_busid_by_adapter(erp_action->adapter),
+			      erp_action->port->wwpn);
+		zfcp_fsf_req_free(erp_action->fsf_req);
+		erp_action->fsf_req = NULL;
+		goto out;
+	}
+
+	ZFCP_LOG_TRACE("close physical port request initiated "
+		       "(adapter %s, port 0x%016Lx)\n",
+		       zfcp_get_busid_by_adapter(erp_action->adapter),
+		       erp_action->port->wwpn);
+ out:
+	write_unlock_irqrestore(&erp_action->adapter->request_queue.queue_lock,
+				lock_flags);
+	return retval;
+}
+
+/*
+ * function:    zfcp_fsf_close_physical_port_handler
+ *
+ * purpose:     is called for finished Close Physical Port FSF command
+ *
+ * returns:
+ */
+static int
+zfcp_fsf_close_physical_port_handler(struct zfcp_fsf_req *fsf_req)
+{
+	int retval = -EINVAL;
+	struct zfcp_port *port;
+	struct zfcp_unit *unit;
+	struct fsf_qtcb_header *header;
+	u16 subtable, rule, counter;
+
+	port = fsf_req->data.close_physical_port.port;
+	header = &fsf_req->qtcb->header;
+
+	if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) {
+		/* don't change port status in our bookkeeping */
+		goto skip_fsfstatus;
+	}
+
+	/* evaluate FSF status in QTCB */
+	switch (header->fsf_status) {
+
+	case FSF_PORT_HANDLE_NOT_VALID:
+		ZFCP_LOG_FLAGS(1, "FSF_PORT_HANDLE_NOT_VALID\n");
+		ZFCP_LOG_INFO("Temporary port identifier 0x%x invalid"
+			      "(adapter %s, port 0x%016Lx). "
+			      "This may happen occasionally.\n",
+			      port->handle,
+			      zfcp_get_busid_by_port(port),
+			      port->wwpn);
+		ZFCP_LOG_DEBUG("status qualifier:\n");
+		ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
+			      (char *) &header->fsf_status_qual,
+			      sizeof (union fsf_status_qual));
+		debug_text_event(fsf_req->adapter->erp_dbf, 1,
+				 "fsf_s_phand_nv");
+		zfcp_erp_adapter_reopen(port->adapter, 0);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_ACCESS_DENIED:
+		ZFCP_LOG_FLAGS(2, "FSF_ACCESS_DENIED\n");
+		ZFCP_LOG_NORMAL("Access denied, cannot close "
+				"physical port 0x%016Lx on adapter %s\n",
+				port->wwpn, zfcp_get_busid_by_port(port));
+		for (counter = 0; counter < 2; counter++) {
+			subtable = header->fsf_status_qual.halfword[counter * 2];
+			rule = header->fsf_status_qual.halfword[counter * 2 + 1];
+			switch (subtable) {
+			case FSF_SQ_CFDC_SUBTABLE_OS:
+			case FSF_SQ_CFDC_SUBTABLE_PORT_WWPN:
+			case FSF_SQ_CFDC_SUBTABLE_PORT_DID:
+			case FSF_SQ_CFDC_SUBTABLE_LUN:
+	       			ZFCP_LOG_INFO("Access denied (%s rule %d)\n",
+					zfcp_act_subtable_type[subtable], rule);
+				break;
+			}
+		}
+		debug_text_event(fsf_req->adapter->erp_dbf, 1, "fsf_s_access");
+		zfcp_erp_port_access_denied(port);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_PORT_BOXED:
+		ZFCP_LOG_FLAGS(2, "FSF_PORT_BOXED\n");
+		ZFCP_LOG_DEBUG("The remote port 0x%016Lx on adapter "
+			       "%s needs to be reopened but it was attempted "
+			       "to close it physically.\n",
+			       port->wwpn,
+			       zfcp_get_busid_by_port(port));
+		debug_text_event(fsf_req->adapter->erp_dbf, 1, "fsf_s_pboxed");
+		zfcp_erp_port_reopen(port, 0);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR |
+			ZFCP_STATUS_FSFREQ_RETRY;
+		break;
+
+	case FSF_ADAPTER_STATUS_AVAILABLE:
+		ZFCP_LOG_FLAGS(2, "FSF_ADAPTER_STATUS_AVAILABLE\n");
+		switch (header->fsf_status_qual.word[0]) {
+		case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE:
+			ZFCP_LOG_FLAGS(2,
+				       "FSF_SQ_INVOKE_LINK_TEST_PROCEDURE\n");
+			debug_text_event(fsf_req->adapter->erp_dbf, 1,
+					 "fsf_sq_ltest");
+			/* This will now be escalated by ERP */
+			fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+			break;
+		case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED:
+			ZFCP_LOG_FLAGS(2,
+				       "FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED\n");
+			/* ERP strategy will escalate */
+			debug_text_event(fsf_req->adapter->erp_dbf, 1,
+					 "fsf_sq_ulp");
+			fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+			break;
+		default:
+			ZFCP_LOG_NORMAL
+			    ("bug: Wrong status qualifier 0x%x arrived.\n",
+			     header->fsf_status_qual.word[0]);
+			debug_text_event(fsf_req->adapter->erp_dbf, 0,
+					 "fsf_sq_inval:");
+			debug_exception(
+				fsf_req->adapter->erp_dbf, 0,
+				&header->fsf_status_qual.word[0], sizeof (u32));
+			break;
+		}
+		break;
+
+	case FSF_GOOD:
+		ZFCP_LOG_FLAGS(3, "FSF_GOOD\n");
+		ZFCP_LOG_DEBUG("Remote port 0x%016Lx via adapter %s "
+			       "physically closed, port handle 0x%x\n",
+			       port->wwpn,
+			       zfcp_get_busid_by_port(port), port->handle);
+		/* can't use generic zfcp_erp_modify_port_status because
+		 * ZFCP_STATUS_COMMON_OPEN must not be reset for the port
+		 */
+		atomic_clear_mask(ZFCP_STATUS_PORT_PHYS_OPEN, &port->status);
+		list_for_each_entry(unit, &port->unit_list_head, list)
+		    atomic_clear_mask(ZFCP_STATUS_COMMON_OPEN, &unit->status);
+		retval = 0;
+		break;
+
+	default:
+		ZFCP_LOG_NORMAL("bug: An unknown FSF Status was presented "
+				"(debug info 0x%x)\n",
+				header->fsf_status);
+		debug_text_event(fsf_req->adapter->erp_dbf, 0, "fsf_s_inval:");
+		debug_exception(fsf_req->adapter->erp_dbf, 0,
+				&header->fsf_status, sizeof (u32));
+		break;
+	}
+
+ skip_fsfstatus:
+	atomic_clear_mask(ZFCP_STATUS_PORT_PHYS_CLOSING, &port->status);
+	return retval;
+}
+
+/*
+ * function:    zfcp_fsf_open_unit
+ *
+ * purpose:
+ *
+ * returns:
+ *
+ * assumptions:	This routine does not check whether the associated
+ *		remote port has already been opened. This should be
+ *		done by calling routines. Otherwise some status
+ *		may be presented by FSF
+ */
+int
+zfcp_fsf_open_unit(struct zfcp_erp_action *erp_action)
+{
+	volatile struct qdio_buffer_element *sbale;
+	unsigned long lock_flags;
+	int retval = 0;
+
+	/* setup new FSF request */
+	retval = zfcp_fsf_req_create(erp_action->adapter,
+				     FSF_QTCB_OPEN_LUN,
+				     ZFCP_WAIT_FOR_SBAL | ZFCP_REQ_AUTO_CLEANUP,
+				     erp_action->adapter->pool.fsf_req_erp,
+				     &lock_flags, &(erp_action->fsf_req));
+	if (retval < 0) {
+		ZFCP_LOG_INFO("error: Could not create open unit request for "
+			      "unit 0x%016Lx on port 0x%016Lx on adapter %s.\n",
+			      erp_action->unit->fcp_lun,
+			      erp_action->unit->port->wwpn,
+			      zfcp_get_busid_by_adapter(erp_action->adapter));
+		goto out;
+	}
+
+	sbale = zfcp_qdio_sbale_req(erp_action->fsf_req,
+                                    erp_action->fsf_req->sbal_curr, 0);
+        sbale[0].flags |= SBAL_FLAGS0_TYPE_READ;
+        sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY;
+
+	erp_action->fsf_req->qtcb->header.port_handle =
+		erp_action->port->handle;
+	erp_action->fsf_req->qtcb->bottom.support.fcp_lun =
+		erp_action->unit->fcp_lun;
+	erp_action->fsf_req->qtcb->bottom.support.option =
+		FSF_OPEN_LUN_SUPPRESS_BOXING;
+	atomic_set_mask(ZFCP_STATUS_COMMON_OPENING, &erp_action->unit->status);
+	erp_action->fsf_req->data.open_unit.unit = erp_action->unit;
+	erp_action->fsf_req->erp_action = erp_action;
+
+	/* start QDIO request for this FSF request */
+	retval = zfcp_fsf_req_send(erp_action->fsf_req, &erp_action->timer);
+	if (retval) {
+		ZFCP_LOG_INFO("error: Could not send an open unit request "
+			      "on the adapter %s, port 0x%016Lx for "
+			      "unit 0x%016Lx\n",
+			      zfcp_get_busid_by_adapter(erp_action->adapter),
+			      erp_action->port->wwpn,
+			      erp_action->unit->fcp_lun);
+		zfcp_fsf_req_free(erp_action->fsf_req);
+		erp_action->fsf_req = NULL;
+		goto out;
+	}
+
+	ZFCP_LOG_TRACE("Open LUN request initiated (adapter %s, "
+		       "port 0x%016Lx, unit 0x%016Lx)\n",
+		       zfcp_get_busid_by_adapter(erp_action->adapter),
+		       erp_action->port->wwpn, erp_action->unit->fcp_lun);
+ out:
+	write_unlock_irqrestore(&erp_action->adapter->request_queue.queue_lock,
+				lock_flags);
+	return retval;
+}
+
+/*
+ * function:    zfcp_fsf_open_unit_handler
+ *
+ * purpose:	is called for finished Open LUN command
+ *
+ * returns:	
+ */
+static int
+zfcp_fsf_open_unit_handler(struct zfcp_fsf_req *fsf_req)
+{
+	int retval = -EINVAL;
+	struct zfcp_adapter *adapter;
+	struct zfcp_unit *unit;
+	struct fsf_qtcb_header *header;
+	struct fsf_qtcb_bottom_support *bottom;
+	struct fsf_queue_designator *queue_designator;
+	u16 subtable, rule, counter;
+	u32 allowed, exclusive, readwrite;
+
+	unit = fsf_req->data.open_unit.unit;
+
+	if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) {
+		/* don't change unit status in our bookkeeping */
+		goto skip_fsfstatus;
+	}
+
+	adapter = fsf_req->adapter;
+	header = &fsf_req->qtcb->header;
+	bottom = &fsf_req->qtcb->bottom.support;
+	queue_designator = &header->fsf_status_qual.fsf_queue_designator;
+
+	allowed   = bottom->lun_access_info & FSF_UNIT_ACCESS_OPEN_LUN_ALLOWED;
+	exclusive = bottom->lun_access_info & FSF_UNIT_ACCESS_EXCLUSIVE;
+	readwrite = bottom->lun_access_info & FSF_UNIT_ACCESS_OUTBOUND_TRANSFER;
+
+	atomic_clear_mask(ZFCP_STATUS_COMMON_ACCESS_DENIED |
+			  ZFCP_STATUS_UNIT_SHARED |
+			  ZFCP_STATUS_UNIT_READONLY,
+			  &unit->status);
+
+	/* evaluate FSF status in QTCB */
+	switch (header->fsf_status) {
+
+	case FSF_PORT_HANDLE_NOT_VALID:
+		ZFCP_LOG_FLAGS(1, "FSF_PORT_HANDLE_NOT_VALID\n");
+		ZFCP_LOG_INFO("Temporary port identifier 0x%x "
+			      "for port 0x%016Lx on adapter %s invalid "
+			      "This may happen occasionally\n",
+			      unit->port->handle,
+			      unit->port->wwpn, zfcp_get_busid_by_unit(unit));
+		ZFCP_LOG_DEBUG("status qualifier:\n");
+		ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
+			      (char *) &header->fsf_status_qual,
+			      sizeof (union fsf_status_qual));
+		debug_text_event(adapter->erp_dbf, 1, "fsf_s_ph_nv");
+		zfcp_erp_adapter_reopen(unit->port->adapter, 0);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_LUN_ALREADY_OPEN:
+		ZFCP_LOG_FLAGS(0, "FSF_LUN_ALREADY_OPEN\n");
+		ZFCP_LOG_NORMAL("bug: Attempted to open unit 0x%016Lx on "
+				"remote port 0x%016Lx on adapter %s twice.\n",
+				unit->fcp_lun,
+				unit->port->wwpn, zfcp_get_busid_by_unit(unit));
+		debug_text_exception(adapter->erp_dbf, 0,
+				     "fsf_s_uopen");
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_ACCESS_DENIED:
+		ZFCP_LOG_FLAGS(2, "FSF_ACCESS_DENIED\n");
+		ZFCP_LOG_NORMAL("Access denied, cannot open unit 0x%016Lx on "
+				"remote port 0x%016Lx on adapter %s\n",
+				unit->fcp_lun, unit->port->wwpn,
+				zfcp_get_busid_by_unit(unit));
+		for (counter = 0; counter < 2; counter++) {
+			subtable = header->fsf_status_qual.halfword[counter * 2];
+			rule = header->fsf_status_qual.halfword[counter * 2 + 1];
+			switch (subtable) {
+			case FSF_SQ_CFDC_SUBTABLE_OS:
+			case FSF_SQ_CFDC_SUBTABLE_PORT_WWPN:
+			case FSF_SQ_CFDC_SUBTABLE_PORT_DID:
+			case FSF_SQ_CFDC_SUBTABLE_LUN:
+				ZFCP_LOG_INFO("Access denied (%s rule %d)\n",
+					zfcp_act_subtable_type[subtable], rule);
+				break;
+			}
+		}
+		debug_text_event(adapter->erp_dbf, 1, "fsf_s_access");
+		zfcp_erp_unit_access_denied(unit);
+		atomic_clear_mask(ZFCP_STATUS_UNIT_SHARED, &unit->status);
+                atomic_clear_mask(ZFCP_STATUS_UNIT_READONLY, &unit->status);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_PORT_BOXED:
+		ZFCP_LOG_FLAGS(2, "FSF_PORT_BOXED\n");
+		ZFCP_LOG_DEBUG("The remote port 0x%016Lx on adapter %s "
+			       "needs to be reopened\n",
+			       unit->port->wwpn, zfcp_get_busid_by_unit(unit));
+		debug_text_event(adapter->erp_dbf, 2, "fsf_s_pboxed");
+		zfcp_erp_port_reopen(unit->port, 0);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR |
+			ZFCP_STATUS_FSFREQ_RETRY;
+		break;
+
+	case FSF_LUN_SHARING_VIOLATION:
+		ZFCP_LOG_FLAGS(2, "FSF_LUN_SHARING_VIOLATION\n");
+		if (header->fsf_status_qual.word[0] != 0) {
+			ZFCP_LOG_NORMAL("FCP-LUN 0x%Lx at the remote port "
+					"with WWPN 0x%Lx "
+					"connected to the adapter %s "
+					"is already in use in LPAR%d, CSS%d\n",
+					unit->fcp_lun,
+					unit->port->wwpn,
+					zfcp_get_busid_by_unit(unit),
+					queue_designator->hla,
+					queue_designator->cssid);
+		} else {
+			subtable = header->fsf_status_qual.halfword[4];
+			rule = header->fsf_status_qual.halfword[5];
+			switch (subtable) {
+			case FSF_SQ_CFDC_SUBTABLE_OS:
+			case FSF_SQ_CFDC_SUBTABLE_PORT_WWPN:
+			case FSF_SQ_CFDC_SUBTABLE_PORT_DID:
+			case FSF_SQ_CFDC_SUBTABLE_LUN:
+				ZFCP_LOG_NORMAL("Access to FCP-LUN 0x%Lx at the "
+						"remote port with WWPN 0x%Lx "
+						"connected to the adapter %s "
+						"is denied (%s rule %d)\n",
+						unit->fcp_lun,
+						unit->port->wwpn,
+						zfcp_get_busid_by_unit(unit),
+						zfcp_act_subtable_type[subtable],
+						rule);
+				break;
+			}
+		}
+		ZFCP_LOG_DEBUG("status qualifier:\n");
+		ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
+			      (char *) &header->fsf_status_qual,
+			      sizeof (union fsf_status_qual));
+		debug_text_event(adapter->erp_dbf, 2,
+				 "fsf_s_l_sh_vio");
+		zfcp_erp_unit_access_denied(unit);
+		atomic_clear_mask(ZFCP_STATUS_UNIT_SHARED, &unit->status);
+		atomic_clear_mask(ZFCP_STATUS_UNIT_READONLY, &unit->status);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_MAXIMUM_NUMBER_OF_LUNS_EXCEEDED:
+		ZFCP_LOG_FLAGS(1, "FSF_MAXIMUM_NUMBER_OF_LUNS_EXCEEDED\n");
+		ZFCP_LOG_INFO("error: The adapter ran out of resources. "
+			      "There is no handle (temporary port identifier) "
+			      "available for unit 0x%016Lx on port 0x%016Lx "
+			      "on adapter %s\n",
+			      unit->fcp_lun,
+			      unit->port->wwpn,
+			      zfcp_get_busid_by_unit(unit));
+		debug_text_event(adapter->erp_dbf, 1,
+				 "fsf_s_max_units");
+		zfcp_erp_unit_failed(unit);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_ADAPTER_STATUS_AVAILABLE:
+		ZFCP_LOG_FLAGS(2, "FSF_ADAPTER_STATUS_AVAILABLE\n");
+		switch (header->fsf_status_qual.word[0]) {
+		case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE:
+			ZFCP_LOG_FLAGS(2,
+				       "FSF_SQ_INVOKE_LINK_TEST_PROCEDURE\n");
+			/* Re-establish link to port */
+			debug_text_event(adapter->erp_dbf, 1,
+					 "fsf_sq_ltest");
+			zfcp_erp_port_reopen(unit->port, 0);
+			fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+			break;
+		case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED:
+			ZFCP_LOG_FLAGS(2,
+				       "FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED\n");
+			/* ERP strategy will escalate */
+			debug_text_event(adapter->erp_dbf, 1,
+					 "fsf_sq_ulp");
+			fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+			break;
+		default:
+			ZFCP_LOG_NORMAL
+			    ("bug: Wrong status qualifier 0x%x arrived.\n",
+			     header->fsf_status_qual.word[0]);
+			debug_text_event(adapter->erp_dbf, 0,
+					 "fsf_sq_inval:");
+			debug_exception(adapter->erp_dbf, 0,
+					&header->fsf_status_qual.word[0],
+				sizeof (u32));
+		}
+		break;
+
+	case FSF_INVALID_COMMAND_OPTION:
+		ZFCP_LOG_FLAGS(2, "FSF_INVALID_COMMAND_OPTION\n");
+		ZFCP_LOG_NORMAL(
+			"Invalid option 0x%x has been specified "
+			"in QTCB bottom sent to the adapter %s\n",
+			bottom->option,
+			zfcp_get_busid_by_adapter(adapter));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		retval = -EINVAL;
+		break;
+
+	case FSF_GOOD:
+		ZFCP_LOG_FLAGS(3, "FSF_GOOD\n");
+		/* save LUN handle assigned by FSF */
+		unit->handle = header->lun_handle;
+		ZFCP_LOG_TRACE("unit 0x%016Lx on remote port 0x%016Lx on "
+			       "adapter %s opened, port handle 0x%x\n",
+			       unit->fcp_lun,
+			       unit->port->wwpn,
+			       zfcp_get_busid_by_unit(unit),
+			       unit->handle);
+		/* mark unit as open */
+		atomic_set_mask(ZFCP_STATUS_COMMON_OPEN, &unit->status);
+
+		if (adapter->supported_features & FSF_FEATURE_LUN_SHARING){
+			if (!exclusive)
+		                atomic_set_mask(ZFCP_STATUS_UNIT_SHARED,
+						&unit->status);
+
+			if (!readwrite) {
+                		atomic_set_mask(ZFCP_STATUS_UNIT_READONLY,
+						&unit->status);
+                		ZFCP_LOG_NORMAL("read-only access for unit "
+						"(adapter %s, wwpn=0x%016Lx, "
+						"fcp_lun=0x%016Lx)\n",
+						zfcp_get_busid_by_unit(unit),
+						unit->port->wwpn,
+						unit->fcp_lun);
+        		}
+
+        		if (exclusive && !readwrite) {
+                		ZFCP_LOG_NORMAL("exclusive access of read-only "
+						"unit not supported\n");
+				zfcp_erp_unit_failed(unit);
+				fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+				zfcp_erp_unit_shutdown(unit, 0);
+        		} else if (!exclusive && readwrite) {
+                		ZFCP_LOG_NORMAL("shared access of read-write "
+						"unit not supported\n");
+                		zfcp_erp_unit_failed(unit);
+				fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+				zfcp_erp_unit_shutdown(unit, 0);
+        		}
+		}
+
+		retval = 0;
+		break;
+
+	default:
+		ZFCP_LOG_NORMAL("bug: An unknown FSF Status was presented "
+				"(debug info 0x%x)\n",
+				header->fsf_status);
+		debug_text_event(adapter->erp_dbf, 0, "fsf_s_inval:");
+		debug_exception(adapter->erp_dbf, 0,
+				&header->fsf_status, sizeof (u32));
+		break;
+	}
+
+ skip_fsfstatus:
+	atomic_clear_mask(ZFCP_STATUS_COMMON_OPENING, &unit->status);
+	return retval;
+}
+
+/*
+ * function:    zfcp_fsf_close_unit
+ *
+ * purpose:
+ *
+ * returns:	address of fsf_req - request successfully initiated
+ *		NULL - 
+ *
+ * assumptions: This routine does not check whether the associated
+ *              remote port/lun has already been opened. This should be
+ *              done by calling routines. Otherwise some status
+ *              may be presented by FSF
+ */
+int
+zfcp_fsf_close_unit(struct zfcp_erp_action *erp_action)
+{
+	volatile struct qdio_buffer_element *sbale;
+	unsigned long lock_flags;
+	int retval = 0;
+
+	/* setup new FSF request */
+	retval = zfcp_fsf_req_create(erp_action->adapter,
+				     FSF_QTCB_CLOSE_LUN,
+				     ZFCP_WAIT_FOR_SBAL | ZFCP_REQ_AUTO_CLEANUP,
+				     erp_action->adapter->pool.fsf_req_erp,
+				     &lock_flags, &(erp_action->fsf_req));
+	if (retval < 0) {
+		ZFCP_LOG_INFO("error: Could not create close unit request for "
+			      "unit 0x%016Lx on port 0x%016Lx on adapter %s.\n",
+			      erp_action->unit->fcp_lun,
+			      erp_action->port->wwpn,
+			      zfcp_get_busid_by_adapter(erp_action->adapter));
+		goto out;
+	}
+
+	sbale = zfcp_qdio_sbale_req(erp_action->fsf_req,
+                                    erp_action->fsf_req->sbal_curr, 0);
+        sbale[0].flags |= SBAL_FLAGS0_TYPE_READ;
+        sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY;
+
+	erp_action->fsf_req->qtcb->header.port_handle =
+	    erp_action->port->handle;
+	erp_action->fsf_req->qtcb->header.lun_handle = erp_action->unit->handle;
+	atomic_set_mask(ZFCP_STATUS_COMMON_CLOSING, &erp_action->unit->status);
+	erp_action->fsf_req->data.close_unit.unit = erp_action->unit;
+	erp_action->fsf_req->erp_action = erp_action;
+
+	/* start QDIO request for this FSF request */
+	retval = zfcp_fsf_req_send(erp_action->fsf_req, &erp_action->timer);
+	if (retval) {
+		ZFCP_LOG_INFO("error: Could not send a close unit request for "
+			      "unit 0x%016Lx on port 0x%016Lx onadapter %s.\n",
+			      erp_action->unit->fcp_lun,
+			      erp_action->port->wwpn,
+			      zfcp_get_busid_by_adapter(erp_action->adapter));
+		zfcp_fsf_req_free(erp_action->fsf_req);
+		erp_action->fsf_req = NULL;
+		goto out;
+	}
+
+	ZFCP_LOG_TRACE("Close LUN request initiated (adapter %s, "
+		       "port 0x%016Lx, unit 0x%016Lx)\n",
+		       zfcp_get_busid_by_adapter(erp_action->adapter),
+		       erp_action->port->wwpn, erp_action->unit->fcp_lun);
+ out:
+	write_unlock_irqrestore(&erp_action->adapter->request_queue.queue_lock,
+				lock_flags);
+	return retval;
+}
+
+/*
+ * function:    zfcp_fsf_close_unit_handler
+ *
+ * purpose:     is called for finished Close LUN FSF command
+ *
+ * returns:
+ */
+static int
+zfcp_fsf_close_unit_handler(struct zfcp_fsf_req *fsf_req)
+{
+	int retval = -EINVAL;
+	struct zfcp_unit *unit;
+
+	unit = fsf_req->data.close_unit.unit;	/* restore unit */
+
+	if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) {
+		/* don't change unit status in our bookkeeping */
+		goto skip_fsfstatus;
+	}
+
+	/* evaluate FSF status in QTCB */
+	switch (fsf_req->qtcb->header.fsf_status) {
+
+	case FSF_PORT_HANDLE_NOT_VALID:
+		ZFCP_LOG_FLAGS(1, "FSF_PORT_HANDLE_NOT_VALID\n");
+		ZFCP_LOG_INFO("Temporary port identifier 0x%x for port "
+			      "0x%016Lx on adapter %s invalid. This may "
+			      "happen in rare circumstances\n",
+			      unit->port->handle,
+			      unit->port->wwpn,
+			      zfcp_get_busid_by_unit(unit));
+		ZFCP_LOG_DEBUG("status qualifier:\n");
+		ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
+			      (char *) &fsf_req->qtcb->header.fsf_status_qual,
+			      sizeof (union fsf_status_qual));
+		debug_text_event(fsf_req->adapter->erp_dbf, 1,
+				 "fsf_s_phand_nv");
+		zfcp_erp_adapter_reopen(unit->port->adapter, 0);
+		zfcp_cmd_dbf_event_fsf("porthinv", fsf_req,
+				       &fsf_req->qtcb->header.fsf_status_qual,
+				       sizeof (union fsf_status_qual));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_LUN_HANDLE_NOT_VALID:
+		ZFCP_LOG_FLAGS(1, "FSF_LUN_HANDLE_NOT_VALID\n");
+		ZFCP_LOG_INFO("Temporary LUN identifier 0x%x of unit "
+			      "0x%016Lx on port 0x%016Lx on adapter %s is "
+			      "invalid. This may happen occasionally.\n",
+			      unit->handle,
+			      unit->fcp_lun,
+			      unit->port->wwpn,
+			      zfcp_get_busid_by_unit(unit));
+		ZFCP_LOG_DEBUG("Status qualifier data:\n");
+		ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
+			      (char *) &fsf_req->qtcb->header.fsf_status_qual,
+			      sizeof (union fsf_status_qual));
+		debug_text_event(fsf_req->adapter->erp_dbf, 1,
+				 "fsf_s_lhand_nv");
+		zfcp_erp_port_reopen(unit->port, 0);
+		zfcp_cmd_dbf_event_fsf("lunhinv", fsf_req,
+				       &fsf_req->qtcb->header.fsf_status_qual,
+				       sizeof (union fsf_status_qual));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_PORT_BOXED:
+		ZFCP_LOG_FLAGS(2, "FSF_PORT_BOXED\n");
+		ZFCP_LOG_DEBUG("The remote port 0x%016Lx on adapter %s "
+			       "needs to be reopened\n",
+			       unit->port->wwpn,
+			       zfcp_get_busid_by_unit(unit));
+		debug_text_event(fsf_req->adapter->erp_dbf, 2, "fsf_s_pboxed");
+		zfcp_erp_port_reopen(unit->port, 0);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR |
+			ZFCP_STATUS_FSFREQ_RETRY;
+		break;
+
+	case FSF_ADAPTER_STATUS_AVAILABLE:
+		ZFCP_LOG_FLAGS(2, "FSF_ADAPTER_STATUS_AVAILABLE\n");
+		switch (fsf_req->qtcb->header.fsf_status_qual.word[0]) {
+		case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE:
+			ZFCP_LOG_FLAGS(2,
+				       "FSF_SQ_INVOKE_LINK_TEST_PROCEDURE\n");
+			/* re-establish link to port */
+			debug_text_event(fsf_req->adapter->erp_dbf, 1,
+					 "fsf_sq_ltest");
+			zfcp_erp_port_reopen(unit->port, 0);
+			fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+			break;
+		case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED:
+			ZFCP_LOG_FLAGS(2,
+				       "FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED\n");
+			/* ERP strategy will escalate */
+			debug_text_event(fsf_req->adapter->erp_dbf, 1,
+					 "fsf_sq_ulp");
+			fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+			break;
+		default:
+			ZFCP_LOG_NORMAL
+			    ("bug: Wrong status qualifier 0x%x arrived.\n",
+			     fsf_req->qtcb->header.fsf_status_qual.word[0]);
+			debug_text_event(fsf_req->adapter->erp_dbf, 0,
+					 "fsf_sq_inval:");
+			debug_exception(
+				fsf_req->adapter->erp_dbf, 0,
+				&fsf_req->qtcb->header.fsf_status_qual.word[0],
+				sizeof (u32));
+			break;
+		}
+		break;
+
+	case FSF_GOOD:
+		ZFCP_LOG_FLAGS(3, "FSF_GOOD\n");
+		ZFCP_LOG_TRACE("unit 0x%016Lx on port 0x%016Lx on adapter %s "
+			       "closed, port handle 0x%x\n",
+			       unit->fcp_lun,
+			       unit->port->wwpn,
+			       zfcp_get_busid_by_unit(unit),
+			       unit->handle);
+		/* mark unit as closed */
+		atomic_clear_mask(ZFCP_STATUS_COMMON_OPEN, &unit->status);
+		retval = 0;
+		break;
+
+	default:
+		ZFCP_LOG_NORMAL("bug: An unknown FSF Status was presented "
+				"(debug info 0x%x)\n",
+				fsf_req->qtcb->header.fsf_status);
+		debug_text_event(fsf_req->adapter->erp_dbf, 0, "fsf_s_inval:");
+		debug_exception(fsf_req->adapter->erp_dbf, 0,
+				&fsf_req->qtcb->header.fsf_status,
+				sizeof (u32));
+		break;
+	}
+
+ skip_fsfstatus:
+	atomic_clear_mask(ZFCP_STATUS_COMMON_CLOSING, &unit->status);
+	return retval;
+}
+
+/**
+ * zfcp_fsf_send_fcp_command_task - initiate an FCP command (for a SCSI command)
+ * @adapter: adapter where scsi command is issued
+ * @unit: unit where command is sent to
+ * @scsi_cmnd: scsi command to be sent
+ * @timer: timer to be started when request is initiated
+ * @req_flags: flags for fsf_request
+ */
+int
+zfcp_fsf_send_fcp_command_task(struct zfcp_adapter *adapter,
+			       struct zfcp_unit *unit,
+			       struct scsi_cmnd * scsi_cmnd,
+			       struct timer_list *timer, int req_flags)
+{
+	struct zfcp_fsf_req *fsf_req = NULL;
+	struct fcp_cmnd_iu *fcp_cmnd_iu;
+	unsigned int sbtype;
+	unsigned long lock_flags;
+	int real_bytes = 0;
+	int retval = 0;
+	int mask;
+
+	/* setup new FSF request */
+	retval = zfcp_fsf_req_create(adapter, FSF_QTCB_FCP_CMND, req_flags,
+				     adapter->pool.fsf_req_scsi,
+				     &lock_flags, &fsf_req);
+	if (unlikely(retval < 0)) {
+		ZFCP_LOG_DEBUG("error: Could not create FCP command request "
+			       "for unit 0x%016Lx on port 0x%016Lx on "
+			       "adapter %s\n",
+			       unit->fcp_lun,
+			       unit->port->wwpn,
+			       zfcp_get_busid_by_adapter(adapter));
+		goto failed_req_create;
+	}
+
+	/*
+	 * associate FSF request with SCSI request
+	 * (need this for look up on abort)
+	 */
+	fsf_req->data.send_fcp_command_task.fsf_req = fsf_req;
+	scsi_cmnd->host_scribble = (char *) &(fsf_req->data);
+
+	/*
+	 * associate SCSI command with FSF request
+	 * (need this for look up on normal command completion)
+	 */
+	fsf_req->data.send_fcp_command_task.scsi_cmnd = scsi_cmnd;
+	fsf_req->data.send_fcp_command_task.start_jiffies = jiffies;
+	fsf_req->data.send_fcp_command_task.unit = unit;
+	ZFCP_LOG_DEBUG("unit=%p, fcp_lun=0x%016Lx\n", unit, unit->fcp_lun);
+
+	/* set handles of unit and its parent port in QTCB */
+	fsf_req->qtcb->header.lun_handle = unit->handle;
+	fsf_req->qtcb->header.port_handle = unit->port->handle;
+
+	/* FSF does not define the structure of the FCP_CMND IU */
+	fcp_cmnd_iu = (struct fcp_cmnd_iu *)
+	    &(fsf_req->qtcb->bottom.io.fcp_cmnd);
+
+	/*
+	 * set depending on data direction:
+	 *      data direction bits in SBALE (SB Type)
+	 *      data direction bits in QTCB
+	 *      data direction bits in FCP_CMND IU
+	 */
+	switch (scsi_cmnd->sc_data_direction) {
+	case DMA_NONE:
+		ZFCP_LOG_FLAGS(3, "DMA_NONE\n");
+		fsf_req->qtcb->bottom.io.data_direction = FSF_DATADIR_CMND;
+		/*
+		 * FIXME(qdio):
+		 * what is the correct type for commands
+		 * without 'real' data buffers?
+		 */
+		sbtype = SBAL_FLAGS0_TYPE_READ;
+		break;
+	case DMA_FROM_DEVICE:
+		ZFCP_LOG_FLAGS(3, "DMA_FROM_DEVICE\n");
+		fsf_req->qtcb->bottom.io.data_direction = FSF_DATADIR_READ;
+		sbtype = SBAL_FLAGS0_TYPE_READ;
+		fcp_cmnd_iu->rddata = 1;
+		break;
+	case DMA_TO_DEVICE:
+		ZFCP_LOG_FLAGS(3, "DMA_TO_DEVICE\n");
+		fsf_req->qtcb->bottom.io.data_direction = FSF_DATADIR_WRITE;
+		sbtype = SBAL_FLAGS0_TYPE_WRITE;
+		fcp_cmnd_iu->wddata = 1;
+		break;
+	case DMA_BIDIRECTIONAL:
+		ZFCP_LOG_FLAGS(0, "DMA_BIDIRECTIONAL not supported\n");
+	default:
+		/*
+		 * dummy, catch this condition earlier
+		 * in zfcp_scsi_queuecommand
+		 */
+		goto failed_scsi_cmnd;
+	}
+
+	/* set FC service class in QTCB (3 per default) */
+	fsf_req->qtcb->bottom.io.service_class = adapter->fc_service_class;
+
+	/* set FCP_LUN in FCP_CMND IU in QTCB */
+	fcp_cmnd_iu->fcp_lun = unit->fcp_lun;
+
+	mask = ZFCP_STATUS_UNIT_READONLY | ZFCP_STATUS_UNIT_SHARED;
+
+	/* set task attributes in FCP_CMND IU in QTCB */
+	if (likely((scsi_cmnd->device->simple_tags) ||
+		   (atomic_test_mask(mask, &unit->status))))
+		fcp_cmnd_iu->task_attribute = SIMPLE_Q;
+	else
+		fcp_cmnd_iu->task_attribute = UNTAGGED;
+
+	/* set additional length of FCP_CDB in FCP_CMND IU in QTCB, if needed */
+	if (unlikely(scsi_cmnd->cmd_len > FCP_CDB_LENGTH)) {
+		fcp_cmnd_iu->add_fcp_cdb_length
+		    = (scsi_cmnd->cmd_len - FCP_CDB_LENGTH) >> 2;
+		ZFCP_LOG_TRACE("SCSI CDB length is 0x%x, "
+			       "additional FCP_CDB length is 0x%x "
+			       "(shifted right 2 bits)\n",
+			       scsi_cmnd->cmd_len,
+			       fcp_cmnd_iu->add_fcp_cdb_length);
+	}
+	/*
+	 * copy SCSI CDB (including additional length, if any) to
+	 * FCP_CDB in FCP_CMND IU in QTCB
+	 */
+	memcpy(fcp_cmnd_iu->fcp_cdb, scsi_cmnd->cmnd, scsi_cmnd->cmd_len);
+
+	/* FCP CMND IU length in QTCB */
+	fsf_req->qtcb->bottom.io.fcp_cmnd_length =
+		sizeof (struct fcp_cmnd_iu) +
+		fcp_cmnd_iu->add_fcp_cdb_length + sizeof (fcp_dl_t);
+
+	/* generate SBALEs from data buffer */
+	real_bytes = zfcp_qdio_sbals_from_scsicmnd(fsf_req, sbtype, scsi_cmnd);
+	if (unlikely(real_bytes < 0)) {
+		if (fsf_req->sbal_number < ZFCP_MAX_SBALS_PER_REQ) {
+			ZFCP_LOG_DEBUG(
+				"Data did not fit into available buffer(s), "
+			       "waiting for more...\n");
+		retval = -EIO;
+	} else {
+		ZFCP_LOG_NORMAL("error: No truncation implemented but "
+				"required. Shutting down unit "
+				"(adapter %s, port 0x%016Lx, "
+				"unit 0x%016Lx)\n",
+				zfcp_get_busid_by_unit(unit),
+				unit->port->wwpn,
+				unit->fcp_lun);
+		zfcp_erp_unit_shutdown(unit, 0);
+		retval = -EINVAL;
+		}
+		goto no_fit;
+	}
+
+	/* set length of FCP data length in FCP_CMND IU in QTCB */
+	zfcp_set_fcp_dl(fcp_cmnd_iu, real_bytes);
+
+	ZFCP_LOG_DEBUG("Sending SCSI command:\n");
+	ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
+		      (char *) scsi_cmnd->cmnd, scsi_cmnd->cmd_len);
+
+	/*
+	 * start QDIO request for this FSF request
+	 *  covered by an SBALE)
+	 */
+	retval = zfcp_fsf_req_send(fsf_req, timer);
+	if (unlikely(retval < 0)) {
+		ZFCP_LOG_INFO("error: Could not send FCP command request "
+			      "on adapter %s, port 0x%016Lx, unit 0x%016Lx\n",
+			      zfcp_get_busid_by_adapter(adapter),
+			      unit->port->wwpn,
+			      unit->fcp_lun);
+		goto send_failed;
+	}
+
+	ZFCP_LOG_TRACE("Send FCP Command initiated (adapter %s, "
+		       "port 0x%016Lx, unit 0x%016Lx)\n",
+		       zfcp_get_busid_by_adapter(adapter),
+		       unit->port->wwpn,
+		       unit->fcp_lun);
+	goto success;
+
+ send_failed:
+ no_fit:
+ failed_scsi_cmnd:
+	zfcp_fsf_req_free(fsf_req);
+	fsf_req = NULL;
+	scsi_cmnd->host_scribble = NULL;
+ success:
+ failed_req_create:
+	write_unlock_irqrestore(&adapter->request_queue.queue_lock, lock_flags);
+	return retval;
+}
+
+/*
+ * function:    zfcp_fsf_send_fcp_command_task_management
+ *
+ * purpose:
+ *
+ * returns:
+ *
+ * FIXME(design): should be watched by a timeout!!!
+ * FIXME(design) shouldn't this be modified to return an int
+ *               also...don't know how though
+ *
+ */
+struct zfcp_fsf_req *
+zfcp_fsf_send_fcp_command_task_management(struct zfcp_adapter *adapter,
+					  struct zfcp_unit *unit,
+					  u8 tm_flags, int req_flags)
+{
+	struct zfcp_fsf_req *fsf_req = NULL;
+	int retval = 0;
+	struct fcp_cmnd_iu *fcp_cmnd_iu;
+	unsigned long lock_flags;
+	volatile struct qdio_buffer_element *sbale;
+
+	/* setup new FSF request */
+	retval = zfcp_fsf_req_create(adapter, FSF_QTCB_FCP_CMND, req_flags,
+				     adapter->pool.fsf_req_scsi,
+				     &lock_flags, &fsf_req);
+	if (retval < 0) {
+		ZFCP_LOG_INFO("error: Could not create FCP command (task "
+			      "management) request for adapter %s, port "
+			      " 0x%016Lx, unit 0x%016Lx.\n",
+			      zfcp_get_busid_by_adapter(adapter),
+			      unit->port->wwpn, unit->fcp_lun);
+		goto out;
+	}
+
+	/*
+	 * Used to decide on proper handler in the return path,
+	 * could be either zfcp_fsf_send_fcp_command_task_handler or
+	 * zfcp_fsf_send_fcp_command_task_management_handler */
+
+	fsf_req->status |= ZFCP_STATUS_FSFREQ_TASK_MANAGEMENT;
+
+	/*
+	 * hold a pointer to the unit being target of this
+	 * task management request
+	 */
+	fsf_req->data.send_fcp_command_task_management.unit = unit;
+
+	/* set FSF related fields in QTCB */
+	fsf_req->qtcb->header.lun_handle = unit->handle;
+	fsf_req->qtcb->header.port_handle = unit->port->handle;
+	fsf_req->qtcb->bottom.io.data_direction = FSF_DATADIR_CMND;
+	fsf_req->qtcb->bottom.io.service_class = adapter->fc_service_class;
+	fsf_req->qtcb->bottom.io.fcp_cmnd_length =
+		sizeof (struct fcp_cmnd_iu) + sizeof (fcp_dl_t);
+
+	sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_curr, 0);
+	sbale[0].flags |= SBAL_FLAGS0_TYPE_WRITE;
+	sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY;
+
+	/* set FCP related fields in FCP_CMND IU in QTCB */
+	fcp_cmnd_iu = (struct fcp_cmnd_iu *)
+		&(fsf_req->qtcb->bottom.io.fcp_cmnd);
+	fcp_cmnd_iu->fcp_lun = unit->fcp_lun;
+	fcp_cmnd_iu->task_management_flags = tm_flags;
+
+	/* start QDIO request for this FSF request */
+	zfcp_fsf_start_scsi_er_timer(adapter);
+	retval = zfcp_fsf_req_send(fsf_req, NULL);
+	if (retval) {
+		del_timer(&adapter->scsi_er_timer);
+		ZFCP_LOG_INFO("error: Could not send an FCP-command (task "
+			      "management) on adapter %s, port 0x%016Lx for "
+			      "unit LUN 0x%016Lx\n",
+			      zfcp_get_busid_by_adapter(adapter),
+			      unit->port->wwpn,
+			      unit->fcp_lun);
+		zfcp_fsf_req_free(fsf_req);
+		fsf_req = NULL;
+		goto out;
+	}
+
+	ZFCP_LOG_TRACE("Send FCP Command (task management function) initiated "
+		       "(adapter %s, port 0x%016Lx, unit 0x%016Lx, "
+		       "tm_flags=0x%x)\n",
+		       zfcp_get_busid_by_adapter(adapter),
+		       unit->port->wwpn,
+		       unit->fcp_lun,
+		       tm_flags);
+ out:
+	write_unlock_irqrestore(&adapter->request_queue.queue_lock, lock_flags);
+	return fsf_req;
+}
+
+/*
+ * function:    zfcp_fsf_send_fcp_command_handler
+ *
+ * purpose:	is called for finished Send FCP Command
+ *
+ * returns:	
+ */
+static int
+zfcp_fsf_send_fcp_command_handler(struct zfcp_fsf_req *fsf_req)
+{
+	int retval = -EINVAL;
+	struct zfcp_unit *unit;
+	struct fsf_qtcb_header *header;
+	u16 subtable, rule, counter;
+
+	header = &fsf_req->qtcb->header;
+
+	if (unlikely(fsf_req->status & ZFCP_STATUS_FSFREQ_TASK_MANAGEMENT))
+		unit = fsf_req->data.send_fcp_command_task_management.unit;
+	else
+		unit = fsf_req->data.send_fcp_command_task.unit;
+
+	if (unlikely(fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR)) {
+		/* go directly to calls of special handlers */
+		goto skip_fsfstatus;
+	}
+
+	/* evaluate FSF status in QTCB */
+	switch (header->fsf_status) {
+
+	case FSF_PORT_HANDLE_NOT_VALID:
+		ZFCP_LOG_FLAGS(1, "FSF_PORT_HANDLE_NOT_VALID\n");
+		ZFCP_LOG_INFO("Temporary port identifier 0x%x for port "
+			      "0x%016Lx on adapter %s invalid\n",
+			      unit->port->handle,
+			      unit->port->wwpn, zfcp_get_busid_by_unit(unit));
+		ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
+			      (char *) &header->fsf_status_qual,
+			      sizeof (union fsf_status_qual));
+		debug_text_event(fsf_req->adapter->erp_dbf, 1,
+				 "fsf_s_phand_nv");
+		zfcp_erp_adapter_reopen(unit->port->adapter, 0);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_LUN_HANDLE_NOT_VALID:
+		ZFCP_LOG_FLAGS(1, "FSF_LUN_HANDLE_NOT_VALID\n");
+		ZFCP_LOG_INFO("Temporary LUN identifier 0x%x for unit "
+			      "0x%016Lx on port 0x%016Lx on adapter %s is "
+			      "invalid. This may happen occasionally.\n",
+			      unit->handle,
+			      unit->fcp_lun,
+			      unit->port->wwpn,
+			      zfcp_get_busid_by_unit(unit));
+		ZFCP_LOG_NORMAL("Status qualifier data:\n");
+		ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_NORMAL,
+			      (char *) &header->fsf_status_qual,
+			      sizeof (union fsf_status_qual));
+		debug_text_event(fsf_req->adapter->erp_dbf, 1,
+				 "fsf_s_uhand_nv");
+		zfcp_erp_port_reopen(unit->port, 0);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_HANDLE_MISMATCH:
+		ZFCP_LOG_FLAGS(0, "FSF_HANDLE_MISMATCH\n");
+		ZFCP_LOG_NORMAL("bug: The port handle 0x%x has changed "
+				"unexpectedly. (adapter %s, port 0x%016Lx, "
+				"unit 0x%016Lx)\n",
+				unit->port->handle,
+				zfcp_get_busid_by_unit(unit),
+				unit->port->wwpn,
+				unit->fcp_lun);
+		ZFCP_LOG_NORMAL("status qualifier:\n");
+		ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_NORMAL,
+			      (char *) &header->fsf_status_qual,
+			      sizeof (union fsf_status_qual));
+		debug_text_event(fsf_req->adapter->erp_dbf, 1,
+				 "fsf_s_hand_mis");
+		zfcp_erp_adapter_reopen(unit->port->adapter, 0);
+		zfcp_cmd_dbf_event_fsf("handmism",
+				       fsf_req,
+				       &header->fsf_status_qual,
+				       sizeof (union fsf_status_qual));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_SERVICE_CLASS_NOT_SUPPORTED:
+		ZFCP_LOG_FLAGS(0, "FSF_SERVICE_CLASS_NOT_SUPPORTED\n");
+		if (fsf_req->adapter->fc_service_class <= 3) {
+			ZFCP_LOG_NORMAL("error: The adapter %s does "
+					"not support fibrechannel class %d.\n",
+					zfcp_get_busid_by_unit(unit),
+					fsf_req->adapter->fc_service_class);
+		} else {
+			ZFCP_LOG_NORMAL("bug: The fibrechannel class at "
+					"adapter %s is invalid. "
+					"(debug info %d)\n",
+					zfcp_get_busid_by_unit(unit),
+					fsf_req->adapter->fc_service_class);
+		}
+		/* stop operation for this adapter */
+		debug_text_exception(fsf_req->adapter->erp_dbf, 0,
+				     "fsf_s_class_nsup");
+		zfcp_erp_adapter_shutdown(unit->port->adapter, 0);
+		zfcp_cmd_dbf_event_fsf("unsclass",
+				       fsf_req,
+				       &header->fsf_status_qual,
+				       sizeof (union fsf_status_qual));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_FCPLUN_NOT_VALID:
+		ZFCP_LOG_FLAGS(0, "FSF_FCPLUN_NOT_VALID\n");
+		ZFCP_LOG_NORMAL("bug: unit 0x%016Lx on port 0x%016Lx on "
+				"adapter %s does not have correct unit "
+				"handle 0x%x\n",
+				unit->fcp_lun,
+				unit->port->wwpn,
+				zfcp_get_busid_by_unit(unit),
+				unit->handle);
+		ZFCP_LOG_DEBUG("status qualifier:\n");
+		ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
+			      (char *) &header->fsf_status_qual,
+			      sizeof (union fsf_status_qual));
+		debug_text_event(fsf_req->adapter->erp_dbf, 1,
+				 "fsf_s_fcp_lun_nv");
+		zfcp_erp_port_reopen(unit->port, 0);
+		zfcp_cmd_dbf_event_fsf("fluninv",
+				       fsf_req,
+				       &header->fsf_status_qual,
+				       sizeof (union fsf_status_qual));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_ACCESS_DENIED:
+		ZFCP_LOG_FLAGS(2, "FSF_ACCESS_DENIED\n");
+		ZFCP_LOG_NORMAL("Access denied, cannot send FCP command to "
+				"unit 0x%016Lx on port 0x%016Lx on "
+				"adapter %s\n",	unit->fcp_lun, unit->port->wwpn,
+				zfcp_get_busid_by_unit(unit));
+		for (counter = 0; counter < 2; counter++) {
+			subtable = header->fsf_status_qual.halfword[counter * 2];
+			rule = header->fsf_status_qual.halfword[counter * 2 + 1];
+			switch (subtable) {
+			case FSF_SQ_CFDC_SUBTABLE_OS:
+			case FSF_SQ_CFDC_SUBTABLE_PORT_WWPN:
+			case FSF_SQ_CFDC_SUBTABLE_PORT_DID:
+			case FSF_SQ_CFDC_SUBTABLE_LUN:
+				ZFCP_LOG_INFO("Access denied (%s rule %d)\n",
+					zfcp_act_subtable_type[subtable], rule);
+				break;
+			}
+		}
+		debug_text_event(fsf_req->adapter->erp_dbf, 1, "fsf_s_access");
+		zfcp_erp_unit_access_denied(unit);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_DIRECTION_INDICATOR_NOT_VALID:
+		ZFCP_LOG_FLAGS(0, "FSF_DIRECTION_INDICATOR_NOT_VALID\n");
+		ZFCP_LOG_INFO("bug: Invalid data direction given for unit "
+			      "0x%016Lx on port 0x%016Lx on adapter %s "
+			      "(debug info %d)\n",
+			      unit->fcp_lun,
+			      unit->port->wwpn,
+			      zfcp_get_busid_by_unit(unit),
+			      fsf_req->qtcb->bottom.io.data_direction);
+		/* stop operation for this adapter */
+		debug_text_event(fsf_req->adapter->erp_dbf, 0,
+				 "fsf_s_dir_ind_nv");
+		zfcp_erp_adapter_shutdown(unit->port->adapter, 0);
+		zfcp_cmd_dbf_event_fsf("dirinv",
+				       fsf_req,
+				       &header->fsf_status_qual,
+				       sizeof (union fsf_status_qual));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_CMND_LENGTH_NOT_VALID:
+		ZFCP_LOG_FLAGS(0, "FSF_CMND_LENGTH_NOT_VALID\n");
+		ZFCP_LOG_NORMAL
+		    ("bug: An invalid control-data-block length field "
+		     "was found in a command for unit 0x%016Lx on port "
+		     "0x%016Lx on adapter %s " "(debug info %d)\n",
+		     unit->fcp_lun, unit->port->wwpn,
+		     zfcp_get_busid_by_unit(unit),
+		     fsf_req->qtcb->bottom.io.fcp_cmnd_length);
+		/* stop operation for this adapter */
+		debug_text_event(fsf_req->adapter->erp_dbf, 0,
+				 "fsf_s_cmd_len_nv");
+		zfcp_erp_adapter_shutdown(unit->port->adapter, 0);
+		zfcp_cmd_dbf_event_fsf("cleninv",
+				       fsf_req,
+				       &header->fsf_status_qual,
+				       sizeof (union fsf_status_qual));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		break;
+
+	case FSF_PORT_BOXED:
+		ZFCP_LOG_FLAGS(2, "FSF_PORT_BOXED\n");
+		ZFCP_LOG_DEBUG("The remote port 0x%016Lx on adapter %s "
+			       "needs to be reopened\n",
+			       unit->port->wwpn, zfcp_get_busid_by_unit(unit));
+		debug_text_event(fsf_req->adapter->erp_dbf, 2, "fsf_s_pboxed");
+		zfcp_erp_port_reopen(unit->port, 0);
+		zfcp_cmd_dbf_event_fsf("portbox", fsf_req,
+				       &header->fsf_status_qual,
+				       sizeof (union fsf_status_qual));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR |
+			ZFCP_STATUS_FSFREQ_RETRY;
+		break;
+
+	case FSF_LUN_BOXED:
+		ZFCP_LOG_FLAGS(0, "FSF_LUN_BOXED\n");
+		ZFCP_LOG_NORMAL("unit needs to be reopened (adapter %s, "
+				"wwpn=0x%016Lx, fcp_lun=0x%016Lx)\n",
+				zfcp_get_busid_by_unit(unit),
+				unit->port->wwpn, unit->fcp_lun);
+		debug_text_event(fsf_req->adapter->erp_dbf, 1, "fsf_s_lboxed");
+		zfcp_erp_unit_reopen(unit, 0);
+		zfcp_cmd_dbf_event_fsf("unitbox", fsf_req,
+				       &header->fsf_status_qual,
+				       sizeof(union fsf_status_qual));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR
+			| ZFCP_STATUS_FSFREQ_RETRY;
+		break;
+
+	case FSF_ADAPTER_STATUS_AVAILABLE:
+		ZFCP_LOG_FLAGS(2, "FSF_ADAPTER_STATUS_AVAILABLE\n");
+		switch (header->fsf_status_qual.word[0]) {
+		case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE:
+			ZFCP_LOG_FLAGS(2,
+				       "FSF_SQ_INVOKE_LINK_TEST_PROCEDURE\n");
+			/* re-establish link to port */
+			debug_text_event(fsf_req->adapter->erp_dbf, 1,
+					 "fsf_sq_ltest");
+			zfcp_erp_port_reopen(unit->port, 0);
+			zfcp_cmd_dbf_event_fsf(
+				"sqltest",
+				fsf_req,
+				&header->fsf_status_qual,
+				sizeof (union fsf_status_qual));
+			fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+			break;
+		case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED:
+			ZFCP_LOG_FLAGS(3,
+				       "FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED\n");
+			/* FIXME(hw) need proper specs for proper action */
+			/* let scsi stack deal with retries and escalation */
+			debug_text_event(fsf_req->adapter->erp_dbf, 1,
+					 "fsf_sq_ulp");
+			zfcp_cmd_dbf_event_fsf(
+				"sqdeperp",
+				fsf_req,
+				&header->fsf_status_qual,
+				sizeof (union fsf_status_qual));
+			fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+			break;
+		default:
+			/* FIXME: shall we consider this a successful transfer? */
+			ZFCP_LOG_NORMAL
+			    ("bug: Wrong status qualifier 0x%x arrived.\n",
+			     header->fsf_status_qual.word[0]);
+			debug_text_event(fsf_req->adapter->erp_dbf, 0,
+					 "fsf_sq_inval:");
+			debug_exception(fsf_req->adapter->erp_dbf, 0,
+					&header->fsf_status_qual.word[0],
+					sizeof(u32));
+			break;
+		}
+		break;
+
+	case FSF_GOOD:
+		ZFCP_LOG_FLAGS(3, "FSF_GOOD\n");
+		break;
+
+	case FSF_FCP_RSP_AVAILABLE:
+		ZFCP_LOG_FLAGS(2, "FSF_FCP_RSP_AVAILABLE\n");
+		break;
+
+	default:
+		debug_text_event(fsf_req->adapter->erp_dbf, 0, "fsf_s_inval:");
+		debug_exception(fsf_req->adapter->erp_dbf, 0,
+				&header->fsf_status, sizeof(u32));
+		break;
+	}
+
+ skip_fsfstatus:
+	if (fsf_req->status & ZFCP_STATUS_FSFREQ_TASK_MANAGEMENT) {
+		retval =
+		    zfcp_fsf_send_fcp_command_task_management_handler(fsf_req);
+	} else {
+		retval = zfcp_fsf_send_fcp_command_task_handler(fsf_req);
+	}
+	return retval;
+}
+
+/*
+ * function:    zfcp_fsf_send_fcp_command_task_handler
+ *
+ * purpose:	evaluates FCP_RSP IU
+ *
+ * returns:	
+ */
+static int
+zfcp_fsf_send_fcp_command_task_handler(struct zfcp_fsf_req *fsf_req)
+{
+	int retval = 0;
+	struct scsi_cmnd *scpnt;
+	struct fcp_rsp_iu *fcp_rsp_iu = (struct fcp_rsp_iu *)
+	    &(fsf_req->qtcb->bottom.io.fcp_rsp);
+	struct fcp_cmnd_iu *fcp_cmnd_iu = (struct fcp_cmnd_iu *)
+	    &(fsf_req->qtcb->bottom.io.fcp_cmnd);
+	u32 sns_len;
+	char *fcp_rsp_info = zfcp_get_fcp_rsp_info_ptr(fcp_rsp_iu);
+	unsigned long flags;
+	struct zfcp_unit *unit = fsf_req->data.send_fcp_command_task.unit;
+
+	read_lock_irqsave(&fsf_req->adapter->abort_lock, flags);
+	scpnt = fsf_req->data.send_fcp_command_task.scsi_cmnd;
+	if (unlikely(!scpnt)) {
+		ZFCP_LOG_DEBUG
+		    ("Command with fsf_req %p is not associated to "
+		     "a scsi command anymore. Aborted?\n", fsf_req);
+		goto out;
+	}
+	if (unlikely(fsf_req->status & ZFCP_STATUS_FSFREQ_ABORTED)) {
+		/* FIXME: (design) mid-layer should handle DID_ABORT like
+		 *        DID_SOFT_ERROR by retrying the request for devices
+		 *        that allow retries.
+		 */
+		ZFCP_LOG_DEBUG("Setting DID_SOFT_ERROR and SUGGEST_RETRY\n");
+		set_host_byte(&scpnt->result, DID_SOFT_ERROR);
+		set_driver_byte(&scpnt->result, SUGGEST_RETRY);
+		goto skip_fsfstatus;
+	}
+
+	if (unlikely(fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR)) {
+		ZFCP_LOG_DEBUG("Setting DID_ERROR\n");
+		set_host_byte(&scpnt->result, DID_ERROR);
+		goto skip_fsfstatus;
+	}
+
+	/* set message byte of result in SCSI command */
+	scpnt->result |= COMMAND_COMPLETE << 8;
+
+	/*
+	 * copy SCSI status code of FCP_STATUS of FCP_RSP IU to status byte
+	 * of result in SCSI command
+	 */
+	scpnt->result |= fcp_rsp_iu->scsi_status;
+	if (unlikely(fcp_rsp_iu->scsi_status)) {
+		/* DEBUG */
+		ZFCP_LOG_DEBUG("status for SCSI Command:\n");
+		ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
+			      scpnt->cmnd, scpnt->cmd_len);
+		ZFCP_LOG_DEBUG("SCSI status code 0x%x\n",
+				fcp_rsp_iu->scsi_status);
+		ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
+			      (void *) fcp_rsp_iu, sizeof (struct fcp_rsp_iu));
+		ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
+			      zfcp_get_fcp_sns_info_ptr(fcp_rsp_iu),
+			      fcp_rsp_iu->fcp_sns_len);
+	}
+
+	/* check FCP_RSP_INFO */
+	if (unlikely(fcp_rsp_iu->validity.bits.fcp_rsp_len_valid)) {
+		ZFCP_LOG_DEBUG("rsp_len is valid\n");
+		switch (fcp_rsp_info[3]) {
+		case RSP_CODE_GOOD:
+			ZFCP_LOG_FLAGS(3, "RSP_CODE_GOOD\n");
+			/* ok, continue */
+			ZFCP_LOG_TRACE("no failure or Task Management "
+				       "Function complete\n");
+			set_host_byte(&scpnt->result, DID_OK);
+			break;
+		case RSP_CODE_LENGTH_MISMATCH:
+			ZFCP_LOG_FLAGS(0, "RSP_CODE_LENGTH_MISMATCH\n");
+			/* hardware bug */
+			ZFCP_LOG_NORMAL("bug: FCP response code indictates "
+					"that the fibrechannel protocol data "
+					"length differs from the burst length. "
+					"The problem occured on unit 0x%016Lx "
+					"on port 0x%016Lx on adapter %s",
+					unit->fcp_lun,
+					unit->port->wwpn,
+					zfcp_get_busid_by_unit(unit));
+			/* dump SCSI CDB as prepared by zfcp */
+			ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
+				      (char *) &fsf_req->qtcb->
+				      bottom.io.fcp_cmnd, FSF_FCP_CMND_SIZE);
+			zfcp_cmd_dbf_event_fsf("clenmis", fsf_req, NULL, 0);
+			set_host_byte(&scpnt->result, DID_ERROR);
+			goto skip_fsfstatus;
+		case RSP_CODE_FIELD_INVALID:
+			ZFCP_LOG_FLAGS(0, "RSP_CODE_FIELD_INVALID\n");
+			/* driver or hardware bug */
+			ZFCP_LOG_NORMAL("bug: FCP response code indictates "
+					"that the fibrechannel protocol data "
+					"fields were incorrectly set up. "
+					"The problem occured on the unit "
+					"0x%016Lx on port 0x%016Lx on "
+					"adapter %s",
+					unit->fcp_lun,
+					unit->port->wwpn,
+					zfcp_get_busid_by_unit(unit));
+			/* dump SCSI CDB as prepared by zfcp */
+			ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
+				      (char *) &fsf_req->qtcb->
+				      bottom.io.fcp_cmnd, FSF_FCP_CMND_SIZE);
+			set_host_byte(&scpnt->result, DID_ERROR);
+			zfcp_cmd_dbf_event_fsf("codeinv", fsf_req, NULL, 0);
+			goto skip_fsfstatus;
+		case RSP_CODE_RO_MISMATCH:
+			ZFCP_LOG_FLAGS(0, "RSP_CODE_RO_MISMATCH\n");
+			/* hardware bug */
+			ZFCP_LOG_NORMAL("bug: The FCP response code indicates "
+					"that conflicting  values for the "
+					"fibrechannel payload offset from the "
+					"header were found. "
+					"The problem occured on unit 0x%016Lx "
+					"on port 0x%016Lx on adapter %s.\n",
+					unit->fcp_lun,
+					unit->port->wwpn,
+					zfcp_get_busid_by_unit(unit));
+			/* dump SCSI CDB as prepared by zfcp */
+			ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
+				      (char *) &fsf_req->qtcb->
+				      bottom.io.fcp_cmnd, FSF_FCP_CMND_SIZE);
+			zfcp_cmd_dbf_event_fsf("codemism", fsf_req, NULL, 0);
+			set_host_byte(&scpnt->result, DID_ERROR);
+			goto skip_fsfstatus;
+		default:
+			ZFCP_LOG_NORMAL("bug: An invalid FCP response "
+					"code was detected for a command. "
+					"The problem occured on the unit "
+					"0x%016Lx on port 0x%016Lx on "
+					"adapter %s (debug info 0x%x)\n",
+					unit->fcp_lun,
+					unit->port->wwpn,
+					zfcp_get_busid_by_unit(unit),
+					fcp_rsp_info[3]);
+			/* dump SCSI CDB as prepared by zfcp */
+			ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
+				      (char *) &fsf_req->qtcb->
+				      bottom.io.fcp_cmnd, FSF_FCP_CMND_SIZE);
+			zfcp_cmd_dbf_event_fsf("undeffcp", fsf_req, NULL, 0);
+			set_host_byte(&scpnt->result, DID_ERROR);
+		}
+	}
+
+	/* check for sense data */
+	if (unlikely(fcp_rsp_iu->validity.bits.fcp_sns_len_valid)) {
+		sns_len = FSF_FCP_RSP_SIZE -
+		    sizeof (struct fcp_rsp_iu) + fcp_rsp_iu->fcp_rsp_len;
+		ZFCP_LOG_TRACE("room for %i bytes sense data in QTCB\n",
+			       sns_len);
+		sns_len = min(sns_len, (u32) SCSI_SENSE_BUFFERSIZE);
+		ZFCP_LOG_TRACE("room for %i bytes sense data in SCSI command\n",
+			       SCSI_SENSE_BUFFERSIZE);
+		sns_len = min(sns_len, fcp_rsp_iu->fcp_sns_len);
+		ZFCP_LOG_TRACE("scpnt->result =0x%x, command was:\n",
+			       scpnt->result);
+		ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_TRACE,
+			      (void *) &scpnt->cmnd, scpnt->cmd_len);
+
+		ZFCP_LOG_TRACE("%i bytes sense data provided by FCP\n",
+			       fcp_rsp_iu->fcp_sns_len);
+		memcpy(&scpnt->sense_buffer,
+		       zfcp_get_fcp_sns_info_ptr(fcp_rsp_iu), sns_len);
+		ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_TRACE,
+			      (void *) &scpnt->sense_buffer, sns_len);
+	}
+
+	/* check for overrun */
+	if (unlikely(fcp_rsp_iu->validity.bits.fcp_resid_over)) {
+		ZFCP_LOG_INFO("A data overrun was detected for a command. "
+			      "unit 0x%016Lx, port 0x%016Lx, adapter %s. "
+			      "The response data length is "
+			      "%d, the original length was %d.\n",
+			      unit->fcp_lun,
+			      unit->port->wwpn,
+			      zfcp_get_busid_by_unit(unit),
+			      fcp_rsp_iu->fcp_resid,
+			      (int) zfcp_get_fcp_dl(fcp_cmnd_iu));
+	}
+
+	/* check for underrun */
+	if (unlikely(fcp_rsp_iu->validity.bits.fcp_resid_under)) {
+		ZFCP_LOG_INFO("A data underrun was detected for a command. "
+			      "unit 0x%016Lx, port 0x%016Lx, adapter %s. "
+			      "The response data length is "
+			      "%d, the original length was %d.\n",
+			      unit->fcp_lun,
+			      unit->port->wwpn,
+			      zfcp_get_busid_by_unit(unit),
+			      fcp_rsp_iu->fcp_resid,
+			      (int) zfcp_get_fcp_dl(fcp_cmnd_iu));
+
+		scpnt->resid = fcp_rsp_iu->fcp_resid;
+		if (scpnt->request_bufflen - scpnt->resid < scpnt->underflow)
+			scpnt->result |= DID_ERROR << 16;
+	}
+
+ skip_fsfstatus:
+	ZFCP_LOG_DEBUG("scpnt->result =0x%x\n", scpnt->result);
+
+	zfcp_cmd_dbf_event_scsi("response", scpnt);
+
+	/* cleanup pointer (need this especially for abort) */
+	scpnt->host_scribble = NULL;
+
+	/*
+	 * NOTE:
+	 * according to the outcome of a discussion on linux-scsi we
+	 * don't need to grab the io_request_lock here since we use
+	 * the new eh
+	 */
+	/* always call back */
+
+	(scpnt->scsi_done) (scpnt);
+
+	/*
+	 * We must hold this lock until scsi_done has been called.
+	 * Otherwise we may call scsi_done after abort regarding this
+	 * command has completed.
+	 * Note: scsi_done must not block!
+	 */
+ out:
+	read_unlock_irqrestore(&fsf_req->adapter->abort_lock, flags);
+	return retval;
+}
+
+/*
+ * function:    zfcp_fsf_send_fcp_command_task_management_handler
+ *
+ * purpose:	evaluates FCP_RSP IU
+ *
+ * returns:	
+ */
+static int
+zfcp_fsf_send_fcp_command_task_management_handler(struct zfcp_fsf_req *fsf_req)
+{
+	int retval = 0;
+	struct fcp_rsp_iu *fcp_rsp_iu = (struct fcp_rsp_iu *)
+	    &(fsf_req->qtcb->bottom.io.fcp_rsp);
+	char *fcp_rsp_info = zfcp_get_fcp_rsp_info_ptr(fcp_rsp_iu);
+	struct zfcp_unit *unit =
+	    fsf_req->data.send_fcp_command_task_management.unit;
+
+	del_timer(&fsf_req->adapter->scsi_er_timer);
+	if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) {
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_TMFUNCFAILED;
+		goto skip_fsfstatus;
+	}
+
+	/* check FCP_RSP_INFO */
+	switch (fcp_rsp_info[3]) {
+	case RSP_CODE_GOOD:
+		ZFCP_LOG_FLAGS(3, "RSP_CODE_GOOD\n");
+		/* ok, continue */
+		ZFCP_LOG_DEBUG("no failure or Task Management "
+			       "Function complete\n");
+		break;
+	case RSP_CODE_TASKMAN_UNSUPP:
+		ZFCP_LOG_FLAGS(0, "RSP_CODE_TASKMAN_UNSUPP\n");
+		ZFCP_LOG_NORMAL("bug: A reuested task management function "
+				"is not supported on the target device "
+				"unit 0x%016Lx, port 0x%016Lx, adapter %s\n ",
+				unit->fcp_lun,
+				unit->port->wwpn,
+				zfcp_get_busid_by_unit(unit));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_TMFUNCNOTSUPP;
+		break;
+	case RSP_CODE_TASKMAN_FAILED:
+		ZFCP_LOG_FLAGS(0, "RSP_CODE_TASKMAN_FAILED\n");
+		ZFCP_LOG_NORMAL("bug: A reuested task management function "
+				"failed to complete successfully. "
+				"unit 0x%016Lx, port 0x%016Lx, adapter %s.\n",
+				unit->fcp_lun,
+				unit->port->wwpn,
+				zfcp_get_busid_by_unit(unit));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_TMFUNCFAILED;
+		break;
+	default:
+		ZFCP_LOG_NORMAL("bug: An invalid FCP response "
+				"code was detected for a command. "
+				"unit 0x%016Lx, port 0x%016Lx, adapter %s "
+				"(debug info 0x%x)\n",
+				unit->fcp_lun,
+				unit->port->wwpn,
+				zfcp_get_busid_by_unit(unit),
+				fcp_rsp_info[3]);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_TMFUNCFAILED;
+	}
+
+      skip_fsfstatus:
+	return retval;
+}
+
+
+/*
+ * function:    zfcp_fsf_control_file
+ *
+ * purpose:     Initiator of the control file upload/download FSF requests
+ *
+ * returns:     0           - FSF request is successfuly created and queued
+ *              -EOPNOTSUPP - The FCP adapter does not have Control File support
+ *              -EINVAL     - Invalid direction specified
+ *              -ENOMEM     - Insufficient memory
+ *              -EPERM      - Cannot create FSF request or place it in QDIO queue
+ */
+int
+zfcp_fsf_control_file(struct zfcp_adapter *adapter,
+                      struct zfcp_fsf_req **fsf_req_ptr,
+                      u32 fsf_command,
+                      u32 option,
+                      struct zfcp_sg_list *sg_list)
+{
+	struct zfcp_fsf_req *fsf_req;
+	struct fsf_qtcb_bottom_support *bottom;
+	volatile struct qdio_buffer_element *sbale;
+	struct timer_list *timer;
+	unsigned long lock_flags;
+	int req_flags = 0;
+	int direction;
+	int retval = 0;
+
+	if (!(adapter->supported_features & FSF_FEATURE_CFDC)) {
+		ZFCP_LOG_INFO("cfdc not supported (adapter %s)\n",
+			      zfcp_get_busid_by_adapter(adapter));
+		retval = -EOPNOTSUPP;
+		goto out;
+	}
+
+	switch (fsf_command) {
+
+	case FSF_QTCB_DOWNLOAD_CONTROL_FILE:
+		direction = SBAL_FLAGS0_TYPE_WRITE;
+		if ((option != FSF_CFDC_OPTION_FULL_ACCESS) &&
+		    (option != FSF_CFDC_OPTION_RESTRICTED_ACCESS))
+			req_flags = ZFCP_WAIT_FOR_SBAL;
+		break;
+
+	case FSF_QTCB_UPLOAD_CONTROL_FILE:
+		direction = SBAL_FLAGS0_TYPE_READ;
+		break;
+
+	default:
+		ZFCP_LOG_INFO("Invalid FSF command code 0x%08x\n", fsf_command);
+		retval = -EINVAL;
+		goto out;
+	}
+
+	timer = kmalloc(sizeof(struct timer_list), GFP_KERNEL);
+	if (!timer) {
+		retval = -ENOMEM;
+		goto out;
+ 	}
+
+	retval = zfcp_fsf_req_create(adapter, fsf_command, req_flags,
+				     NULL, &lock_flags, &fsf_req);
+	if (retval < 0) {
+		ZFCP_LOG_INFO("error: Could not create FSF request for the "
+			      "adapter %s\n",
+			zfcp_get_busid_by_adapter(adapter));
+		retval = -EPERM;
+		goto unlock_queue_lock;
+	}
+
+	sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_curr, 0);
+	sbale[0].flags |= direction;
+
+	bottom = &fsf_req->qtcb->bottom.support;
+	bottom->operation_subtype = FSF_CFDC_OPERATION_SUBTYPE;
+	bottom->option = option;
+
+	if (sg_list->count > 0) {
+		int bytes;
+
+		bytes = zfcp_qdio_sbals_from_sg(fsf_req, direction,
+						sg_list->sg, sg_list->count,
+						ZFCP_MAX_SBALS_PER_REQ);
+                if (bytes != ZFCP_CFDC_MAX_CONTROL_FILE_SIZE) {
+			ZFCP_LOG_INFO(
+				"error: Could not create sufficient number of "
+				"SBALS for an FSF request to the adapter %s\n",
+				zfcp_get_busid_by_adapter(adapter));
+			retval = -ENOMEM;
+			goto free_fsf_req;
+		}
+	} else
+		sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY;
+
+	init_timer(timer);
+	timer->function = zfcp_fsf_request_timeout_handler;
+	timer->data = (unsigned long) adapter;
+	timer->expires = ZFCP_FSF_REQUEST_TIMEOUT;
+
+	retval = zfcp_fsf_req_send(fsf_req, timer);
+	if (retval < 0) {
+		ZFCP_LOG_INFO("initiation of cfdc up/download failed"
+			      "(adapter %s)\n",
+			      zfcp_get_busid_by_adapter(adapter));
+		retval = -EPERM;
+		goto free_fsf_req;
+	}
+	write_unlock_irqrestore(&adapter->request_queue.queue_lock, lock_flags);
+
+	ZFCP_LOG_NORMAL("Control file %s FSF request has been sent to the "
+			"adapter %s\n",
+			fsf_command == FSF_QTCB_DOWNLOAD_CONTROL_FILE ?
+			"download" : "upload",
+			zfcp_get_busid_by_adapter(adapter));
+
+	wait_event(fsf_req->completion_wq,
+	           fsf_req->status & ZFCP_STATUS_FSFREQ_COMPLETED);
+
+	*fsf_req_ptr = fsf_req;
+	del_timer_sync(timer);
+	goto free_timer;
+
+ free_fsf_req:
+	zfcp_fsf_req_free(fsf_req);
+ unlock_queue_lock:
+	write_unlock_irqrestore(&adapter->request_queue.queue_lock, lock_flags);
+ free_timer:
+	kfree(timer);
+ out:
+	return retval;
+}
+
+
+/*
+ * function:    zfcp_fsf_control_file_handler
+ *
+ * purpose:     Handler of the control file upload/download FSF requests
+ *
+ * returns:     0       - FSF request successfuly processed
+ *              -EAGAIN - Operation has to be repeated because of a temporary problem
+ *              -EACCES - There is no permission to execute an operation
+ *              -EPERM  - The control file is not in a right format
+ *              -EIO    - There is a problem with the FCP adapter
+ *              -EINVAL - Invalid operation
+ *              -EFAULT - User space memory I/O operation fault
+ */
+static int
+zfcp_fsf_control_file_handler(struct zfcp_fsf_req *fsf_req)
+{
+	struct zfcp_adapter *adapter = fsf_req->adapter;
+	struct fsf_qtcb_header *header = &fsf_req->qtcb->header;
+	struct fsf_qtcb_bottom_support *bottom = &fsf_req->qtcb->bottom.support;
+	int retval = 0;
+
+	if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) {
+		retval = -EINVAL;
+		goto skip_fsfstatus;
+	}
+
+	switch (header->fsf_status) {
+
+	case FSF_GOOD:
+		ZFCP_LOG_FLAGS(2, "FSF_GOOD\n");
+		ZFCP_LOG_NORMAL(
+			"The FSF request has been successfully completed "
+			"on the adapter %s\n",
+			zfcp_get_busid_by_adapter(adapter));
+		break;
+
+	case FSF_OPERATION_PARTIALLY_SUCCESSFUL:
+		ZFCP_LOG_FLAGS(2, "FSF_OPERATION_PARTIALLY_SUCCESSFUL\n");
+		if (bottom->operation_subtype == FSF_CFDC_OPERATION_SUBTYPE) {
+			switch (header->fsf_status_qual.word[0]) {
+
+			case FSF_SQ_CFDC_COULD_NOT_HARDEN_ON_SE:
+				ZFCP_LOG_NORMAL(
+					"CFDC of the adapter %s could not "
+					"be saved on the SE\n",
+					zfcp_get_busid_by_adapter(adapter));
+				break;
+
+			case FSF_SQ_CFDC_COULD_NOT_HARDEN_ON_SE2:
+				ZFCP_LOG_NORMAL(
+					"CFDC of the adapter %s could not "
+					"be copied to the secondary SE\n",
+					zfcp_get_busid_by_adapter(adapter));
+				break;
+
+			default:
+				ZFCP_LOG_NORMAL(
+					"CFDC could not be hardened "
+					"on the adapter %s\n",
+					zfcp_get_busid_by_adapter(adapter));
+			}
+		}
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		retval = -EAGAIN;
+		break;
+
+	case FSF_AUTHORIZATION_FAILURE:
+		ZFCP_LOG_FLAGS(2, "FSF_AUTHORIZATION_FAILURE\n");
+		ZFCP_LOG_NORMAL(
+			"Adapter %s does not accept privileged commands\n",
+			zfcp_get_busid_by_adapter(adapter));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		retval = -EACCES;
+		break;
+
+	case FSF_CFDC_ERROR_DETECTED:
+		ZFCP_LOG_FLAGS(2, "FSF_CFDC_ERROR_DETECTED\n");
+		ZFCP_LOG_NORMAL(
+			"Error at position %d in the CFDC, "
+			"CFDC is discarded by the adapter %s\n",
+			header->fsf_status_qual.word[0],
+			zfcp_get_busid_by_adapter(adapter));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		retval = -EPERM;
+		break;
+
+	case FSF_CONTROL_FILE_UPDATE_ERROR:
+		ZFCP_LOG_FLAGS(2, "FSF_CONTROL_FILE_UPDATE_ERROR\n");
+		ZFCP_LOG_NORMAL(
+			"Adapter %s cannot harden the control file, "
+			"file is discarded\n",
+			zfcp_get_busid_by_adapter(adapter));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		retval = -EIO;
+		break;
+
+	case FSF_CONTROL_FILE_TOO_LARGE:
+		ZFCP_LOG_FLAGS(2, "FSF_CONTROL_FILE_TOO_LARGE\n");
+		ZFCP_LOG_NORMAL(
+			"Control file is too large, file is discarded "
+			"by the adapter %s\n",
+			zfcp_get_busid_by_adapter(adapter));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		retval = -EIO;
+		break;
+
+	case FSF_ACCESS_CONFLICT_DETECTED:
+		ZFCP_LOG_FLAGS(2, "FSF_ACCESS_CONFLICT_DETECTED\n");
+		if (bottom->operation_subtype == FSF_CFDC_OPERATION_SUBTYPE)
+			ZFCP_LOG_NORMAL(
+				"CFDC has been discarded by the adapter %s, "
+				"because activation would impact "
+				"%d active connection(s)\n",
+				zfcp_get_busid_by_adapter(adapter),
+				header->fsf_status_qual.word[0]);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		retval = -EIO;
+		break;
+
+	case FSF_CONFLICTS_OVERRULED:
+		ZFCP_LOG_FLAGS(2, "FSF_CONFLICTS_OVERRULED\n");
+		if (bottom->operation_subtype == FSF_CFDC_OPERATION_SUBTYPE)
+			ZFCP_LOG_NORMAL(
+				"CFDC has been activated on the adapter %s, "
+				"but activation has impacted "
+				"%d active connection(s)\n",
+				zfcp_get_busid_by_adapter(adapter),
+				header->fsf_status_qual.word[0]);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		retval = -EIO;
+		break;
+
+	case FSF_UNKNOWN_OP_SUBTYPE:
+		ZFCP_LOG_FLAGS(2, "FSF_UNKNOWN_OP_SUBTYPE\n");
+		ZFCP_LOG_NORMAL("unknown operation subtype (adapter: %s, "
+				"op_subtype=0x%x)\n",
+				zfcp_get_busid_by_adapter(adapter),
+				bottom->operation_subtype);
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		retval = -EINVAL;
+		break;
+
+	case FSF_INVALID_COMMAND_OPTION:
+		ZFCP_LOG_FLAGS(2, "FSF_INVALID_COMMAND_OPTION\n");
+		ZFCP_LOG_NORMAL(
+			"Invalid option 0x%x has been specified "
+			"in QTCB bottom sent to the adapter %s\n",
+			bottom->option,
+			zfcp_get_busid_by_adapter(adapter));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		retval = -EINVAL;
+		break;
+
+	default:
+		ZFCP_LOG_NORMAL(
+			"bug: An unknown/unexpected FSF status 0x%08x "
+			"was presented on the adapter %s\n",
+			header->fsf_status,
+			zfcp_get_busid_by_adapter(adapter));
+		debug_text_event(fsf_req->adapter->erp_dbf, 0, "fsf_sq_inval");
+		debug_exception(fsf_req->adapter->erp_dbf, 0,
+			&header->fsf_status_qual.word[0], sizeof(u32));
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
+		retval = -EINVAL;
+		break;
+	}
+
+skip_fsfstatus:
+	return retval;
+}
+
+
+/*
+ * function:    zfcp_fsf_req_wait_and_cleanup
+ *
+ * purpose:
+ *
+ * FIXME(design): signal seems to be <0 !!!
+ * returns:	0	- request completed (*status is valid), cleanup succ.
+ *		<0	- request completed (*status is valid), cleanup failed
+ *		>0	- signal which interrupted waiting (*status invalid),
+ *			  request not completed, no cleanup
+ *
+ *		*status is a copy of status of completed fsf_req
+ */
+int
+zfcp_fsf_req_wait_and_cleanup(struct zfcp_fsf_req *fsf_req,
+			      int interruptible, u32 * status)
+{
+	int retval = 0;
+	int signal = 0;
+
+	if (interruptible) {
+		__wait_event_interruptible(fsf_req->completion_wq,
+					   fsf_req->status &
+					   ZFCP_STATUS_FSFREQ_COMPLETED,
+					   signal);
+		if (signal) {
+			ZFCP_LOG_DEBUG("Caught signal %i while waiting for the "
+				       "completion of the request at %p\n",
+				       signal, fsf_req);
+			retval = signal;
+			goto out;
+		}
+	} else {
+		__wait_event(fsf_req->completion_wq,
+			     fsf_req->status & ZFCP_STATUS_FSFREQ_COMPLETED);
+	}
+
+	*status = fsf_req->status;
+
+	/* cleanup request */
+	zfcp_fsf_req_cleanup(fsf_req);
+ out:
+	return retval;
+}
+
+static inline int
+zfcp_fsf_req_sbal_check(unsigned long *flags,
+			struct zfcp_qdio_queue *queue, int needed)
+{
+	write_lock_irqsave(&queue->queue_lock, *flags);
+	if (likely(atomic_read(&queue->free_count) >= needed))
+		return 1;
+	write_unlock_irqrestore(&queue->queue_lock, *flags);
+	return 0;
+}
+
+/*
+ * set qtcb pointer in fsf_req and initialize QTCB
+ */
+static inline void
+zfcp_fsf_req_qtcb_init(struct zfcp_fsf_req *fsf_req, u32 fsf_cmd)
+{
+	if (likely(fsf_req->qtcb != NULL)) {
+		fsf_req->qtcb->prefix.req_id = (unsigned long)fsf_req;
+		fsf_req->qtcb->prefix.ulp_info = ZFCP_ULP_INFO_VERSION;
+		fsf_req->qtcb->prefix.qtcb_type = fsf_qtcb_type[fsf_cmd];
+		fsf_req->qtcb->prefix.qtcb_version = ZFCP_QTCB_VERSION;
+		fsf_req->qtcb->header.req_handle = (unsigned long)fsf_req;
+		fsf_req->qtcb->header.fsf_command = fsf_cmd;
+	}
+}
+
+/**
+ * zfcp_fsf_req_sbal_get - try to get one SBAL in the request queue
+ * @adapter: adapter for which request queue is examined
+ * @req_flags: flags indicating whether to wait for needed SBAL or not
+ * @lock_flags: lock_flags if queue_lock is taken
+ * Return: 0 on success, otherwise -EIO, or -ERESTARTSYS
+ * Locks: lock adapter->request_queue->queue_lock on success
+ */
+static int
+zfcp_fsf_req_sbal_get(struct zfcp_adapter *adapter, int req_flags,
+		      unsigned long *lock_flags)
+{
+        long ret;
+        struct zfcp_qdio_queue *req_queue = &adapter->request_queue;
+
+        if (unlikely(req_flags & ZFCP_WAIT_FOR_SBAL)) {
+                ret = wait_event_interruptible_timeout(adapter->request_wq,
+			zfcp_fsf_req_sbal_check(lock_flags, req_queue, 1),
+						       ZFCP_SBAL_TIMEOUT);
+		if (ret < 0)
+			return ret;
+		if (!ret)
+			return -EIO;
+        } else if (!zfcp_fsf_req_sbal_check(lock_flags, req_queue, 1))
+                return -EIO;
+
+        return 0;
+}
+
+/*
+ * function:    zfcp_fsf_req_create
+ *
+ * purpose:	create an FSF request at the specified adapter and
+ *		setup common fields
+ *
+ * returns:	-ENOMEM if there was insufficient memory for a request
+ *              -EIO if no qdio buffers could be allocate to the request
+ *              -EINVAL/-EPERM on bug conditions in req_dequeue
+ *              0 in success
+ *
+ * note:        The created request is returned by reference.
+ *
+ * locks:	lock of concerned request queue must not be held,
+ *		but is held on completion (write, irqsave)
+ */
+int
+zfcp_fsf_req_create(struct zfcp_adapter *adapter, u32 fsf_cmd, int req_flags,
+		    mempool_t *pool, unsigned long *lock_flags,
+		    struct zfcp_fsf_req **fsf_req_p)
+{
+	volatile struct qdio_buffer_element *sbale;
+	struct zfcp_fsf_req *fsf_req = NULL;
+	int ret = 0;
+	struct zfcp_qdio_queue *req_queue = &adapter->request_queue;
+
+	/* allocate new FSF request */
+	fsf_req = zfcp_fsf_req_alloc(pool, req_flags);
+	if (unlikely(NULL == fsf_req)) {
+		ZFCP_LOG_DEBUG("error: Could not put an FSF request into"
+			       "the outbound (send) queue.\n");
+		ret = -ENOMEM;
+		goto failed_fsf_req;
+	}
+
+        zfcp_fsf_req_qtcb_init(fsf_req, fsf_cmd);
+
+	/* initialize waitqueue which may be used to wait on 
+	   this request completion */
+	init_waitqueue_head(&fsf_req->completion_wq);
+
+        ret = zfcp_fsf_req_sbal_get(adapter, req_flags, lock_flags);
+        if(ret < 0) {
+                goto failed_sbals;
+	}
+
+	/*
+	 * We hold queue_lock here. Check if QDIOUP is set and let request fail
+	 * if it is not set (see also *_open_qdio and *_close_qdio).
+	 */
+
+	if (!atomic_test_mask(ZFCP_STATUS_ADAPTER_QDIOUP, &adapter->status)) {
+		write_unlock_irqrestore(&req_queue->queue_lock, *lock_flags);
+		ret = -EIO;
+		goto failed_sbals;
+	}
+
+	fsf_req->adapter = adapter;	/* pointer to "parent" adapter */
+	fsf_req->fsf_command = fsf_cmd;
+	fsf_req->sbal_number = 1;
+	fsf_req->sbal_first = req_queue->free_index;
+	fsf_req->sbal_curr = req_queue->free_index;
+        fsf_req->sbale_curr = 1;
+
+	if (likely(req_flags & ZFCP_REQ_AUTO_CLEANUP)) {
+		fsf_req->status |= ZFCP_STATUS_FSFREQ_CLEANUP;
+	}
+
+	sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_curr, 0);
+
+	/* setup common SBALE fields */
+	sbale[0].addr = fsf_req;
+	sbale[0].flags |= SBAL_FLAGS0_COMMAND;
+	if (likely(fsf_req->qtcb != NULL)) {
+		sbale[1].addr = (void *) fsf_req->qtcb;
+		sbale[1].length = sizeof(struct fsf_qtcb);
+	}
+
+	ZFCP_LOG_TRACE("got %i free BUFFERs starting at index %i\n",
+                       fsf_req->sbal_number, fsf_req->sbal_first);
+
+	goto success;
+
+ failed_sbals:
+/* dequeue new FSF request previously enqueued */
+	zfcp_fsf_req_free(fsf_req);
+	fsf_req = NULL;
+
+ failed_fsf_req:
+	write_lock_irqsave(&req_queue->queue_lock, *lock_flags);
+ success:
+	*fsf_req_p = fsf_req;
+	return ret;
+}
+
+/*
+ * function:    zfcp_fsf_req_send
+ *
+ * purpose:	start transfer of FSF request via QDIO
+ *
+ * returns:	0 - request transfer succesfully started
+ *		!0 - start of request transfer failed
+ */
+static int
+zfcp_fsf_req_send(struct zfcp_fsf_req *fsf_req, struct timer_list *timer)
+{
+	struct zfcp_adapter *adapter;
+	struct zfcp_qdio_queue *req_queue;
+	volatile struct qdio_buffer_element *sbale;
+	int new_distance_from_int;
+	unsigned long flags;
+	int inc_seq_no = 1;
+	int retval = 0;
+
+	adapter = fsf_req->adapter;
+	req_queue = &adapter->request_queue,
+
+
+	/* FIXME(debug): remove it later */
+	sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_first, 0);
+	ZFCP_LOG_DEBUG("SBALE0 flags=0x%x\n", sbale[0].flags);
+	ZFCP_LOG_TRACE("HEX DUMP OF SBALE1 PAYLOAD:\n");
+	ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_TRACE, (char *) sbale[1].addr,
+		      sbale[1].length);
+
+	/* set sequence counter in QTCB */
+	if (likely(fsf_req->qtcb)) {
+		fsf_req->qtcb->prefix.req_seq_no = adapter->fsf_req_seq_no;
+		fsf_req->seq_no = adapter->fsf_req_seq_no;
+		ZFCP_LOG_TRACE("FSF request %p of adapter %s gets "
+			       "FSF sequence counter value of %i\n",
+			       fsf_req,
+			       zfcp_get_busid_by_adapter(adapter),
+			       fsf_req->qtcb->prefix.req_seq_no);
+	} else
+		inc_seq_no = 0;
+
+	/* put allocated FSF request at list tail */
+	write_lock_irqsave(&adapter->fsf_req_list_lock, flags);
+	list_add_tail(&fsf_req->list, &adapter->fsf_req_list_head);
+	write_unlock_irqrestore(&adapter->fsf_req_list_lock, flags);
+
+	/* figure out expiration time of timeout and start timeout */
+	if (unlikely(timer)) {
+		timer->expires += jiffies;
+		add_timer(timer);
+	}
+
+	ZFCP_LOG_TRACE("request queue of adapter %s: "
+		       "next free SBAL is %i, %i free SBALs\n",
+		       zfcp_get_busid_by_adapter(adapter),
+		       req_queue->free_index,
+		       atomic_read(&req_queue->free_count));
+
+	ZFCP_LOG_DEBUG("calling do_QDIO adapter %s, flags=0x%x, queue_no=%i, "
+		       "index_in_queue=%i, count=%i, buffers=%p\n",
+		       zfcp_get_busid_by_adapter(adapter),
+		       QDIO_FLAG_SYNC_OUTPUT,
+		       0, fsf_req->sbal_first, fsf_req->sbal_number,
+		       &req_queue->buffer[fsf_req->sbal_first]);
+
+	/*
+	 * adjust the number of free SBALs in request queue as well as
+	 * position of first one
+	 */
+	atomic_sub(fsf_req->sbal_number, &req_queue->free_count);
+	ZFCP_LOG_TRACE("free_count=%d\n", atomic_read(&req_queue->free_count));
+	req_queue->free_index += fsf_req->sbal_number;	  /* increase */
+	req_queue->free_index %= QDIO_MAX_BUFFERS_PER_Q;  /* wrap if needed */
+	new_distance_from_int = zfcp_qdio_determine_pci(req_queue, fsf_req);
+
+	retval = do_QDIO(adapter->ccw_device,
+			 QDIO_FLAG_SYNC_OUTPUT,
+			 0, fsf_req->sbal_first, fsf_req->sbal_number, NULL);
+
+	if (unlikely(retval)) {
+		/* Queues are down..... */
+		retval = -EIO;
+		/*
+		 * FIXME(potential race):
+		 * timer might be expired (absolutely unlikely)
+		 */
+		if (timer)
+			del_timer(timer);
+		write_lock_irqsave(&adapter->fsf_req_list_lock, flags);
+		list_del(&fsf_req->list);
+		write_unlock_irqrestore(&adapter->fsf_req_list_lock, flags);
+		/*
+		 * adjust the number of free SBALs in request queue as well as
+		 * position of first one
+		 */
+		zfcp_qdio_zero_sbals(req_queue->buffer,
+				     fsf_req->sbal_first, fsf_req->sbal_number);
+		atomic_add(fsf_req->sbal_number, &req_queue->free_count);
+		req_queue->free_index -= fsf_req->sbal_number;	 /* increase */
+		req_queue->free_index += QDIO_MAX_BUFFERS_PER_Q;
+		req_queue->free_index %= QDIO_MAX_BUFFERS_PER_Q; /* wrap */
+		ZFCP_LOG_DEBUG
+			("error: do_QDIO failed. Buffers could not be enqueued "
+			 "to request queue.\n");
+	} else {
+		req_queue->distance_from_int = new_distance_from_int;
+		/*
+		 * increase FSF sequence counter -
+		 * this must only be done for request successfully enqueued to
+		 * QDIO this rejected requests may be cleaned up by calling
+		 * routines  resulting in missing sequence counter values
+		 * otherwise,
+		 */
+		/* Don't increase for unsolicited status */
+		if (likely(inc_seq_no)) {
+			adapter->fsf_req_seq_no++;
+			ZFCP_LOG_TRACE
+			    ("FSF sequence counter value of adapter %s "
+			     "increased to %i\n",
+			     zfcp_get_busid_by_adapter(adapter),
+			     adapter->fsf_req_seq_no);
+		}
+		/* count FSF requests pending */
+		atomic_inc(&adapter->fsf_reqs_active);
+	}
+	return retval;
+}
+
+/*
+ * function:    zfcp_fsf_req_cleanup
+ *
+ * purpose:	cleans up an FSF request and removes it from the specified list
+ *
+ * returns:
+ *
+ * assumption:	no pending SB in SBALEs other than QTCB
+ */
+void
+zfcp_fsf_req_cleanup(struct zfcp_fsf_req *fsf_req)
+{
+	struct zfcp_adapter *adapter = fsf_req->adapter;
+	unsigned long flags;
+
+	write_lock_irqsave(&adapter->fsf_req_list_lock, flags);
+	list_del(&fsf_req->list);
+	write_unlock_irqrestore(&adapter->fsf_req_list_lock, flags);
+	zfcp_fsf_req_free(fsf_req);
+}
+
+#undef ZFCP_LOG_AREA
diff --git a/drivers/s390/scsi/zfcp_fsf.h b/drivers/s390/scsi/zfcp_fsf.h
new file mode 100644
index 0000000..5889956
--- /dev/null
+++ b/drivers/s390/scsi/zfcp_fsf.h
@@ -0,0 +1,472 @@
+/* 
+ * 
+ * linux/drivers/s390/scsi/zfcp_fsf.h
+ * 
+ * FCP adapter driver for IBM eServer zSeries 
+ * 
+ * (C) Copyright IBM Corp. 2002, 2004
+ *
+ * Author(s): Martin Peschke <mpeschke@de.ibm.com> 
+ *            Raimund Schroeder <raimund.schroeder@de.ibm.com> 
+ *            Aron Zeh
+ *            Wolfgang Taphorn
+ *            Stefan Bader <stefan.bader@de.ibm.com> 
+ *            Heiko Carstens <heiko.carstens@de.ibm.com>
+ *            Andreas Herrmann <aherrman@de.ibm.com>
+ *            Volker Sameske <sameske@de.ibm.com>
+ * 
+ * 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, or (at your option) 
+ * any later version. 
+ * 
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA. 
+ */
+
+#ifndef FSF_H
+#define FSF_H
+
+#define FSF_QTCB_VERSION1			0x00000001
+#define FSF_QTCB_CURRENT_VERSION		FSF_QTCB_VERSION1
+
+/* FSF commands */
+#define	FSF_QTCB_FCP_CMND			0x00000001
+#define	FSF_QTCB_ABORT_FCP_CMND			0x00000002
+#define	FSF_QTCB_OPEN_PORT_WITH_DID		0x00000005
+#define	FSF_QTCB_OPEN_LUN			0x00000006
+#define	FSF_QTCB_CLOSE_LUN			0x00000007
+#define	FSF_QTCB_CLOSE_PORT			0x00000008
+#define	FSF_QTCB_CLOSE_PHYSICAL_PORT		0x00000009
+#define	FSF_QTCB_SEND_ELS			0x0000000B
+#define	FSF_QTCB_SEND_GENERIC			0x0000000C
+#define	FSF_QTCB_EXCHANGE_CONFIG_DATA		0x0000000D
+#define	FSF_QTCB_EXCHANGE_PORT_DATA		0x0000000E
+#define FSF_QTCB_DOWNLOAD_CONTROL_FILE		0x00000012
+#define FSF_QTCB_UPLOAD_CONTROL_FILE		0x00000013
+
+/* FSF QTCB types */
+#define FSF_IO_COMMAND				0x00000001
+#define FSF_SUPPORT_COMMAND			0x00000002
+#define FSF_CONFIG_COMMAND			0x00000003
+#define FSF_PORT_COMMAND			0x00000004
+
+/* FSF control file upload/download operations' subtype and options */
+#define FSF_CFDC_OPERATION_SUBTYPE		0x00020001
+#define FSF_CFDC_OPTION_NORMAL_MODE		0x00000000
+#define FSF_CFDC_OPTION_FORCE			0x00000001
+#define FSF_CFDC_OPTION_FULL_ACCESS		0x00000002
+#define FSF_CFDC_OPTION_RESTRICTED_ACCESS	0x00000004
+
+/* FSF protocol stati */
+#define FSF_PROT_GOOD				0x00000001
+#define FSF_PROT_QTCB_VERSION_ERROR		0x00000010
+#define FSF_PROT_SEQ_NUMB_ERROR			0x00000020
+#define FSF_PROT_UNSUPP_QTCB_TYPE		0x00000040
+#define FSF_PROT_HOST_CONNECTION_INITIALIZING	0x00000080
+#define FSF_PROT_FSF_STATUS_PRESENTED		0x00000100
+#define FSF_PROT_DUPLICATE_REQUEST_ID		0x00000200
+#define FSF_PROT_LINK_DOWN                      0x00000400
+#define FSF_PROT_REEST_QUEUE                    0x00000800
+#define FSF_PROT_ERROR_STATE			0x01000000
+
+/* FSF stati */
+#define FSF_GOOD				0x00000000
+#define FSF_PORT_ALREADY_OPEN			0x00000001
+#define FSF_LUN_ALREADY_OPEN			0x00000002
+#define FSF_PORT_HANDLE_NOT_VALID		0x00000003
+#define FSF_LUN_HANDLE_NOT_VALID		0x00000004
+#define FSF_HANDLE_MISMATCH			0x00000005
+#define FSF_SERVICE_CLASS_NOT_SUPPORTED		0x00000006
+#define FSF_FCPLUN_NOT_VALID			0x00000009
+#define FSF_ACCESS_DENIED			0x00000010
+#define FSF_LUN_SHARING_VIOLATION               0x00000012
+#define FSF_FCP_COMMAND_DOES_NOT_EXIST		0x00000022
+#define FSF_DIRECTION_INDICATOR_NOT_VALID	0x00000030
+#define FSF_CMND_LENGTH_NOT_VALID		0x00000033
+#define FSF_MAXIMUM_NUMBER_OF_PORTS_EXCEEDED	0x00000040
+#define FSF_MAXIMUM_NUMBER_OF_LUNS_EXCEEDED	0x00000041
+#define FSF_ELS_COMMAND_REJECTED		0x00000050
+#define FSF_GENERIC_COMMAND_REJECTED		0x00000051
+#define FSF_OPERATION_PARTIALLY_SUCCESSFUL	0x00000052
+#define FSF_AUTHORIZATION_FAILURE		0x00000053
+#define FSF_CFDC_ERROR_DETECTED			0x00000054
+#define FSF_CONTROL_FILE_UPDATE_ERROR		0x00000055
+#define FSF_CONTROL_FILE_TOO_LARGE		0x00000056
+#define FSF_ACCESS_CONFLICT_DETECTED		0x00000057
+#define FSF_CONFLICTS_OVERRULED			0x00000058
+#define FSF_PORT_BOXED				0x00000059
+#define FSF_LUN_BOXED				0x0000005A
+#define FSF_EXCHANGE_CONFIG_DATA_INCOMPLETE	0x0000005B
+#define FSF_PAYLOAD_SIZE_MISMATCH		0x00000060
+#define FSF_REQUEST_SIZE_TOO_LARGE		0x00000061
+#define FSF_RESPONSE_SIZE_TOO_LARGE		0x00000062
+#define FSF_SBAL_MISMATCH			0x00000063
+#define FSF_OPEN_PORT_WITHOUT_PRLI		0x00000064
+#define FSF_ADAPTER_STATUS_AVAILABLE		0x000000AD
+#define FSF_FCP_RSP_AVAILABLE			0x000000AF
+#define FSF_UNKNOWN_COMMAND			0x000000E2
+#define FSF_UNKNOWN_OP_SUBTYPE                  0x000000E3
+#define FSF_INVALID_COMMAND_OPTION              0x000000E5
+/* #define FSF_ERROR                             0x000000FF  */
+
+#define FSF_STATUS_QUALIFIER_SIZE		16
+
+/* FSF status qualifier, recommendations */
+#define FSF_SQ_NO_RECOM				0x00
+#define FSF_SQ_FCP_RSP_AVAILABLE		0x01
+#define FSF_SQ_RETRY_IF_POSSIBLE		0x02
+#define FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED	0x03
+#define FSF_SQ_INVOKE_LINK_TEST_PROCEDURE	0x04
+#define FSF_SQ_ULP_PROGRAMMING_ERROR		0x05
+#define FSF_SQ_COMMAND_ABORTED			0x06
+#define FSF_SQ_NO_RETRY_POSSIBLE		0x07
+
+/* FSF status qualifier for CFDC commands */
+#define FSF_SQ_CFDC_COULD_NOT_HARDEN_ON_SE	0x00000001
+#define FSF_SQ_CFDC_COULD_NOT_HARDEN_ON_SE2	0x00000002
+/* CFDC subtable codes */
+#define FSF_SQ_CFDC_SUBTABLE_OS			0x0001
+#define FSF_SQ_CFDC_SUBTABLE_PORT_WWPN		0x0002
+#define FSF_SQ_CFDC_SUBTABLE_PORT_DID		0x0003
+#define FSF_SQ_CFDC_SUBTABLE_LUN		0x0004
+
+/* FSF status qualifier (most significant 4 bytes), local link down */
+#define FSF_PSQ_LINK_NOLIGHT			0x00000004
+#define FSF_PSQ_LINK_WRAPPLUG			0x00000008
+#define FSF_PSQ_LINK_NOFCP			0x00000010
+
+/* payload size in status read buffer */
+#define FSF_STATUS_READ_PAYLOAD_SIZE		4032
+
+/* number of status read buffers that should be sent by ULP */
+#define FSF_STATUS_READS_RECOM			16
+
+/* status types in status read buffer */
+#define FSF_STATUS_READ_PORT_CLOSED		0x00000001
+#define FSF_STATUS_READ_INCOMING_ELS		0x00000002
+#define FSF_STATUS_READ_SENSE_DATA_AVAIL        0x00000003
+#define FSF_STATUS_READ_BIT_ERROR_THRESHOLD	0x00000004
+#define FSF_STATUS_READ_LINK_DOWN		0x00000005 /* FIXME: really? */
+#define FSF_STATUS_READ_LINK_UP          	0x00000006
+#define FSF_STATUS_READ_CFDC_UPDATED		0x0000000A
+#define FSF_STATUS_READ_CFDC_HARDENED		0x0000000B
+
+/* status subtypes in status read buffer */
+#define FSF_STATUS_READ_SUB_CLOSE_PHYS_PORT	0x00000001
+#define FSF_STATUS_READ_SUB_ERROR_PORT		0x00000002
+
+/* status subtypes for CFDC */
+#define FSF_STATUS_READ_SUB_CFDC_HARDENED_ON_SE	0x00000002
+#define FSF_STATUS_READ_SUB_CFDC_HARDENED_ON_SE2 0x0000000F
+
+/* topologie that is detected by the adapter */
+#define FSF_TOPO_ERROR				0x00000000
+#define FSF_TOPO_P2P				0x00000001
+#define FSF_TOPO_FABRIC				0x00000002
+#define FSF_TOPO_AL				0x00000003
+#define FSF_TOPO_FABRIC_VIRT			0x00000004
+
+/* data direction for FCP commands */
+#define FSF_DATADIR_WRITE			0x00000001
+#define FSF_DATADIR_READ			0x00000002
+#define FSF_DATADIR_READ_WRITE			0x00000003
+#define FSF_DATADIR_CMND			0x00000004
+
+/* fc service class */
+#define FSF_CLASS_1				0x00000001
+#define FSF_CLASS_2				0x00000002
+#define FSF_CLASS_3				0x00000003
+
+/* SBAL chaining */
+#define FSF_MAX_SBALS_PER_REQ			36
+#define FSF_MAX_SBALS_PER_ELS_REQ		2
+
+/* logging space behind QTCB */
+#define FSF_QTCB_LOG_SIZE			1024
+
+/* channel features */
+#define FSF_FEATURE_QTCB_SUPPRESSION            0x00000001
+#define FSF_FEATURE_CFDC			0x00000002
+#define FSF_FEATURE_LUN_SHARING			0x00000004
+#define FSF_FEATURE_HBAAPI_MANAGEMENT           0x00000010
+#define FSF_FEATURE_ELS_CT_CHAINED_SBALS        0x00000020
+
+/* option */
+#define FSF_OPEN_LUN_SUPPRESS_BOXING		0x00000001
+#define FSF_OPEN_LUN_REPLICATE_SENSE		0x00000002
+
+/* adapter types */
+#define FSF_ADAPTER_TYPE_FICON                  0x00000001
+#define FSF_ADAPTER_TYPE_FICON_EXPRESS          0x00000002
+
+/* port types */
+#define FSF_HBA_PORTTYPE_UNKNOWN		0x00000001
+#define FSF_HBA_PORTTYPE_NOTPRESENT		0x00000003
+#define FSF_HBA_PORTTYPE_NPORT			0x00000005
+#define FSF_HBA_PORTTYPE_PTP			0x00000021
+/* following are not defined and used by FSF Spec
+   but are additionally defined by FC-HBA */
+#define FSF_HBA_PORTTYPE_OTHER			0x00000002
+#define FSF_HBA_PORTTYPE_NOTPRESENT		0x00000003
+#define FSF_HBA_PORTTYPE_NLPORT			0x00000006
+#define FSF_HBA_PORTTYPE_FLPORT			0x00000007
+#define FSF_HBA_PORTTYPE_FPORT			0x00000008
+#define FSF_HBA_PORTTYPE_LPORT			0x00000020
+
+/* port states */
+#define FSF_HBA_PORTSTATE_UNKNOWN		0x00000001
+#define FSF_HBA_PORTSTATE_ONLINE		0x00000002
+#define FSF_HBA_PORTSTATE_OFFLINE		0x00000003
+#define FSF_HBA_PORTSTATE_LINKDOWN		0x00000006
+#define FSF_HBA_PORTSTATE_ERROR			0x00000007
+
+/* IO states of adapter */
+#define FSF_IOSTAT_NPORT_RJT			0x00000004
+#define FSF_IOSTAT_FABRIC_RJT			0x00000005
+#define FSF_IOSTAT_LS_RJT			0x00000009
+
+/* open LUN access flags*/
+#define FSF_UNIT_ACCESS_OPEN_LUN_ALLOWED	0x01000000
+#define FSF_UNIT_ACCESS_EXCLUSIVE		0x02000000
+#define FSF_UNIT_ACCESS_OUTBOUND_TRANSFER	0x10000000
+
+struct fsf_queue_designator;
+struct fsf_status_read_buffer;
+struct fsf_port_closed_payload;
+struct fsf_bit_error_payload;
+union  fsf_prot_status_qual;
+struct fsf_qual_version_error;
+struct fsf_qual_sequence_error;
+struct fsf_qtcb_prefix;
+struct fsf_qtcb_header;
+struct fsf_qtcb_bottom_config;
+struct fsf_qtcb_bottom_support;
+struct fsf_qtcb_bottom_io;
+union  fsf_qtcb_bottom;
+
+struct fsf_queue_designator {
+	u8  cssid;
+	u8  chpid;
+	u8  hla;
+	u8  ua;
+	u32 res1;
+} __attribute__ ((packed));
+
+struct fsf_port_closed_payload {
+	struct fsf_queue_designator queue_designator;
+	u32                         port_handle;
+} __attribute__ ((packed));
+
+struct fsf_bit_error_payload {
+	u32 res1;
+	u32 link_failure_error_count;
+	u32 loss_of_sync_error_count;
+	u32 loss_of_signal_error_count;
+	u32 primitive_sequence_error_count;
+	u32 invalid_transmission_word_error_count;
+	u32 crc_error_count;
+	u32 primitive_sequence_event_timeout_count;
+	u32 elastic_buffer_overrun_error_count;
+	u32 fcal_arbitration_timeout_count;
+	u32 advertised_receive_b2b_credit;
+	u32 current_receive_b2b_credit;
+	u32 advertised_transmit_b2b_credit;
+	u32 current_transmit_b2b_credit;
+} __attribute__ ((packed));
+
+struct fsf_status_read_buffer {
+	u32 status_type;
+	u32 status_subtype;
+	u32 length;
+	u32 res1;
+	struct fsf_queue_designator queue_designator;
+	u32 d_id;
+	u32 class;
+	u64 fcp_lun;
+	u8  res3[24];
+	u8  payload[FSF_STATUS_READ_PAYLOAD_SIZE];
+} __attribute__ ((packed));
+
+struct fsf_qual_version_error {
+	u32 fsf_version;
+	u32 res1[3];
+} __attribute__ ((packed));
+
+struct fsf_qual_sequence_error {
+	u32 exp_req_seq_no;
+	u32 res1[3];
+} __attribute__ ((packed));
+
+struct fsf_qual_locallink_error {
+	u32 code;
+	u32 res1[3];
+} __attribute__ ((packed));
+
+union fsf_prot_status_qual {
+	struct fsf_qual_version_error   version_error;
+	struct fsf_qual_sequence_error  sequence_error;
+	struct fsf_qual_locallink_error locallink_error;
+} __attribute__ ((packed));
+
+struct fsf_qtcb_prefix {
+	u64 req_id;
+	u32 qtcb_version;
+	u32 ulp_info;
+	u32 qtcb_type;
+	u32 req_seq_no;
+	u32 prot_status;
+	union fsf_prot_status_qual prot_status_qual;
+	u8  res1[20];
+} __attribute__ ((packed));
+
+union fsf_status_qual {
+	u8  byte[FSF_STATUS_QUALIFIER_SIZE];
+	u16 halfword[FSF_STATUS_QUALIFIER_SIZE / sizeof (u16)];
+	u32 word[FSF_STATUS_QUALIFIER_SIZE / sizeof (u32)];
+	struct fsf_queue_designator fsf_queue_designator;
+} __attribute__ ((packed));
+
+struct fsf_qtcb_header {
+	u64 req_handle;
+	u32 fsf_command;
+	u32 res1;
+	u32 port_handle;
+	u32 lun_handle;
+	u32 res2;
+	u32 fsf_status;
+	union fsf_status_qual fsf_status_qual;
+	u8  res3[28];
+	u16 log_start;
+	u16 log_length;
+	u8  res4[16];
+} __attribute__ ((packed));
+
+struct fsf_nport_serv_param {
+	u8  common_serv_param[16];
+	u64 wwpn;
+	u64 wwnn;
+	u8  class1_serv_param[16];
+	u8  class2_serv_param[16];
+	u8  class3_serv_param[16];
+	u8  class4_serv_param[16];
+	u8  vendor_version_level[16];
+	u8  res1[16];
+} __attribute__ ((packed));
+
+struct fsf_plogi {
+	u32    code;
+	struct fsf_nport_serv_param serv_param;
+} __attribute__ ((packed));
+
+#define FSF_FCP_CMND_SIZE	288
+#define FSF_FCP_RSP_SIZE	128
+
+struct fsf_qtcb_bottom_io {
+	u32 data_direction;
+	u32 service_class;
+	u8  res1[8];
+	u32 fcp_cmnd_length;
+	u8  res2[12];
+	u8  fcp_cmnd[FSF_FCP_CMND_SIZE];
+	u8  fcp_rsp[FSF_FCP_RSP_SIZE];
+	u8  res3[64];
+} __attribute__ ((packed));
+
+struct fsf_qtcb_bottom_support {
+	u32 operation_subtype;
+	u8  res1[12];
+	u32 d_id;
+	u32 option;
+	u64 fcp_lun;
+	u64 res2;
+	u64 req_handle;
+	u32 service_class;
+	u8  res3[3];
+	u8  timeout;
+        u32 lun_access_info;
+        u8  res4[180];
+	u32 els1_length;
+	u32 els2_length;
+	u32 req_buf_length;
+	u32 resp_buf_length;
+	u8  els[256];
+} __attribute__ ((packed));
+
+struct fsf_qtcb_bottom_config {
+	u32 lic_version;
+	u32 feature_selection;
+	u32 high_qtcb_version;
+	u32 low_qtcb_version;
+	u32 max_qtcb_size;
+	u32 max_data_transfer_size;
+	u32 supported_features;
+	u8  res1[4];
+	u32 fc_topology;
+	u32 fc_link_speed;
+	u32 adapter_type;
+	u32 peer_d_id;
+	u8 res2[12];
+	u32 s_id;
+	struct fsf_nport_serv_param nport_serv_param;
+	u8 res3[8];
+	u32 adapter_ports;
+	u32 hardware_version;
+	u8 serial_number[32];
+	u8 res4[272];
+} __attribute__ ((packed));
+
+struct fsf_qtcb_bottom_port {
+	u8 res1[8];
+	u32 fc_port_id;
+	u32 port_type;
+	u32 port_state;
+	u32 class_of_service;	/* should be 0x00000006 for class 2 and 3 */
+	u8 supported_fc4_types[32]; /* should be 0x00000100 for scsi fcp */
+	u8 active_fc4_types[32];
+	u32 supported_speed;	/* 0x0001 for 1 GBit/s or 0x0002 for 2 GBit/s */
+	u32 maximum_frame_size;	/* fixed value of 2112 */
+	u64 seconds_since_last_reset;
+	u64 tx_frames;
+	u64 tx_words;
+	u64 rx_frames;
+	u64 rx_words;
+	u64 lip;		/* 0 */
+	u64 nos;		/* currently 0 */
+	u64 error_frames;	/* currently 0 */
+	u64 dumped_frames;	/* currently 0 */
+	u64 link_failure;
+	u64 loss_of_sync;
+	u64 loss_of_signal;
+	u64 psp_error_counts;
+	u64 invalid_tx_words;
+	u64 invalid_crcs;
+	u64 input_requests;
+	u64 output_requests;
+	u64 control_requests;
+	u64 input_mb;		/* where 1 MByte == 1.000.000 Bytes */
+	u64 output_mb;		/* where 1 MByte == 1.000.000 Bytes */
+	u8 res2[256];
+} __attribute__ ((packed));
+
+union fsf_qtcb_bottom {
+	struct fsf_qtcb_bottom_io      io;
+	struct fsf_qtcb_bottom_support support;
+	struct fsf_qtcb_bottom_config  config;
+	struct fsf_qtcb_bottom_port port;
+};
+
+struct fsf_qtcb {
+	struct fsf_qtcb_prefix prefix;
+	struct fsf_qtcb_header header;
+	union  fsf_qtcb_bottom bottom;
+	u8 log[FSF_QTCB_LOG_SIZE];
+} __attribute__ ((packed));
+
+#endif				/* FSF_H */
diff --git a/drivers/s390/scsi/zfcp_qdio.c b/drivers/s390/scsi/zfcp_qdio.c
new file mode 100644
index 0000000..06e862d
--- /dev/null
+++ b/drivers/s390/scsi/zfcp_qdio.c
@@ -0,0 +1,868 @@
+/*
+ * linux/drivers/s390/scsi/zfcp_qdio.c
+ *
+ * FCP adapter driver for IBM eServer zSeries
+ *
+ * QDIO related routines
+ *
+ * (C) Copyright IBM Corp. 2002, 2004
+ *
+ * Authors:
+ *      Martin Peschke <mpeschke@de.ibm.com>
+ *      Raimund Schroeder <raimund.schroeder@de.ibm.com>
+ *      Wolfgang Taphorn
+ *      Heiko Carstens <heiko.carstens@de.ibm.com>
+ *      Andreas Herrmann <aherrman@de.ibm.com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#define ZFCP_QDIO_C_REVISION "$Revision: 1.20 $"
+
+#include "zfcp_ext.h"
+
+static inline void zfcp_qdio_sbal_limit(struct zfcp_fsf_req *, int);
+static inline volatile struct qdio_buffer_element *zfcp_qdio_sbale_get
+	(struct zfcp_qdio_queue *, int, int);
+static inline volatile struct qdio_buffer_element *zfcp_qdio_sbale_resp
+	(struct zfcp_fsf_req *, int, int);
+static inline volatile struct qdio_buffer_element *zfcp_qdio_sbal_chain
+	(struct zfcp_fsf_req *, unsigned long);
+static inline volatile struct qdio_buffer_element *zfcp_qdio_sbale_next
+	(struct zfcp_fsf_req *, unsigned long);
+static inline int zfcp_qdio_sbals_zero(struct zfcp_qdio_queue *, int, int);
+static inline int zfcp_qdio_sbals_wipe(struct zfcp_fsf_req *);
+static inline void zfcp_qdio_sbale_fill
+	(struct zfcp_fsf_req *, unsigned long, void *, int);
+static inline int zfcp_qdio_sbals_from_segment
+	(struct zfcp_fsf_req *, unsigned long, void *, unsigned long);
+static inline int zfcp_qdio_sbals_from_buffer
+	(struct zfcp_fsf_req *, unsigned long, void *, unsigned long, int);
+
+static qdio_handler_t zfcp_qdio_request_handler;
+static qdio_handler_t zfcp_qdio_response_handler;
+static int zfcp_qdio_handler_error_check(struct zfcp_adapter *,
+					 unsigned int,
+					 unsigned int, unsigned int);
+
+#define ZFCP_LOG_AREA                   ZFCP_LOG_AREA_QDIO
+
+/*
+ * Allocates BUFFER memory to each of the pointers of the qdio_buffer_t 
+ * array in the adapter struct.
+ * Cur_buf is the pointer array and count can be any number of required 
+ * buffers, the page-fitting arithmetic is done entirely within this funciton.
+ *
+ * returns:	number of buffers allocated
+ * locks:       must only be called with zfcp_data.config_sema taken
+ */
+static int
+zfcp_qdio_buffers_enqueue(struct qdio_buffer **cur_buf, int count)
+{
+	int buf_pos;
+	int qdio_buffers_per_page;
+	int page_pos = 0;
+	struct qdio_buffer *first_in_page = NULL;
+
+	qdio_buffers_per_page = PAGE_SIZE / sizeof (struct qdio_buffer);
+	ZFCP_LOG_TRACE("buffers_per_page=%d\n", qdio_buffers_per_page);
+
+	for (buf_pos = 0; buf_pos < count; buf_pos++) {
+		if (page_pos == 0) {
+			cur_buf[buf_pos] = (struct qdio_buffer *)
+			    get_zeroed_page(GFP_KERNEL);
+			if (cur_buf[buf_pos] == NULL) {
+				ZFCP_LOG_INFO("error: allocation of "
+					      "QDIO buffer failed \n");
+				goto out;
+			}
+			first_in_page = cur_buf[buf_pos];
+		} else {
+			cur_buf[buf_pos] = first_in_page + page_pos;
+
+		}
+		/* was initialised to zero */
+		page_pos++;
+		page_pos %= qdio_buffers_per_page;
+	}
+ out:
+	return buf_pos;
+}
+
+/*
+ * Frees BUFFER memory for each of the pointers of the struct qdio_buffer array
+ * in the adapter struct cur_buf is the pointer array and count can be any
+ * number of buffers in the array that should be freed starting from buffer 0
+ *
+ * locks:       must only be called with zfcp_data.config_sema taken
+ */
+static void
+zfcp_qdio_buffers_dequeue(struct qdio_buffer **cur_buf, int count)
+{
+	int buf_pos;
+	int qdio_buffers_per_page;
+
+	qdio_buffers_per_page = PAGE_SIZE / sizeof (struct qdio_buffer);
+	ZFCP_LOG_TRACE("buffers_per_page=%d\n", qdio_buffers_per_page);
+
+	for (buf_pos = 0; buf_pos < count; buf_pos += qdio_buffers_per_page)
+		free_page((unsigned long) cur_buf[buf_pos]);
+	return;
+}
+
+/* locks:       must only be called with zfcp_data.config_sema taken */
+int
+zfcp_qdio_allocate_queues(struct zfcp_adapter *adapter)
+{
+	int buffer_count;
+	int retval = 0;
+
+	buffer_count =
+	    zfcp_qdio_buffers_enqueue(&(adapter->request_queue.buffer[0]),
+				      QDIO_MAX_BUFFERS_PER_Q);
+	if (buffer_count < QDIO_MAX_BUFFERS_PER_Q) {
+		ZFCP_LOG_DEBUG("only %d QDIO buffers allocated for request "
+			       "queue\n", buffer_count);
+		zfcp_qdio_buffers_dequeue(&(adapter->request_queue.buffer[0]),
+					  buffer_count);
+		retval = -ENOMEM;
+		goto out;
+	}
+
+	buffer_count =
+	    zfcp_qdio_buffers_enqueue(&(adapter->response_queue.buffer[0]),
+				      QDIO_MAX_BUFFERS_PER_Q);
+	if (buffer_count < QDIO_MAX_BUFFERS_PER_Q) {
+		ZFCP_LOG_DEBUG("only %d QDIO buffers allocated for response "
+			       "queue", buffer_count);
+		zfcp_qdio_buffers_dequeue(&(adapter->response_queue.buffer[0]),
+					  buffer_count);
+		ZFCP_LOG_TRACE("freeing request_queue buffers\n");
+		zfcp_qdio_buffers_dequeue(&(adapter->request_queue.buffer[0]),
+					  QDIO_MAX_BUFFERS_PER_Q);
+		retval = -ENOMEM;
+		goto out;
+	}
+ out:
+	return retval;
+}
+
+/* locks:       must only be called with zfcp_data.config_sema taken */
+void
+zfcp_qdio_free_queues(struct zfcp_adapter *adapter)
+{
+	ZFCP_LOG_TRACE("freeing request_queue buffers\n");
+	zfcp_qdio_buffers_dequeue(&(adapter->request_queue.buffer[0]),
+				  QDIO_MAX_BUFFERS_PER_Q);
+
+	ZFCP_LOG_TRACE("freeing response_queue buffers\n");
+	zfcp_qdio_buffers_dequeue(&(adapter->response_queue.buffer[0]),
+				  QDIO_MAX_BUFFERS_PER_Q);
+}
+
+int
+zfcp_qdio_allocate(struct zfcp_adapter *adapter)
+{
+	struct qdio_initialize *init_data;
+
+	init_data = &adapter->qdio_init_data;
+
+	init_data->cdev = adapter->ccw_device;
+	init_data->q_format = QDIO_SCSI_QFMT;
+	memcpy(init_data->adapter_name, &adapter->name, 8);
+	init_data->qib_param_field_format = 0;
+	init_data->qib_param_field = NULL;
+	init_data->input_slib_elements = NULL;
+	init_data->output_slib_elements = NULL;
+	init_data->min_input_threshold = ZFCP_MIN_INPUT_THRESHOLD;
+	init_data->max_input_threshold = ZFCP_MAX_INPUT_THRESHOLD;
+	init_data->min_output_threshold = ZFCP_MIN_OUTPUT_THRESHOLD;
+	init_data->max_output_threshold = ZFCP_MAX_OUTPUT_THRESHOLD;
+	init_data->no_input_qs = 1;
+	init_data->no_output_qs = 1;
+	init_data->input_handler = zfcp_qdio_response_handler;
+	init_data->output_handler = zfcp_qdio_request_handler;
+	init_data->int_parm = (unsigned long) adapter;
+	init_data->flags = QDIO_INBOUND_0COPY_SBALS |
+	    QDIO_OUTBOUND_0COPY_SBALS | QDIO_USE_OUTBOUND_PCIS;
+	init_data->input_sbal_addr_array =
+	    (void **) (adapter->response_queue.buffer);
+	init_data->output_sbal_addr_array =
+	    (void **) (adapter->request_queue.buffer);
+
+	return qdio_allocate(init_data);
+}
+
+/*
+ * function:   	zfcp_qdio_handler_error_check
+ *
+ * purpose:     called by the response handler to determine error condition
+ *
+ * returns:	error flag
+ *
+ */
+static inline int
+zfcp_qdio_handler_error_check(struct zfcp_adapter *adapter,
+			      unsigned int status,
+			      unsigned int qdio_error, unsigned int siga_error)
+{
+	int retval = 0;
+
+	if (ZFCP_LOG_CHECK(ZFCP_LOG_LEVEL_TRACE)) {
+		if (status & QDIO_STATUS_INBOUND_INT) {
+			ZFCP_LOG_TRACE("status is"
+				       " QDIO_STATUS_INBOUND_INT \n");
+		}
+		if (status & QDIO_STATUS_OUTBOUND_INT) {
+			ZFCP_LOG_TRACE("status is"
+				       " QDIO_STATUS_OUTBOUND_INT \n");
+		}
+	}			// if (ZFCP_LOG_CHECK(ZFCP_LOG_LEVEL_TRACE))
+	if (unlikely(status & QDIO_STATUS_LOOK_FOR_ERROR)) {
+		retval = -EIO;
+
+		ZFCP_LOG_FLAGS(1, "QDIO_STATUS_LOOK_FOR_ERROR \n");
+
+		ZFCP_LOG_INFO("QDIO problem occurred (status=0x%x, "
+			      "qdio_error=0x%x, siga_error=0x%x)\n",
+			      status, qdio_error, siga_error);
+
+		if (status & QDIO_STATUS_ACTIVATE_CHECK_CONDITION) {
+			ZFCP_LOG_FLAGS(2,
+				       "QDIO_STATUS_ACTIVATE_CHECK_CONDITION\n");
+		}
+		if (status & QDIO_STATUS_MORE_THAN_ONE_QDIO_ERROR) {
+			ZFCP_LOG_FLAGS(2,
+				       "QDIO_STATUS_MORE_THAN_ONE_QDIO_ERROR\n");
+		}
+		if (status & QDIO_STATUS_MORE_THAN_ONE_SIGA_ERROR) {
+			ZFCP_LOG_FLAGS(2,
+				       "QDIO_STATUS_MORE_THAN_ONE_SIGA_ERROR\n");
+		}
+
+		if (siga_error & QDIO_SIGA_ERROR_ACCESS_EXCEPTION) {
+			ZFCP_LOG_FLAGS(2, "QDIO_SIGA_ERROR_ACCESS_EXCEPTION\n");
+		}
+
+		if (siga_error & QDIO_SIGA_ERROR_B_BIT_SET) {
+			ZFCP_LOG_FLAGS(2, "QDIO_SIGA_ERROR_B_BIT_SET\n");
+		}
+
+		switch (qdio_error) {
+		case 0:
+			ZFCP_LOG_FLAGS(3, "QDIO_OK");
+			break;
+		case SLSB_P_INPUT_ERROR:
+			ZFCP_LOG_FLAGS(1, "SLSB_P_INPUT_ERROR\n");
+			break;
+		case SLSB_P_OUTPUT_ERROR:
+			ZFCP_LOG_FLAGS(1, "SLSB_P_OUTPUT_ERROR\n");
+			break;
+		default:
+			ZFCP_LOG_NORMAL("bug: unknown QDIO error 0x%x\n",
+					qdio_error);
+			break;
+		}
+		/* Restarting IO on the failed adapter from scratch */
+		debug_text_event(adapter->erp_dbf, 1, "qdio_err");
+               /*
+                * Since we have been using this adapter, it is save to assume
+                * that it is not failed but recoverable. The card seems to
+                * report link-up events by self-initiated queue shutdown.
+                * That is why we need to clear the the link-down flag
+                * which is set again in case we have missed by a mile.
+                */
+               zfcp_erp_adapter_reopen(
+                       adapter, 
+                       ZFCP_STATUS_ADAPTER_LINK_UNPLUGGED |
+                       ZFCP_STATUS_COMMON_ERP_FAILED);
+	}
+	return retval;
+}
+
+/*
+ * function:    zfcp_qdio_request_handler
+ *
+ * purpose:	is called by QDIO layer for completed SBALs in request queue
+ *
+ * returns:	(void)
+ */
+static void
+zfcp_qdio_request_handler(struct ccw_device *ccw_device,
+			  unsigned int status,
+			  unsigned int qdio_error,
+			  unsigned int siga_error,
+			  unsigned int queue_number,
+			  int first_element,
+			  int elements_processed,
+			  unsigned long int_parm)
+{
+	struct zfcp_adapter *adapter;
+	struct zfcp_qdio_queue *queue;
+
+	adapter = (struct zfcp_adapter *) int_parm;
+	queue = &adapter->request_queue;
+
+	ZFCP_LOG_DEBUG("adapter %s, first=%d, elements_processed=%d\n",
+		       zfcp_get_busid_by_adapter(adapter),
+		       first_element, elements_processed);
+
+	if (unlikely(zfcp_qdio_handler_error_check(adapter, status, qdio_error,
+					           siga_error)))
+		goto out;
+	/*
+	 * we stored address of struct zfcp_adapter  data structure
+	 * associated with irq in int_parm
+	 */
+
+	/* cleanup all SBALs being program-owned now */
+	zfcp_qdio_zero_sbals(queue->buffer, first_element, elements_processed);
+
+	/* increase free space in outbound queue */
+	atomic_add(elements_processed, &queue->free_count);
+	ZFCP_LOG_DEBUG("free_count=%d\n", atomic_read(&queue->free_count));
+	wake_up(&adapter->request_wq);
+	ZFCP_LOG_DEBUG("elements_processed=%d, free count=%d\n",
+		       elements_processed, atomic_read(&queue->free_count));
+ out:
+	return;
+}
+
+/*
+ * function:   	zfcp_qdio_response_handler
+ *
+ * purpose:	is called by QDIO layer for completed SBALs in response queue
+ *
+ * returns:	(void)
+ */
+static void
+zfcp_qdio_response_handler(struct ccw_device *ccw_device,
+			   unsigned int status,
+			   unsigned int qdio_error,
+			   unsigned int siga_error,
+			   unsigned int queue_number,
+			   int first_element,
+			   int elements_processed,
+			   unsigned long int_parm)
+{
+	struct zfcp_adapter *adapter;
+	struct zfcp_qdio_queue *queue;
+	int buffer_index;
+	int i;
+	struct qdio_buffer *buffer;
+	int retval = 0;
+	u8 count;
+	u8 start;
+	volatile struct qdio_buffer_element *buffere = NULL;
+	int buffere_index;
+
+	adapter = (struct zfcp_adapter *) int_parm;
+	queue = &adapter->response_queue;
+
+	if (unlikely(zfcp_qdio_handler_error_check(adapter, status, qdio_error,
+					           siga_error)))
+		goto out;
+
+	/*
+	 * we stored address of struct zfcp_adapter  data structure
+	 * associated with irq in int_parm
+	 */
+
+	buffere = &(queue->buffer[first_element]->element[0]);
+	ZFCP_LOG_DEBUG("first BUFFERE flags=0x%x\n", buffere->flags);
+	/*
+	 * go through all SBALs from input queue currently
+	 * returned by QDIO layer
+	 */
+
+	for (i = 0; i < elements_processed; i++) {
+
+		buffer_index = first_element + i;
+		buffer_index %= QDIO_MAX_BUFFERS_PER_Q;
+		buffer = queue->buffer[buffer_index];
+
+		/* go through all SBALEs of SBAL */
+		for (buffere_index = 0;
+		     buffere_index < QDIO_MAX_ELEMENTS_PER_BUFFER;
+		     buffere_index++) {
+
+			/* look for QDIO request identifiers in SB */
+			buffere = &buffer->element[buffere_index];
+			retval = zfcp_qdio_reqid_check(adapter,
+						       (void *) buffere->addr);
+
+			if (retval) {
+				ZFCP_LOG_NORMAL("bug: unexpected inbound "
+						"packet on adapter %s "
+						"(reqid=0x%lx, "
+						"first_element=%d, "
+						"elements_processed=%d)\n",
+						zfcp_get_busid_by_adapter(adapter),
+						(unsigned long) buffere->addr,
+						first_element,
+						elements_processed);
+				ZFCP_LOG_NORMAL("hex dump of inbound buffer "
+						"at address %p "
+						"(buffer_index=%d, "
+						"buffere_index=%d)\n", buffer,
+						buffer_index, buffere_index);
+				ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_NORMAL,
+					      (char *) buffer, SBAL_SIZE);
+			}
+			/*
+			 * A single used SBALE per inbound SBALE has been
+			 * implemented by QDIO so far. Hope they will
+			 * do some optimisation. Will need to change to
+			 * unlikely() then.
+			 */
+			if (likely(buffere->flags & SBAL_FLAGS_LAST_ENTRY))
+				break;
+		};
+
+		if (unlikely(!(buffere->flags & SBAL_FLAGS_LAST_ENTRY))) {
+			ZFCP_LOG_NORMAL("bug: End of inbound data "
+					"not marked!\n");
+		}
+	}
+
+	/*
+	 * put range of SBALs back to response queue
+	 * (including SBALs which have already been free before)
+	 */
+	count = atomic_read(&queue->free_count) + elements_processed;
+	start = queue->free_index;
+
+	ZFCP_LOG_TRACE("calling do_QDIO on adapter %s (flags=0x%x, "
+		       "queue_no=%i, index_in_queue=%i, count=%i, "
+		       "buffers=0x%lx\n",
+		       zfcp_get_busid_by_adapter(adapter),
+		       QDIO_FLAG_SYNC_INPUT | QDIO_FLAG_UNDER_INTERRUPT,
+		       0, start, count, (unsigned long) &queue->buffer[start]);
+
+	retval = do_QDIO(ccw_device,
+			 QDIO_FLAG_SYNC_INPUT | QDIO_FLAG_UNDER_INTERRUPT,
+			 0, start, count, NULL);
+
+	if (unlikely(retval)) {
+		atomic_set(&queue->free_count, count);
+		ZFCP_LOG_DEBUG("clearing of inbound data regions failed, "
+			       "queues may be down "
+			       "(count=%d, start=%d, retval=%d)\n",
+			       count, start, retval);
+	} else {
+		queue->free_index += count;
+		queue->free_index %= QDIO_MAX_BUFFERS_PER_Q;
+		atomic_set(&queue->free_count, 0);
+		ZFCP_LOG_TRACE("%i buffers enqueued to response "
+			       "queue at position %i\n", count, start);
+	}
+ out:
+	return;
+}
+
+/*
+ * function:	zfcp_qdio_reqid_check
+ *
+ * purpose:	checks for valid reqids or unsolicited status
+ *
+ * returns:	0 - valid request id or unsolicited status
+ *		!0 - otherwise
+ */
+int
+zfcp_qdio_reqid_check(struct zfcp_adapter *adapter, void *sbale_addr)
+{
+	struct zfcp_fsf_req *fsf_req;
+	int retval = 0;
+
+	/* invalid (per convention used in this driver) */
+	if (unlikely(!sbale_addr)) {
+		ZFCP_LOG_NORMAL("bug: invalid reqid\n");
+		retval = -EINVAL;
+		goto out;
+	}
+
+	/* valid request id and thus (hopefully :) valid fsf_req address */
+	fsf_req = (struct zfcp_fsf_req *) sbale_addr;
+
+	if (unlikely(adapter != fsf_req->adapter)) {
+		ZFCP_LOG_NORMAL("bug: invalid reqid (fsf_req=%p, "
+				"fsf_req->adapter=%p, adapter=%p)\n",
+				fsf_req, fsf_req->adapter, adapter);
+		retval = -EINVAL;
+		goto out;
+	}
+
+	ZFCP_LOG_TRACE("fsf_req at %p, QTCB at %p\n", fsf_req, fsf_req->qtcb);
+	if (likely(fsf_req->qtcb)) {
+		ZFCP_LOG_TRACE("hex dump of QTCB:\n");
+		ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_TRACE, (char *) fsf_req->qtcb,
+			      sizeof(struct fsf_qtcb));
+	}
+
+	/* finish the FSF request */
+	zfcp_fsf_req_complete(fsf_req);
+ out:
+	return retval;
+}
+
+/**
+ * zfcp_qdio_sbale_get - return pointer to SBALE of qdio_queue
+ * @queue: queue from which SBALE should be returned
+ * @sbal: specifies number of SBAL in queue
+ * @sbale: specifes number of SBALE in SBAL
+ */
+static inline volatile struct qdio_buffer_element *
+zfcp_qdio_sbale_get(struct zfcp_qdio_queue *queue, int sbal, int sbale)
+{
+	return &queue->buffer[sbal]->element[sbale];
+}
+
+/**
+ * zfcp_qdio_sbale_req - return pointer to SBALE of request_queue for
+ *	a struct zfcp_fsf_req
+ */
+inline volatile struct qdio_buffer_element *
+zfcp_qdio_sbale_req(struct zfcp_fsf_req *fsf_req, int sbal, int sbale)
+{
+	return zfcp_qdio_sbale_get(&fsf_req->adapter->request_queue,
+				   sbal, sbale);
+}
+
+/**
+ * zfcp_qdio_sbale_resp - return pointer to SBALE of response_queue for
+ *	a struct zfcp_fsf_req
+ */
+static inline volatile struct qdio_buffer_element *
+zfcp_qdio_sbale_resp(struct zfcp_fsf_req *fsf_req, int sbal, int sbale)
+{
+	return zfcp_qdio_sbale_get(&fsf_req->adapter->response_queue,
+				   sbal, sbale);
+}
+
+/**
+ * zfcp_qdio_sbale_curr - return current SBALE on request_queue for
+ *	a struct zfcp_fsf_req
+ */
+inline volatile struct qdio_buffer_element *
+zfcp_qdio_sbale_curr(struct zfcp_fsf_req *fsf_req)
+{
+	return zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_curr,
+				   fsf_req->sbale_curr);
+}
+
+/**
+ * zfcp_qdio_sbal_limit - determine maximum number of SBALs that can be used
+ *	on the request_queue for a struct zfcp_fsf_req
+ * @fsf_req: the number of the last SBAL that can be used is stored herein
+ * @max_sbals: used to pass an upper limit for the number of SBALs
+ *
+ * Note: We can assume at least one free SBAL in the request_queue when called.
+ */
+static inline void
+zfcp_qdio_sbal_limit(struct zfcp_fsf_req *fsf_req, int max_sbals)
+{
+	int count = atomic_read(&fsf_req->adapter->request_queue.free_count);
+	count = min(count, max_sbals);
+	fsf_req->sbal_last  = fsf_req->sbal_first;
+	fsf_req->sbal_last += (count - 1);
+	fsf_req->sbal_last %= QDIO_MAX_BUFFERS_PER_Q;
+}
+
+/**
+ * zfcp_qdio_sbal_chain - chain SBALs if more than one SBAL is needed for a
+ *	request
+ * @fsf_req: zfcp_fsf_req to be processed
+ * @sbtype: SBAL flags which have to be set in first SBALE of new SBAL
+ *
+ * This function changes sbal_curr, sbale_curr, sbal_number of fsf_req.
+ */
+static inline volatile struct qdio_buffer_element *
+zfcp_qdio_sbal_chain(struct zfcp_fsf_req *fsf_req, unsigned long sbtype)
+{
+	volatile struct qdio_buffer_element *sbale;
+
+	/* set last entry flag in current SBALE of current SBAL */
+	sbale = zfcp_qdio_sbale_curr(fsf_req);
+	sbale->flags |= SBAL_FLAGS_LAST_ENTRY;
+
+	/* don't exceed last allowed SBAL */
+	if (fsf_req->sbal_curr == fsf_req->sbal_last)
+		return NULL;
+
+	/* set chaining flag in first SBALE of current SBAL */
+	sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_curr, 0);
+	sbale->flags |= SBAL_FLAGS0_MORE_SBALS;
+
+	/* calculate index of next SBAL */
+	fsf_req->sbal_curr++;
+	fsf_req->sbal_curr %= QDIO_MAX_BUFFERS_PER_Q;
+
+	/* keep this requests number of SBALs up-to-date */
+	fsf_req->sbal_number++;
+
+	/* start at first SBALE of new SBAL */
+	fsf_req->sbale_curr = 0;
+
+	/* set storage-block type for new SBAL */
+	sbale = zfcp_qdio_sbale_curr(fsf_req);
+	sbale->flags |= sbtype;
+
+	return sbale;
+}
+
+/**
+ * zfcp_qdio_sbale_next - switch to next SBALE, chain SBALs if needed
+ */
+static inline volatile struct qdio_buffer_element *
+zfcp_qdio_sbale_next(struct zfcp_fsf_req *fsf_req, unsigned long sbtype)
+{
+	if (fsf_req->sbale_curr == ZFCP_LAST_SBALE_PER_SBAL)
+		return zfcp_qdio_sbal_chain(fsf_req, sbtype);
+
+	fsf_req->sbale_curr++;
+
+	return zfcp_qdio_sbale_curr(fsf_req);
+}
+
+/**
+ * zfcp_qdio_sbals_zero - initialize SBALs between first and last in queue
+ *	with zero from
+ */
+static inline int
+zfcp_qdio_sbals_zero(struct zfcp_qdio_queue *queue, int first, int last)
+{
+	struct qdio_buffer **buf = queue->buffer;
+	int curr = first;
+	int count = 0;
+
+	for(;;) {
+		curr %= QDIO_MAX_BUFFERS_PER_Q;
+		count++;
+		memset(buf[curr], 0, sizeof(struct qdio_buffer));
+		if (curr == last)
+			break;
+		curr++;
+	}
+	return count;
+}
+
+
+/**
+ * zfcp_qdio_sbals_wipe - reset all changes in SBALs for an fsf_req
+ */
+static inline int
+zfcp_qdio_sbals_wipe(struct zfcp_fsf_req *fsf_req)
+{
+	return zfcp_qdio_sbals_zero(&fsf_req->adapter->request_queue,
+				    fsf_req->sbal_first, fsf_req->sbal_curr);
+}
+
+
+/**
+ * zfcp_qdio_sbale_fill - set address and lenght in current SBALE
+ *	on request_queue
+ */
+static inline void
+zfcp_qdio_sbale_fill(struct zfcp_fsf_req *fsf_req, unsigned long sbtype,
+		     void *addr, int length)
+{
+	volatile struct qdio_buffer_element *sbale;
+
+	sbale = zfcp_qdio_sbale_curr(fsf_req);
+	sbale->addr = addr;
+	sbale->length = length;
+}
+
+/**
+ * zfcp_qdio_sbals_from_segment - map memory segment to SBALE(s)
+ * @fsf_req: request to be processed
+ * @sbtype: SBALE flags
+ * @start_addr: address of memory segment
+ * @total_length: length of memory segment
+ *
+ * Alignment and length of the segment determine how many SBALEs are needed
+ * for the memory segment.
+ */
+static inline int
+zfcp_qdio_sbals_from_segment(struct zfcp_fsf_req *fsf_req, unsigned long sbtype,
+			     void *start_addr, unsigned long total_length)
+{
+	unsigned long remaining, length;
+	void *addr;
+
+	/* split segment up heeding page boundaries */
+	for (addr = start_addr, remaining = total_length; remaining > 0;
+	     addr += length, remaining -= length) {
+		/* get next free SBALE for new piece */
+		if (NULL == zfcp_qdio_sbale_next(fsf_req, sbtype)) {
+			/* no SBALE left, clean up and leave */
+			zfcp_qdio_sbals_wipe(fsf_req);
+			return -EINVAL;
+		}
+		/* calculate length of new piece */
+		length = min(remaining,
+			     (PAGE_SIZE - ((unsigned long) addr &
+					   (PAGE_SIZE - 1))));
+		/* fill current SBALE with calculated piece */
+		zfcp_qdio_sbale_fill(fsf_req, sbtype, addr, length);
+	}
+	return total_length;
+}
+
+
+/**
+ * zfcp_qdio_sbals_from_sg - fill SBALs from scatter-gather list
+ * @fsf_req: request to be processed
+ * @sbtype: SBALE flags
+ * @sg: scatter-gather list
+ * @sg_count: number of elements in scatter-gather list
+ * @max_sbals: upper bound for number of SBALs to be used
+ */
+inline int
+zfcp_qdio_sbals_from_sg(struct zfcp_fsf_req *fsf_req, unsigned long sbtype,
+                        struct scatterlist *sg,	int sg_count, int max_sbals)
+{
+	int sg_index;
+	struct scatterlist *sg_segment;
+	int retval;
+	volatile struct qdio_buffer_element *sbale;
+	int bytes = 0;
+
+	/* figure out last allowed SBAL */
+	zfcp_qdio_sbal_limit(fsf_req, max_sbals);
+
+	/* set storage-block type for current SBAL */
+	sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_curr, 0);
+	sbale->flags |= sbtype;
+
+	/* process all segements of scatter-gather list */
+	for (sg_index = 0, sg_segment = sg, bytes = 0;
+	     sg_index < sg_count;
+	     sg_index++, sg_segment++) {
+		retval = zfcp_qdio_sbals_from_segment(
+				fsf_req,
+				sbtype,
+				zfcp_sg_to_address(sg_segment),
+				sg_segment->length);
+		if (retval < 0) {
+			bytes = retval;
+			goto out;
+		} else
+                        bytes += retval;
+	}
+	/* assume that no other SBALEs are to follow in the same SBAL */
+	sbale = zfcp_qdio_sbale_curr(fsf_req);
+	sbale->flags |= SBAL_FLAGS_LAST_ENTRY;
+out:
+	return bytes;
+}
+
+
+/**
+ * zfcp_qdio_sbals_from_buffer - fill SBALs from buffer
+ * @fsf_req: request to be processed
+ * @sbtype: SBALE flags
+ * @buffer: data buffer
+ * @length: length of buffer
+ * @max_sbals: upper bound for number of SBALs to be used
+ */
+static inline int
+zfcp_qdio_sbals_from_buffer(struct zfcp_fsf_req *fsf_req, unsigned long sbtype,
+			    void *buffer, unsigned long length, int max_sbals)
+{
+	struct scatterlist sg_segment;
+
+	zfcp_address_to_sg(buffer, &sg_segment);
+	sg_segment.length = length;
+
+	return zfcp_qdio_sbals_from_sg(fsf_req, sbtype, &sg_segment, 1,
+                                       max_sbals);
+}
+
+
+/**
+ * zfcp_qdio_sbals_from_scsicmnd - fill SBALs from scsi command
+ * @fsf_req: request to be processed
+ * @sbtype: SBALE flags
+ * @scsi_cmnd: either scatter-gather list or buffer contained herein is used
+ *	to fill SBALs
+ */
+inline int
+zfcp_qdio_sbals_from_scsicmnd(struct zfcp_fsf_req *fsf_req,
+			      unsigned long sbtype, struct scsi_cmnd *scsi_cmnd)
+{
+	if (scsi_cmnd->use_sg) {
+		return zfcp_qdio_sbals_from_sg(fsf_req,	sbtype,
+                                               (struct scatterlist *)
+                                               scsi_cmnd->request_buffer,
+                                               scsi_cmnd->use_sg,
+                                               ZFCP_MAX_SBALS_PER_REQ);
+	} else {
+                return zfcp_qdio_sbals_from_buffer(fsf_req, sbtype,
+                                                   scsi_cmnd->request_buffer,
+                                                   scsi_cmnd->request_bufflen,
+                                                   ZFCP_MAX_SBALS_PER_REQ);
+	}
+}
+
+/**
+ * zfcp_qdio_determine_pci - set PCI flag in first SBALE on qdio queue if needed
+ */
+int
+zfcp_qdio_determine_pci(struct zfcp_qdio_queue *req_queue,
+			struct zfcp_fsf_req *fsf_req)
+{
+	int new_distance_from_int;
+	int pci_pos;
+	volatile struct qdio_buffer_element *sbale;
+
+	new_distance_from_int = req_queue->distance_from_int +
+                fsf_req->sbal_number;
+
+	if (unlikely(new_distance_from_int >= ZFCP_QDIO_PCI_INTERVAL)) {
+		new_distance_from_int %= ZFCP_QDIO_PCI_INTERVAL;
+                pci_pos  = fsf_req->sbal_first;
+		pci_pos += fsf_req->sbal_number;
+		pci_pos -= new_distance_from_int;
+		pci_pos -= 1;
+		pci_pos %= QDIO_MAX_BUFFERS_PER_Q;
+		sbale = zfcp_qdio_sbale_req(fsf_req, pci_pos, 0);
+		sbale->flags |= SBAL_FLAGS0_PCI;
+	}
+	return new_distance_from_int;
+}
+
+/*
+ * function:	zfcp_zero_sbals
+ *
+ * purpose:	zeros specified range of SBALs
+ *
+ * returns:
+ */
+void
+zfcp_qdio_zero_sbals(struct qdio_buffer *buf[], int first, int clean_count)
+{
+	int cur_pos;
+	int index;
+
+	for (cur_pos = first; cur_pos < (first + clean_count); cur_pos++) {
+		index = cur_pos % QDIO_MAX_BUFFERS_PER_Q;
+		memset(buf[index], 0, sizeof (struct qdio_buffer));
+		ZFCP_LOG_TRACE("zeroing BUFFER %d at address %p\n",
+			       index, buf[index]);
+	}
+}
+
+#undef ZFCP_LOG_AREA
diff --git a/drivers/s390/scsi/zfcp_scsi.c b/drivers/s390/scsi/zfcp_scsi.c
new file mode 100644
index 0000000..e21b547
--- /dev/null
+++ b/drivers/s390/scsi/zfcp_scsi.c
@@ -0,0 +1,949 @@
+/* 
+ * 
+ * linux/drivers/s390/scsi/zfcp_scsi.c
+ * 
+ * FCP adapter driver for IBM eServer zSeries 
+ * 
+ * (C) Copyright IBM Corp. 2002, 2004
+ *
+ * Author(s): Martin Peschke <mpeschke@de.ibm.com> 
+ *            Raimund Schroeder <raimund.schroeder@de.ibm.com> 
+ *            Aron Zeh
+ *            Wolfgang Taphorn
+ *            Stefan Bader <stefan.bader@de.ibm.com> 
+ *            Heiko Carstens <heiko.carstens@de.ibm.com> 
+ *            Andreas Herrmann <aherrman@de.ibm.com>
+ * 
+ * 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, or (at your option) 
+ * any later version. 
+ * 
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA. 
+ */
+
+#define ZFCP_LOG_AREA			ZFCP_LOG_AREA_SCSI
+
+#define ZFCP_SCSI_REVISION "$Revision: 1.74 $"
+
+#include "zfcp_ext.h"
+
+static void zfcp_scsi_slave_destroy(struct scsi_device *sdp);
+static int zfcp_scsi_slave_alloc(struct scsi_device *sdp);
+static int zfcp_scsi_slave_configure(struct scsi_device *sdp);
+static int zfcp_scsi_queuecommand(struct scsi_cmnd *,
+				  void (*done) (struct scsi_cmnd *));
+static int zfcp_scsi_eh_abort_handler(struct scsi_cmnd *);
+static int zfcp_scsi_eh_device_reset_handler(struct scsi_cmnd *);
+static int zfcp_scsi_eh_bus_reset_handler(struct scsi_cmnd *);
+static int zfcp_scsi_eh_host_reset_handler(struct scsi_cmnd *);
+static int zfcp_task_management_function(struct zfcp_unit *, u8);
+
+static struct zfcp_unit *zfcp_unit_lookup(struct zfcp_adapter *, int, scsi_id_t,
+					  scsi_lun_t);
+static struct zfcp_port *zfcp_port_lookup(struct zfcp_adapter *, int,
+					  scsi_id_t);
+
+static struct device_attribute *zfcp_sysfs_sdev_attrs[];
+
+struct scsi_transport_template *zfcp_transport_template;
+
+struct zfcp_data zfcp_data = {
+	.scsi_host_template = {
+	      name:	               ZFCP_NAME,
+	      proc_name:               "zfcp",
+	      proc_info:               NULL,
+	      detect:	               NULL,
+	      slave_alloc:             zfcp_scsi_slave_alloc,
+	      slave_configure:         zfcp_scsi_slave_configure,
+	      slave_destroy:           zfcp_scsi_slave_destroy,
+	      queuecommand:            zfcp_scsi_queuecommand,
+	      eh_abort_handler:        zfcp_scsi_eh_abort_handler,
+	      eh_device_reset_handler: zfcp_scsi_eh_device_reset_handler,
+	      eh_bus_reset_handler:    zfcp_scsi_eh_bus_reset_handler,
+	      eh_host_reset_handler:   zfcp_scsi_eh_host_reset_handler,
+			               /* FIXME(openfcp): Tune */
+	      can_queue:               4096,
+	      this_id:	               0,
+	      /*
+	       * FIXME:
+	       * one less? can zfcp_create_sbale cope with it?
+	       */
+	      sg_tablesize:            ZFCP_MAX_SBALES_PER_REQ,
+	      cmd_per_lun:             1,
+	      unchecked_isa_dma:       0,
+	      use_clustering:          1,
+	      sdev_attrs:              zfcp_sysfs_sdev_attrs,
+	},
+	.driver_version = ZFCP_VERSION,
+	/* rest initialised with zeros */
+};
+
+/* Find start of Response Information in FCP response unit*/
+char *
+zfcp_get_fcp_rsp_info_ptr(struct fcp_rsp_iu *fcp_rsp_iu)
+{
+	char *fcp_rsp_info_ptr;
+
+	fcp_rsp_info_ptr =
+	    (unsigned char *) fcp_rsp_iu + (sizeof (struct fcp_rsp_iu));
+
+	return fcp_rsp_info_ptr;
+}
+
+/* Find start of Sense Information in FCP response unit*/
+char *
+zfcp_get_fcp_sns_info_ptr(struct fcp_rsp_iu *fcp_rsp_iu)
+{
+	char *fcp_sns_info_ptr;
+
+	fcp_sns_info_ptr =
+	    (unsigned char *) fcp_rsp_iu + (sizeof (struct fcp_rsp_iu));
+	if (fcp_rsp_iu->validity.bits.fcp_rsp_len_valid)
+		fcp_sns_info_ptr = (char *) fcp_sns_info_ptr +
+		    fcp_rsp_iu->fcp_rsp_len;
+
+	return fcp_sns_info_ptr;
+}
+
+fcp_dl_t *
+zfcp_get_fcp_dl_ptr(struct fcp_cmnd_iu * fcp_cmd)
+{
+	int additional_length = fcp_cmd->add_fcp_cdb_length << 2;
+	fcp_dl_t *fcp_dl_addr;
+
+	fcp_dl_addr = (fcp_dl_t *)
+		((unsigned char *) fcp_cmd +
+		 sizeof (struct fcp_cmnd_iu) + additional_length);
+	/*
+	 * fcp_dl_addr = start address of fcp_cmnd structure + 
+	 * size of fixed part + size of dynamically sized add_dcp_cdb field
+	 * SEE FCP-2 documentation
+	 */
+	return fcp_dl_addr;
+}
+
+fcp_dl_t
+zfcp_get_fcp_dl(struct fcp_cmnd_iu * fcp_cmd)
+{
+	return *zfcp_get_fcp_dl_ptr(fcp_cmd);
+}
+
+void
+zfcp_set_fcp_dl(struct fcp_cmnd_iu *fcp_cmd, fcp_dl_t fcp_dl)
+{
+	*zfcp_get_fcp_dl_ptr(fcp_cmd) = fcp_dl;
+}
+
+/*
+ * note: it's a bit-or operation not an assignment
+ * regarding the specified byte
+ */
+static inline void
+set_byte(u32 * result, char status, char pos)
+{
+	*result |= status << (pos * 8);
+}
+
+void
+set_host_byte(u32 * result, char status)
+{
+	set_byte(result, status, 2);
+}
+
+void
+set_driver_byte(u32 * result, char status)
+{
+	set_byte(result, status, 3);
+}
+
+/*
+ * function:	zfcp_scsi_slave_alloc
+ *
+ * purpose:
+ *
+ * returns:
+ */
+
+static int
+zfcp_scsi_slave_alloc(struct scsi_device *sdp)
+{
+	struct zfcp_adapter *adapter;
+	struct zfcp_unit *unit;
+	unsigned long flags;
+	int retval = -ENODEV;
+
+	adapter = (struct zfcp_adapter *) sdp->host->hostdata[0];
+	if (!adapter)
+		goto out;
+
+	read_lock_irqsave(&zfcp_data.config_lock, flags);
+	unit = zfcp_unit_lookup(adapter, sdp->channel, sdp->id, sdp->lun);
+	if (unit) {
+		sdp->hostdata = unit;
+		unit->device = sdp;
+		zfcp_unit_get(unit);
+		retval = 0;
+	}
+	read_unlock_irqrestore(&zfcp_data.config_lock, flags);
+ out:
+	return retval;
+}
+
+/*
+ * function:	zfcp_scsi_slave_destroy
+ *
+ * purpose:
+ *
+ * returns:
+ */
+
+static void
+zfcp_scsi_slave_destroy(struct scsi_device *sdpnt)
+{
+	struct zfcp_unit *unit = (struct zfcp_unit *) sdpnt->hostdata;
+
+	if (unit) {
+		sdpnt->hostdata = NULL;
+		unit->device = NULL;
+		zfcp_unit_put(unit);
+	} else {
+		ZFCP_LOG_NORMAL("bug: no unit associated with SCSI device at "
+				"address %p\n", sdpnt);
+	}
+}
+
+/* 
+ * called from scsi midlayer to allow finetuning of a device.
+ */
+static int
+zfcp_scsi_slave_configure(struct scsi_device *sdp)
+{
+	if (sdp->tagged_supported)
+		scsi_adjust_queue_depth(sdp, MSG_SIMPLE_TAG, ZFCP_CMND_PER_LUN);
+	else
+		scsi_adjust_queue_depth(sdp, 0, 1);
+	return 0;
+}
+
+/**
+ * zfcp_scsi_command_fail - set result in scsi_cmnd and call scsi_done function
+ * @scpnt: pointer to struct scsi_cmnd where result is set
+ * @result: result to be set in scpnt (e.g. DID_ERROR)
+ */
+static void
+zfcp_scsi_command_fail(struct scsi_cmnd *scpnt, int result)
+{
+	set_host_byte(&scpnt->result, result);
+	zfcp_cmd_dbf_event_scsi("failing", scpnt);
+	/* return directly */
+	scpnt->scsi_done(scpnt);
+}
+
+/**
+ * zfcp_scsi_command_async - worker for zfcp_scsi_queuecommand and
+ *	zfcp_scsi_command_sync
+ * @adapter: adapter where scsi command is issued
+ * @unit: unit to which scsi command is sent
+ * @scpnt: scsi command to be sent
+ * @timer: timer to be started if request is successfully initiated
+ *
+ * Note: In scsi_done function must be set in scpnt.
+ */
+int
+zfcp_scsi_command_async(struct zfcp_adapter *adapter, struct zfcp_unit *unit,
+			struct scsi_cmnd *scpnt, struct timer_list *timer)
+{
+	int tmp;
+	int retval;
+
+	retval = 0;
+
+	BUG_ON((adapter == NULL) || (adapter != unit->port->adapter));
+	BUG_ON(scpnt->scsi_done == NULL);
+
+	if (unlikely(NULL == unit)) {
+		zfcp_scsi_command_fail(scpnt, DID_NO_CONNECT);
+		goto out;
+	}
+
+	if (unlikely(
+	      atomic_test_mask(ZFCP_STATUS_COMMON_ERP_FAILED, &unit->status) ||
+	     !atomic_test_mask(ZFCP_STATUS_COMMON_RUNNING, &unit->status))) {
+		ZFCP_LOG_DEBUG("stopping SCSI I/O on unit 0x%016Lx on port "
+			       "0x%016Lx on adapter %s\n",
+			       unit->fcp_lun, unit->port->wwpn,
+			       zfcp_get_busid_by_adapter(adapter));
+		zfcp_scsi_command_fail(scpnt, DID_ERROR);
+		goto out;
+	}
+
+	if (unlikely(
+	     !atomic_test_mask(ZFCP_STATUS_COMMON_UNBLOCKED, &unit->status))) {
+		ZFCP_LOG_DEBUG("adapter %s not ready or unit 0x%016Lx "
+			       "on port 0x%016Lx in recovery\n",
+			       zfcp_get_busid_by_unit(unit),
+			       unit->fcp_lun, unit->port->wwpn);
+		retval = SCSI_MLQUEUE_DEVICE_BUSY;
+		goto out;
+	}
+
+	tmp = zfcp_fsf_send_fcp_command_task(adapter, unit, scpnt, timer,
+					     ZFCP_REQ_AUTO_CLEANUP);
+
+	if (unlikely(tmp < 0)) {
+		ZFCP_LOG_DEBUG("error: initiation of Send FCP Cmnd failed\n");
+		retval = SCSI_MLQUEUE_HOST_BUSY;
+	}
+
+out:
+	return retval;
+}
+
+void
+zfcp_scsi_command_sync_handler(struct scsi_cmnd *scpnt)
+{
+	struct completion *wait = (struct completion *) scpnt->SCp.ptr;
+	complete(wait);
+}
+
+
+/**
+ * zfcp_scsi_command_sync - send a SCSI command and wait for completion
+ * @unit: unit where command is sent to
+ * @scpnt: scsi command to be sent
+ * @timer: timer to be started if request is successfully initiated
+ * Return: 0
+ *
+ * Errors are indicated in scpnt->result
+ */
+int
+zfcp_scsi_command_sync(struct zfcp_unit *unit, struct scsi_cmnd *scpnt,
+		       struct timer_list *timer)
+{
+	int ret;
+	DECLARE_COMPLETION(wait);
+
+	scpnt->SCp.ptr = (void *) &wait;  /* silent re-use */
+	scpnt->scsi_done = zfcp_scsi_command_sync_handler;
+	ret = zfcp_scsi_command_async(unit->port->adapter, unit, scpnt, timer);
+	if (ret == 0)
+		wait_for_completion(&wait);
+
+	scpnt->SCp.ptr = NULL;
+
+	return 0;
+}
+
+/*
+ * function:	zfcp_scsi_queuecommand
+ *
+ * purpose:	enqueues a SCSI command to the specified target device
+ *
+ * returns:	0 - success, SCSI command enqueued
+ *		!0 - failure
+ */
+int
+zfcp_scsi_queuecommand(struct scsi_cmnd *scpnt,
+		       void (*done) (struct scsi_cmnd *))
+{
+	struct zfcp_unit *unit;
+	struct zfcp_adapter *adapter;
+
+	/* reset the status for this request */
+	scpnt->result = 0;
+	scpnt->host_scribble = NULL;
+	scpnt->scsi_done = done;
+
+	/*
+	 * figure out adapter and target device
+	 * (stored there by zfcp_scsi_slave_alloc)
+	 */
+	adapter = (struct zfcp_adapter *) scpnt->device->host->hostdata[0];
+	unit = (struct zfcp_unit *) scpnt->device->hostdata;
+
+	return zfcp_scsi_command_async(adapter, unit, scpnt, NULL);
+}
+
+/*
+ * function:    zfcp_unit_lookup
+ *
+ * purpose:
+ *
+ * returns:
+ *
+ * context:	
+ */
+static struct zfcp_unit *
+zfcp_unit_lookup(struct zfcp_adapter *adapter, int channel, scsi_id_t id,
+		 scsi_lun_t lun)
+{
+	struct zfcp_port *port;
+	struct zfcp_unit *unit, *retval = NULL;
+
+	list_for_each_entry(port, &adapter->port_list_head, list) {
+		if (id != port->scsi_id)
+			continue;
+		list_for_each_entry(unit, &port->unit_list_head, list) {
+			if (lun == unit->scsi_lun) {
+				retval = unit;
+				goto out;
+			}
+		}
+	}
+ out:
+	return retval;
+}
+
+static struct zfcp_port *
+zfcp_port_lookup(struct zfcp_adapter *adapter, int channel, scsi_id_t id)
+{
+	struct zfcp_port *port;
+
+	list_for_each_entry(port, &adapter->port_list_head, list) {
+		if (id == port->scsi_id)
+			return port;
+	}
+	return (struct zfcp_port *) NULL;
+}
+
+/*
+ * function:	zfcp_scsi_eh_abort_handler
+ *
+ * purpose:	tries to abort the specified (timed out) SCSI command
+ *
+ * note: 	We do not need to care for a SCSI command which completes
+ *		normally but late during this abort routine runs.
+ *		We are allowed to return late commands to the SCSI stack.
+ *		It tracks the state of commands and will handle late commands.
+ *		(Usually, the normal completion of late commands is ignored with
+ *		respect to the running abort operation. Grep for 'done_late'
+ *		in the SCSI stacks sources.)
+ *
+ * returns:	SUCCESS	- command has been aborted and cleaned up in internal
+ *			  bookkeeping,
+ *			  SCSI stack won't be called for aborted command
+ *		FAILED	- otherwise
+ */
+int
+zfcp_scsi_eh_abort_handler(struct scsi_cmnd *scpnt)
+{
+	int retval = SUCCESS;
+	struct zfcp_fsf_req *new_fsf_req, *old_fsf_req;
+	struct zfcp_adapter *adapter = (struct zfcp_adapter *) scpnt->device->host->hostdata[0];
+	struct zfcp_unit *unit = (struct zfcp_unit *) scpnt->device->hostdata;
+	struct zfcp_port *port = unit->port;
+	struct Scsi_Host *scsi_host = scpnt->device->host;
+	union zfcp_req_data *req_data = NULL;
+	unsigned long flags;
+	u32 status = 0;
+
+	/* the components of a abort_dbf record (fixed size record) */
+	u64 dbf_scsi_cmnd = (unsigned long) scpnt;
+	char dbf_opcode[ZFCP_ABORT_DBF_LENGTH];
+	wwn_t dbf_wwn = port->wwpn;
+	fcp_lun_t dbf_fcp_lun = unit->fcp_lun;
+	u64 dbf_retries = scpnt->retries;
+	u64 dbf_allowed = scpnt->allowed;
+	u64 dbf_timeout = 0;
+	u64 dbf_fsf_req = 0;
+	u64 dbf_fsf_status = 0;
+	u64 dbf_fsf_qual[2] = { 0, 0 };
+	char dbf_result[ZFCP_ABORT_DBF_LENGTH] = "##undef";
+
+	memset(dbf_opcode, 0, ZFCP_ABORT_DBF_LENGTH);
+	memcpy(dbf_opcode,
+	       scpnt->cmnd,
+	       min(scpnt->cmd_len, (unsigned char) ZFCP_ABORT_DBF_LENGTH));
+
+	ZFCP_LOG_INFO("aborting scsi_cmnd=%p on adapter %s\n",
+		      scpnt, zfcp_get_busid_by_adapter(adapter));
+
+	spin_unlock_irq(scsi_host->host_lock);
+
+	/*
+	 * Race condition between normal (late) completion and abort has
+	 * to be avoided.
+	 * The entirity of all accesses to scsi_req have to be atomic.
+	 * scsi_req is usually part of the fsf_req and thus we block the
+	 * release of fsf_req as long as we need to access scsi_req.
+	 */
+	write_lock_irqsave(&adapter->abort_lock, flags);
+
+	/*
+	 * Check whether command has just completed and can not be aborted.
+	 * Even if the command has just been completed late, we can access
+	 * scpnt since the SCSI stack does not release it at least until
+	 * this routine returns. (scpnt is parameter passed to this routine
+	 * and must not disappear during abort even on late completion.)
+	 */
+	req_data = (union zfcp_req_data *) scpnt->host_scribble;
+	/* DEBUG */
+	ZFCP_LOG_DEBUG("req_data=%p\n", req_data);
+	if (!req_data) {
+		ZFCP_LOG_DEBUG("late command completion overtook abort\n");
+		/*
+		 * That's it.
+		 * Do not initiate abort but return SUCCESS.
+		 */
+		write_unlock_irqrestore(&adapter->abort_lock, flags);
+		retval = SUCCESS;
+		strncpy(dbf_result, "##late1", ZFCP_ABORT_DBF_LENGTH);
+		goto out;
+	}
+
+	/* Figure out which fsf_req needs to be aborted. */
+	old_fsf_req = req_data->send_fcp_command_task.fsf_req;
+
+	dbf_fsf_req = (unsigned long) old_fsf_req;
+	dbf_timeout =
+	    (jiffies - req_data->send_fcp_command_task.start_jiffies) / HZ;
+
+	ZFCP_LOG_DEBUG("old_fsf_req=%p\n", old_fsf_req);
+	if (!old_fsf_req) {
+		write_unlock_irqrestore(&adapter->abort_lock, flags);
+		ZFCP_LOG_NORMAL("bug: no old fsf request found\n");
+		ZFCP_LOG_NORMAL("req_data:\n");
+		ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_NORMAL,
+			      (char *) req_data, sizeof (union zfcp_req_data));
+		ZFCP_LOG_NORMAL("scsi_cmnd:\n");
+		ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_NORMAL,
+			      (char *) scpnt, sizeof (struct scsi_cmnd));
+		retval = FAILED;
+		strncpy(dbf_result, "##bug:r", ZFCP_ABORT_DBF_LENGTH);
+		goto out;
+	}
+	old_fsf_req->data.send_fcp_command_task.scsi_cmnd = NULL;
+	/* mark old request as being aborted */
+	old_fsf_req->status |= ZFCP_STATUS_FSFREQ_ABORTING;
+	/*
+	 * We have to collect all information (e.g. unit) needed by 
+	 * zfcp_fsf_abort_fcp_command before calling that routine
+	 * since that routine is not allowed to access
+	 * fsf_req which it is going to abort.
+	 * This is because of we need to release fsf_req_list_lock
+	 * before calling zfcp_fsf_abort_fcp_command.
+	 * Since this lock will not be held, fsf_req may complete
+	 * late and may be released meanwhile.
+	 */
+	ZFCP_LOG_DEBUG("unit 0x%016Lx (%p)\n", unit->fcp_lun, unit);
+
+	/*
+	 * We block (call schedule)
+	 * That's why we must release the lock and enable the
+	 * interrupts before.
+	 * On the other hand we do not need the lock anymore since
+	 * all critical accesses to scsi_req are done.
+	 */
+	write_unlock_irqrestore(&adapter->abort_lock, flags);
+	/* call FSF routine which does the abort */
+	new_fsf_req = zfcp_fsf_abort_fcp_command((unsigned long) old_fsf_req,
+						 adapter, unit, 0);
+	ZFCP_LOG_DEBUG("new_fsf_req=%p\n", new_fsf_req);
+	if (!new_fsf_req) {
+		retval = FAILED;
+		ZFCP_LOG_NORMAL("error: initiation of Abort FCP Cmnd "
+				"failed\n");
+		strncpy(dbf_result, "##nores", ZFCP_ABORT_DBF_LENGTH);
+		goto out;
+	}
+
+	/* wait for completion of abort */
+	ZFCP_LOG_DEBUG("waiting for cleanup...\n");
+#if 1
+	/*
+	 * FIXME:
+	 * copying zfcp_fsf_req_wait_and_cleanup code is not really nice
+	 */
+	__wait_event(new_fsf_req->completion_wq,
+		     new_fsf_req->status & ZFCP_STATUS_FSFREQ_COMPLETED);
+	status = new_fsf_req->status;
+	dbf_fsf_status = new_fsf_req->qtcb->header.fsf_status;
+	/*
+	 * Ralphs special debug load provides timestamps in the FSF
+	 * status qualifier. This might be specified later if being
+	 * useful for debugging aborts.
+	 */
+	dbf_fsf_qual[0] =
+	    *(u64 *) & new_fsf_req->qtcb->header.fsf_status_qual.word[0];
+	dbf_fsf_qual[1] =
+	    *(u64 *) & new_fsf_req->qtcb->header.fsf_status_qual.word[2];
+	zfcp_fsf_req_cleanup(new_fsf_req);
+#else
+	retval = zfcp_fsf_req_wait_and_cleanup(new_fsf_req,
+					       ZFCP_UNINTERRUPTIBLE, &status);
+#endif
+	ZFCP_LOG_DEBUG("Waiting for cleanup complete, status=0x%x\n", status);
+	/* status should be valid since signals were not permitted */
+	if (status & ZFCP_STATUS_FSFREQ_ABORTSUCCEEDED) {
+		retval = SUCCESS;
+		strncpy(dbf_result, "##succ", ZFCP_ABORT_DBF_LENGTH);
+	} else if (status & ZFCP_STATUS_FSFREQ_ABORTNOTNEEDED) {
+		retval = SUCCESS;
+		strncpy(dbf_result, "##late2", ZFCP_ABORT_DBF_LENGTH);
+	} else {
+		retval = FAILED;
+		strncpy(dbf_result, "##fail", ZFCP_ABORT_DBF_LENGTH);
+	}
+
+ out:
+	debug_event(adapter->abort_dbf, 1, &dbf_scsi_cmnd, sizeof (u64));
+	debug_event(adapter->abort_dbf, 1, &dbf_opcode, ZFCP_ABORT_DBF_LENGTH);
+	debug_event(adapter->abort_dbf, 1, &dbf_wwn, sizeof (wwn_t));
+	debug_event(adapter->abort_dbf, 1, &dbf_fcp_lun, sizeof (fcp_lun_t));
+	debug_event(adapter->abort_dbf, 1, &dbf_retries, sizeof (u64));
+	debug_event(adapter->abort_dbf, 1, &dbf_allowed, sizeof (u64));
+	debug_event(adapter->abort_dbf, 1, &dbf_timeout, sizeof (u64));
+	debug_event(adapter->abort_dbf, 1, &dbf_fsf_req, sizeof (u64));
+	debug_event(adapter->abort_dbf, 1, &dbf_fsf_status, sizeof (u64));
+	debug_event(adapter->abort_dbf, 1, &dbf_fsf_qual[0], sizeof (u64));
+	debug_event(adapter->abort_dbf, 1, &dbf_fsf_qual[1], sizeof (u64));
+	debug_text_event(adapter->abort_dbf, 1, dbf_result);
+
+	spin_lock_irq(scsi_host->host_lock);
+	return retval;
+}
+
+/*
+ * function:	zfcp_scsi_eh_device_reset_handler
+ *
+ * purpose:
+ *
+ * returns:
+ */
+int
+zfcp_scsi_eh_device_reset_handler(struct scsi_cmnd *scpnt)
+{
+	int retval;
+	struct zfcp_unit *unit = (struct zfcp_unit *) scpnt->device->hostdata;
+	struct Scsi_Host *scsi_host = scpnt->device->host;
+
+	spin_unlock_irq(scsi_host->host_lock);
+
+	if (!unit) {
+		ZFCP_LOG_NORMAL("bug: Tried reset for nonexistent unit\n");
+		retval = SUCCESS;
+		goto out;
+	}
+	ZFCP_LOG_NORMAL("resetting unit 0x%016Lx\n", unit->fcp_lun);
+
+	/*
+	 * If we do not know whether the unit supports 'logical unit reset'
+	 * then try 'logical unit reset' and proceed with 'target reset'
+	 * if 'logical unit reset' fails.
+	 * If the unit is known not to support 'logical unit reset' then
+	 * skip 'logical unit reset' and try 'target reset' immediately.
+	 */
+	if (!atomic_test_mask(ZFCP_STATUS_UNIT_NOTSUPPUNITRESET,
+			      &unit->status)) {
+		retval =
+		    zfcp_task_management_function(unit, FCP_LOGICAL_UNIT_RESET);
+		if (retval) {
+			ZFCP_LOG_DEBUG("unit reset failed (unit=%p)\n", unit);
+			if (retval == -ENOTSUPP)
+				atomic_set_mask
+				    (ZFCP_STATUS_UNIT_NOTSUPPUNITRESET,
+				     &unit->status);
+			/* fall through and try 'target reset' next */
+		} else {
+			ZFCP_LOG_DEBUG("unit reset succeeded (unit=%p)\n",
+				       unit);
+			/* avoid 'target reset' */
+			retval = SUCCESS;
+			goto out;
+		}
+	}
+	retval = zfcp_task_management_function(unit, FCP_TARGET_RESET);
+	if (retval) {
+		ZFCP_LOG_DEBUG("target reset failed (unit=%p)\n", unit);
+		retval = FAILED;
+	} else {
+		ZFCP_LOG_DEBUG("target reset succeeded (unit=%p)\n", unit);
+		retval = SUCCESS;
+	}
+ out:
+	spin_lock_irq(scsi_host->host_lock);
+	return retval;
+}
+
+static int
+zfcp_task_management_function(struct zfcp_unit *unit, u8 tm_flags)
+{
+	struct zfcp_adapter *adapter = unit->port->adapter;
+	int retval;
+	int status;
+	struct zfcp_fsf_req *fsf_req;
+
+	/* issue task management function */
+	fsf_req = zfcp_fsf_send_fcp_command_task_management
+		(adapter, unit, tm_flags, 0);
+	if (!fsf_req) {
+		ZFCP_LOG_INFO("error: creation of task management request "
+			      "failed for unit 0x%016Lx on port 0x%016Lx on  "
+			      "adapter %s\n", unit->fcp_lun, unit->port->wwpn,
+			      zfcp_get_busid_by_adapter(adapter));
+		retval = -ENOMEM;
+		goto out;
+	}
+
+	retval = zfcp_fsf_req_wait_and_cleanup(fsf_req,
+					       ZFCP_UNINTERRUPTIBLE, &status);
+	/*
+	 * check completion status of task management function
+	 * (status should always be valid since no signals permitted)
+	 */
+	if (status & ZFCP_STATUS_FSFREQ_TMFUNCFAILED)
+		retval = -EIO;
+	else if (status & ZFCP_STATUS_FSFREQ_TMFUNCNOTSUPP)
+		retval = -ENOTSUPP;
+	else
+		retval = 0;
+ out:
+	return retval;
+}
+
+/*
+ * function:	zfcp_scsi_eh_bus_reset_handler
+ *
+ * purpose:
+ *
+ * returns:
+ */
+int
+zfcp_scsi_eh_bus_reset_handler(struct scsi_cmnd *scpnt)
+{
+	int retval = 0;
+	struct zfcp_unit *unit;
+	struct Scsi_Host *scsi_host = scpnt->device->host;
+
+	spin_unlock_irq(scsi_host->host_lock);
+
+	unit = (struct zfcp_unit *) scpnt->device->hostdata;
+	ZFCP_LOG_NORMAL("bus reset because of problems with "
+			"unit 0x%016Lx\n", unit->fcp_lun);
+	zfcp_erp_adapter_reopen(unit->port->adapter, 0);
+	zfcp_erp_wait(unit->port->adapter);
+	retval = SUCCESS;
+
+	spin_lock_irq(scsi_host->host_lock);
+	return retval;
+}
+
+/*
+ * function:	zfcp_scsi_eh_host_reset_handler
+ *
+ * purpose:
+ *
+ * returns:
+ */
+int
+zfcp_scsi_eh_host_reset_handler(struct scsi_cmnd *scpnt)
+{
+	int retval = 0;
+	struct zfcp_unit *unit;
+	struct Scsi_Host *scsi_host = scpnt->device->host;
+
+	spin_unlock_irq(scsi_host->host_lock);
+
+	unit = (struct zfcp_unit *) scpnt->device->hostdata;
+	ZFCP_LOG_NORMAL("host reset because of problems with "
+			"unit 0x%016Lx\n", unit->fcp_lun);
+	zfcp_erp_adapter_reopen(unit->port->adapter, 0);
+	zfcp_erp_wait(unit->port->adapter);
+	retval = SUCCESS;
+
+	spin_lock_irq(scsi_host->host_lock);
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:
+ */
+int
+zfcp_adapter_scsi_register(struct zfcp_adapter *adapter)
+{
+	int retval = 0;
+	static unsigned int unique_id = 0;
+
+	/* register adapter as SCSI host with mid layer of SCSI stack */
+	adapter->scsi_host = scsi_host_alloc(&zfcp_data.scsi_host_template,
+					     sizeof (struct zfcp_adapter *));
+	if (!adapter->scsi_host) {
+		ZFCP_LOG_NORMAL("error: registration with SCSI stack failed "
+				"for adapter %s ",
+				zfcp_get_busid_by_adapter(adapter));
+		retval = -EIO;
+		goto out;
+	}
+	ZFCP_LOG_DEBUG("host registered, scsi_host=%p\n", adapter->scsi_host);
+
+	/* tell the SCSI stack some characteristics of this adapter */
+	adapter->scsi_host->max_id = 1;
+	adapter->scsi_host->max_lun = 1;
+	adapter->scsi_host->max_channel = 0;
+	adapter->scsi_host->unique_id = unique_id++;	/* FIXME */
+	adapter->scsi_host->max_cmd_len = ZFCP_MAX_SCSI_CMND_LENGTH;
+	adapter->scsi_host->transportt = zfcp_transport_template;
+	/*
+	 * Reverse mapping of the host number to avoid race condition
+	 */
+	adapter->scsi_host_no = adapter->scsi_host->host_no;
+
+	/*
+	 * save a pointer to our own adapter data structure within
+	 * hostdata field of SCSI host data structure
+	 */
+	adapter->scsi_host->hostdata[0] = (unsigned long) adapter;
+
+	if (scsi_add_host(adapter->scsi_host, &adapter->ccw_device->dev)) {
+		scsi_host_put(adapter->scsi_host);
+		retval = -EIO;
+		goto out;
+	}
+	atomic_set_mask(ZFCP_STATUS_ADAPTER_REGISTERED, &adapter->status);
+ out:
+	return retval;
+}
+
+/*
+ * function:	
+ *
+ * purpose:	
+ *
+ * returns:
+ */
+void
+zfcp_adapter_scsi_unregister(struct zfcp_adapter *adapter)
+{
+	struct Scsi_Host *shost;
+
+	shost = adapter->scsi_host;
+	if (!shost)
+		return;
+	scsi_remove_host(shost);
+	scsi_host_put(shost);
+	adapter->scsi_host = NULL;
+	adapter->scsi_host_no = 0;
+	atomic_clear_mask(ZFCP_STATUS_ADAPTER_REGISTERED, &adapter->status);
+
+	return;
+}
+
+
+void
+zfcp_fsf_start_scsi_er_timer(struct zfcp_adapter *adapter)
+{
+	adapter->scsi_er_timer.function = zfcp_fsf_scsi_er_timeout_handler;
+	adapter->scsi_er_timer.data = (unsigned long) adapter;
+	adapter->scsi_er_timer.expires = jiffies + ZFCP_SCSI_ER_TIMEOUT;
+	add_timer(&adapter->scsi_er_timer);
+}
+
+/*
+ * Support functions for FC transport class
+ */
+static void
+zfcp_get_port_id(struct scsi_target *starget)
+{
+	struct Scsi_Host *shost = dev_to_shost(starget->dev.parent);
+	struct zfcp_adapter *adapter = (struct zfcp_adapter *)shost->hostdata[0];
+	struct zfcp_port *port;
+	unsigned long flags;
+
+	read_lock_irqsave(&zfcp_data.config_lock, flags);
+	port = zfcp_port_lookup(adapter, starget->channel, starget->id);
+	if (port)
+		fc_starget_port_id(starget) = port->d_id;
+	else
+		fc_starget_port_id(starget) = -1;
+	read_unlock_irqrestore(&zfcp_data.config_lock, flags);
+}
+
+static void
+zfcp_get_port_name(struct scsi_target *starget)
+{
+	struct Scsi_Host *shost = dev_to_shost(starget->dev.parent);
+	struct zfcp_adapter *adapter = (struct zfcp_adapter *)shost->hostdata[0];
+	struct zfcp_port *port;
+	unsigned long flags;
+
+	read_lock_irqsave(&zfcp_data.config_lock, flags);
+	port = zfcp_port_lookup(adapter, starget->channel, starget->id);
+	if (port)
+		fc_starget_port_name(starget) = port->wwpn;
+	else
+		fc_starget_port_name(starget) = -1;
+	read_unlock_irqrestore(&zfcp_data.config_lock, flags);
+}
+
+static void
+zfcp_get_node_name(struct scsi_target *starget)
+{
+	struct Scsi_Host *shost = dev_to_shost(starget->dev.parent);
+	struct zfcp_adapter *adapter = (struct zfcp_adapter *)shost->hostdata[0];
+	struct zfcp_port *port;
+	unsigned long flags;
+
+	read_lock_irqsave(&zfcp_data.config_lock, flags);
+	port = zfcp_port_lookup(adapter, starget->channel, starget->id);
+	if (port)
+		fc_starget_node_name(starget) = port->wwnn;
+	else
+		fc_starget_node_name(starget) = -1;
+	read_unlock_irqrestore(&zfcp_data.config_lock, flags);
+}
+
+struct fc_function_template zfcp_transport_functions = {
+	.get_starget_port_id = zfcp_get_port_id,
+	.get_starget_port_name = zfcp_get_port_name,
+	.get_starget_node_name = zfcp_get_node_name,
+	.show_starget_port_id = 1,
+	.show_starget_port_name = 1,
+	.show_starget_node_name = 1,
+};
+
+/**
+ * ZFCP_DEFINE_SCSI_ATTR
+ * @_name:   name of show attribute
+ * @_format: format string
+ * @_value:  value to print
+ *
+ * Generates attribute for a unit.
+ */
+#define ZFCP_DEFINE_SCSI_ATTR(_name, _format, _value)                    \
+static ssize_t zfcp_sysfs_scsi_##_name##_show(struct device *dev,        \
+                                              char *buf)                 \
+{                                                                        \
+        struct scsi_device *sdev;                                        \
+        struct zfcp_unit *unit;                                          \
+                                                                         \
+        sdev = to_scsi_device(dev);                                      \
+        unit = sdev->hostdata;                                           \
+        return sprintf(buf, _format, _value);                            \
+}                                                                        \
+                                                                         \
+static DEVICE_ATTR(_name, S_IRUGO, zfcp_sysfs_scsi_##_name##_show, NULL);
+
+ZFCP_DEFINE_SCSI_ATTR(hba_id, "%s\n", zfcp_get_busid_by_unit(unit));
+ZFCP_DEFINE_SCSI_ATTR(wwpn, "0x%016llx\n", unit->port->wwpn);
+ZFCP_DEFINE_SCSI_ATTR(fcp_lun, "0x%016llx\n", unit->fcp_lun);
+
+static struct device_attribute *zfcp_sysfs_sdev_attrs[] = {
+	&dev_attr_fcp_lun,
+	&dev_attr_wwpn,
+	&dev_attr_hba_id,
+	NULL
+};
+
+#undef ZFCP_LOG_AREA
diff --git a/drivers/s390/scsi/zfcp_sysfs_adapter.c b/drivers/s390/scsi/zfcp_sysfs_adapter.c
new file mode 100644
index 0000000..ff28ade
--- /dev/null
+++ b/drivers/s390/scsi/zfcp_sysfs_adapter.c
@@ -0,0 +1,298 @@
+/*
+ * linux/drivers/s390/scsi/zfcp_sysfs_adapter.c
+ *
+ * FCP adapter driver for IBM eServer zSeries
+ *
+ * sysfs adapter related routines
+ *
+ * (C) Copyright IBM Corp. 2003, 2004
+ *
+ * Authors:
+ *      Martin Peschke <mpeschke@de.ibm.com>
+ *	Heiko Carstens <heiko.carstens@de.ibm.com>
+ *      Andreas Herrmann <aherrman@de.ibm.com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#define ZFCP_SYSFS_ADAPTER_C_REVISION "$Revision: 1.38 $"
+
+#include "zfcp_ext.h"
+
+#define ZFCP_LOG_AREA                   ZFCP_LOG_AREA_CONFIG
+
+static const char fc_topologies[5][25] = {
+	"<error>",
+	"point-to-point",
+	"fabric",
+	"arbitrated loop",
+	"fabric (virt. adapter)"
+};
+
+/**
+ * ZFCP_DEFINE_ADAPTER_ATTR
+ * @_name:   name of show attribute
+ * @_format: format string
+ * @_value:  value to print
+ *
+ * Generates attributes for an adapter.
+ */
+#define ZFCP_DEFINE_ADAPTER_ATTR(_name, _format, _value)                      \
+static ssize_t zfcp_sysfs_adapter_##_name##_show(struct device *dev,          \
+						 char *buf)                   \
+{                                                                             \
+	struct zfcp_adapter *adapter;                                         \
+                                                                              \
+	adapter = dev_get_drvdata(dev);                                       \
+	return sprintf(buf, _format, _value);                                 \
+}                                                                             \
+                                                                              \
+static DEVICE_ATTR(_name, S_IRUGO, zfcp_sysfs_adapter_##_name##_show, NULL);
+
+ZFCP_DEFINE_ADAPTER_ATTR(status, "0x%08x\n", atomic_read(&adapter->status));
+ZFCP_DEFINE_ADAPTER_ATTR(wwnn, "0x%016llx\n", adapter->wwnn);
+ZFCP_DEFINE_ADAPTER_ATTR(wwpn, "0x%016llx\n", adapter->wwpn);
+ZFCP_DEFINE_ADAPTER_ATTR(s_id, "0x%06x\n", adapter->s_id);
+ZFCP_DEFINE_ADAPTER_ATTR(card_version, "0x%04x\n", adapter->hydra_version);
+ZFCP_DEFINE_ADAPTER_ATTR(lic_version, "0x%08x\n", adapter->fsf_lic_version);
+ZFCP_DEFINE_ADAPTER_ATTR(fc_link_speed, "%d Gb/s\n", adapter->fc_link_speed);
+ZFCP_DEFINE_ADAPTER_ATTR(fc_service_class, "%d\n", adapter->fc_service_class);
+ZFCP_DEFINE_ADAPTER_ATTR(fc_topology, "%s\n",
+			 fc_topologies[adapter->fc_topology]);
+ZFCP_DEFINE_ADAPTER_ATTR(hardware_version, "0x%08x\n",
+			 adapter->hardware_version);
+ZFCP_DEFINE_ADAPTER_ATTR(serial_number, "%17s\n", adapter->serial_number);
+ZFCP_DEFINE_ADAPTER_ATTR(scsi_host_no, "0x%x\n", adapter->scsi_host_no);
+ZFCP_DEFINE_ADAPTER_ATTR(in_recovery, "%d\n", atomic_test_mask
+			 (ZFCP_STATUS_COMMON_ERP_INUSE, &adapter->status));
+
+/**
+ * zfcp_sysfs_port_add_store - add a port to sysfs tree
+ * @dev: pointer to belonging device
+ * @buf: pointer to input buffer
+ * @count: number of bytes in buffer
+ *
+ * Store function of the "port_add" attribute of an adapter.
+ */
+static ssize_t
+zfcp_sysfs_port_add_store(struct device *dev, const char *buf, size_t count)
+{
+	wwn_t wwpn;
+	char *endp;
+	struct zfcp_adapter *adapter;
+	struct zfcp_port *port;
+	int retval = -EINVAL;
+
+	down(&zfcp_data.config_sema);
+
+	adapter = dev_get_drvdata(dev);
+	if (atomic_test_mask(ZFCP_STATUS_COMMON_REMOVE, &adapter->status)) {
+		retval = -EBUSY;
+		goto out;
+	}
+
+	wwpn = simple_strtoull(buf, &endp, 0);
+	if ((endp + 1) < (buf + count))
+		goto out;
+
+	port = zfcp_port_enqueue(adapter, wwpn, 0, 0);
+	if (!port)
+		goto out;
+
+	retval = 0;
+
+	zfcp_erp_port_reopen(port, 0);
+	zfcp_erp_wait(port->adapter);
+	zfcp_port_put(port);
+ out:
+	up(&zfcp_data.config_sema);
+	return retval ? retval : (ssize_t) count;
+}
+
+static DEVICE_ATTR(port_add, S_IWUSR, NULL, zfcp_sysfs_port_add_store);
+
+/**
+ * zfcp_sysfs_port_remove_store - remove a port from sysfs tree
+ * @dev: pointer to belonging device
+ * @buf: pointer to input buffer
+ * @count: number of bytes in buffer
+ *
+ * Store function of the "port_remove" attribute of an adapter.
+ */
+static ssize_t
+zfcp_sysfs_port_remove_store(struct device *dev, const char *buf, size_t count)
+{
+	struct zfcp_adapter *adapter;
+	struct zfcp_port *port;
+	wwn_t wwpn;
+	char *endp;
+	int retval = 0;
+
+	down(&zfcp_data.config_sema);
+
+	adapter = dev_get_drvdata(dev);
+	if (atomic_test_mask(ZFCP_STATUS_COMMON_REMOVE, &adapter->status)) {
+		retval = -EBUSY;
+		goto out;
+	}
+
+	wwpn = simple_strtoull(buf, &endp, 0);
+	if ((endp + 1) < (buf + count)) {
+		retval = -EINVAL;
+		goto out;
+	}
+
+	write_lock_irq(&zfcp_data.config_lock);
+	port = zfcp_get_port_by_wwpn(adapter, wwpn);
+	if (port && (atomic_read(&port->refcount) == 0)) {
+		zfcp_port_get(port);
+		atomic_set_mask(ZFCP_STATUS_COMMON_REMOVE, &port->status);
+		list_move(&port->list, &adapter->port_remove_lh);
+	}
+	else {
+		port = NULL;
+	}
+	write_unlock_irq(&zfcp_data.config_lock);
+
+	if (!port) {
+		retval = -ENXIO;
+		goto out;
+	}
+
+	zfcp_erp_port_shutdown(port, 0);
+	zfcp_erp_wait(adapter);
+	zfcp_port_put(port);
+	zfcp_port_dequeue(port);
+ out:
+	up(&zfcp_data.config_sema);
+	return retval ? retval : (ssize_t) count;
+}
+
+static DEVICE_ATTR(port_remove, S_IWUSR, NULL, zfcp_sysfs_port_remove_store);
+
+/**
+ * zfcp_sysfs_adapter_failed_store - failed state of adapter
+ * @dev: pointer to belonging device
+ * @buf: pointer to input buffer
+ * @count: number of bytes in buffer
+ *
+ * Store function of the "failed" attribute of an adapter.
+ * If a "0" gets written to "failed", error recovery will be
+ * started for the belonging adapter.
+ */
+static ssize_t
+zfcp_sysfs_adapter_failed_store(struct device *dev,
+				const char *buf, size_t count)
+{
+	struct zfcp_adapter *adapter;
+	unsigned int val;
+	char *endp;
+	int retval = 0;
+
+	down(&zfcp_data.config_sema);
+
+	adapter = dev_get_drvdata(dev);
+	if (atomic_test_mask(ZFCP_STATUS_COMMON_REMOVE, &adapter->status)) {
+		retval = -EBUSY;
+		goto out;
+	}
+
+	val = simple_strtoul(buf, &endp, 0);
+	if (((endp + 1) < (buf + count)) || (val != 0)) {
+		retval = -EINVAL;
+		goto out;
+	}
+
+	zfcp_erp_modify_adapter_status(adapter, ZFCP_STATUS_COMMON_RUNNING,
+				       ZFCP_SET);
+	zfcp_erp_adapter_reopen(adapter, ZFCP_STATUS_COMMON_ERP_FAILED);
+	zfcp_erp_wait(adapter);
+ out:
+	up(&zfcp_data.config_sema);
+	return retval ? retval : (ssize_t) count;
+}
+
+/**
+ * zfcp_sysfs_adapter_failed_show - failed state of adapter
+ * @dev: pointer to belonging device
+ * @buf: pointer to input buffer
+ *
+ * Show function of "failed" attribute of adapter. Will be
+ * "0" if adapter is working, otherwise "1".
+ */
+static ssize_t
+zfcp_sysfs_adapter_failed_show(struct device *dev, char *buf)
+{
+	struct zfcp_adapter *adapter;
+
+	adapter = dev_get_drvdata(dev);
+	if (atomic_test_mask(ZFCP_STATUS_COMMON_ERP_FAILED, &adapter->status))
+		return sprintf(buf, "1\n");
+	else
+		return sprintf(buf, "0\n");
+}
+
+static DEVICE_ATTR(failed, S_IWUSR | S_IRUGO, zfcp_sysfs_adapter_failed_show,
+		   zfcp_sysfs_adapter_failed_store);
+
+static struct attribute *zfcp_adapter_attrs[] = {
+	&dev_attr_failed.attr,
+	&dev_attr_in_recovery.attr,
+	&dev_attr_port_remove.attr,
+	&dev_attr_port_add.attr,
+	&dev_attr_wwnn.attr,
+	&dev_attr_wwpn.attr,
+	&dev_attr_s_id.attr,
+	&dev_attr_card_version.attr,
+	&dev_attr_lic_version.attr,
+	&dev_attr_fc_link_speed.attr,
+	&dev_attr_fc_service_class.attr,
+	&dev_attr_fc_topology.attr,
+	&dev_attr_scsi_host_no.attr,
+	&dev_attr_status.attr,
+	&dev_attr_hardware_version.attr,
+	&dev_attr_serial_number.attr,
+	NULL
+};
+
+static struct attribute_group zfcp_adapter_attr_group = {
+	.attrs = zfcp_adapter_attrs,
+};
+
+/**
+ * zfcp_sysfs_create_adapter_files - create sysfs adapter files
+ * @dev: pointer to belonging device
+ *
+ * Create all attributes of the sysfs representation of an adapter.
+ */
+int
+zfcp_sysfs_adapter_create_files(struct device *dev)
+{
+	return sysfs_create_group(&dev->kobj, &zfcp_adapter_attr_group);
+}
+
+/**
+ * zfcp_sysfs_remove_adapter_files - remove sysfs adapter files
+ * @dev: pointer to belonging device
+ *
+ * Remove all attributes of the sysfs representation of an adapter.
+ */
+void
+zfcp_sysfs_adapter_remove_files(struct device *dev)
+{
+	sysfs_remove_group(&dev->kobj, &zfcp_adapter_attr_group);
+}
+
+#undef ZFCP_LOG_AREA
diff --git a/drivers/s390/scsi/zfcp_sysfs_driver.c b/drivers/s390/scsi/zfcp_sysfs_driver.c
new file mode 100644
index 0000000..77a5e2d
--- /dev/null
+++ b/drivers/s390/scsi/zfcp_sysfs_driver.c
@@ -0,0 +1,135 @@
+/*
+ * linux/drivers/s390/scsi/zfcp_sysfs_driver.c
+ *
+ * FCP adapter driver for IBM eServer zSeries
+ *
+ * sysfs driver related routines
+ *
+ * (C) Copyright IBM Corp. 2003, 2004
+ *
+ * Authors:
+ *      Martin Peschke <mpeschke@de.ibm.com>
+ *	Heiko Carstens <heiko.carstens@de.ibm.com>
+ *      Andreas Herrmann <aherrman@de.ibm.com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#define ZFCP_SYSFS_DRIVER_C_REVISION "$Revision: 1.17 $"
+
+#include "zfcp_ext.h"
+
+#define ZFCP_LOG_AREA                   ZFCP_LOG_AREA_CONFIG
+
+/**
+ * ZFCP_DEFINE_DRIVER_ATTR - define for all loglevels sysfs attributes
+ * @_name:       name of attribute
+ * @_define:     name of ZFCP loglevel define
+ *
+ * Generates store function for a sysfs loglevel attribute of zfcp driver.
+ */
+#define ZFCP_DEFINE_DRIVER_ATTR(_name, _define)                               \
+static ssize_t zfcp_sysfs_loglevel_##_name##_store(struct device_driver *drv, \
+						   const char *buf,           \
+						   size_t count)              \
+{                                                                             \
+	unsigned int loglevel;                                                \
+	unsigned int new_loglevel;                                            \
+	char *endp;                                                           \
+                                                                              \
+	new_loglevel = simple_strtoul(buf, &endp, 0);                         \
+	if ((endp + 1) < (buf + count))                                       \
+		return -EINVAL;                                               \
+	if (new_loglevel > 3)                                                 \
+		return -EINVAL;                                               \
+	down(&zfcp_data.config_sema);                                         \
+	loglevel = atomic_read(&zfcp_data.loglevel);                          \
+	loglevel &= ~((unsigned int) 0xf << (ZFCP_LOG_AREA_##_define << 2));  \
+	loglevel |= new_loglevel << (ZFCP_LOG_AREA_##_define << 2);           \
+	atomic_set(&zfcp_data.loglevel, loglevel);                            \
+	up(&zfcp_data.config_sema);                                           \
+	return count;                                                         \
+}                                                                             \
+                                                                              \
+static ssize_t zfcp_sysfs_loglevel_##_name##_show(struct device_driver *dev,  \
+						  char *buf)                  \
+{                                                                             \
+	return sprintf(buf,"%d\n", (unsigned int)                             \
+		       ZFCP_GET_LOG_VALUE(ZFCP_LOG_AREA_##_define));          \
+}                                                                             \
+                                                                              \
+static DRIVER_ATTR(loglevel_##_name, S_IWUSR | S_IRUGO,                       \
+		   zfcp_sysfs_loglevel_##_name##_show,                        \
+		   zfcp_sysfs_loglevel_##_name##_store);
+
+ZFCP_DEFINE_DRIVER_ATTR(other, OTHER);
+ZFCP_DEFINE_DRIVER_ATTR(scsi, SCSI);
+ZFCP_DEFINE_DRIVER_ATTR(fsf, FSF);
+ZFCP_DEFINE_DRIVER_ATTR(config, CONFIG);
+ZFCP_DEFINE_DRIVER_ATTR(cio, CIO);
+ZFCP_DEFINE_DRIVER_ATTR(qdio, QDIO);
+ZFCP_DEFINE_DRIVER_ATTR(erp, ERP);
+ZFCP_DEFINE_DRIVER_ATTR(fc, FC);
+
+static ssize_t zfcp_sysfs_version_show(struct device_driver *dev,
+					      char *buf)
+{
+	return sprintf(buf, "%s\n", zfcp_data.driver_version);
+}
+
+static DRIVER_ATTR(version, S_IRUGO, zfcp_sysfs_version_show, NULL);
+
+static struct attribute *zfcp_driver_attrs[] = {
+	&driver_attr_loglevel_other.attr,
+	&driver_attr_loglevel_scsi.attr,
+	&driver_attr_loglevel_fsf.attr,
+	&driver_attr_loglevel_config.attr,
+	&driver_attr_loglevel_cio.attr,
+	&driver_attr_loglevel_qdio.attr,
+	&driver_attr_loglevel_erp.attr,
+	&driver_attr_loglevel_fc.attr,
+	&driver_attr_version.attr,
+	NULL
+};
+
+static struct attribute_group zfcp_driver_attr_group = {
+	.attrs = zfcp_driver_attrs,
+};
+
+/**
+ * zfcp_sysfs_create_driver_files - create sysfs driver files
+ * @dev: pointer to belonging device
+ *
+ * Create all sysfs attributes of the zfcp device driver
+ */
+int
+zfcp_sysfs_driver_create_files(struct device_driver *drv)
+{
+	return sysfs_create_group(&drv->kobj, &zfcp_driver_attr_group);
+}
+
+/**
+ * zfcp_sysfs_remove_driver_files - remove sysfs driver files
+ * @dev: pointer to belonging device
+ *
+ * Remove all sysfs attributes of the zfcp device driver
+ */
+void
+zfcp_sysfs_driver_remove_files(struct device_driver *drv)
+{
+	sysfs_remove_group(&drv->kobj, &zfcp_driver_attr_group);
+}
+
+#undef ZFCP_LOG_AREA
diff --git a/drivers/s390/scsi/zfcp_sysfs_port.c b/drivers/s390/scsi/zfcp_sysfs_port.c
new file mode 100644
index 0000000..6aafb2a
--- /dev/null
+++ b/drivers/s390/scsi/zfcp_sysfs_port.c
@@ -0,0 +1,311 @@
+/*
+ * linux/drivers/s390/scsi/zfcp_sysfs_port.c
+ *
+ * FCP adapter driver for IBM eServer zSeries
+ *
+ * sysfs port related routines
+ *
+ * (C) Copyright IBM Corp. 2003, 2004
+ *
+ * Authors:
+ *      Martin Peschke <mpeschke@de.ibm.com>
+ *	Heiko Carstens <heiko.carstens@de.ibm.com>
+ *      Andreas Herrmann <aherrman@de.ibm.com>
+ *      Volker Sameske <sameske@de.ibm.com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#define ZFCP_SYSFS_PORT_C_REVISION "$Revision: 1.47 $"
+
+#include "zfcp_ext.h"
+
+#define ZFCP_LOG_AREA                   ZFCP_LOG_AREA_CONFIG
+
+/**
+ * zfcp_sysfs_port_release - gets called when a struct device port is released
+ * @dev: pointer to belonging device
+ */
+void
+zfcp_sysfs_port_release(struct device *dev)
+{
+	kfree(dev);
+}
+
+/**
+ * ZFCP_DEFINE_PORT_ATTR
+ * @_name:   name of show attribute
+ * @_format: format string
+ * @_value:  value to print
+ *
+ * Generates attributes for a port.
+ */
+#define ZFCP_DEFINE_PORT_ATTR(_name, _format, _value)                    \
+static ssize_t zfcp_sysfs_port_##_name##_show(struct device *dev,        \
+                                              char *buf)                 \
+{                                                                        \
+        struct zfcp_port *port;                                          \
+                                                                         \
+        port = dev_get_drvdata(dev);                                     \
+        return sprintf(buf, _format, _value);                            \
+}                                                                        \
+                                                                         \
+static DEVICE_ATTR(_name, S_IRUGO, zfcp_sysfs_port_##_name##_show, NULL);
+
+ZFCP_DEFINE_PORT_ATTR(status, "0x%08x\n", atomic_read(&port->status));
+ZFCP_DEFINE_PORT_ATTR(wwnn, "0x%016llx\n", port->wwnn);
+ZFCP_DEFINE_PORT_ATTR(d_id, "0x%06x\n", port->d_id);
+ZFCP_DEFINE_PORT_ATTR(scsi_id, "0x%x\n", port->scsi_id);
+ZFCP_DEFINE_PORT_ATTR(in_recovery, "%d\n", atomic_test_mask
+		      (ZFCP_STATUS_COMMON_ERP_INUSE, &port->status));
+ZFCP_DEFINE_PORT_ATTR(access_denied, "%d\n", atomic_test_mask
+		      (ZFCP_STATUS_COMMON_ACCESS_DENIED, &port->status));
+
+/**
+ * zfcp_sysfs_unit_add_store - add a unit to sysfs tree
+ * @dev: pointer to belonging device
+ * @buf: pointer to input buffer
+ * @count: number of bytes in buffer
+ *
+ * Store function of the "unit_add" attribute of a port.
+ */
+static ssize_t
+zfcp_sysfs_unit_add_store(struct device *dev, const char *buf, size_t count)
+{
+	fcp_lun_t fcp_lun;
+	char *endp;
+	struct zfcp_port *port;
+	struct zfcp_unit *unit;
+	int retval = -EINVAL;
+
+	down(&zfcp_data.config_sema);
+
+	port = dev_get_drvdata(dev);
+	if (atomic_test_mask(ZFCP_STATUS_COMMON_REMOVE, &port->status)) {
+		retval = -EBUSY;
+		goto out;
+	}
+
+	fcp_lun = simple_strtoull(buf, &endp, 0);
+	if ((endp + 1) < (buf + count))
+		goto out;
+
+	unit = zfcp_unit_enqueue(port, fcp_lun);
+	if (!unit)
+		goto out;
+
+	retval = 0;
+
+	zfcp_erp_unit_reopen(unit, 0);
+	zfcp_erp_wait(unit->port->adapter);
+	zfcp_unit_put(unit);
+ out:
+	up(&zfcp_data.config_sema);
+	return retval ? retval : (ssize_t) count;
+}
+
+static DEVICE_ATTR(unit_add, S_IWUSR, NULL, zfcp_sysfs_unit_add_store);
+
+/**
+ * zfcp_sysfs_unit_remove_store - remove a unit from sysfs tree
+ * @dev: pointer to belonging device
+ * @buf: pointer to input buffer
+ * @count: number of bytes in buffer
+ */
+static ssize_t
+zfcp_sysfs_unit_remove_store(struct device *dev, const char *buf, size_t count)
+{
+	struct zfcp_port *port;
+	struct zfcp_unit *unit;
+	fcp_lun_t fcp_lun;
+	char *endp;
+	int retval = 0;
+
+	down(&zfcp_data.config_sema);
+
+	port = dev_get_drvdata(dev);
+	if (atomic_test_mask(ZFCP_STATUS_COMMON_REMOVE, &port->status)) {
+		retval = -EBUSY;
+		goto out;
+	}
+
+	fcp_lun = simple_strtoull(buf, &endp, 0);
+	if ((endp + 1) < (buf + count)) {
+		retval = -EINVAL;
+		goto out;
+	}
+
+	write_lock_irq(&zfcp_data.config_lock);
+	unit = zfcp_get_unit_by_lun(port, fcp_lun);
+	if (unit && (atomic_read(&unit->refcount) == 0)) {
+		zfcp_unit_get(unit);
+		atomic_set_mask(ZFCP_STATUS_COMMON_REMOVE, &unit->status);
+		list_move(&unit->list, &port->unit_remove_lh);
+	}
+	else {
+		unit = NULL;
+	}
+	write_unlock_irq(&zfcp_data.config_lock);
+
+	if (!unit) {
+		retval = -ENXIO;
+		goto out;
+	}
+
+	zfcp_erp_unit_shutdown(unit, 0);
+	zfcp_erp_wait(unit->port->adapter);
+	zfcp_unit_put(unit);
+	zfcp_unit_dequeue(unit);
+ out:
+	up(&zfcp_data.config_sema);
+	return retval ? retval : (ssize_t) count;
+}
+
+static DEVICE_ATTR(unit_remove, S_IWUSR, NULL, zfcp_sysfs_unit_remove_store);
+
+/**
+ * zfcp_sysfs_port_failed_store - failed state of port
+ * @dev: pointer to belonging device
+ * @buf: pointer to input buffer
+ * @count: number of bytes in buffer
+ *
+ * Store function of the "failed" attribute of a port.
+ * If a "0" gets written to "failed", error recovery will be
+ * started for the belonging port.
+ */
+static ssize_t
+zfcp_sysfs_port_failed_store(struct device *dev, const char *buf, size_t count)
+{
+	struct zfcp_port *port;
+	unsigned int val;
+	char *endp;
+	int retval = 0;
+
+	down(&zfcp_data.config_sema);
+
+	port = dev_get_drvdata(dev);
+	if (atomic_test_mask(ZFCP_STATUS_COMMON_REMOVE, &port->status)) {
+		retval = -EBUSY;
+		goto out;
+	}
+
+	val = simple_strtoul(buf, &endp, 0);
+	if (((endp + 1) < (buf + count)) || (val != 0)) {
+		retval = -EINVAL;
+		goto out;
+	}
+
+	zfcp_erp_modify_port_status(port, ZFCP_STATUS_COMMON_RUNNING, ZFCP_SET);
+	zfcp_erp_port_reopen(port, ZFCP_STATUS_COMMON_ERP_FAILED);
+	zfcp_erp_wait(port->adapter);
+ out:
+	up(&zfcp_data.config_sema);
+	return retval ? retval : (ssize_t) count;
+}
+
+/**
+ * zfcp_sysfs_port_failed_show - failed state of port
+ * @dev: pointer to belonging device
+ * @buf: pointer to input buffer
+ *
+ * Show function of "failed" attribute of port. Will be
+ * "0" if port is working, otherwise "1".
+ */
+static ssize_t
+zfcp_sysfs_port_failed_show(struct device *dev, char *buf)
+{
+	struct zfcp_port *port;
+
+	port = dev_get_drvdata(dev);
+	if (atomic_test_mask(ZFCP_STATUS_COMMON_ERP_FAILED, &port->status))
+		return sprintf(buf, "1\n");
+	else
+		return sprintf(buf, "0\n");
+}
+
+static DEVICE_ATTR(failed, S_IWUSR | S_IRUGO, zfcp_sysfs_port_failed_show,
+		   zfcp_sysfs_port_failed_store);
+
+/**
+ * zfcp_port_common_attrs
+ * sysfs attributes that are common for all kind of fc ports.
+ */
+static struct attribute *zfcp_port_common_attrs[] = {
+	&dev_attr_failed.attr,
+	&dev_attr_in_recovery.attr,
+	&dev_attr_status.attr,
+	&dev_attr_wwnn.attr,
+	&dev_attr_d_id.attr,
+	&dev_attr_access_denied.attr,
+	NULL
+};
+
+static struct attribute_group zfcp_port_common_attr_group = {
+	.attrs = zfcp_port_common_attrs,
+};
+
+/**
+ * zfcp_port_no_ns_attrs
+ * sysfs attributes not to be used for nameserver ports.
+ */
+static struct attribute *zfcp_port_no_ns_attrs[] = {
+	&dev_attr_unit_add.attr,
+	&dev_attr_unit_remove.attr,
+	&dev_attr_scsi_id.attr,
+	NULL
+};
+
+static struct attribute_group zfcp_port_no_ns_attr_group = {
+	.attrs = zfcp_port_no_ns_attrs,
+};
+
+/**
+ * zfcp_sysfs_port_create_files - create sysfs port files
+ * @dev: pointer to belonging device
+ *
+ * Create all attributes of the sysfs representation of a port.
+ */
+int
+zfcp_sysfs_port_create_files(struct device *dev, u32 flags)
+{
+	int retval;
+
+	retval = sysfs_create_group(&dev->kobj, &zfcp_port_common_attr_group);
+
+	if ((flags & ZFCP_STATUS_PORT_WKA) || retval)
+		return retval;
+
+	retval = sysfs_create_group(&dev->kobj, &zfcp_port_no_ns_attr_group);
+	if (retval)
+		sysfs_remove_group(&dev->kobj, &zfcp_port_common_attr_group);
+
+	return retval;
+}
+
+/**
+ * zfcp_sysfs_port_remove_files - remove sysfs port files
+ * @dev: pointer to belonging device
+ *
+ * Remove all attributes of the sysfs representation of a port.
+ */
+void
+zfcp_sysfs_port_remove_files(struct device *dev, u32 flags)
+{
+	sysfs_remove_group(&dev->kobj, &zfcp_port_common_attr_group);
+	if (!(flags & ZFCP_STATUS_PORT_WKA))
+		sysfs_remove_group(&dev->kobj, &zfcp_port_no_ns_attr_group);
+}
+
+#undef ZFCP_LOG_AREA
diff --git a/drivers/s390/scsi/zfcp_sysfs_unit.c b/drivers/s390/scsi/zfcp_sysfs_unit.c
new file mode 100644
index 0000000..87c0b461
--- /dev/null
+++ b/drivers/s390/scsi/zfcp_sysfs_unit.c
@@ -0,0 +1,179 @@
+/*
+ * linux/drivers/s390/scsi/zfcp_sysfs_unit.c
+ *
+ * FCP adapter driver for IBM eServer zSeries
+ *
+ * sysfs unit related routines
+ *
+ * (C) Copyright IBM Corp. 2003, 2004
+ *
+ * Authors:
+ *      Martin Peschke <mpeschke@de.ibm.com>
+ *	Heiko Carstens <heiko.carstens@de.ibm.com>
+ *      Andreas Herrmann <aherrman@de.ibm.com>
+ *      Volker Sameske <sameske@de.ibm.com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#define ZFCP_SYSFS_UNIT_C_REVISION "$Revision: 1.30 $"
+
+#include "zfcp_ext.h"
+
+#define ZFCP_LOG_AREA                   ZFCP_LOG_AREA_CONFIG
+
+/**
+ * zfcp_sysfs_unit_release - gets called when a struct device unit is released
+ * @dev: pointer to belonging device
+ */
+void
+zfcp_sysfs_unit_release(struct device *dev)
+{
+	kfree(dev);
+}
+
+/**
+ * ZFCP_DEFINE_UNIT_ATTR
+ * @_name:   name of show attribute
+ * @_format: format string
+ * @_value:  value to print
+ *
+ * Generates attribute for a unit.
+ */
+#define ZFCP_DEFINE_UNIT_ATTR(_name, _format, _value)                    \
+static ssize_t zfcp_sysfs_unit_##_name##_show(struct device *dev,        \
+                                              char *buf)                 \
+{                                                                        \
+        struct zfcp_unit *unit;                                          \
+                                                                         \
+        unit = dev_get_drvdata(dev);                                     \
+        return sprintf(buf, _format, _value);                            \
+}                                                                        \
+                                                                         \
+static DEVICE_ATTR(_name, S_IRUGO, zfcp_sysfs_unit_##_name##_show, NULL);
+
+ZFCP_DEFINE_UNIT_ATTR(status, "0x%08x\n", atomic_read(&unit->status));
+ZFCP_DEFINE_UNIT_ATTR(scsi_lun, "0x%x\n", unit->scsi_lun);
+ZFCP_DEFINE_UNIT_ATTR(in_recovery, "%d\n", atomic_test_mask
+		      (ZFCP_STATUS_COMMON_ERP_INUSE, &unit->status));
+ZFCP_DEFINE_UNIT_ATTR(access_denied, "%d\n", atomic_test_mask
+		      (ZFCP_STATUS_COMMON_ACCESS_DENIED, &unit->status));
+ZFCP_DEFINE_UNIT_ATTR(access_shared, "%d\n", atomic_test_mask
+		      (ZFCP_STATUS_UNIT_SHARED, &unit->status));
+ZFCP_DEFINE_UNIT_ATTR(access_readonly, "%d\n", atomic_test_mask
+		      (ZFCP_STATUS_UNIT_READONLY, &unit->status));
+
+/**
+ * zfcp_sysfs_unit_failed_store - failed state of unit
+ * @dev: pointer to belonging device
+ * @buf: pointer to input buffer
+ * @count: number of bytes in buffer
+ *
+ * Store function of the "failed" attribute of a unit.
+ * If a "0" gets written to "failed", error recovery will be
+ * started for the belonging unit.
+ */
+static ssize_t
+zfcp_sysfs_unit_failed_store(struct device *dev, const char *buf, size_t count)
+{
+	struct zfcp_unit *unit;
+	unsigned int val;
+	char *endp;
+	int retval = 0;
+
+	down(&zfcp_data.config_sema);
+	unit = dev_get_drvdata(dev);
+	if (atomic_test_mask(ZFCP_STATUS_COMMON_REMOVE, &unit->status)) {
+		retval = -EBUSY;
+		goto out;
+	}
+
+	val = simple_strtoul(buf, &endp, 0);
+	if (((endp + 1) < (buf + count)) || (val != 0)) {
+		retval = -EINVAL;
+		goto out;
+	}
+
+	zfcp_erp_modify_unit_status(unit, ZFCP_STATUS_COMMON_RUNNING, ZFCP_SET);
+	zfcp_erp_unit_reopen(unit, ZFCP_STATUS_COMMON_ERP_FAILED);
+	zfcp_erp_wait(unit->port->adapter);
+ out:
+	up(&zfcp_data.config_sema);
+	return retval ? retval : (ssize_t) count;
+}
+
+/**
+ * zfcp_sysfs_unit_failed_show - failed state of unit
+ * @dev: pointer to belonging device
+ * @buf: pointer to input buffer
+ *
+ * Show function of "failed" attribute of unit. Will be
+ * "0" if unit is working, otherwise "1".
+ */
+static ssize_t
+zfcp_sysfs_unit_failed_show(struct device *dev, char *buf)
+{
+	struct zfcp_unit *unit;
+
+	unit = dev_get_drvdata(dev);
+	if (atomic_test_mask(ZFCP_STATUS_COMMON_ERP_FAILED, &unit->status))
+		return sprintf(buf, "1\n");
+	else
+		return sprintf(buf, "0\n");
+}
+
+static DEVICE_ATTR(failed, S_IWUSR | S_IRUGO, zfcp_sysfs_unit_failed_show,
+		   zfcp_sysfs_unit_failed_store);
+
+static struct attribute *zfcp_unit_attrs[] = {
+	&dev_attr_scsi_lun.attr,
+	&dev_attr_failed.attr,
+	&dev_attr_in_recovery.attr,
+	&dev_attr_status.attr,
+	&dev_attr_access_denied.attr,
+	&dev_attr_access_shared.attr,
+	&dev_attr_access_readonly.attr,
+	NULL
+};
+
+static struct attribute_group zfcp_unit_attr_group = {
+	.attrs = zfcp_unit_attrs,
+};
+
+/** 
+ * zfcp_sysfs_create_unit_files - create sysfs unit files
+ * @dev: pointer to belonging device
+ *
+ * Create all attributes of the sysfs representation of a unit.
+ */
+int
+zfcp_sysfs_unit_create_files(struct device *dev)
+{
+	return sysfs_create_group(&dev->kobj, &zfcp_unit_attr_group);
+}
+
+/** 
+ * zfcp_sysfs_remove_unit_files - remove sysfs unit files
+ * @dev: pointer to belonging device
+ *
+ * Remove all attributes of the sysfs representation of a unit.
+ */
+void
+zfcp_sysfs_unit_remove_files(struct device *dev)
+{
+	sysfs_remove_group(&dev->kobj, &zfcp_unit_attr_group);
+}
+
+#undef ZFCP_LOG_AREA