view usr/src/uts/common/io/fcoe/fcoe.c @ 10264:1196af6129ec

PSARC 2008/311 FCoE (Fibre Channel over Ethernet) Initiator 6823827 FCoE initiator for Leadville
author Zhong Wang <Zhong.Wang@Sun.COM>
date Wed, 05 Aug 2009 17:14:23 -0700
parents adcf72c91c4f
children 7f165444fae6
line wrap: on
line source

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * The following notice accompanied the original version of this file:
 *
 * BSD LICENSE
 *
 * Copyright(c) 2007 Intel Corporation. All rights reserved.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *   * Neither the name of Intel Corporation nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Common FCoE interface interacts with MAC and FCoE clients, managing
 * FCoE ports, doing MAC address discovery/managment, and FC frame
 * encapsulation/decapsulation
 */

#include <sys/stat.h>
#include <sys/conf.h>
#include <sys/file.h>
#include <sys/cred.h>

#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/byteorder.h>
#include <sys/atomic.h>
#include <sys/sysmacros.h>
#include <sys/cmn_err.h>
#include <sys/crc32.h>
#include <sys/strsubr.h>

#include <sys/mac_client.h>

/*
 * FCoE header files
 */
#include <sys/fcoe/fcoeio.h>
#include <sys/fcoe/fcoe_common.h>

/*
 * Driver's own header files
 */
#include <fcoe.h>
#include <fcoe_fc.h>
#include <fcoe_eth.h>

/*
 * Function forward declaration
 */
static int fcoe_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
static int fcoe_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
static int fcoe_bus_ctl(dev_info_t *fca_dip, dev_info_t *rip,
    ddi_ctl_enum_t op, void *arg, void *result);
static int fcoe_open(dev_t *devp, int flag, int otype, cred_t *credp);
static int fcoe_close(dev_t dev, int flag, int otype, cred_t *credp);
static int fcoe_ioctl(dev_t dev, int cmd, intptr_t data, int mode,
    cred_t *credp, int *rval);
static int fcoe_copyin_iocdata(intptr_t data, int mode, fcoeio_t **fcoeio,
    void **ibuf, void **abuf, void **obuf);
static int fcoe_copyout_iocdata(intptr_t data, int mode, fcoeio_t *fcoeio,
    void *obuf);
static int fcoe_iocmd(fcoe_soft_state_t *ss, intptr_t data, int mode);
static int fcoe_attach_init(fcoe_soft_state_t *this_ss);
static int fcoe_detach_uninit(fcoe_soft_state_t *this_ss);
static int fcoe_initchild(dev_info_t *fcoe_dip, dev_info_t *client_dip);
static int fcoe_uninitchild(dev_info_t *fcoe_dip, dev_info_t *client_dip);
static void fcoe_init_wwn_from_mac(uint8_t *wwn, uint8_t *mac,
    int is_pwwn, uint8_t idx);
static fcoe_mac_t *fcoe_create_mac_by_id(datalink_id_t linkid);
static int fcoe_cmp_wwn(fcoe_mac_t *checkedmac);
static void fcoe_watchdog(void *arg);
static void fcoe_worker_init();
static int fcoe_worker_fini();
static void fcoe_worker_frame();
static int fcoe_get_port_list(fcoe_port_instance_t *ports, int count);

/*
 * Driver identificaton stuff
 */
static struct cb_ops fcoe_cb_ops = {
	fcoe_open,
	fcoe_close,
	nodev,
	nodev,
	nodev,
	nodev,
	nodev,
	fcoe_ioctl,
	nodev,
	nodev,
	nodev,
	nochpoll,
	ddi_prop_op,
	0,
	D_MP | D_NEW | D_HOTPLUG,
	CB_REV,
	nodev,
	nodev
};

static struct bus_ops fcoe_busops = {
	BUSO_REV,
	nullbusmap,			/* bus_map */
	NULL,				/* bus_get_intrspec */
	NULL,				/* bus_add_intrspec */
	NULL,				/* bus_remove_intrspec */
	i_ddi_map_fault,		/* bus_map_fault */
	ddi_dma_map,			/* bus_dma_map */
	ddi_dma_allochdl,		/* bus_dma_allochdl */
	ddi_dma_freehdl,		/* bus_dma_freehdl */
	ddi_dma_bindhdl,		/* bus_dma_bindhdl */
	ddi_dma_unbindhdl,		/* bus_unbindhdl */
	ddi_dma_flush,			/* bus_dma_flush */
	ddi_dma_win,			/* bus_dma_win */
	ddi_dma_mctl,			/* bus_dma_ctl */
	fcoe_bus_ctl,			/* bus_ctl */
	ddi_bus_prop_op,		/* bus_prop_op */
	NULL,				/* bus_get_eventcookie */
	NULL,				/* bus_add_eventcall */
	NULL,				/* bus_remove_event */
	NULL,				/* bus_post_event */
	NULL,				/* bus_intr_ctl */
	NULL,				/* bus_config */
	NULL,				/* bus_unconfig */
	NULL,				/* bus_fm_init */
	NULL,				/* bus_fm_fini */
	NULL,				/* bus_fm_access_enter */
	NULL,				/* bus_fm_access_exit */
	NULL,				/* bus_power */
	NULL
};

static struct dev_ops fcoe_ops = {
	DEVO_REV,
	0,
	nodev,
	nulldev,
	nulldev,
	fcoe_attach,
	fcoe_detach,
	nodev,
	&fcoe_cb_ops,
	&fcoe_busops,
	ddi_power,
	ddi_quiesce_not_needed
};

#define	FCOE_VERSION	"20090729-1.01"
#define	FCOE_NAME	"FCoE Transport v" FCOE_VERSION
#define	TASKQ_NAME_LEN	32

static struct modldrv modldrv = {
	&mod_driverops,
	FCOE_NAME,
	&fcoe_ops,
};

static struct modlinkage modlinkage = {
	MODREV_1, &modldrv, NULL
};

/*
 * TRACE for all FCoE related modules
 */
static kmutex_t fcoe_trace_buf_lock;
static int	fcoe_trace_buf_curndx	= 0;
static int	fcoe_trace_on		= 1;
static caddr_t	fcoe_trace_buf		= NULL;
static clock_t	fcoe_trace_start	= 0;
static caddr_t	ftb			= NULL;
static int	fcoe_trace_buf_size	= (1 * 1024 * 1024);

/*
 * Driver's global variables
 */
const fcoe_ver_e	 fcoe_ver_now	  = FCOE_VER_NOW;
static void		*fcoe_state	  = NULL;
fcoe_soft_state_t	*fcoe_global_ss	  = NULL;
int			 fcoe_use_ext_log = 1;

static ddi_taskq_t	*fcoe_worker_taskq;
static fcoe_worker_t	*fcoe_workers;
static uint32_t		fcoe_nworkers_running;

const char		*fcoe_workers_num = "workers-number";
volatile int		fcoe_nworkers;

/*
 * Common loadable module entry points _init, _fini, _info
 */

int
_init(void)
{
	int ret;

	ret = ddi_soft_state_init(&fcoe_state, sizeof (fcoe_soft_state_t), 0);
	if (ret == 0) {
		ret = mod_install(&modlinkage);
		if (ret != 0) {
			ddi_soft_state_fini(&fcoe_state);
		} else {
			fcoe_trace_start = ddi_get_lbolt();
			ftb = kmem_zalloc(fcoe_trace_buf_size,
			    KM_SLEEP);
			fcoe_trace_buf = ftb;
			mutex_init(&fcoe_trace_buf_lock, NULL, MUTEX_DRIVER, 0);
		}
	}

	FCOE_LOG("fcoe", "exit _init with %x", ret);

	return (ret);
}

int
_fini(void)
{
	int ret;

	ret = mod_remove(&modlinkage);
	if (ret == 0) {
		ddi_soft_state_fini(&fcoe_state);
	}

	FCOE_LOG("fcoe", "exit _fini with %x", ret);
	if (ret == 0) {
		kmem_free(fcoe_trace_buf, fcoe_trace_buf_size);
		mutex_destroy(&fcoe_trace_buf_lock);
	}

	return (ret);
}

int
_info(struct modinfo *modinfop)
{
	return (mod_info(&modlinkage, modinfop));
}

/*
 * Autoconfiguration entry points: attach, detach, getinfo
 */

static int
fcoe_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
	int			 ret = DDI_FAILURE;
	int			 fcoe_ret;
	int			 instance;
	fcoe_soft_state_t	*ss;

	instance = ddi_get_instance(dip);
	switch (cmd) {
	case DDI_ATTACH:
		ret = ddi_soft_state_zalloc(fcoe_state, instance);
		if (ret == DDI_FAILURE) {
			FCOE_LOG(0, "soft_state_zalloc-%x/%x", ret, instance);
			return (ret);
		}

		ss = ddi_get_soft_state(fcoe_state, instance);
		ss->ss_dip = dip;

		ASSERT(fcoe_global_ss == NULL);
		fcoe_global_ss = ss;
		fcoe_ret = fcoe_attach_init(ss);
		if (fcoe_ret == FCOE_SUCCESS) {
			ret = DDI_SUCCESS;
		}

		FCOE_LOG("fcoe", "fcoe_attach_init end with-%x", fcoe_ret);
		break;

	case DDI_RESUME:
		ret = DDI_SUCCESS;
		break;

	default:
		FCOE_LOG("fcoe", "unsupported attach cmd-%x", cmd);
		break;
	}

	return (ret);
}

static int
fcoe_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
	int			 ret = DDI_FAILURE;
	int			 fcoe_ret;
	int			 instance;
	fcoe_soft_state_t	*ss;

	instance = ddi_get_instance(dip);
	ss = ddi_get_soft_state(fcoe_state, instance);
	if (ss == NULL) {
		return (ret);
	}

	ASSERT(fcoe_global_ss != NULL);
	ASSERT(dip == fcoe_global_ss->ss_dip);
	switch (cmd) {
	case DDI_DETACH:
		fcoe_ret = fcoe_detach_uninit(ss);
		if (fcoe_ret == FCOE_SUCCESS) {
			ret = DDI_SUCCESS;
			fcoe_global_ss = NULL;
		}

		break;

	case DDI_SUSPEND:
		ret = DDI_SUCCESS;
		break;

	default:
		FCOE_LOG(0, "unsupported detach cmd-%x", cmd);
		break;
	}

	return (ret);
}

/*
 * FCA driver's intercepted bus control operations.
 */
static int
fcoe_bus_ctl(dev_info_t *fcoe_dip, dev_info_t *rip,
    ddi_ctl_enum_t op, void *clientarg, void *result)
{
	int ret;
	switch (op) {
	case DDI_CTLOPS_REPORTDEV:
	case DDI_CTLOPS_IOMIN:
		ret = DDI_SUCCESS;
		break;

	case DDI_CTLOPS_INITCHILD:
		ret = fcoe_initchild(fcoe_dip, (dev_info_t *)clientarg);
		break;

	case DDI_CTLOPS_UNINITCHILD:
		ret = fcoe_uninitchild(fcoe_dip, (dev_info_t *)clientarg);
		break;

	default:
		ret = ddi_ctlops(fcoe_dip, rip, op, clientarg, result);
		break;
	}

	return (ret);
}

/*
 * We need specify the dev address for client driver's instance, or we
 * can't online client driver's instance.
 */
/* ARGSUSED */
static int
fcoe_initchild(dev_info_t *fcoe_dip, dev_info_t *client_dip)
{
	char	client_addr[FCOE_STR_LEN];
	int	rval;

	rval = ddi_prop_get_int(DDI_DEV_T_ANY, client_dip,
	    DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, "mac_id", -1);
	if (rval == -1) {
		FCOE_LOG(__FUNCTION__, "no mac_id property: %p", client_dip);
		return (DDI_FAILURE);
	}

	bzero(client_addr, FCOE_STR_LEN);
	(void) sprintf((char *)client_addr, "%x,0", rval);
	ddi_set_name_addr(client_dip, client_addr);
	return (DDI_SUCCESS);
}

/* ARGSUSED */
static int
fcoe_uninitchild(dev_info_t *fcoe_dip, dev_info_t *client_dip)
{
	ddi_set_name_addr(client_dip, NULL);
	return (DDI_SUCCESS);
}

/*
 * Device access entry points
 */
static int
fcoe_open(dev_t *devp, int flag, int otype, cred_t *credp)
{
	int			 instance;
	fcoe_soft_state_t	*ss;

	if (otype != OTYP_CHR) {
		return (EINVAL);
	}

	/*
	 * Since this is for debugging only, only allow root to issue ioctl now
	 */
	if (drv_priv(credp) != 0) {
		return (EPERM);
	}

	instance = (int)getminor(*devp);
	ss = ddi_get_soft_state(fcoe_state, instance);
	if (ss == NULL) {
		return (ENXIO);
	}

	mutex_enter(&ss->ss_ioctl_mutex);
	if (ss->ss_ioctl_flags & FCOE_IOCTL_FLAG_EXCL) {
		/*
		 * It is already open for exclusive access.
		 * So shut the door on this caller.
		 */
		mutex_exit(&ss->ss_ioctl_mutex);
		return (EBUSY);
	}

	if (flag & FEXCL) {
		if (ss->ss_ioctl_flags & FCOE_IOCTL_FLAG_OPEN) {
			/*
			 * Exclusive operation not possible
			 * as it is already opened
			 */
			mutex_exit(&ss->ss_ioctl_mutex);
			return (EBUSY);
		}
		ss->ss_ioctl_flags |= FCOE_IOCTL_FLAG_EXCL;
	}

	ss->ss_ioctl_flags |= FCOE_IOCTL_FLAG_OPEN;
	mutex_exit(&ss->ss_ioctl_mutex);

	return (0);
}

/* ARGSUSED */
static int
fcoe_close(dev_t dev, int flag, int otype, cred_t *credp)
{
	int			 instance;
	fcoe_soft_state_t	*ss;

	if (otype != OTYP_CHR) {
		return (EINVAL);
	}

	instance = (int)getminor(dev);
	ss = ddi_get_soft_state(fcoe_state, instance);
	if (ss == NULL) {
		return (ENXIO);
	}

	mutex_enter(&ss->ss_ioctl_mutex);
	if ((ss->ss_ioctl_flags & FCOE_IOCTL_FLAG_OPEN) == 0) {
		mutex_exit(&ss->ss_ioctl_mutex);
		return (ENODEV);
	}

	ss->ss_ioctl_flags &= ~FCOE_IOCTL_FLAG_MASK;
	mutex_exit(&ss->ss_ioctl_mutex);

	return (0);
}

/* ARGSUSED */
static int
fcoe_ioctl(dev_t dev, int cmd, intptr_t data, int mode,
    cred_t *credp, int *rval)
{
	fcoe_soft_state_t	*ss;
	int			 ret = 0;

	if (drv_priv(credp) != 0) {
		return (EPERM);
	}

	ss = ddi_get_soft_state(fcoe_state, (int32_t)getminor(dev));
	if (ss == NULL) {
		return (ENXIO);
	}

	mutex_enter(&ss->ss_ioctl_mutex);
	if ((ss->ss_ioctl_flags & FCOE_IOCTL_FLAG_OPEN) == 0) {
		mutex_exit(&ss->ss_ioctl_mutex);
		return (ENXIO);
	}
	mutex_exit(&ss->ss_ioctl_mutex);

	switch (cmd) {
	case FCOEIO_CMD:
		ret = fcoe_iocmd(ss, data, mode);
		break;
	default:
		FCOE_LOG(0, "fcoe_ioctl: ioctl-0x%02X", cmd);
		ret = ENOTTY;
		break;
	}

	return (ret);
}

static int
fcoe_copyin_iocdata(intptr_t data, int mode, fcoeio_t **fcoeio,
    void **ibuf, void **abuf, void **obuf)
{
	int ret = 0;

	*ibuf = NULL;
	*abuf = NULL;
	*obuf = NULL;
	*fcoeio = kmem_zalloc(sizeof (fcoeio_t), KM_SLEEP);
	if (ddi_copyin((void *)data, *fcoeio, sizeof (fcoeio_t), mode) != 0) {
		ret = EFAULT;
		goto copyin_iocdata_fail;
	}

	if ((*fcoeio)->fcoeio_ilen > FCOEIO_MAX_BUF_LEN ||
	    (*fcoeio)->fcoeio_alen > FCOEIO_MAX_BUF_LEN ||
	    (*fcoeio)->fcoeio_olen > FCOEIO_MAX_BUF_LEN) {
		ret = EFAULT;
		goto copyin_iocdata_fail;
	}

	if ((*fcoeio)->fcoeio_ilen) {
		*ibuf = kmem_zalloc((*fcoeio)->fcoeio_ilen, KM_SLEEP);
		if (ddi_copyin((void *)(unsigned long)(*fcoeio)->fcoeio_ibuf,
		    *ibuf, (*fcoeio)->fcoeio_ilen, mode) != 0) {
			ret = EFAULT;
			goto copyin_iocdata_fail;
		}
	}

	if ((*fcoeio)->fcoeio_alen) {
		*abuf = kmem_zalloc((*fcoeio)->fcoeio_alen, KM_SLEEP);
		if (ddi_copyin((void *)(unsigned long)(*fcoeio)->fcoeio_abuf,
		    *abuf, (*fcoeio)->fcoeio_alen, mode) != 0) {
			ret = EFAULT;
			goto copyin_iocdata_fail;
		}
	}

	if ((*fcoeio)->fcoeio_olen) {
		*obuf = kmem_zalloc((*fcoeio)->fcoeio_olen, KM_SLEEP);
	}
	return (ret);

copyin_iocdata_fail:
	if (*abuf) {
		kmem_free(*abuf, (*fcoeio)->fcoeio_alen);
		*abuf = NULL;
	}

	if (*ibuf) {
		kmem_free(*ibuf, (*fcoeio)->fcoeio_ilen);
		*ibuf = NULL;
	}

	kmem_free(*fcoeio, sizeof (fcoeio_t));
	return (ret);
}

static int
fcoe_copyout_iocdata(intptr_t data, int mode, fcoeio_t *fcoeio, void *obuf)
{
	if (fcoeio->fcoeio_olen) {
		if (ddi_copyout(obuf,
		    (void *)(unsigned long)fcoeio->fcoeio_obuf,
		    fcoeio->fcoeio_olen, mode) != 0) {
			return (EFAULT);
		}
	}

	if (ddi_copyout(fcoeio, (void *)data, sizeof (fcoeio_t), mode) != 0) {
		return (EFAULT);
	}
	return (0);
}

static int
fcoe_iocmd(fcoe_soft_state_t *ss, intptr_t data, int mode)
{
	int		ret;
	fcoe_mac_t	*fcoe_mac;
	void		*ibuf = NULL;
	void		*obuf = NULL;
	void		*abuf = NULL;
	fcoeio_t	*fcoeio;

	ret = fcoe_copyin_iocdata(data, mode, &fcoeio, &ibuf, &abuf, &obuf);
	if (ret != 0) {
		goto fcoeiocmd_release_buf;
	}

	/*
	 * If an exclusive open was demanded during open, ensure that
	 * only one thread can execute an ioctl at a time
	 */
	mutex_enter(&ss->ss_ioctl_mutex);
	if (ss->ss_ioctl_flags & FCOE_IOCTL_FLAG_EXCL) {
		if (ss->ss_ioctl_flags & FCOE_IOCTL_FLAG_EXCL_BUSY) {
			mutex_exit(&ss->ss_ioctl_mutex);
			fcoeio->fcoeio_status = FCOEIOE_BUSY;
			ret = EBUSY;
			goto fcoeiocmd_release_buf;
		}
		ss->ss_ioctl_flags |= FCOE_IOCTL_FLAG_EXCL_BUSY;
	}
	mutex_exit(&ss->ss_ioctl_mutex);

	fcoeio->fcoeio_status = 0;

	switch (fcoeio->fcoeio_cmd) {
	case FCOEIO_CREATE_FCOE_PORT: {
		fcoeio_create_port_param_t	*param =
		    (fcoeio_create_port_param_t *)ibuf;
		int		cmpwwn = 0;
		fcoe_port_t	*eport;

		if (fcoeio->fcoeio_ilen !=
		    sizeof (fcoeio_create_port_param_t) ||
		    fcoeio->fcoeio_xfer != FCOEIO_XFER_WRITE) {
			fcoeio->fcoeio_status = FCOEIOE_INVAL_ARG;
			ret = EINVAL;
			break;
		}

		mutex_enter(&ss->ss_ioctl_mutex);
		fcoe_mac = fcoe_create_mac_by_id(param->fcp_mac_linkid);
		if (fcoe_mac == NULL) {
			mutex_exit(&ss->ss_ioctl_mutex);
			fcoeio->fcoeio_status = FCOEIOE_CREATE_MAC;
			ret = EIO;
			break;
		}

		if (fcoe_mac->fm_flags & FCOE_MAC_FLAG_ENABLED) {
			mutex_exit(&ss->ss_ioctl_mutex);
			fcoeio->fcoeio_status = FCOEIOE_ALREADY;
			ret = EALREADY;
			break;
		} else {
			ret = fcoe_open_mac(fcoe_mac, param->fcp_force_promisc,
			    &fcoeio->fcoeio_status);
			if (ret != 0) {
				fcoe_destroy_mac(fcoe_mac);
				mutex_exit(&ss->ss_ioctl_mutex);
				if (fcoeio->fcoeio_status == 0) {
					fcoeio->fcoeio_status =
					    FCOEIOE_OPEN_MAC;
				}
				ret = EIO;
				break;
			} else {
				fcoe_mac->fm_flags |= FCOE_MAC_FLAG_ENABLED;
			}
		}

		/*
		 * Provide PWWN and NWWN based on mac address
		 */
		eport = &fcoe_mac->fm_eport;
		if (!param->fcp_pwwn_provided) {
			fcoe_init_wwn_from_mac(eport->eport_portwwn,
			    fcoe_mac->fm_current_addr, 1, 0);
		} else {
			(void) memcpy(eport->eport_portwwn, param->fcp_pwwn, 8);
		}

		if (!param->fcp_nwwn_provided) {
			fcoe_init_wwn_from_mac(eport->eport_nodewwn,
			    fcoe_mac->fm_current_addr, 0, 0);
		} else {
			(void) memcpy(eport->eport_nodewwn, param->fcp_nwwn, 8);
		}

		cmpwwn = fcoe_cmp_wwn(fcoe_mac);

		if (cmpwwn != 0) {
			if (cmpwwn == 1) {
				fcoeio->fcoeio_status = FCOEIOE_PWWN_CONFLICTED;
			} else if (cmpwwn == -1) {
				fcoeio->fcoeio_status = FCOEIOE_NWWN_CONFLICTED;
			}
			(void) fcoe_close_mac(fcoe_mac);
			fcoe_destroy_mac(fcoe_mac);
			mutex_exit(&ss->ss_ioctl_mutex);
			ret = ENOTUNIQ;
			break;
		}

		if (ret == 0) {
			ret = fcoe_create_port(ss->ss_dip,
			    fcoe_mac,
			    (param->fcp_port_type == FCOE_CLIENT_TARGET));
			if (ret != 0) {
				(void) fcoe_close_mac(fcoe_mac);
				fcoe_destroy_mac(fcoe_mac);
				fcoeio->fcoeio_status = FCOEIOE_CREATE_PORT;
				ret = EIO;
			}
		}
		mutex_exit(&ss->ss_ioctl_mutex);

		break;
	}

	case FCOEIO_DELETE_FCOE_PORT: {
		fcoeio_delete_port_param_t *del_port_param =
		    (fcoeio_delete_port_param_t *)ibuf;
		uint64_t *is_target = (uint64_t *)obuf;

		if (fcoeio->fcoeio_ilen < sizeof (fcoeio_delete_port_param_t) ||
		    fcoeio->fcoeio_olen != sizeof (uint64_t) ||
		    fcoeio->fcoeio_xfer != FCOEIO_XFER_RW) {
			fcoeio->fcoeio_status = FCOEIOE_INVAL_ARG;
			ret = EINVAL;
			break;
		}

		mutex_enter(&ss->ss_ioctl_mutex);
		ret = fcoe_delete_port(ss->ss_dip, fcoeio,
		    del_port_param->fdp_mac_linkid, is_target);
		mutex_exit(&ss->ss_ioctl_mutex);
		FCOE_LOG("fcoe", "fcoe_delete_port %x return: %d",
		    del_port_param->fdp_mac_linkid, ret);
		break;
	}

	case FCOEIO_GET_FCOE_PORT_LIST: {
		fcoe_port_list_t *list = (fcoe_port_list_t *)obuf;
		int		count;

		if (fcoeio->fcoeio_xfer != FCOEIO_XFER_READ ||
		    fcoeio->fcoeio_olen < sizeof (fcoe_port_list_t)) {
			fcoeio->fcoeio_status = FCOEIOE_INVAL_ARG;
			ret = EINVAL;
			break;
		}
		mutex_enter(&ss->ss_ioctl_mutex);

		list->numPorts = 1 + (fcoeio->fcoeio_olen -
		    sizeof (fcoe_port_list_t))/sizeof (fcoe_port_instance_t);

		count = fcoe_get_port_list(list->ports, list->numPorts);

		if (count > list->numPorts) {
			fcoeio->fcoeio_status = FCOEIOE_MORE_DATA;
			ret = ENOSPC;
		}
		list->numPorts = count;
		mutex_exit(&ss->ss_ioctl_mutex);

		break;

	}

	default:
		return (ENOTTY);
	}

	FCOE_LOG("fcoe", "fcoe_ioctl %x returned %d, fcoeio_status = %d",
	    fcoeio->fcoeio_cmd, ret, fcoeio->fcoeio_status);

fcoeiocmd_release_buf:
	if (ret == 0) {
		ret = fcoe_copyout_iocdata(data, mode, fcoeio, obuf);
	} else if (fcoeio->fcoeio_status) {
		(void) fcoe_copyout_iocdata(data, mode, fcoeio, obuf);
	}

	if (obuf != NULL) {
		kmem_free(obuf, fcoeio->fcoeio_olen);
		obuf = NULL;
	}
	if (abuf != NULL) {
		kmem_free(abuf, fcoeio->fcoeio_alen);
		abuf = NULL;
	}

	if (ibuf != NULL) {
		kmem_free(ibuf, fcoeio->fcoeio_ilen);
		ibuf = NULL;
	}
	kmem_free(fcoeio, sizeof (fcoeio_t));

	return (ret);
}

/*
 * Finish final initialization
 */
static int
fcoe_attach_init(fcoe_soft_state_t *ss)
{
	char taskq_name[TASKQ_NAME_LEN];

	if (ddi_create_minor_node(ss->ss_dip, "admin", S_IFCHR,
	    ddi_get_instance(ss->ss_dip), DDI_PSEUDO, 0) != DDI_SUCCESS) {
		FCOE_LOG("FCOE", "ddi_create_minor_node failed");
		return (FCOE_FAILURE);
	}

	/*
	 * watchdog responsible for release frame and dispatch events
	 */
	(void) snprintf(taskq_name, sizeof (taskq_name), "fcoe_mac");
	taskq_name[TASKQ_NAME_LEN - 1] = 0;
	if ((ss->ss_watchdog_taskq = ddi_taskq_create(NULL,
	    taskq_name, 2, TASKQ_DEFAULTPRI, 0)) == NULL) {
		return (FCOE_FAILURE);
	}

	ss->ss_ioctl_flags = 0;
	mutex_init(&ss->ss_ioctl_mutex, NULL, MUTEX_DRIVER, NULL);
	list_create(&ss->ss_mac_list, sizeof (fcoe_mac_t),
	    offsetof(fcoe_mac_t, fm_ss_node));
	list_create(&ss->ss_pfrm_list, sizeof (fcoe_i_frame_t),
	    offsetof(fcoe_i_frame_t, fmi_pending_node));

	mutex_init(&ss->ss_watch_mutex, 0, MUTEX_DRIVER, 0);
	cv_init(&ss->ss_watch_cv, NULL, CV_DRIVER, NULL);
	ss->ss_flags &= ~SS_FLAG_TERMINATE_WATCHDOG;
	(void) ddi_taskq_dispatch(ss->ss_watchdog_taskq,
	    fcoe_watchdog, ss, DDI_SLEEP);
	while ((ss->ss_flags & SS_FLAG_WATCHDOG_RUNNING) == 0) {
		delay(10);
	}
	fcoe_nworkers = ddi_prop_get_int(DDI_DEV_T_ANY, ss->ss_dip,
	    DDI_PROP_NOTPROM | DDI_PROP_DONTPASS, (char *)fcoe_workers_num, 4);
	if (fcoe_nworkers < 1) {
		fcoe_nworkers = 4;
	}
	fcoe_worker_init();

	ddi_report_dev(ss->ss_dip);
	return (FCOE_SUCCESS);
}

/*
 * Finish final uninitialization
 */
static int
fcoe_detach_uninit(fcoe_soft_state_t *ss)
{
	int ret;
	if (!list_is_empty(&ss->ss_mac_list)) {
		FCOE_LOG("fcoe", "ss_mac_list is not empty when detach");
		return (FCOE_FAILURE);
	}

	if ((ret = fcoe_worker_fini()) != FCOE_SUCCESS) {
		return (ret);
	}

	/*
	 * Stop watchdog
	 */
	if (ss->ss_flags & SS_FLAG_WATCHDOG_RUNNING) {
		mutex_enter(&ss->ss_watch_mutex);
		ss->ss_flags |= SS_FLAG_TERMINATE_WATCHDOG;
		cv_broadcast(&ss->ss_watch_cv);
		mutex_exit(&ss->ss_watch_mutex);
		while (ss->ss_flags & SS_FLAG_WATCHDOG_RUNNING) {
			delay(10);
		}
	}

	ddi_taskq_destroy(ss->ss_watchdog_taskq);
	mutex_destroy(&ss->ss_watch_mutex);
	cv_destroy(&ss->ss_watch_cv);

	ddi_remove_minor_node(ss->ss_dip, NULL);
	mutex_destroy(&ss->ss_ioctl_mutex);
	list_destroy(&ss->ss_mac_list);

	return (FCOE_SUCCESS);
}

/*
 * Return mac instance if it exist, or else return NULL.
 */
fcoe_mac_t *
fcoe_lookup_mac_by_id(datalink_id_t linkid)
{
	fcoe_mac_t	*mac = NULL;

	ASSERT(MUTEX_HELD(&fcoe_global_ss->ss_ioctl_mutex));
	for (mac = list_head(&fcoe_global_ss->ss_mac_list); mac;
	    mac = list_next(&fcoe_global_ss->ss_mac_list, mac)) {
		if (linkid != mac->fm_linkid) {
			continue;
		}
		return (mac);
	}
	return (NULL);
}

/*
 * port wwn will start with 20:..., node wwn will start with 10:...
 */
static void
fcoe_init_wwn_from_mac(uint8_t *wwn, uint8_t *mac, int is_pwwn, uint8_t idx)
{
	ASSERT(wwn != NULL);
	ASSERT(mac != NULL);
	wwn[0] = (is_pwwn + 1) << 4;
	wwn[1] = idx;
	bcopy(mac, wwn + 2, ETHERADDRL);
}

/*
 * Return fcoe_mac if it exists, otherwise create a new one
 */
static fcoe_mac_t *
fcoe_create_mac_by_id(datalink_id_t linkid)
{
	fcoe_mac_t	*mac = NULL;
	ASSERT(MUTEX_HELD(&fcoe_global_ss->ss_ioctl_mutex));

	mac = fcoe_lookup_mac_by_id(linkid);
	if (mac != NULL) {
		FCOE_LOG("fcoe", "fcoe_create_mac_by_id found one mac %d",
		    linkid);
		return (mac);
	}

	mac = kmem_zalloc(sizeof (fcoe_mac_t), KM_SLEEP);
	mac->fm_linkid = linkid;
	mac->fm_flags = 0;
	mac->fm_ss = fcoe_global_ss;
	list_insert_tail(&mac->fm_ss->ss_mac_list, mac);
	FCOE_LOG("fcoe", "fcoe_create_mac_by_id created one mac %d", linkid);
	return (mac);
}

void
fcoe_destroy_mac(fcoe_mac_t *mac)
{
	ASSERT(mac != NULL);
	list_remove(&mac->fm_ss->ss_mac_list, mac);
	kmem_free(mac, sizeof (fcoe_mac_t));
}

/*
 * raw frame layout:
 * ethernet header + vlan header (optional) + FCoE header +
 * FC frame + FCoE tailer
 */
/* ARGSUSED */
mblk_t *
fcoe_get_mblk(fcoe_mac_t *mac, uint32_t raw_frame_size)
{
	mblk_t	*mp;
	int	 err;

	/*
	 * FCFH_SIZE + PADDING_SIZE
	 */
	ASSERT(raw_frame_size >= 60);
	while ((mp = allocb((size_t)raw_frame_size, 0)) == NULL) {
		if ((err = strwaitbuf((size_t)raw_frame_size, BPRI_LO)) != 0) {
			FCOE_LOG("fcoe_get_mblk", "strwaitbuf return %d", err);
			return (NULL);
		}
	}
	mp->b_wptr = mp->b_rptr + raw_frame_size;

	/*
	 * We should always zero FC frame header
	 */
	bzero(mp->b_rptr + PADDING_HEADER_SIZE,
	    sizeof (fcoe_fc_frame_header_t));
	return (mp);
}

static void
fcoe_watchdog(void *arg)
{
	fcoe_soft_state_t	*ss	   = (fcoe_soft_state_t *)arg;
	fcoe_i_frame_t		*fmi;
	fcoe_mac_t		*mac = NULL;

	FCOE_LOG("fcoe", "fcoe_soft_state is %p", ss);

	mutex_enter(&ss->ss_watch_mutex);
	ss->ss_flags |= SS_FLAG_WATCHDOG_RUNNING;
	while ((ss->ss_flags & SS_FLAG_TERMINATE_WATCHDOG) == 0) {
		while (fmi = (fcoe_i_frame_t *)list_head(&ss->ss_pfrm_list)) {
			list_remove(&ss->ss_pfrm_list, fmi);
			mutex_exit(&ss->ss_watch_mutex);

			mac = EPORT2MAC(fmi->fmi_frame->frm_eport);
			mac->fm_client.ect_release_sol_frame(fmi->fmi_frame);

			mutex_enter(&ss->ss_watch_mutex);
			mac->fm_frm_cnt--;
		}

		ss->ss_flags |= SS_FLAG_DOG_WAITING;
		(void) cv_wait(&ss->ss_watch_cv, &ss->ss_watch_mutex);
		ss->ss_flags &= ~SS_FLAG_DOG_WAITING;
	}

	ss->ss_flags &= ~SS_FLAG_WATCHDOG_RUNNING;
	mutex_exit(&ss->ss_watch_mutex);
}

static void
fcoe_worker_init()
{
	uint32_t i;

	fcoe_nworkers_running = 0;
	fcoe_worker_taskq = ddi_taskq_create(0, "FCOE_WORKER_TASKQ",
	    fcoe_nworkers, TASKQ_DEFAULTPRI, 0);
	fcoe_workers = (fcoe_worker_t *)kmem_zalloc(sizeof (fcoe_worker_t) *
	    fcoe_nworkers, KM_SLEEP);
	for (i = 0; i < fcoe_nworkers; i++) {
		fcoe_worker_t *w = &fcoe_workers[i];
		mutex_init(&w->worker_lock, NULL, MUTEX_DRIVER, NULL);
		cv_init(&w->worker_cv, NULL, CV_DRIVER, NULL);
		w->worker_flags &= ~FCOE_WORKER_TERMINATE;
		list_create(&w->worker_frm_list, sizeof (fcoe_i_frame_t),
		    offsetof(fcoe_i_frame_t, fmi_pending_node));
		(void) ddi_taskq_dispatch(fcoe_worker_taskq, fcoe_worker_frame,
		    w, DDI_SLEEP);
	}
	while (fcoe_nworkers_running != fcoe_nworkers) {
		delay(10);
	}
}

static int
fcoe_worker_fini()
{
	uint32_t i;

	for (i = 0; i < fcoe_nworkers; i++) {
		fcoe_worker_t *w = &fcoe_workers[i];
		mutex_enter(&w->worker_lock);
		if (w->worker_flags & FCOE_WORKER_STARTED) {
			w->worker_flags |= FCOE_WORKER_TERMINATE;
			cv_signal(&w->worker_cv);
		}
		mutex_exit(&w->worker_lock);
	}

	while (fcoe_nworkers_running != 0) {
		delay(drv_usectohz(10000));
	}

	ddi_taskq_destroy(fcoe_worker_taskq);
	kmem_free(fcoe_workers, sizeof (fcoe_worker_t) * fcoe_nworkers);
	fcoe_workers = NULL;
	return (FCOE_SUCCESS);
}

static int
fcoe_crc_verify(fcoe_frame_t *frm)
{
	uint32_t crc;
	uint8_t *crc_array = FRM2FMI(frm)->fmi_fft->fft_crc;
	uint32_t crc_from_frame = ~(crc_array[0] | (crc_array[1] << 8) |
	    (crc_array[2] << 16) | (crc_array[3] << 24));
	CRC32(crc, frm->frm_fc_frame, frm->frm_fc_frame_size, -1U, crc32_table);
	return (crc == crc_from_frame ? FCOE_SUCCESS : FCOE_FAILURE);
}

static void
fcoe_worker_frame(void *arg)
{
	fcoe_worker_t	*w = (fcoe_worker_t *)arg;
	fcoe_i_frame_t	*fmi;
	int		ret;

	atomic_add_32(&fcoe_nworkers_running, 1);
	mutex_enter(&w->worker_lock);
	w->worker_flags |= FCOE_WORKER_STARTED | FCOE_WORKER_ACTIVE;
	while ((w->worker_flags & FCOE_WORKER_TERMINATE) == 0) {
		/*
		 * loop through the frames
		 */
		while (fmi = list_head(&w->worker_frm_list)) {
			list_remove(&w->worker_frm_list, fmi);
			mutex_exit(&w->worker_lock);
			/*
			 * do the checksum
			 */
			ret = fcoe_crc_verify(fmi->fmi_frame);
			if (ret == FCOE_SUCCESS) {
				fmi->fmi_mac->fm_client.ect_rx_frame(
				    fmi->fmi_frame);
			} else {
				fcoe_release_frame(fmi->fmi_frame);
			}
			mutex_enter(&w->worker_lock);
			w->worker_ntasks--;
		}
		w->worker_flags &= ~FCOE_WORKER_ACTIVE;
		cv_wait(&w->worker_cv, &w->worker_lock);
		w->worker_flags |= FCOE_WORKER_ACTIVE;
	}
	w->worker_flags &= ~(FCOE_WORKER_STARTED | FCOE_WORKER_ACTIVE);
	mutex_exit(&w->worker_lock);
	atomic_add_32(&fcoe_nworkers_running, -1);
	list_destroy(&w->worker_frm_list);
}

void
fcoe_post_frame(fcoe_frame_t *frm)
{
	fcoe_worker_t *w;
	uint16_t	oxid = FRM_OXID(frm);

	w = &fcoe_workers[oxid % fcoe_nworkers_running];
	mutex_enter(&w->worker_lock);
	list_insert_tail(&w->worker_frm_list, frm->frm_fcoe_private);
	w->worker_ntasks++;
	if ((w->worker_flags & FCOE_WORKER_ACTIVE) == 0) {
		cv_signal(&w->worker_cv);
	}
	mutex_exit(&w->worker_lock);
}

/*
 * The max length of every LOG is 158
 */
void
fcoe_trace(caddr_t ident, const char *fmt, ...)
{
	va_list args;
	char	tbuf[160];
	int	len;
	clock_t curclock;
	clock_t usec;

	if (fcoe_trace_on == 0) {
		return;
	}

	curclock = ddi_get_lbolt();
	usec = (curclock - fcoe_trace_start) * usec_per_tick;
	len = snprintf(tbuf, 158, "%lu.%03lus 0t%lu %s ", (usec /
	    (1000 * 1000)), ((usec % (1000 * 1000)) / 1000),
	    curclock, (ident ? ident : "unknown"));
	va_start(args, fmt);
	len += vsnprintf(tbuf + len, 158 - len, fmt, args);
	va_end(args);

	if (len > 158) {
		len = 158;
	}
	tbuf[len++] = '\n';
	tbuf[len] = 0;

	mutex_enter(&fcoe_trace_buf_lock);
	bcopy(tbuf, &fcoe_trace_buf[fcoe_trace_buf_curndx], len+1);
	fcoe_trace_buf_curndx += len;
	if (fcoe_trace_buf_curndx > (fcoe_trace_buf_size - 320)) {
		fcoe_trace_buf_curndx = 0;
	}
	mutex_exit(&fcoe_trace_buf_lock);
}

/*
 * Check whether the pwwn or nwwn already exist or not
 * Return value:
 * 1: PWWN conflicted
 * -1: NWWN conflicted
 * 0: No conflict
 */
static int
fcoe_cmp_wwn(fcoe_mac_t *checkedmac)
{
	fcoe_mac_t	*mac;
	uint8_t		*nwwn, *pwwn, *cnwwn, *cpwwn;

	cnwwn = checkedmac->fm_eport.eport_nodewwn;
	cpwwn = checkedmac->fm_eport.eport_portwwn;
	ASSERT(MUTEX_HELD(&fcoe_global_ss->ss_ioctl_mutex));

	for (mac = list_head(&fcoe_global_ss->ss_mac_list); mac;
	    mac = list_next(&fcoe_global_ss->ss_mac_list, mac)) {
		if (mac == checkedmac) {
			continue;
		}
		nwwn = mac->fm_eport.eport_nodewwn;
		pwwn = mac->fm_eport.eport_portwwn;

		if (memcmp(nwwn, cnwwn, 8) == 0) {
			return (-1);
		}

		if (memcmp(pwwn, cpwwn, 8) == 0) {
			return (1);
		}
	}
	return (0);
}

static int
fcoe_get_port_list(fcoe_port_instance_t *ports, int count)
{
	fcoe_mac_t	*mac = NULL;
	int		i = 0;

	ASSERT(ports != NULL);
	ASSERT(MUTEX_HELD(&fcoe_global_ss->ss_ioctl_mutex));

	for (mac = list_head(&fcoe_global_ss->ss_mac_list); mac;
	    mac = list_next(&fcoe_global_ss->ss_mac_list, mac)) {
		if (i < count) {
			bcopy(mac->fm_eport.eport_portwwn,
			    ports[i].fpi_pwwn, 8);
			ports[i].fpi_mac_linkid = mac->fm_linkid;
			bcopy(mac->fm_current_addr,
			    ports[i].fpi_mac_current_addr, ETHERADDRL);
			bcopy(mac->fm_primary_addr,
			    ports[i].fpi_mac_factory_addr, ETHERADDRL);
			ports[i].fpi_port_type =
			    EPORT_CLT_TYPE(&mac->fm_eport);
			ports[i].fpi_mtu_size =
			    mac->fm_eport.eport_mtu;
			ports[i].fpi_mac_promisc =
			    mac->fm_promisc_handle != NULL ? 1 : 0;
		}
		i++;
	}
	return (i);
}