[BACK]Return to zfcp_aux.c CVS log [TXT][DIR] Up to [Development] / linux-2.6-xfs / drivers / s390 / scsi

File: [Development] / linux-2.6-xfs / drivers / s390 / scsi / zfcp_aux.c (download)

Revision 1.1, Tue Dec 30 23:58:53 2003 UTC (13 years, 10 months ago) by cattelan
Branch: MAIN

Initial Import 2.6.0

/*
 *
 * linux/drivers/s390/scsi/zfcp_aux.c
 *
 * FCP adapter driver for IBM eServer zSeries
 *
 * Copyright 2002 IBM Corporation
 * Author(s): Martin Peschke <mpeschke@de.ibm.com>
 *            Raimund Schroeder <raimund.schroeder@de.ibm.com>
 *            Aron Zeh <arzeh@de.ibm.com>
 *            Wolfgang Taphorn <taphorn@de.ibm.com>
 *            Stefan Bader <stefan.bader@de.ibm.com>
 *            Heiko Carstens <heiko.carstens@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.
 */

/* this drivers version (do not edit !!! generated and updated by cvs) */
#define ZFCP_AUX_REVISION "$Revision: 1.65 $"

/********************** INCLUDES *********************************************/

#include <linux/init.h>
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/ctype.h>
#include <linux/mm.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/version.h>
#include <linux/list.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/time.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/workqueue.h>

#include "zfcp_ext.h"

#include <asm/semaphore.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/ebcdic.h>
#include <asm/cpcmd.h>		/* Debugging only */
#include <asm/processor.h>	/* Debugging only */

/* accumulated log level (module parameter) */
static u32 loglevel = ZFCP_LOG_LEVEL_DEFAULTS;
/*********************** FUNCTION PROTOTYPES *********************************/

/* written against the module interface */
static int __init  zfcp_module_init(void);
static void __exit zfcp_module_exit(void);

int zfcp_reboot_handler(struct notifier_block *, unsigned long, void *);

/* FCP related */
static void zfcp_nameserver_request_handler(struct zfcp_fsf_req *);

/* miscellaneous */
#ifdef ZFCP_STAT_REQSIZES
static int zfcp_statistics_init_all(void);
static int zfcp_statistics_clear_all(void);
static int zfcp_statistics_clear(struct list_head *);
static int zfcp_statistics_new(struct list_head *, u32);
#endif

/*********************** KERNEL/MODULE PARAMETERS  ***************************/

/* declare driver module init/cleanup functions */
module_init(zfcp_module_init);
module_exit(zfcp_module_exit);

MODULE_AUTHOR("Heiko Carstens <heiko.carstens@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");
/* what this driver module is about */
MODULE_DESCRIPTION
    ("FCP (SCSI over Fibre Channel) HBA driver for IBM eServer zSeries");
MODULE_LICENSE("GPL");
/* log level may be provided as a module parameter */
module_param(loglevel, uint, 0);
/* short explaination of the previous module parameter */
MODULE_PARM_DESC(loglevel,
		 "log levels, 8 nibbles: "
		 "(unassigned) ERP QDIO DIO 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 % 32) != 31)
		printk("\n");
}

/****************************************************************/
/************** Uncategorised Functions *************************/
/****************************************************************/

#define ZFCP_LOG_AREA			ZFCP_LOG_AREA_OTHER
#define ZFCP_LOG_AREA_PREFIX		ZFCP_LOG_AREA_PREFIX_OTHER

#ifdef ZFCP_STAT_REQSIZES

static int
zfcp_statistics_clear(struct list_head *head)
{
	int retval = 0;
	unsigned long flags;
	struct zfcp_statistics *stat, *tmp;

	write_lock_irqsave(&zfcp_data.stat_lock, flags);
	list_for_each_entry_safe(stat, tmp, head, list) {
		list_del(&stat->list);
		kfree(stat);
	}
	write_unlock_irqrestore(&zfcp_data.stat_lock, flags);

	return retval;
}

/* Add new statistics entry */
static int
zfcp_statistics_new(struct list_head *head, u32 num)
{
	int retval = 0;
	struct zfcp_statistics *stat;

	stat = kmalloc(sizeof (struct zfcp_statistics), GFP_ATOMIC);
	if (stat) {
		memset(stat, 0, sizeof (struct zfcp_statistics));
		stat->num = num;
		stat->occurrence = 1;
		list_add_tail(&stat->list, head);
	} else
		zfcp_data.stat_errors++;

	return retval;
}

int
zfcp_statistics_inc(struct list_head *head, u32 num)
{
	int retval = 0;
	unsigned long flags;
	struct zfcp_statistics *stat;

	write_lock_irqsave(&zfcp_data.stat_lock, flags);
	list_for_each_entry(stat, head, list) {
		if (stat->num == num) {
			stat->occurrence++;
			goto unlock;
		}
	}
	/* occurrence must be initialized to 1 */
	zfcp_statistics_new(head, num);
 unlock:
	write_unlock_irqrestore(&zfcp_data.stat_lock, flags);
	return retval;
}

static int
zfcp_statistics_init_all(void)
{
	int retval = 0;

	rwlock_init(&zfcp_data.stat_lock);
	INIT_LIST_HEAD(&zfcp_data.read_req_head);
	INIT_LIST_HEAD(&zfcp_data.write_req_head);
	INIT_LIST_HEAD(&zfcp_data.read_sg_head);
	INIT_LIST_HEAD(&zfcp_data.write_sg_head);
	INIT_LIST_HEAD(&zfcp_data.read_sguse_head);
	INIT_LIST_HEAD(&zfcp_data.write_sguse_head);
	return retval;
}

static int
zfcp_statistics_clear_all(void)
{
	int retval = 0;

	zfcp_statistics_clear(&zfcp_data.read_req_head);
	zfcp_statistics_clear(&zfcp_data.write_req_head);
	zfcp_statistics_clear(&zfcp_data.read_sg_head);
	zfcp_statistics_clear(&zfcp_data.write_sg_head);
	zfcp_statistics_clear(&zfcp_data.read_sguse_head);
	zfcp_statistics_clear(&zfcp_data.write_sguse_head);
	return retval;
}

#endif /* ZFCP_STAT_REQSIZES */

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)
{
#ifdef ZFCP_DEBUG_COMMANDS
	struct zfcp_adapter *adapter = fsf_req->adapter;
	Scsi_Cmnd *scsi_cmnd;
	int level = 3;
	int i;
	unsigned long flags;

	write_lock_irqsave(&adapter->cmd_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));
		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));
	}
	write_unlock_irqrestore(&adapter->cmd_dbf_lock, flags);
#endif
}

void
zfcp_cmd_dbf_event_scsi(const char *text, Scsi_Cmnd * scsi_cmnd)
{
#ifdef ZFCP_DEBUG_COMMANDS
	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);
	write_lock_irqsave(&adapter->cmd_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));
	if (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, "");
	}
	write_unlock_irqrestore(&adapter->cmd_dbf_lock, flags);
#endif
}

void
zfcp_in_els_dbf_event(struct zfcp_adapter *adapter, const char *text,
		      struct fsf_status_read_buffer *status_buffer, int length)
{
#ifdef ZFCP_DEBUG_INCOMING_ELS
	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));
#endif
}

static int __init
zfcp_module_init(void)
{

	int retval = 0;

	atomic_set(&zfcp_data.loglevel, loglevel);

	ZFCP_LOG_DEBUG(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");

	ZFCP_LOG_TRACE("Start Address of module: 0x%lx\n",
		       (unsigned long) &zfcp_module_init);

	/* 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);

#ifdef ZFCP_STAT_REQSIZES
	zfcp_statistics_init_all();
#endif

	/* Initialise proc semaphores */
	sema_init(&zfcp_data.config_sema, 1);

	/* initialise configuration rw lock */
	rwlock_init(&zfcp_data.config_lock);

	zfcp_data.reboot_notifier.notifier_call = zfcp_reboot_handler;
	register_reboot_notifier(&zfcp_data.reboot_notifier);

	/* 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("Registering with common I/O layer failed.\n");
		goto out_ccw_register;
	}
	goto out;

 out_ccw_register:
#ifdef ZFCP_STAT_REQSIZES
	zfcp_statistics_clear_all();
#endif

 out:
	return retval;
}

static void __exit
zfcp_module_exit(void)
{
	unregister_reboot_notifier(&zfcp_data.reboot_notifier);
	zfcp_ccw_unregister();
#ifdef ZFCP_STAT_REQSIZES
	zfcp_statistics_clear_all();
#endif
	ZFCP_LOG_DEBUG("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
}

/*
 * This function is called automatically by the kernel whenever a reboot or a 
 * shut-down is initiated and zfcp is still loaded
 *
 * locks:       zfcp_data.config_sema is taken prior to shutting down the module
 *              and removing all structures
 * returns:     NOTIFY_DONE in all cases
 */
int
zfcp_reboot_handler(struct notifier_block *notifier, unsigned long code,
		    void *ptr)
{
	int retval = NOTIFY_DONE;

	/* block access to config (for rest of lifetime of this Linux) */
	down(&zfcp_data.config_sema);
	zfcp_erp_adapter_shutdown_all();

	return retval;
}

#undef ZFCP_LOG_AREA
#undef ZFCP_LOG_AREA_PREFIX

/****************************************************************/
/****** Functions for configuration/set-up of structures ********/
/****************************************************************/

#define ZFCP_LOG_AREA			ZFCP_LOG_AREA_CONFIG
#define ZFCP_LOG_AREA_PREFIX		ZFCP_LOG_AREA_PREFIX_CONFIG

#ifndef MODULE
/* zfcp_loglevel boot_parameter */
static int __init
zfcp_loglevel_setup(char *str)
{
	loglevel = simple_strtoul(str, NULL, 0);
	ZFCP_LOG_TRACE("loglevel is 0x%x\n", loglevel);
	return 1;		/* why just 1? */
}

__setup("zfcp_loglevel=", zfcp_loglevel_setup);
#endif				/* not MODULE */

/**
 * 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: lun to search for
 * Traverses list of all units of a port and returns pointer to a unit
 * if lun of a unit matches.
 */

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 unit in unit list of port by fcp lun
 * @adapter: pointer to adapter to search for port
 * @wwpn: wwpn to search for
 * Traverses list of all ports of an adapter and returns a pointer to a port
 * if wwpn of a port matches.
 */

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_test_mask(ZFCP_STATUS_COMMON_REMOVE, &port->status))
		{
			found = 1;
			break;
		}
	}
	return found ? port : NULL;
}

/*
 * Enqueues a logical unit at the end of the unit list associated with the 
 * specified port. Also sets up some unit internal structures.
 *
 * returns:	pointer to unit with a usecount of 1 if a new unit was
 *              successfully enqueued
 *              NULL otherwise
 * locks:	config_sema must be held to serialise changes to the unit list
 */
struct zfcp_unit *
zfcp_unit_enqueue(struct zfcp_port *port, fcp_lun_t fcp_lun)
{
	struct zfcp_unit *unit;

	/*
	 * 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;
	/*
	 * FIXME: reuse of scsi_luns!
	 */
	unit->scsi_lun = port->max_scsi_lun + 1;
	unit->fcp_lun = fcp_lun;
	unit->common_magic = ZFCP_MAGIC;
	unit->specific_magic = ZFCP_MAGIC_UNIT;

	/* 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)) {
		/*
		 * failed to create all sysfs attributes, therefore the unit
		 * must be put on the unit_remove listhead of the port where
		 * the release function expects it.
		 */
		write_lock_irq(&zfcp_data.config_lock);
		list_add_tail(&unit->list, &port->unit_remove_lh);
		write_unlock_irq(&zfcp_data.config_lock);
		device_unregister(&unit->sysfs_device);
		return NULL;
	}

	/*
	 * update max SCSI LUN of logical units attached to parent remote port
	 */
	port->max_scsi_lun++;

	/*
	 * update max SCSI LUN of logical units attached to parent adapter
	 */
	if (port->adapter->max_scsi_lun < port->max_scsi_lun)
		port->adapter->max_scsi_lun = port->max_scsi_lun;

	/*
	 * update max SCSI LUN of logical units attached to host (SCSI stack)
	 */
	if (port->adapter->scsi_host &&
	    (port->adapter->scsi_host->max_lun < port->max_scsi_lun))
		port->adapter->scsi_host->max_lun = port->max_scsi_lun + 1;

	zfcp_unit_get(unit);

	/* unit is new and needs to be added to list */
	write_lock_irq(&zfcp_data.config_lock);
	atomic_clear_mask(ZFCP_STATUS_COMMON_REMOVE, &unit->status);
	atomic_set_mask(ZFCP_STATUS_COMMON_RUNNING, &unit->status);
	list_add_tail(&unit->list, &port->unit_list_head);
	write_unlock_irq(&zfcp_data.config_lock);

	port->units++;

	return unit;
}

/* locks:  config_sema must be held */
void
zfcp_unit_dequeue(struct zfcp_unit *unit)
{
	/* remove specified unit data structure from list */
	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);

	kfree(unit);

	return;
}

static void *
zfcp_mempool_alloc(int 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.erp_fsf = mempool_create(
		1,
		zfcp_mempool_alloc,
		zfcp_mempool_free,
		(void *) ZFCP_QTCB_AND_REQ_SIZE);
	if (!adapter->pool.erp_fsf) {
		ZFCP_LOG_INFO
		    ("error: FCP command buffer pool allocation failed\n");
		return -ENOMEM;
	}

	adapter->pool.nameserver = mempool_create(
		1,
		zfcp_mempool_alloc,
		zfcp_mempool_free,
		(void *) (2 *  sizeof (struct fc_ct_iu)));
	if (!adapter->pool.nameserver) {
		ZFCP_LOG_INFO
		    ("error: Nameserver buffer pool allocation failed\n");
		return -ENOMEM;
	}

	adapter->pool.status_read_fsf = mempool_create(
		ZFCP_STATUS_READS_RECOM,
		zfcp_mempool_alloc,
		zfcp_mempool_free,
		(void *) sizeof (struct zfcp_fsf_req));
	if (!adapter->pool.status_read_fsf) {
		ZFCP_LOG_INFO
		    ("error: Status read request pool allocation failed\n");
		return -ENOMEM;
	}

	adapter->pool.status_read_buf = mempool_create(
		ZFCP_STATUS_READS_RECOM,
		zfcp_mempool_alloc,
		zfcp_mempool_free,
		(void *) sizeof (struct	fsf_status_read_buffer));
	if (!adapter->pool.status_read_buf) {
		ZFCP_LOG_INFO
		    ("error: Status read buffer pool allocation failed\n");
		return -ENOMEM;
	}

	adapter->pool.fcp_command_fsf = mempool_create(
		1,
		zfcp_mempool_alloc,
		zfcp_mempool_free,
		(void *)
		ZFCP_QTCB_AND_REQ_SIZE);
	if (!adapter->pool.fcp_command_fsf) {
		ZFCP_LOG_INFO
		    ("error: FCP command buffer pool allocation failed\n");
		return -ENOMEM;
	}
	init_timer(&adapter->pool.fcp_command_fsf_timer);
	adapter->pool.fcp_command_fsf_timer.function =
	    zfcp_erp_scsi_low_mem_buffer_timeout_handler;
	adapter->pool.fcp_command_fsf_timer.data = (unsigned long) adapter;

	return 0;
}

/* locks:       must only be called with zfcp_data.config_sema taken */
static void
zfcp_free_low_mem_buffers(struct zfcp_adapter *adapter)
{
	if (adapter->pool.status_read_fsf)
		mempool_destroy(adapter->pool.status_read_fsf);
	if (adapter->pool.status_read_buf)
		mempool_destroy(adapter->pool.status_read_buf);
	if (adapter->pool.nameserver)
		mempool_destroy(adapter->pool.nameserver);
	if (adapter->pool.erp_fsf)
		mempool_destroy(adapter->pool.erp_fsf);
	if (adapter->pool.fcp_command_fsf)
		mempool_destroy(adapter->pool.fcp_command_fsf);
}

/*
 * 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;
	char dbf_name[20];

	/*
	 * 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)
		goto failed_low_mem_buffers;

	/* set magics */
	adapter->common_magic = ZFCP_MAGIC;
	adapter->specific_magic = ZFCP_MAGIC_ADAPTER;

	/* 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 scsi faking structures */
	rwlock_init(&adapter->fake_list_lock);
	init_timer(&adapter->fake_scsi_timer);

	/* initialise some erp stuff */
	init_waitqueue_head(&adapter->erp_thread_wqh);
	init_waitqueue_head(&adapter->erp_done_wqh);

	/* notification when there are no outstanding SCSI commands */
	init_waitqueue_head(&adapter->scsi_reqs_active_wq);

	/* 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;

#ifdef ZFCP_DEBUG_REQUESTS
	/* debug feature area which records fsf request sequence numbers */
	sprintf(dbf_name, ZFCP_REQ_DBF_NAME "0x%s",
		zfcp_get_busid_by_adapter(adapter));
	adapter->req_dbf = debug_register(dbf_name,
					  ZFCP_REQ_DBF_INDEX,
					  ZFCP_REQ_DBF_AREAS,
					  ZFCP_REQ_DBF_LENGTH);
	if (!adapter->req_dbf) {
		ZFCP_LOG_INFO
		    ("error: Out of resources. Request debug feature for "
		     "adapter %s could not be generated.\n",
		     zfcp_get_busid_by_adapter(adapter));
		retval = -ENOMEM;
		goto failed_req_dbf;
	}
	debug_register_view(adapter->req_dbf, &debug_hex_ascii_view);
	debug_set_level(adapter->req_dbf, ZFCP_REQ_DBF_LEVEL);
	debug_text_event(adapter->req_dbf, 1, "zzz");
#endif				/* ZFCP_DEBUG_REQUESTS */

#ifdef ZFCP_DEBUG_COMMANDS
	/* debug feature area which records SCSI command failures (hostbyte) */
	rwlock_init(&adapter->cmd_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);
	if (!adapter->cmd_dbf) {
		ZFCP_LOG_INFO
		    ("error: Out of resources. Command debug feature for "
		     "adapter %s could not be generated.\n",
		     zfcp_get_busid_by_adapter(adapter));
		retval = -ENOMEM;
		goto failed_cmd_dbf;
	}
	debug_register_view(adapter->cmd_dbf, &debug_hex_ascii_view);
	debug_set_level(adapter->cmd_dbf, ZFCP_CMD_DBF_LEVEL);
#endif				/* ZFCP_DEBUG_COMMANDS */

#ifdef ZFCP_DEBUG_ABORTS
	/* 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);
	if (!adapter->abort_dbf) {
		ZFCP_LOG_INFO
		    ("error: Out of resources. Abort debug feature for "
		     "adapter %s could not be generated.\n",
		     zfcp_get_busid_by_adapter(adapter));
		retval = -ENOMEM;
		goto failed_abort_dbf;
	}
	debug_register_view(adapter->abort_dbf, &debug_hex_ascii_view);
	debug_set_level(adapter->abort_dbf, ZFCP_ABORT_DBF_LEVEL);
#endif				/* ZFCP_DEBUG_ABORTS */

#ifdef ZFCP_DEBUG_INCOMING_ELS
	/* debug feature area which records SCSI command aborts */
	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);
	if (!adapter->in_els_dbf) {
		ZFCP_LOG_INFO("error: Out of resources. ELS debug feature for "
			      "adapter %s could not be generated.\n",
			      zfcp_get_busid_by_adapter(adapter));
		retval = -ENOMEM;
		goto failed_in_els_dbf;
	}
	debug_register_view(adapter->in_els_dbf, &debug_hex_ascii_view);
	debug_set_level(adapter->in_els_dbf, ZFCP_IN_ELS_DBF_LEVEL);
#endif				/* ZFCP_DEBUG_INCOMING_ELS */

	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);
	if (!adapter->erp_dbf) {
		ZFCP_LOG_INFO("error: Out of resources. ERP debug feature for "
			      "adapter %s could not be generated.\n",
			      zfcp_get_busid_by_adapter(adapter));
		retval = -ENOMEM;
		goto failed_erp_dbf;
	}
	debug_register_view(adapter->erp_dbf, &debug_hex_ascii_view);
	debug_set_level(adapter->erp_dbf, ZFCP_ERP_DBF_LEVEL);

	retval = zfcp_erp_thread_setup(adapter);
	if (retval) {
		ZFCP_LOG_INFO("error: out of resources. "
			      "error recovery thread for the adapter %s "
			      "could not be started\n",
			      zfcp_get_busid_by_adapter(adapter));
		goto thread_failed;
	}

	/* put allocated adapter at list tail */
	write_lock_irq(&zfcp_data.config_lock);
	atomic_clear_mask(ZFCP_STATUS_COMMON_REMOVE, &adapter->status);
	atomic_set_mask(ZFCP_STATUS_COMMON_RUNNING, &adapter->status);
	list_add_tail(&adapter->list, &zfcp_data.adapter_list_head);
	write_unlock_irq(&zfcp_data.config_lock);

	zfcp_data.adapters++;

	goto out;

 thread_failed:
	if (qdio_free(adapter->ccw_device) != 0)
		ZFCP_LOG_NORMAL
		    ("bug: could not free memory used by data transfer "
		     "mechanism for adapter %s\n",
		     zfcp_get_busid_by_adapter(adapter));

	debug_unregister(adapter->erp_dbf);

 failed_erp_dbf:
#ifdef ZFCP_DEBUG_INCOMING_ELS
	debug_unregister(adapter->in_els_dbf);
 failed_in_els_dbf:
#endif

#ifdef ZFCP_DEBUG_ABORTS
	debug_unregister(adapter->abort_dbf);
 failed_abort_dbf:
#endif

#ifdef ZFCP_DEBUG_COMMANDS
	debug_unregister(adapter->cmd_dbf);
 failed_cmd_dbf:
#endif

#ifdef ZFCP_DEBUG_REQUESTS
	debug_unregister(adapter->req_dbf);
 failed_req_dbf:
#endif
	zfcp_sysfs_adapter_remove_files(&ccw_device->dev);
 sysfs_failed:
	dev_set_drvdata(&ccw_device->dev, NULL);
 failed_low_mem_buffers:
	zfcp_free_low_mem_buffers(adapter);
	qdio_free(ccw_device);
 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;

	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 is still in use, "
				"%i requests are still outstanding "
				"(debug info 0x%lx)\n",
				zfcp_get_busid_by_adapter(adapter),
				atomic_read(&adapter->fsf_reqs_active),
				(unsigned long) adapter);
		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 0x%lx removed from list, "
		       "%i adapters still in list\n",
		       (unsigned long) adapter, zfcp_data.adapters);

	retval = zfcp_erp_thread_kill(adapter);

	retval |= qdio_free(adapter->ccw_device);
	if (retval)
		ZFCP_LOG_NORMAL
		    ("bug: could not free memory used by data transfer "
		     "mechanism for adapter %s\n",
		     zfcp_get_busid_by_adapter(adapter));

	debug_unregister(adapter->erp_dbf);

#ifdef ZFCP_DEBUG_REQUESTS
	debug_unregister(adapter->req_dbf);
#endif

#ifdef ZFCP_DEBUG_COMMANDS
	debug_unregister(adapter->cmd_dbf);
#endif
#ifdef ZFCP_DEBUG_ABORTS
	debug_unregister(adapter->abort_dbf);
#endif

#ifdef ZFCP_DEBUG_INCOMING_ELS
	debug_unregister(adapter->in_els_dbf);
#endif

	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;
}

/*
 * Enqueues a remote port at the end of the port list.
 * All port internal structures are set-up and the proc-fs entry is also 
 * allocated. Some SCSI-stack structures are modified for the port.
 *
 * returns:	0            if a new port was successfully enqueued
 *              ZFCP_KNOWN   if a port with the requested wwpn already exists
 *              -ENOMEM      if allocation failed
 *              -EINVAL      if at least one of the specified parameters was wrong
 * locks:       config_sema must be held to serialise changes to the port list
 *              within this function (must not be held on entry)
 */
struct zfcp_port *
zfcp_port_enqueue(struct zfcp_adapter *adapter, wwn_t wwpn, u32 status)
{
	struct zfcp_port *port;
	int check_scsi_id;
	int check_wwpn;

	check_scsi_id = !(status & ZFCP_STATUS_PORT_NO_SCSI_ID);
	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_scsi_id)
		port->scsi_id = adapter->max_scsi_id + 1;

	if (check_wwpn)
		port->wwpn = wwpn;

	atomic_set_mask(status, &port->status);

	port->common_magic = ZFCP_MAGIC;
	port->specific_magic = ZFCP_MAGIC_PORT;

	/* setup for sysfs registration */
	if (status & ZFCP_STATUS_PORT_NAMESERVER)
		snprintf(port->sysfs_device.bus_id, BUS_ID_SIZE, "nameserver");
	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)) {
		/*
		 * failed to create all sysfs attributes, therefore the port
		 * must be put on the port_remove listhead of the adapter
		 * where the release function expects it.
		 */
		write_lock_irq(&zfcp_data.config_lock);
		list_add_tail(&port->list, &adapter->port_remove_lh);
		write_unlock_irq(&zfcp_data.config_lock);
		device_unregister(&port->sysfs_device);
		return NULL;
	}

	if (check_scsi_id) {
		/*
		 * update max. SCSI ID of remote ports attached to
		 * "parent" adapter if necessary
		 * (do not care about the adapters own SCSI ID)
		 */
		adapter->max_scsi_id++;

		/*
		 * update max. SCSI ID of remote ports attached to
		 * "parent" host (SCSI stack) if necessary
		 */
		if (adapter->scsi_host &&
		    (adapter->scsi_host->max_id < adapter->max_scsi_id + 1))
			adapter->scsi_host->max_id = adapter->max_scsi_id + 1;
	}

	zfcp_port_get(port);

	write_lock_irq(&zfcp_data.config_lock);
	atomic_clear_mask(ZFCP_STATUS_COMMON_REMOVE, &port->status);
	atomic_set_mask(ZFCP_STATUS_COMMON_RUNNING, &port->status);
	list_add_tail(&port->list, &adapter->port_list_head);
	write_unlock_irq(&zfcp_data.config_lock);

	adapter->ports++;

	return port;
}

/*
 * returns:	0 - struct zfcp_port data structure successfully removed
 *		!0 - struct zfcp_port data structure could not be removed
 *			(e.g. still used)
 * locks :	port list write lock is assumed to be held by caller
 */
void
zfcp_port_dequeue(struct zfcp_port *port)
{
	/* remove specified port from list */
	write_lock_irq(&zfcp_data.config_lock);
	list_del(&port->list);
	write_unlock_irq(&zfcp_data.config_lock);

	port->adapter->ports--;
	zfcp_adapter_put(port->adapter);

	kfree(port);

	return;
}

/* Enqueues a nameserver port */
int
zfcp_nameserver_enqueue(struct zfcp_adapter *adapter)
{
	struct zfcp_port *port;

	/* generate port structure */
	port = zfcp_port_enqueue(adapter, 0, ZFCP_STATUS_PORT_NAMESERVER);
	if (!port) {
		ZFCP_LOG_INFO("error: Could not establish a connection to the "
			      "fabric name server connected to the "
			      "adapter %s\n",
			      zfcp_get_busid_by_adapter(adapter));
		return -ENXIO;
	}
	/* set special D_ID */
	port->d_id = ZFCP_DID_NAMESERVER;
	adapter->nameserver_port = port;
	zfcp_adapter_get(adapter);
	zfcp_port_put(port);

	return 0;
}

#undef ZFCP_LOG_AREA
#undef ZFCP_LOG_AREA_PREFIX

/****************************************************************/
/******* Fibre Channel Standard related Functions  **************/
/****************************************************************/

#define ZFCP_LOG_AREA                   ZFCP_LOG_AREA_FC
#define ZFCP_LOG_AREA_PREFIX            ZFCP_LOG_AREA_PREFIX_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;
	int i;
	int reopen_unknown = 0;
	int no_entries;
	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++) {
		int known;
		int range_mask;
		int no_notifications;

		range_mask = 0;
		no_notifications = 0;
		known = 0;
		/* 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;
			no_notifications = 1;
			break;
		case ZFCP_AREA_ADDRESS:
			ZFCP_LOG_FLAGS(1, "ZFCP_AREA_ADDRESS\n");
			/* skip head and start with 1st element */
			range_mask = ZFCP_PORTS_RANGE_AREA;
			no_notifications = ZFCP_NO_PORTS_PER_AREA;
			break;
		case ZFCP_DOMAIN_ADDRESS:
			ZFCP_LOG_FLAGS(1, "ZFCP_DOMAIN_ADDRESS\n");
			range_mask = ZFCP_PORTS_RANGE_DOMAIN;
			no_notifications = ZFCP_NO_PORTS_PER_DOMAIN;
			break;
		case ZFCP_FABRIC_ADDRESS:
			ZFCP_LOG_FLAGS(1, "ZFCP_FABRIC_ADDRESS\n");
			range_mask = ZFCP_PORTS_RANGE_FABRIC;
			no_notifications = ZFCP_NO_PORTS_PER_FABRIC;
			break;
		default:
			/* cannot happen */
			break;
		}
		read_lock_irqsave(&zfcp_data.config_lock, flags);
		list_for_each_entry(port, &adapter->port_list_head, list) {
			/* Do we know this port? If not skip it. */
			if (!atomic_test_mask
			    (ZFCP_STATUS_PORT_DID_DID, &port->status))
				continue;
			/*
			 * FIXME: race: d_id might being invalidated
			 * (...DID_DID reset)
			 */
			if ((port->d_id & range_mask)
			    == (fcp_rscn_element->nport_did & range_mask)) {
				known++;
				ZFCP_LOG_TRACE("known=%d, reopen did 0x%x\n",
					       known,
					       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
				    ("Received state change notification."
				     "Trying to reopen the port with wwpn "
				     "0x%Lx.\n", port->wwpn);
				debug_text_event(adapter->erp_dbf, 1,
						 "unsol_els_rscnk:");
				zfcp_erp_port_reopen(port, 0);
			}
		}
		read_unlock_irqrestore(&zfcp_data.config_lock, flags);
		ZFCP_LOG_TRACE("known %d, no_notifications %d\n",
			       known, no_notifications);
		if (known < no_notifications) {
			ZFCP_LOG_DEBUG
			    ("At least one unknown port changed state. "
			     "Unknown ports need to be reopened.\n");
			reopen_unknown = 1;
		}
	}			// for (i=1; i < no_entries; i++)

	if (reopen_unknown) {
		ZFCP_LOG_DEBUG("At least one unknown did "
			       "underwent a state change.\n");
		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_DID_DID
					       | ZFCP_STATUS_PORT_NAMESERVER),
					      &port->status)) {
				ZFCP_LOG_INFO
				    ("Received state change notification."
				     "Trying to open the port with wwpn "
				     "0x%Lx. Hope it's there now.\n",
				     port->wwpn);
				debug_text_event(adapter->erp_dbf, 1,
						 "unsol_els_rscnu:");
				zfcp_erp_port_reopen(port,
						     ZFCP_STATUS_COMMON_ERP_FAILED);
			}
		}
		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("Re-open port indication received "
			       "for the non-existing port with D_ID "
			       "0x%3.3x, on the adapter "
			       "%s. Ignored.\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("Re-open port indication received "
			       "for the non-existing port with D_ID "
			       "0x%3.3x, on the adapter "
			       "%s. Ignored.\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%x) received "
			"for the 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);
}

/*
 * function:	zfcp_release_nameserver_buffers
 *
 * purpose:	
 *
 * returns:
 */
static void
zfcp_release_nameserver_buffers(struct zfcp_fsf_req *fsf_req)
{
	struct zfcp_adapter *adapter = fsf_req->adapter;
	void *buffer = fsf_req->data.send_generic.outbuf;

	/* FIXME: not sure about appeal of this new flag (martin) */
	if (fsf_req->status & ZFCP_STATUS_FSFREQ_POOLBUF)
		mempool_free(buffer, adapter->pool.nameserver);
	else
		kfree(buffer);
}

/*
 * function:	zfcp_get_nameserver_buffers
 *
 * purpose:	
 *
 * returns:
 *
 * locks:       fsf_request_list_lock is held when doing buffer pool 
 *              operations
 */
static int
zfcp_get_nameserver_buffers(struct zfcp_fsf_req *fsf_req)
{
	struct zfcp_send_generic *data = &fsf_req->data.send_generic;
	struct zfcp_adapter *adapter = fsf_req->adapter;
	int retval = 0;

	data->outbuf = kmalloc(2 * sizeof (struct fc_ct_iu), GFP_KERNEL);
	if (data->outbuf) {
		memset(data->outbuf, 0, 2 * sizeof (struct fc_ct_iu));
	} else {
		ZFCP_LOG_DEBUG("Out of memory. Could not allocate at "
			       "least one of the buffers "
			       "required for a name-server request on the"
			       "adapter %s directly.. trying emergency pool\n",
			       zfcp_get_busid_by_adapter(adapter));
		data->outbuf =
		    mempool_alloc(adapter->pool.nameserver, GFP_KERNEL);
		if (!data->outbuf) {
			ZFCP_LOG_DEBUG
				("Out of memory. Could not get emergency "
				 "buffer required for a name-server request "
				 "on the adapter %s. All buffers are in "
				 "use.\n",
				 zfcp_get_busid_by_adapter(adapter));
			retval = -ENOMEM;
			goto out;
		}
		memset(data->outbuf, 0, 2 * sizeof (struct fc_ct_iu));
		fsf_req->status |= ZFCP_STATUS_FSFREQ_POOLBUF;
	}
	data->outbuf_length = sizeof (struct fc_ct_iu);
	data->inbuf_length = sizeof (struct fc_ct_iu);
	data->inbuf =
	    (char *) ((unsigned long) data->outbuf + sizeof (struct fc_ct_iu));
 out:
	return retval;
}

/*
 * function:	zfcp_nameserver_request
 *
 * purpose:	
 *
 * returns:
 */
int
zfcp_nameserver_request(struct zfcp_erp_action *erp_action)
{
	int retval = 0;
	struct fc_ct_iu *fc_ct_iu;
	unsigned long lock_flags;

	/* setup new FSF request */
	retval = zfcp_fsf_req_create(erp_action->adapter,
				     FSF_QTCB_SEND_GENERIC,
				     &lock_flags,
				     ZFCP_WAIT_FOR_SBAL | ZFCP_REQ_AUTO_CLEANUP,
				     &(erp_action->fsf_req));
	if (retval < 0) {
		ZFCP_LOG_INFO("error: Out of resources. Could not create a "
			      "nameserver registration request for "
			      "adapter %s.\n",
			      zfcp_get_busid_by_adapter(erp_action->adapter));
		goto failed_req;
	}
	retval = zfcp_get_nameserver_buffers(erp_action->fsf_req);
	if (retval < 0) {
		ZFCP_LOG_INFO("error: Out of memory. Could not allocate one of "
			      "the buffers required for a nameserver request "
			      "on adapter %s.\n",
			      zfcp_get_busid_by_adapter(erp_action->adapter));
		goto failed_buffers;
	}

	/* setup name-server request in first page */
	fc_ct_iu =
	    (struct fc_ct_iu *) erp_action->fsf_req->data.send_generic.outbuf;
	fc_ct_iu->revision = ZFCP_CT_REVISION;
	fc_ct_iu->gs_type = ZFCP_CT_DIRECTORY_SERVICE;
	fc_ct_iu->gs_subtype = ZFCP_CT_NAME_SERVER;
	fc_ct_iu->options = ZFCP_CT_SYNCHRONOUS;
	fc_ct_iu->cmd_rsp_code = ZFCP_CT_GID_PN;
	fc_ct_iu->max_res_size = ZFCP_CT_MAX_SIZE;
	fc_ct_iu->data.wwpn = erp_action->port->wwpn;

	erp_action->fsf_req->data.send_generic.handler =
	    zfcp_nameserver_request_handler;
	erp_action->fsf_req->data.send_generic.handler_data =
	    (unsigned long) erp_action->port;
	erp_action->fsf_req->data.send_generic.port =
	    erp_action->adapter->nameserver_port;
	erp_action->fsf_req->erp_action = erp_action;

	/* send this one */
	retval = zfcp_fsf_send_generic(erp_action->fsf_req,
				       ZFCP_NAMESERVER_TIMEOUT,
				       &lock_flags,
				       &erp_action->timer);
	if (retval) {
		ZFCP_LOG_INFO("error: Could not send a"
			      "nameserver request command to adapter %s\n",
			      zfcp_get_busid_by_adapter(erp_action->adapter));
		goto failed_send;
	}

	goto out;

 failed_send:
	zfcp_release_nameserver_buffers(erp_action->fsf_req);

 failed_buffers:
	zfcp_fsf_req_free(erp_action->fsf_req);
	erp_action->fsf_req = NULL;

 failed_req:
 out:
	write_unlock_irqrestore(&erp_action->adapter->request_queue.queue_lock,
				lock_flags);
	return retval;
}

/*
 * function:	zfcp_nameserver_request_handler
 *
 * purpose:	
 *
 * returns:
 */
static void
zfcp_nameserver_request_handler(struct zfcp_fsf_req *fsf_req)
{
	struct fc_ct_iu *fc_ct_iu_resp =
	    (struct fc_ct_iu *) (fsf_req->data.send_generic.inbuf);
	struct fc_ct_iu *fc_ct_iu_req =
	    (struct fc_ct_iu *) (fsf_req->data.send_generic.outbuf);
	struct zfcp_port *port =
	    (struct zfcp_port *) fsf_req->data.send_generic.handler_data;

	if (fc_ct_iu_resp->revision != ZFCP_CT_REVISION)
		goto failed;
	if (fc_ct_iu_resp->gs_type != ZFCP_CT_DIRECTORY_SERVICE)
		goto failed;
	if (fc_ct_iu_resp->gs_subtype != ZFCP_CT_NAME_SERVER)
		goto failed;
	if (fc_ct_iu_resp->options != ZFCP_CT_SYNCHRONOUS)
		goto failed;
	if (fc_ct_iu_resp->cmd_rsp_code != ZFCP_CT_ACCEPT) {
		/* FIXME: do we need some specific erp entry points */
		atomic_set_mask(ZFCP_STATUS_PORT_INVALID_WWPN, &port->status);
		goto failed;
	}
	/* paranoia */
	if (fc_ct_iu_req->data.wwpn != port->wwpn) {
		ZFCP_LOG_NORMAL("bug: Port WWPN returned by nameserver lookup "
				"does not correspond to "
				"the expected value on the adapter %s. "
				"(debug info 0x%Lx, 0x%Lx)\n",
				zfcp_get_busid_by_port(port),
				port->wwpn, fc_ct_iu_req->data.wwpn);
		goto failed;
	}

	/* looks like a valid d_id */
	port->d_id = ZFCP_DID_MASK & fc_ct_iu_resp->data.d_id;
	atomic_set_mask(ZFCP_STATUS_PORT_DID_DID, &port->status);
	ZFCP_LOG_DEBUG("busid %s:  WWPN=0x%Lx ---> D_ID=0x%6.6x\n",
		       zfcp_get_busid_by_port(port),
		       port->wwpn, (unsigned int) port->d_id);
	goto out;

 failed:
	ZFCP_LOG_NORMAL("warning: WWPN 0x%Lx not found by nameserver lookup "
			"using the adapter %s\n",
			port->wwpn, zfcp_get_busid_by_port(port));
	ZFCP_LOG_DEBUG("CT IUs do not match:\n");
	ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
		      (char *) fc_ct_iu_req, sizeof (struct fc_ct_iu));
	ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
		      (char *) fc_ct_iu_resp, sizeof (struct fc_ct_iu));

 out:
	zfcp_release_nameserver_buffers(fsf_req);
}

#undef ZFCP_LOG_AREA
#undef ZFCP_LOG_AREA_PREFIX