view usr/src/uts/common/io/fibre-channel/fca/fcoei/fcoei_lv.c @ 13716:3db0c1979163

2562 FCoE iniator mistreats its ints Reviewed by: Jason King <jason.brian.king@gmail.com> Reviewed by: Garrett D'Amore <garrett@damore.org> Approved by: Garrett D'Amore <garrett@damore.org>
author Richard Lowe <richlowe@richlowe.net>
date Mon, 16 May 2011 00:27:26 +0100
parents 76234fce8dc5
children
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 (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * This file defines interfaces between FCOE and LEADVILLE
 */

/*
 * Driver kernel header files
 */
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/stat.h>
#include <sys/pci.h>
#include <sys/sunddi.h>
#include <sys/modctl.h>
#include <sys/file.h>
#include <sys/cred.h>
#include <sys/byteorder.h>
#include <sys/atomic.h>
#include <sys/scsi/scsi.h>
#include <sys/mac_client.h>
#include <sys/modhash.h>

/*
 * LEADVILLE header files
 */
#include <sys/fibre-channel/fc.h>
#include <sys/fibre-channel/impl/fc_fcaif.h>

/*
 * COMSTAR head files (BIT_* macro)
 */
#include <sys/stmf_defines.h>

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

/*
 * Driver's own header files
 */
#include <fcoei.h>

/*
 * forward declaration of static functions
 */
static void fcoei_port_enabled(void *arg);

static void fcoei_populate_hba_fru_details(fcoei_soft_state_t *ss,
    fc_fca_port_info_t *port_info);

static void fcoei_initiate_ct_req(fcoei_exchange_t *xch);
static void fcoei_initiate_fcp_cmd(fcoei_exchange_t *xch);
static void fcoei_initiate_els_req(fcoei_exchange_t *xch);
static void fcoei_initiate_els_resp(fcoei_exchange_t *xch);

static void fcoei_fill_els_logi_cmd(fc_packet_t *fpkt, fcoe_frame_t *frm);
static void fcoei_fill_els_prli_cmd(fc_packet_t *fpkt, fcoe_frame_t *frm);
static void fcoei_fill_els_logo_cmd(fc_packet_t *fpkt, fcoe_frame_t *frm);
static void fcoei_fill_els_scr_cmd(fc_packet_t *fpkt, fcoe_frame_t *frm);
static void fcoei_fill_els_adisc_cmd(fc_packet_t *fpkt, fcoe_frame_t *frm);
static void fcoei_fill_els_linit_cmd(fc_packet_t *fpkt, fcoe_frame_t *frm);
static void fcoei_fill_els_rls_cmd(fc_packet_t *fpkt, fcoe_frame_t *frm);
static void fcoei_fill_els_rnid_cmd(fc_packet_t *fpkt, fcoe_frame_t *frm);

static void fcoei_fill_els_acc_resp(fc_packet_t *fpkt, fcoe_frame_t *frm);
static void fcoei_fill_els_rjt_resp(fc_packet_t *fpkt, fcoe_frame_t *frm);
static void fcoei_fill_els_logi_resp(fc_packet_t *fpkt, fcoe_frame_t *frm);
static void fcoei_fill_els_prli_resp(fc_packet_t *fpkt, fcoe_frame_t *frm);
static void fcoei_fill_els_logo_resp(fc_packet_t *fpkt, fcoe_frame_t *frm);
static void fcoei_fill_els_adisc_resp(fc_packet_t *fpkt, fcoe_frame_t *frm);

static void fcoei_logo_peer(void *arg);
static void fcoei_fpkt_comp(fc_packet_t *fpkt);

static uint32_t
fcoei_xch_abort(mod_hash_key_t key, mod_hash_val_t *val, void *arg);


/*
 * fcoei_bind_port
 *	Bind LV port instance with fcoei soft state
 *
 * Input:
 *	dip = dev info of fcoei soft state
 *	port_info = fcoei specific parameters about LV port
 *	bind_info = LV specific parameters about fcoei soft state
 *
 * Returns:
 *	The pointer to fcoei soft state
 *
 * Comments:
 *	Unpon the completion of this call, the port must be offline.
 *	fcoei_port_enabled could trigger it to online
 */
static void *
fcoei_bind_port(dev_info_t *dip, fc_fca_port_info_t *port_info,
    fc_fca_bind_info_t *bind_info)
{
	fcoei_soft_state_t	*ss;

	/*
	 * get state info based on the dip
	 */
	ss = (fcoei_soft_state_t *)
	    ddi_get_soft_state(fcoei_state, ddi_get_instance(dip));
	if (!ss) {
		FCOEI_LOG(__FUNCTION__, "ss is NULL");
		return (NULL);
	}

	/*
	 * make sure this port isn't bound
	 */
	if (ss->ss_flags & SS_FLAG_LV_BOUND) {
		port_info->pi_error = FC_ALREADY;
		FCOEI_LOG(__FUNCTION__, "ss has been bound");
		return (NULL);
	}

	if (bind_info->port_num) {
		/*
		 * make sure request is in bounds
		 */
		port_info->pi_error = FC_OUTOFBOUNDS;
		FCOEI_LOG(__FUNCTION__, "port_num is not 0");
		return (NULL);
	}

	/*
	 * stash the ss_bind_info supplied by the FC Transport
	 */
	bcopy(bind_info, &ss->ss_bind_info, sizeof (fc_fca_bind_info_t));
	ss->ss_port = bind_info->port_handle;

	/*
	 * RNID parameter
	 */
	port_info->pi_rnid_params.status = FC_FAILURE;

	/*
	 * populate T11 FC-HBA details
	 */
	fcoei_populate_hba_fru_details(ss, port_info);

	/*
	 * set port's current state, and it is always offline before binding
	 *
	 * We hack pi_port_state to tell LV if it's NODMA_FCA
	 */
	port_info->pi_port_state = FC_STATE_FCA_IS_NODMA;

	/*
	 * copy login param
	 */
	bcopy(&ss->ss_els_logi, &port_info->pi_login_params,
	    sizeof (la_els_logi_t));

	/*
	 * Mark it as bound
	 */
	atomic_or_32(&ss->ss_flags, SS_FLAG_LV_BOUND);

	/*
	 * Let fcoe to report the link status
	 */
	fcoei_port_enabled((void *)ss);

	FCOEI_LOG(__FUNCTION__, "Exit fcoei_bind_port: %p", ss);
	return (ss);
}

/*
 * fcoei_unbind_port
 *	Un-bind the fcoei port
 *
 * Input:
 *	fca_handle = fcoei soft state set in fcoei_bind_port
 *
 * Returns:
 *	N/A
 *
 * Comments:
 *	Clear binding flag
 */
static void
fcoei_unbind_port(void *fca_handle)
{
	fcoei_soft_state_t *ss = (fcoei_soft_state_t *)fca_handle;

	atomic_and_32(&ss->ss_flags, ~SS_FLAG_LV_BOUND);
	ss->ss_eport->eport_ctl(ss->ss_eport, FCOE_CMD_PORT_OFFLINE, NULL);
	FCOEI_LOG(__FUNCTION__, "Exit fcoei_unbind_port: %p", ss);
}

/*
 * fcoei_init_pkt
 *	Initialize fcoei related part of fc_packet
 *
 * Input:
 *	fca_handle = fcoei soft state set in fcoei_bind_port
 *	fpkt = The pointer to fc_packet
 *	sleep = This call can sleep or not
 *
 * Returns:
 *	FC_SUCCESS - Initialization completed successfully
 *
 * Comments:
 *	Link the exchange elements with proper objects
 */
/* ARGSUSED */
static int
fcoei_init_pkt(void *fca_handle, fc_packet_t *fpkt, int sleep)
{
	fcoei_soft_state_t	*ss  = (fcoei_soft_state_t *)fca_handle;
	fcoei_exchange_t	*xch = FPKT2XCH(fpkt);

	ASSERT(sleep + 1);
	xch->xch_ss = ss;
	xch->xch_fpkt = fpkt;
	xch->xch_flags = 0;
	return (FC_SUCCESS);
}

/*
 * fcoei_un_init_pkt
 *	Uninitialize fcoei related part of fc_packet
 *
 * Input:
 *	fca_handle = fcoei soft state set in fcoei_bind_port
 *	fpkt = The pointer to fc_packet
 *
 * Returns:
 *	FC_SUCCESS - Uninitialize successfully
 *
 * Comments:
 *	Very simple, just return successfully
 */
/* ARGSUSED */
static int
fcoei_un_init_pkt(void *fca_handle, fc_packet_t *fpkt)
{
	ASSERT(fca_handle && fpkt);
	return (FC_SUCCESS);
}

/*
 * fcoei_get_cap
 *	Export FCA hardware and software capability.
 *
 * Input:
 *	fca_handle = fcoei soft state set in fcoei_bind_port
 *	cap = pointer to the capability string
 *	ptr = buffer pointer for returning capability
 *
 * Returns:
 *	FC_CAP_ERROR - no such capability
 *	FC_CAP_FOUND - the capability was returned and cannot be set
 *
 * Comments:
 *	FC_CAP_UNSOL_BUF is one important capability, it will affect the
 *	implementation of fcoei_ub_alloc/free.
 */
static int
fcoei_get_cap(void * fca_handle, char *cap, void *ptr)
{
	fcoei_soft_state_t	*ss   = (fcoei_soft_state_t *)fca_handle;
	uint32_t		*rptr = (uint32_t *)ptr;
	int			 rval = FC_CAP_FOUND;

	ASSERT(fca_handle);
	FCOEI_LOG(__FUNCTION__, "cap: %s", cap);
	if (strcmp(cap, FC_NODE_WWN) == 0) {
		bcopy(&ss->ss_els_logi.node_ww_name.raw_wwn[0], ptr, 8);
	} else if (strcmp(cap, FC_LOGIN_PARAMS) == 0) {
		bcopy((void *)&ss->ss_els_logi, ptr, sizeof (la_els_logi_t));
	} else if (strcmp(cap, FC_CAP_UNSOL_BUF) == 0) {
		*rptr = (uint32_t)0;
	} else if (strcmp(cap, FC_CAP_NOSTREAM_ON_UNALIGN_BUF) == 0) {
		*rptr = (uint32_t)FC_ALLOW_STREAMING;
	} else if (strcmp(cap, FC_CAP_PAYLOAD_SIZE) == 0) {
		*rptr = (uint32_t)2136;
	} else if (strcmp(cap, FC_CAP_POST_RESET_BEHAVIOR) == 0) {
		*rptr = FC_RESET_RETURN_ALL;
	} else if (strcmp(cap, FC_CAP_FCP_DMA) == 0) {
		*rptr = FC_NO_DVMA_SPACE;
	} else {
		rval = FC_CAP_ERROR;
		FCOEI_LOG(__FUNCTION__, "not supported");
	}

	return (rval);
}

/*
 * fcoei_set_cap
 *	Allow the FC Transport to set FCA capabilities if possible
 *
 * Input:
 *	fca_handle = fcoei soft state set in fcoei_bind_port
 *	cap = pointer to the capabilities string.
 *	ptr = buffer pointer for capability.
 *
 * Returns:
 *	FC_CAP_ERROR - no such capability
 *
 * Comments:
 *	Currently, all capabilities can't be changed.
 */
static int
fcoei_set_cap(void * fca_handle, char *cap, void *ptr)
{
	FCOEI_LOG(__FUNCTION__, "cap: %s, %p, %p", cap, fca_handle, ptr);
	return (FC_CAP_ERROR);
}

/*
 * fcoei_getmap
 *	Get lilp map
 *
 * Input:
 *	fca_handle = fcoei soft state set in fcoei_bind_port
 *	mapbuf = the buffer to store lilp map
 *
 * Returns:
 *	FC_FAILURE - Can't get the lilp map
 *
 * Comments:
 *	fcoei can't work in loop topology, so it should never get called
 */
static int
fcoei_getmap(void * fca_handle, fc_lilpmap_t *mapbuf)
{
	FCOEI_LOG(__FUNCTION__, "not: %p-%p", fca_handle, mapbuf);
	return (FC_FAILURE);
}

/*
 * fcoei_ub_alloc
 *	Pre-allocate unsolicited buffers at the request of LV
 *
 * Input:
 *	fca_handle = fcoei soft state set in fcoei_bind_port
 *	tokens = token array for each buffer.
 *	size = number of tokens
 *	count = the acutual number of allocated unsolicited buffers
 *	type = unsolicited buffer type
 *
 * Returns:
 *	FC_SUCCESS - The requested buffers have been freeed
 *
 * Comments:
 *	fcoei_get_cap will set UNSOL_BUF to 0, so it should never get called.
 */
static int
fcoei_ub_alloc(void * fca_handle, uint64_t tokens[], uint32_t size,
    uint32_t *count, uint32_t type)
{
	FCOEI_LOG(__FUNCTION__, "not: %p-%p-%x-%p-%x", fca_handle, tokens,
	    size, count, type);
	return (FC_SUCCESS);
}

/*
 * fcoei_ub_free
 *	Free the pre-allocated unsolicited buffers at the request of LV
 *
 * Input:
 *	fca_handle = fcoei soft state set in fcoei_bind_port
 *	count = number of buffers.
 *	tokens = token array for each buffer.
 *
 * Returns:
 *	FC_SUCCESS - The requested buffers have been freeed
 *
 * Comments:
 *	fcoei_get_cap will set UNSOL_BUF to 0, so it should never get called.
 */
static int
fcoei_ub_free(void * fca_handle, uint32_t count, uint64_t tokens[])
{
	FCOEI_EXT_LOG(__FUNCTION__, "not: %p-%x-%p", fca_handle, count, tokens);
	return (FC_SUCCESS);
}

/*
 * fcoei_ub_release
 *	Release unsolicited buffers from FC Transport to FCA for future use
 *
 * Input:
 *	fca_handle = fcoei soft state set in fcoei_bind_port
 *	count = number of buffers.
 *	tokens = token array for each buffer.
 *
 * Returns:
 *	FC_SUCCESS - The requested buffers have been released.
 *	FC_FAILURE - The requested buffers have not been released.
 *
 * Comments:
 *	It will always succeed. It has nothing to do with fcoei_ub_alloc/free.
 */
static int
fcoei_ub_release(void * fca_handle, uint32_t count, uint64_t tokens[])
{
	fc_unsol_buf_t *ub = *((fc_unsol_buf_t **)tokens);

	if (count != 1) {
		FCOEI_LOG(__FUNCTION__, "count is not 1: %p", fca_handle);
		return (FC_FAILURE);
	}

	kmem_free(ub->ub_buffer, ub->ub_bufsize);
	kmem_free(ub, sizeof (fc_unsol_buf_t));
	FCOEI_EXT_LOG(__FUNCTION__, "ub is freeed");
	return (FC_SUCCESS);
}

/*
 * fcoei_abort
 *	Direct FCA driver to abort an outstanding exchange associated with a
 *	specified fc_packet_t struct
 *
 * Input:
 *	fca_handle - fcoei soft state set in fcoei_bind_port
 *	fpkt - A pointer to the fc_packet_t for the exchange to be aborted.
 *	flags - Set to KM_SLEEP if the function may sleep, or KM_NOSLEEP if
 *		the function may not sleep.
 *
 * Returns:
 *	FC_ABORTED - The specified exchange was successfully aborted.
 *	FC_ABORTING - The specified exchange is being aborted.
 *	FC_ABORT_FAILED - The specified exchange could not be aborted.
 *	FC_TRANSPORT_ERROR - A transport error occurred while attempting to
 *		abort the specified exchange.
 *	FC_BADEXCHANGE - The specified exchange does not exist.
 *
 * Comments:
 *	After the exchange is aborted, the FCA driver must update the relevant
 *	fields in the fc_packet_t struct as per normal exchange completion and
 *	call the pkt_comp function to return the fc_packet_t struct to the FC
 *	Transport.
 *	When an exchange is successfully aborted, the FCA driver must set the
 *	pkt_reason field in the fc_packet_t to FC_REASON_ABORTED and the
 *	pkt_state field in the fc_packet_t to FC_PKT_LOCAL_RJT before returning
 *	the fc_packet_t to the FC Transport.
 *
 *	Unfortunately, LV doesn't conform to the spec. It will take all these
 *	legal return value as failure to abort.
 */
static int
fcoei_abort(void * fca_handle, fc_packet_t *fpkt, int flags)
{
	FCOEI_LOG(__FUNCTION__, "not: %p-%p-%x", fca_handle, fpkt, flags);
	return (FC_SUCCESS);
}

/*
 * fcoei_reset
 *	Reset link or hardware
 *
 * Input:
 *	fca_handle = fcoei soft state set in fcoei_bind_port
 *	cmd = reset type command
 *
 * Returns:
 *	FC_SUCCESS - Reset has completed successfully
 *	FC_FAILURE - Reset has failed
 *
 * Comments:
 *	N/A
 */
static int
fcoei_reset(void * fca_handle, uint32_t cmd)
{
	int			 rval = FC_SUCCESS;
	fcoei_soft_state_t	*ss   = (fcoei_soft_state_t *)fca_handle;
	fcoei_event_t *ae;

	switch (cmd) {
	case FC_FCA_LINK_RESET:
		if (ss->ss_link_state != FC_STATE_ONLINE) {
			FCOEI_LOG(__FUNCTION__, "not online now: ss-%p", ss);
			rval = FC_FAILURE;
			break;
		}

		/*
		 * This is linkreset phase I
		 */
		fcoei_logo_peer(ss);
		delay(FCOE_SEC2TICK(1) / 10);
		ss->ss_eport->eport_ctl(ss->ss_eport, FCOE_CMD_PORT_OFFLINE, 0);
		fcoei_port_event(ss->ss_eport, FCOE_NOTIFY_EPORT_LINK_DOWN);

		/*
		 * Perpare linkreset phase II
		 */
		ae = kmem_zalloc(sizeof (*ae), KM_SLEEP);
		ae->ae_type = AE_EVENT_RESET;
		ae->ae_obj = ss;

		mutex_enter(&ss->ss_watchdog_mutex);
		list_insert_tail(&ss->ss_event_list, ae);
		mutex_exit(&ss->ss_watchdog_mutex);
		break;

	case FC_FCA_RESET:
		break;

	case FC_FCA_CORE:
		break;

	case FC_FCA_RESET_CORE:
		break;

	default:
		rval = FC_FAILURE;
		FCOEI_LOG(__FUNCTION__, "cmd-%x not supported", cmd);
		break;
	}

	return (rval);
}

/*
 * fcoei_port_manage
 *	Perform various port management operations at the request of LV
 *
 * Input:
 *	fca_handle = fcoei soft state set in fcoei_bind_port
 *	pm = the pointer to the struct specifying the port management operation
 *
 * Returns:
 *	FC_SUCCESS - The request completed successfully
 *	FC_FAILURE - The request did not complete successfully
 *
 * Comments:
 *	N/A
 */
static int
fcoei_port_manage(void * fca_handle, fc_fca_pm_t *pm)
{
	int	rval = FC_FAILURE;
	fcoei_soft_state_t	*ss = (fcoei_soft_state_t *)fca_handle;

	if (fca_handle == NULL || pm == NULL) {
		return (rval);
	}

	FCOEI_LOG(__FUNCTION__, "code0x%x, %p", pm->pm_cmd_code, fca_handle);
	switch (pm->pm_cmd_code) {

	case FC_PORT_GET_NODE_ID:
	{
		if (pm->pm_data_len < sizeof (fc_rnid_t)) {
			rval = FC_NOMEM;
			break;
		}
		ss->ss_rnid.port_id = ss->ss_p2p_info.fca_d_id;
		bcopy((void *)&ss->ss_rnid,
		    pm->pm_data_buf, sizeof (fc_rnid_t));
		rval = FC_SUCCESS;
		break;
	}

	case FC_PORT_SET_NODE_ID:
	{
		if (pm->pm_data_len < sizeof (fc_rnid_t)) {
			rval = FC_NOMEM;
			break;
		}
		bcopy(pm->pm_data_buf,
		    (void *)&ss->ss_rnid, sizeof (fc_rnid_t));
		rval = FC_SUCCESS;
		break;
	}

	default:
		FCOEI_LOG(__FUNCTION__, "unsupported cmd-%x", pm->pm_cmd_code);
		rval = FC_INVALID_REQUEST;
		break;
	}

	return (rval);
}

/*
 * fcoei_get_device
 *	Get fcoei remote port with FCID of d_id
 *
 * Input:
 *	fca_handle = fcoei soft state set in fcoei_bind_port
 *	d_id = 24-bit FCID of remote port
 *
 * Returns:
 *	The pointer to fcoei remote port
 *
 * Comments:
 *	fcoei has no remote port device
 */
static void *
fcoei_get_device(void *fca_handle, fc_portid_t d_id)
{
	FCOEI_EXT_LOG(__FUNCTION__, "not supported: %p-%x", fca_handle, d_id);
	return (NULL);
}

/*
 * fcoei_notify
 *	Notify the change of target device
 *
 * Input:
 *	fca_handle = fcoei soft state set in fcoei_bind_port
 *	cmd = detailed cmd
 *
 * Returns:
 *	FC_SUCCESS - Notification completed successfully
 *
 * Comments:
 *	It's only needed to support non-COMSTAR FC target, so it should
 *	never get called.
 */
static int
fcoei_notify(void *fca_handle, uint32_t cmd)
{
	FCOEI_LOG(__FUNCTION__, "not supported: %p-%x", fca_handle, cmd);
	return (FC_SUCCESS);
}

/*
 * fcoei_transport
 *	Submit FCP/CT requests
 *
 * Input:
 *	fca_handle - fcoei soft state set in fcoei_bind_port
 *	fpkt - LV fc_packet
 *
 * Returns:
 *	N/A
 *
 * Comments:
 *	N/A
 */
static int
fcoei_transport(void *fca_handle, fc_packet_t *fpkt)
{
	fcoei_soft_state_t	*ss  = (fcoei_soft_state_t *)fca_handle;
	fcoei_exchange_t	*xch = FPKT2XCH(fpkt);
	uint16_t		 pkt_tran_flags = fpkt->pkt_tran_flags;

	xch->xch_start_tick = ddi_get_lbolt();
	xch->xch_end_tick = xch->xch_start_tick +
	    FCOE_SEC2TICK(fpkt->pkt_timeout);
	xch->xch_ae.ae_type = AE_EVENT_EXCHANGE;
	xch->xch_ae.ae_obj = xch;

	if (pkt_tran_flags & FC_TRAN_NO_INTR) {
		FCOEI_LOG(__FUNCTION__, "AaA polling: %p-%p", fpkt, xch);
		sema_init(&xch->xch_sema, 0, NULL, SEMA_DRIVER, NULL);
	}

	mutex_enter(&ss->ss_watchdog_mutex);
	list_insert_tail(&ss->ss_event_list, &xch->xch_ae);
	if (ss->ss_flags & SS_FLAG_WATCHDOG_IDLE) {
		cv_signal(&ss->ss_watchdog_cv);
	}
	mutex_exit(&ss->ss_watchdog_mutex);

	if (pkt_tran_flags & FC_TRAN_NO_INTR) {
		FCOEI_LOG(__FUNCTION__, "BaB polling: %p-%p", fpkt, xch);
		sema_p(&xch->xch_sema);
		sema_destroy(&xch->xch_sema);
		FCOEI_LOG(__FUNCTION__, "after polling: %p-%p", fpkt, xch);
	}

	return (FC_SUCCESS);
}

/*
 * fcoei_els_send
 *	Submit ELS request or response
 *
 * Input:
 *	fca_handle - fcoei soft state set in fcoei_bind_port
 *	fpkt = LV fc_packet
 *
 * Returns:
 *	N/A
 *
 * Comments:
 *	N/A
 */
static int
fcoei_els_send(void *fca_handle, fc_packet_t *fpkt)
{
	fcoei_soft_state_t	*ss  = (fcoei_soft_state_t *)fca_handle;
	fcoei_exchange_t	*xch = FPKT2XCH(fpkt);

	if (fpkt->pkt_tran_flags & FC_TRAN_NO_INTR) {
		FCOEI_LOG(__FUNCTION__, "ELS poll mode is not supported");
		return (FC_BADPACKET);
	}

	xch->xch_start_tick = ddi_get_lbolt();
	xch->xch_end_tick = xch->xch_start_tick +
	    FCOE_SEC2TICK(fpkt->pkt_timeout);
	xch->xch_ae.ae_type = AE_EVENT_EXCHANGE;
	xch->xch_ae.ae_obj = xch;

	/*
	 * LV could release ub after this call, so we must save the ub type
	 * for later use
	 */
	if (fpkt->pkt_cmd_fhdr.r_ctl == R_CTL_ELS_RSP) {
		((uint8_t *)&fpkt->pkt_fca_rsvd1)[0] =
		    ((fc_unsol_buf_t *)fpkt->pkt_ub_resp_token)->ub_buffer[0];
	}

	mutex_enter(&ss->ss_watchdog_mutex);
	list_insert_tail(&ss->ss_event_list, &xch->xch_ae);
	if (ss->ss_flags & SS_FLAG_WATCHDOG_IDLE) {
		cv_signal(&ss->ss_watchdog_cv);
	}
	mutex_exit(&ss->ss_watchdog_mutex);

	return (FC_SUCCESS);
}

/*
 * fcoei_populate_hba_fru_details
 *	Fill detailed information about HBA
 *
 * Input:
 *	ss - fcoei soft state
 *	port_info = fc_fca_port_info_t that need be updated
 *
 * Returns:
 *	N/A
 *
 * Comments:
 *	N/A
 */
static void
fcoei_populate_hba_fru_details(fcoei_soft_state_t *ss,
    fc_fca_port_info_t *port_info)
{
	fca_port_attrs_t *port_attrs = &(port_info->pi_attrs);
	int	instance;

	ASSERT(ss != NULL);
	(void) snprintf(port_attrs->manufacturer, FCHBA_MANUFACTURER_LEN,
	    "Sun Microsystems, Inc.");
	(void) snprintf(port_attrs->driver_name, FCHBA_DRIVER_NAME_LEN,
	    "%s", FCOEI_NAME_VERSION);
	(void) snprintf(port_attrs->driver_version, FCHBA_DRIVER_VERSION_LEN,
	    "%s", FCOEI_VERSION);
	(void) strcpy(port_attrs->serial_number, "N/A");
	(void) strcpy(port_attrs->hardware_version, "N/A");
	(void) strcpy(port_attrs->model, "FCoE Virtual FC HBA");
	(void) strcpy(port_attrs->model_description, "N/A");
	(void) strcpy(port_attrs->firmware_version, "N/A");
	(void) strcpy(port_attrs->option_rom_version, "N/A");

	port_attrs->vendor_specific_id = 0xFC0E;
	port_attrs->max_frame_size = FCOE_MAX_FC_FRAME_SIZE;
	port_attrs->supported_cos = 0x10000000;
	port_attrs->supported_speed = FC_HBA_PORTSPEED_1GBIT |
	    FC_HBA_PORTSPEED_10GBIT;
	instance = ddi_get_instance(ss->ss_dip);
	port_attrs->hba_fru_details.high =
	    (short)((instance & 0xffff0000) >> 16);
	port_attrs->hba_fru_details.low =
	    (short)(instance & 0x0000ffff);
}

/*
 * fcoei_port_enabled
 *	Notify fcoe that the port has been enabled
 *
 * Input:
 *	arg = the related soft state
 *
 * Returns:
 *	N/A
 *
 * Comments:
 *	Only after this, fcoe will report the link status to us
 */
static void
fcoei_port_enabled(void *arg)
{
	fcoei_soft_state_t	*ss  = (fcoei_soft_state_t *)arg;

	ss->ss_eport->eport_ctl(ss->ss_eport, FCOE_CMD_PORT_ONLINE, NULL);
}


/*
 * fcoei_initiate_ct_req
 *	Fill and submit CT request
 *
 * Input:
 *	xch - the exchange that will be initiated
 *
 * Returns:
 *	N/A
 *
 * Comments:
 *	N/A
 */
static void
fcoei_initiate_ct_req(fcoei_exchange_t *xch)
{
	fc_packet_t	*fpkt	 = xch->xch_fpkt;
	fc_ct_header_t	*ct	 = (fc_ct_header_t *)(void *)fpkt->pkt_cmd;
	uint8_t		*bp	 = (uint8_t *)fpkt->pkt_cmd;
	fcoe_frame_t	*frm;
	int		 offset;
	int		 idx;
	uint32_t	 cmd_len = fpkt->pkt_cmdlen;

	/*
	 * Ensure it's 4-byte aligned
	 */
	cmd_len = P2ROUNDUP(cmd_len, 4);

	/*
	 * Allocate CT request frame
	 */
	frm = xch->xch_ss->ss_eport->eport_alloc_frame(xch->xch_ss->ss_eport,
	    cmd_len + FCFH_SIZE, NULL);
	if (frm == NULL) {
		FCOEI_LOG(__FUNCTION__, "failed to alloc: %p", xch);
		return;
	}

	bzero(frm->frm_payload, cmd_len);
	xch->xch_cnt = xch->xch_ss->ss_sol_cnt;
	atomic_add_32(xch->xch_cnt, 1);

	FFM_R_CTL(fpkt->pkt_cmd_fhdr.r_ctl, frm);
	FFM_D_ID(fpkt->pkt_cmd_fhdr.d_id, frm);
	FFM_S_ID(fpkt->pkt_cmd_fhdr.s_id, frm);
	FFM_TYPE(fpkt->pkt_cmd_fhdr.type, frm);
	FFM_F_CTL(fpkt->pkt_cmd_fhdr.f_ctl, frm);
	FFM_OXID(xch->xch_oxid, frm);
	FFM_RXID(xch->xch_rxid, frm);
	fcoei_init_ifm(frm, xch);

	/*
	 * CT header (FC payload)
	 */
	offset = 0;
	FCOE_V2B_1(ct->ct_rev, FPLD + offset);

	offset = 1;
	FCOE_V2B_3(ct->ct_inid, FPLD + offset);

	offset = 4;
	FCOE_V2B_1(ct->ct_fcstype, FPLD + offset);

	offset = 5;
	FCOE_V2B_1(ct->ct_fcssubtype, FPLD + offset);

	offset = 6;
	FCOE_V2B_1(ct->ct_options, FPLD + offset);

	offset = 8;
	FCOE_V2B_2(ct->ct_cmdrsp, FPLD + offset);

	offset = 10;
	FCOE_V2B_2(ct->ct_aiusize, FPLD + offset);

	offset = 13;
	FCOE_V2B_1(ct->ct_reason, FPLD + offset);

	offset = 14;
	FCOE_V2B_1(ct->ct_expln, FPLD + offset);

	offset = 15;
	FCOE_V2B_1(ct->ct_vendor, FPLD + offset);

	/*
	 * CT payload (FC payload)
	 */
	switch (ct->ct_fcstype) {
	case FCSTYPE_DIRECTORY:
		switch (ct->ct_cmdrsp) {
		case NS_GA_NXT:
		case NS_GPN_ID:
		case NS_GNN_ID:
		case NS_GCS_ID:
		case NS_GFT_ID:
		case NS_GSPN_ID:
		case NS_GPT_ID:
		case NS_GID_FT:
		case NS_GID_PT:
		case NS_DA_ID:
			offset = 16;
			FCOE_V2B_4(((uint32_t *)(intptr_t)(bp + offset))[0],
			    FPLD + offset);
			break;

		case NS_GID_PN:
			offset = 16;
			bcopy(bp + offset, FPLD + offset, 8);
			break;

		case NS_RNN_ID:
		case NS_RPN_ID:
			offset = 16;
			FCOE_V2B_4(((uint32_t *)(intptr_t)(bp + offset))[0],
			    FPLD + offset);

			offset = 20;
			bcopy(bp + offset, FPLD + offset, 8);
			break;

		case NS_RSPN_ID:
			offset = 16;
			FCOE_V2B_4(((uint32_t *)(intptr_t)(bp + offset))[0],
			    FPLD + offset);

			offset = 20;
			bcopy(bp + offset, FPLD + offset, bp[20] + 1);
			break;

		case NS_RSNN_NN:
			offset = 16;
			bcopy(bp + offset, FPLD + offset, 8);

			offset = 24;
			bcopy(bp + offset, FPLD + offset, bp[24] + 1);
			break;

		case NS_RFT_ID:
			offset = 16;
			FCOE_V2B_4(((uint32_t *)(intptr_t)(bp + offset))[0],
			    FPLD + offset);

			/*
			 * fp use bcopy to copy fp_fc4_types,
			 * we need to swap order for each integer
			 */
			offset = 20;
			for (idx = 0; idx < 8; idx++) {
				FCOE_V2B_4(
				    ((uint32_t *)(intptr_t)(bp + offset))[0],
				    FPLD + offset);
				offset += 4;
			}
			break;

		case NS_RCS_ID:
		case NS_RPT_ID:
			offset = 16;
			FCOE_V2B_4(((uint32_t *)(intptr_t)(bp + offset))[0],
			    FPLD + offset);

			offset = 20;
			FCOE_V2B_4(((uint32_t *)(intptr_t)(bp + offset))[0],
			    FPLD + offset);
			break;

		case NS_RIP_NN:
			offset = 16;
			bcopy(bp + offset, FPLD + offset, 24);
			break;

		default:
			fcoei_complete_xch(xch, frm, FC_PKT_FAILURE,
			    FC_REASON_CMD_UNSUPPORTED);
			break;
		}
		break; /* FCSTYPE_DIRECTORY */

	case FCSTYPE_MGMTSERVICE:
		switch (ct->ct_cmdrsp) {
		case MS_GIEL:
			FCOEI_LOG(__FUNCTION__,
			    "MS_GIEL ct_fcstype %x, ct_cmdrsp: %x",
			    ct->ct_fcstype, ct->ct_cmdrsp);
			break;

		default:
			fcoei_complete_xch(xch, frm, FC_PKT_FAILURE,
			    FC_REASON_CMD_UNSUPPORTED);
			break;
		}
		break; /* FCSTYPE_MGMTSERVICE */

	default:
		fcoei_complete_xch(xch, frm, FC_PKT_FAILURE,
		    FC_REASON_CMD_UNSUPPORTED);
		break;
	}
	xch->xch_ss->ss_eport->eport_tx_frame(frm);
}

/*
 * fcoei_initiate_fcp_cmd
 *	Submit FCP command
 *
 * Input:
 *	xch - the exchange to be submitted
 *
 * Returns:
 *	N/A
 *
 * Comments:
 *	N/A
 */
static void
fcoei_initiate_fcp_cmd(fcoei_exchange_t *xch)
{
	fc_packet_t	*fpkt = xch->xch_fpkt;
	fcoe_frame_t	*frm;
	fcp_cmd_t	*fcp_cmd_iu = (fcp_cmd_t *)(void *)fpkt->pkt_cmd;
	int		 offset = 0;

	ASSERT((fpkt->pkt_cmdlen % 4) == 0);
	frm = xch->xch_ss->ss_eport->eport_alloc_frame(xch->xch_ss->ss_eport,
	    fpkt->pkt_cmdlen + FCFH_SIZE, NULL);
	if (!frm) {
		ASSERT(0);
	} else {
		fcoei_init_ifm(frm, xch);
		bzero(frm->frm_payload, fpkt->pkt_cmdlen);
	}

	/*
	 * This will affect timing check
	 */
	xch->xch_cnt = xch->xch_ss->ss_sol_cnt;
	atomic_add_32(xch->xch_cnt, 1);

	/*
	 * Set exchange residual bytes
	 */
	xch->xch_resid = (int)fpkt->pkt_datalen;

	/*
	 * Fill FCP command IU
	 *
	 * fcp_ent_addr
	 */
	FCOE_V2B_2(fcp_cmd_iu->fcp_ent_addr.ent_addr_0,
	    frm->frm_payload + offset);
	offset += 2;
	FCOE_V2B_2(fcp_cmd_iu->fcp_ent_addr.ent_addr_1,
	    frm->frm_payload + offset);
	offset += 2;
	FCOE_V2B_2(fcp_cmd_iu->fcp_ent_addr.ent_addr_2,
	    frm->frm_payload + offset);
	offset += 2;
	FCOE_V2B_2(fcp_cmd_iu->fcp_ent_addr.ent_addr_3,
	    frm->frm_payload + offset);
	/*
	 * fcp_cntl
	 */
	offset = offsetof(fcp_cmd_t, fcp_cntl);
	frm->frm_payload[offset] = 0;

	offset += 1;
	frm->frm_payload[offset] = fcp_cmd_iu->fcp_cntl.cntl_qtype & 0x07;
	offset += 1;
	frm->frm_payload[offset] =
	    (fcp_cmd_iu->fcp_cntl.cntl_kill_tsk << 7) |
	    (fcp_cmd_iu->fcp_cntl.cntl_clr_aca << 6) |
	    (fcp_cmd_iu->fcp_cntl.cntl_reset_tgt << 5) |
	    (fcp_cmd_iu->fcp_cntl.cntl_reset_lun << 4) |
	    (fcp_cmd_iu->fcp_cntl.cntl_clr_tsk << 2) |
	    (fcp_cmd_iu->fcp_cntl.cntl_abort_tsk << 1);
	offset += 1;
	frm->frm_payload[offset] =
	    (fcp_cmd_iu->fcp_cntl.cntl_read_data << 1) |
	    (fcp_cmd_iu->fcp_cntl.cntl_write_data);
	/*
	 * fcp_cdb
	 */
	offset = offsetof(fcp_cmd_t, fcp_cdb);
	bcopy(fcp_cmd_iu->fcp_cdb, frm->frm_payload + offset, FCP_CDB_SIZE);
	/*
	 * fcp_data_len
	 */
	offset += FCP_CDB_SIZE;
	FCOE_V2B_4(fcp_cmd_iu->fcp_data_len, frm->frm_payload + offset);

	/*
	 * FC frame header
	 */
	FRM2IFM(frm)->ifm_rctl = fpkt->pkt_cmd_fhdr.r_ctl;

	FFM_R_CTL(fpkt->pkt_cmd_fhdr.r_ctl, frm);
	FFM_D_ID(fpkt->pkt_cmd_fhdr.d_id, frm);
	FFM_S_ID(fpkt->pkt_cmd_fhdr.s_id, frm);
	FFM_TYPE(fpkt->pkt_cmd_fhdr.type, frm);
	FFM_F_CTL(0x290000, frm);
	FFM_OXID(xch->xch_oxid, frm);
	FFM_RXID(xch->xch_rxid, frm);

	xch->xch_ss->ss_eport->eport_tx_frame(frm);
}

/*
 * fcoei_initiate_els_req
 *	Initiate ELS request
 *
 * Input:
 *	xch = the exchange that will be initiated
 *
 * Returns:
 *	N/A
 *
 * Comments:
 *	N/A
 */
static void
fcoei_initiate_els_req(fcoei_exchange_t *xch)
{
	fc_packet_t	*fpkt = xch->xch_fpkt;
	fcoe_frame_t	*frm;
	ls_code_t	*els_code;

	ASSERT((fpkt->pkt_cmdlen % 4) == 0);
	frm = xch->xch_ss->ss_eport->eport_alloc_frame(xch->xch_ss->ss_eport,
	    fpkt->pkt_cmdlen + FCFH_SIZE, NULL);
	if (!frm) {
		ASSERT(0);
	} else {
		fcoei_init_ifm(frm, xch);
		bzero(frm->frm_payload, fpkt->pkt_cmdlen);
	}

	/*
	 * This will affect timing check
	 */
	xch->xch_cnt = xch->xch_ss->ss_sol_cnt;
	atomic_add_32(xch->xch_cnt, 1);

	els_code = (ls_code_t *)(void *)fpkt->pkt_cmd;
	switch (els_code->ls_code) {
	case LA_ELS_FLOGI:
		/*
		 * For FLOGI, we expect response within E_D_TOV
		 */
		xch->xch_start_tick = ddi_get_lbolt();
		xch->xch_end_tick = xch->xch_start_tick +
		    FCOE_SEC2TICK(2);
		xch->xch_ss->ss_flags &= ~SS_FLAG_FLOGI_FAILED;
		/* FALLTHROUGH */

	case LA_ELS_PLOGI:
		fcoei_fill_els_logi_cmd(fpkt, frm);
		break;

	case LA_ELS_PRLI:
		fcoei_fill_els_prli_cmd(fpkt, frm);
		break;

	case LA_ELS_SCR:
		fcoei_fill_els_scr_cmd(fpkt, frm);
		break;

	case LA_ELS_LINIT:
		fcoei_fill_els_linit_cmd(fpkt, frm);
		break;

	case LA_ELS_ADISC:
		fcoei_fill_els_adisc_cmd(fpkt, frm);
		break;

	case LA_ELS_LOGO:
		/*
		 * For LOGO, we expect response within E_D_TOV
		 */
		xch->xch_start_tick = ddi_get_lbolt();
		xch->xch_end_tick = xch->xch_start_tick +
		    FCOE_SEC2TICK(2);
		fcoei_fill_els_logo_cmd(fpkt, frm);
		break;
	case LA_ELS_RLS:
		fcoei_fill_els_rls_cmd(fpkt, frm);
		break;
	case LA_ELS_RNID:
		fcoei_fill_els_rnid_cmd(fpkt, frm);
		break;
	default:
		fcoei_complete_xch(xch, frm, FC_PKT_FAILURE,
		    FC_REASON_CMD_UNSUPPORTED);
		return;
	}

	/*
	 * set ifm_rtcl
	 */
	FRM2IFM(frm)->ifm_rctl = fpkt->pkt_cmd_fhdr.r_ctl;

	/*
	 * FCPH
	 */
	FFM_R_CTL(fpkt->pkt_cmd_fhdr.r_ctl, frm);
	FFM_D_ID(fpkt->pkt_cmd_fhdr.d_id, frm);
	FFM_S_ID(fpkt->pkt_cmd_fhdr.s_id, frm);
	FFM_TYPE(fpkt->pkt_cmd_fhdr.type, frm);
	FFM_F_CTL(0x290000, frm);
	FFM_OXID(xch->xch_oxid, frm);
	FFM_RXID(xch->xch_rxid, frm);

	xch->xch_ss->ss_eport->eport_tx_frame(frm);
}

/*
 * fcoei_initiate_els_resp
 *	Originate ELS response
 *
 * Input:
 *	xch = the associated exchange
 *
 * Returns:
 *	N/A
 *
 * Comments:
 *	N/A
 */
static void
fcoei_initiate_els_resp(fcoei_exchange_t *xch)
{
	fc_packet_t	*fpkt = xch->xch_fpkt;
	fcoe_frame_t	*frm;

	ASSERT((fpkt->pkt_cmdlen % 4) == 0);
	frm = xch->xch_ss->ss_eport->eport_alloc_frame(xch->xch_ss->ss_eport,
	    fpkt->pkt_cmdlen + FCFH_SIZE, NULL);
	if (!frm) {
		ASSERT(0);
	} else {
		fcoei_init_ifm(frm, xch);
		bzero(frm->frm_payload, fpkt->pkt_cmdlen);
	}

	/*
	 * This will affect timing check
	 */
	xch->xch_cnt = xch->xch_ss->ss_unsol_cnt;
	atomic_add_32(xch->xch_cnt, 1);

	/*
	 * Set ifm_rctl
	 */
	FRM2IFM(frm)->ifm_rctl = fpkt->pkt_cmd_fhdr.r_ctl;

	/*
	 * FCPH
	 */
	FFM_R_CTL(fpkt->pkt_cmd_fhdr.r_ctl, frm);
	FFM_D_ID(fpkt->pkt_cmd_fhdr.d_id, frm);
	FFM_S_ID(fpkt->pkt_cmd_fhdr.s_id, frm);
	FFM_TYPE(fpkt->pkt_cmd_fhdr.type, frm);
	FFM_F_CTL(0x980000, frm);
	FFM_OXID(xch->xch_oxid, frm);
	FFM_RXID(xch->xch_rxid, frm);

	switch (((uint8_t *)&fpkt->pkt_fca_rsvd1)[0]) {
	case LA_ELS_FLOGI:
		fcoei_fill_els_logi_resp(fpkt, frm);
		break;

	case LA_ELS_PLOGI:
		if (FRM2SS(frm)->ss_eport->eport_flags &
		    EPORT_FLAG_IS_DIRECT_P2P) {
			FRM2SS(frm)->ss_p2p_info.fca_d_id = FRM_S_ID(frm);
			FRM2SS(frm)->ss_p2p_info.d_id = FRM_D_ID(frm);
		}

		fcoei_fill_els_logi_resp(fpkt, frm);
		break;

	case LA_ELS_PRLI:
		fcoei_fill_els_prli_resp(fpkt, frm);
		break;

	case LA_ELS_ADISC:
		fcoei_fill_els_adisc_resp(fpkt, frm);
		break;

	case LA_ELS_LOGO:
		fcoei_fill_els_logo_resp(fpkt, frm);
		break;
	case LA_ELS_RSCN:
		fcoei_fill_els_acc_resp(fpkt, frm);
		break;

	default:
		fcoei_complete_xch(xch, frm, FC_PKT_FAILURE,
		    FC_REASON_CMD_UNSUPPORTED);
		return;
	}

	xch->xch_ss->ss_eport->eport_tx_frame(frm);
}

/*
 * fcoei_fill_els_logi_cmd
 *	Fill SCR (state change register) command frame
 *
 * Input:
 *	fpkt = LV fc_packet
 *	frm = Unsolicited frame containing LOGI response
 *
 * Returns:
 *	N/A
 *
 * Comments:
 *	N/A
 */
static void
fcoei_fill_els_logi_cmd(fc_packet_t *fpkt, fcoe_frame_t *frm)
{
	la_els_logi_t	*els_logi = (la_els_logi_t *)(void *)fpkt->pkt_cmd;
	int		 offset;

	/*
	 * fill ls_code
	 */
	offset = 0;
	FCOE_V2B_1(els_logi->ls_code.ls_code, FPLD + offset);

	/*
	 * fill common service parameters
	 */
	offset = 4;
	FCOE_V2B_2(els_logi->common_service.fcph_version, FPLD + offset);

	offset = 6;
	FCOE_V2B_2(els_logi->common_service.btob_credit, FPLD + offset);

	offset = 8;
	FCOE_V2B_2(els_logi->common_service.cmn_features, FPLD + offset);

	offset = 10;
	FCOE_V2B_2(els_logi->common_service.rx_bufsize, FPLD + offset);

	offset = 12;
	FCOE_V2B_2(els_logi->common_service.conc_sequences, FPLD + offset);

	offset = 14;
	FCOE_V2B_2(els_logi->common_service.relative_offset, FPLD + offset);

	offset = 16;
	FCOE_V2B_4(els_logi->common_service.e_d_tov, FPLD + offset);

	/*
	 * port/node wwn
	 */
	offset = 20;
	bcopy(&els_logi->nport_ww_name, FPLD + offset, 8);

	offset = 28;
	bcopy(&els_logi->node_ww_name, FPLD + offset, 8);

	/*
	 * class_3
	 */
	offset = 68;
	FCOE_V2B_2(els_logi->class_3.class_opt, FPLD + offset);

	offset = 70;
	FCOE_V2B_2(els_logi->class_3.initiator_ctl, FPLD + offset);

	offset = 72;
	FCOE_V2B_2(els_logi->class_3.recipient_ctl, FPLD + offset);

	offset = 74;
	FCOE_V2B_2(els_logi->class_3.rcv_size, FPLD + offset);

	offset = 76;
	FCOE_V2B_2(els_logi->class_3.conc_sequences, FPLD + offset);

	offset = 78;
	FCOE_V2B_2(els_logi->class_3.n_port_e_to_e_credit, FPLD + offset);

	offset = 80;
	FCOE_V2B_2(els_logi->class_3.open_seq_per_xchng, FPLD + offset);
	/*
	 * needn't touch other fields
	 */
}

/*
 * fcoei_fill_prli_cmd
 *	Fill PRLI command frame
 *
 * Input:
 *	fpkt = LV fc_packet
 *	frm = Unsolicited frame containing PRLI response
 *
 * Returns:
 *	N/A
 *
 * Comments:
 *	N/A
 */
static void
fcoei_fill_els_prli_cmd(fc_packet_t *fpkt, fcoe_frame_t *frm)
{
	int		 offset	    = 0;
	la_els_prli_t	*els_prli   = (la_els_prli_t *)(void *)fpkt->pkt_cmd;
	struct fcp_prli *fcp_spp    =
	    (struct fcp_prli *)(void *)els_prli->service_params;

	/*
	 * fill basic PRLI fields
	 */
	offset = 0;
	FCOE_V2B_1(els_prli->ls_code, FPLD + offset);

	offset = 1;
	FCOE_V2B_1(els_prli->page_length, FPLD + offset);

	offset = 2;
	FCOE_V2B_2(els_prli->payload_length, FPLD + offset);

	/*
	 * fill FCP service parameters page
	 */
	offset = 4;
	FCOE_V2B_1(fcp_spp->type, FPLD + offset);

	/*
	 * PRLI flags, only 3 bits are valid
	 */
	offset = 6;

	FCOE_V2B_2(((fcp_spp->orig_process_assoc_valid << 15) |
	    (fcp_spp->resp_process_assoc_valid << 14) |
	    (fcp_spp->establish_image_pair << 13)), FPLD + offset);

	/*
	 * process associator
	 */
	offset = 8;
	FCOE_V2B_4(fcp_spp->orig_process_associator, FPLD + offset);

	offset = 12;
	FCOE_V2B_4(fcp_spp->resp_process_associator, FPLD + offset);

	/*
	 * FC-4 type
	 */
	offset = 16;
	FCOE_V2B_4((fcp_spp->retry << 8) |
	    (fcp_spp->confirmed_compl_allowed << 7) |
	    (fcp_spp->data_overlay_allowed << 6) |
	    (fcp_spp->initiator_fn << 5) | (fcp_spp->target_fn << 4) |
	    (fcp_spp->read_xfer_rdy_disabled << 1) |
	    (fcp_spp->write_xfer_rdy_disabled), FPLD + offset);
}

/*
 * fcoei_fill_els_scr_cmd
 *	Fill SCR (state change register) command frame
 *
 * Input:
 *	fpkt = LV fc_packet
 *	frm = Unsolicited frame containing SCR command
 *
 * Returns:
 *	N/A
 *
 * Comments:
 *	N/A
 */
static void
fcoei_fill_els_scr_cmd(fc_packet_t *fpkt, fcoe_frame_t *frm)
{
	fc_scr_req_t	*els_scr = (fc_scr_req_t *)(void *)fpkt->pkt_cmd;
	int		 offset;

	offset = 0;
	FCOE_V2B_1(els_scr->ls_code.ls_code, FPLD + offset);

	offset = 7;
	FCOE_V2B_1(els_scr->scr_func, FPLD + offset);
}

/*
 * fcoei_fill_els_adisc_cmd
 *	Fill ADISC command frame
 *
 * Input:
 *	fpkt = LV fc_packet
 *	frm = Unsolicited frame containing ADISC command
 *
 * Returns:
 *	N/A
 *
 * Comments:
 *	N/A
 */
static void
fcoei_fill_els_adisc_cmd(fc_packet_t *fpkt, fcoe_frame_t *frm)
{
	la_els_adisc_t	*els_adisc = (la_els_adisc_t *)(void *)fpkt->pkt_cmd;
	int		 offset;

	offset = 0;
	FCOE_V2B_1(els_adisc->ls_code.ls_code, FPLD + offset);

	offset = 5;
	FCOE_V2B_3(els_adisc->hard_addr.hard_addr, FPLD + offset);

	offset = 8;
	bcopy(&els_adisc->port_wwn, FPLD + offset, 8);

	offset = 16;
	bcopy(&els_adisc->node_wwn, FPLD + offset, 8);

	offset = 25;
	FCOE_V2B_3(els_adisc->nport_id.port_id, FPLD + offset);
}

/*
 * fcoei_fill_els_linit_cmd
 *	Fill LINIT command frame
 *
 * Input:
 *	fpkt = LV fc_packet
 *	frm = Unsolicited frame containing LINIT command
 *
 * Returns:
 *	N/A
 *
 * Comments:
 *	N/A
 */
/* ARGSUSED */
static void
fcoei_fill_els_linit_cmd(fc_packet_t *fpkt, fcoe_frame_t *frm)
{
	ASSERT(fpkt && frm);
}

/*
 * fcoei_fill_els_logo_cmd
 *	Fill LOGO command frame
 *
 * Input:
 *	fpkt = LV fc_packet
 *	frm = Unsolicited frame containing LOGO command
 *
 * Returns:
 *	N/A
 *
 * Comments:
 *	N/A
 */
static void
fcoei_fill_els_logo_cmd(fc_packet_t *fpkt, fcoe_frame_t *frm)
{
	la_els_logo_t	*els_logo   = (la_els_logo_t *)(void *)fpkt->pkt_cmd;
	int		 offset;

	offset = 0;
	FCOE_V2B_1(els_logo->ls_code.ls_code, FPLD + offset);

	offset = 5;
	FCOE_V2B_3(els_logo->nport_id.port_id, FPLD + offset);

	offset = 8;
	bcopy(&els_logo->nport_ww_name, FPLD + offset, 8);
}

static void
fcoei_fill_els_rls_cmd(fc_packet_t *fpkt, fcoe_frame_t *frm)
{
	la_els_rls_t	*els_rls = (la_els_rls_t *)(void *)fpkt->pkt_cmd;
	int		offset;

	offset = 0;
	FCOE_V2B_1(els_rls->ls_code.ls_code, FPLD + offset);

	offset = 5;
	FCOE_V2B_3(els_rls->rls_portid.port_id, FPLD + offset);
}

static void
fcoei_fill_els_rnid_cmd(fc_packet_t *fpkt, fcoe_frame_t *frm)
{
	la_els_rnid_t *els_rnid = (la_els_rnid_t *)(void *)fpkt->pkt_cmd;
	int		offset;

	offset = 0;
	FCOE_V2B_1(els_rnid->ls_code.ls_code, FPLD + offset);

	offset = 4;
	bcopy(&els_rnid->data_format, FPLD + offset, 1);
}
/*
 * fcoei_fill_els_acc_resp
 *	Fill ELS ACC response frame
 *
 * Input:
 *	fpkt = LV fc_packet
 *	frm = Unsolicited frame containing ELS ACC response
 *
 * Returns:
 *	N/A
 *
 * Comments:
 *	N/A
 */
static void
fcoei_fill_els_acc_resp(fc_packet_t *fpkt, fcoe_frame_t *frm)
{
	ls_code_t	*els_code = (ls_code_t *)(void *)fpkt->pkt_cmd;
	int		 offset;

	offset = 0;
	FCOE_V2B_1(els_code->ls_code, FPLD + offset);

	offset = 1;
	FCOE_V2B_3(els_code->mbz, FPLD + offset);
}

/*
 * fcoei_fill_els_rjt_resp
 *	Fill ELS RJT response frame
 *
 * Input:
 *	fpkt = LV fc_packet
 *	frm = Unsolicited frame containg ELS RJT response
 *
 * Returns:
 *	N/A
 *
 * Comments:
 *	N/A
 */
static void
fcoei_fill_els_rjt_resp(fc_packet_t *fpkt, fcoe_frame_t *frm)
{
	la_els_rjt_t	*els_rjt = (la_els_rjt_t *)(void *)fpkt->pkt_cmd;
	int		 offset;

	offset = 0; /* reset ls code */
	FCOE_V2B_1(els_rjt->ls_code.ls_code, FPLD + offset);

	offset = 5; /* reason code */
	FCOE_V2B_1(els_rjt->action, FPLD + offset);

	offset = 6; /* reason explanation */
	FCOE_V2B_1(els_rjt->reason, FPLD + offset);

	offset = 7; /* vendor unique */
	FCOE_V2B_1(els_rjt->vu, FPLD + offset);
}

/*
 * fcoei_fill_els_adisc_resp
 *	Fill ADISC response frame
 *
 * Input:
 *	fpkt = LV fc_packet
 *	frm = Unsolicited frame containing ADISC response
 *
 * Returns:
 *	N/A
 *
 * Comments:
 *	N/A
 */
static void
fcoei_fill_els_adisc_resp(fc_packet_t *fpkt, fcoe_frame_t *frm)
{
	la_els_adisc_t	*els_adisc = (la_els_adisc_t *)(void *)fpkt->pkt_cmd;
	int		 offset;

	if (els_adisc->ls_code.ls_code == LA_ELS_RJT) {
		fcoei_fill_els_rjt_resp(fpkt, frm);
	} else {
		offset = 0;
		FCOE_V2B_1(els_adisc->ls_code.ls_code, FPLD + offset);

		offset = 5;
		FCOE_V2B_3(els_adisc->hard_addr.hard_addr, FPLD + offset);

		offset = 8;
		bcopy(&els_adisc->port_wwn, FPLD + offset, FC_WWN_SIZE);

		offset = 16;
		bcopy(&els_adisc->node_wwn, FPLD + offset, FC_WWN_SIZE);

		offset = 25;
		FCOE_V2B_3(els_adisc->nport_id.port_id, FPLD + offset);
	}
}

/*
 * fcoei_fill_els_logi_resp
 *	Fill FLOGI/PLOGI response frame
 *
 * Input:
 *	fpkt = LV fc_packet
 *	frm = Unsolicited frame containing LOGI response
 *
 * Returns:
 *	N/A
 *
 * Comments:
 *	N/A
 */
static void
fcoei_fill_els_logi_resp(fc_packet_t *fpkt, fcoe_frame_t *frm)
{
	ls_code_t	*els_code = (ls_code_t *)(void *)fpkt->pkt_cmd;

	if (els_code->ls_code == LA_ELS_RJT) {
		fcoei_fill_els_rjt_resp(fpkt, frm);
	} else {
		fcoei_fill_els_logi_cmd(fpkt, frm);
	}
}

/*
 * fcoei_fill_els_prli_resp
 *	Fill PRLI response frame
 *
 * Input:
 *	fpkt = LV fc_packet
 *	frm = Unsolicited frame containing PRLI response
 *
 * Returns:
 *	N/A
 *
 * Comments:
 *	N/A
 */
static void
fcoei_fill_els_prli_resp(fc_packet_t *fpkt, fcoe_frame_t *frm)
{
	ls_code_t	*els_code = (ls_code_t *)(void *)fpkt->pkt_cmd;

	if (els_code->ls_code == LA_ELS_RJT) {
		fcoei_fill_els_rjt_resp(fpkt, frm);
	} else {
		fcoei_fill_els_prli_cmd(fpkt, frm);
	}
}

/*
 * fcoei_fill_els_logo_resp
 *	Fill LOGO response frame
 *
 * Input:
 *	fpkt = LV fc_packet
 *	frm = Unsolicited frame containing LOGO response
 *
 * Returns:
 *	N/A
 *
 * Comments:
 *	N/A
 */
static void
fcoei_fill_els_logo_resp(fc_packet_t *fpkt, fcoe_frame_t *frm)
{
	ls_code_t	*els_code   = (ls_code_t *)(void *)fpkt->pkt_cmd;

	if (els_code->ls_code == LA_ELS_RJT) {
		fcoei_fill_els_rjt_resp(fpkt, frm);
	} else {
		fcoei_fill_els_acc_resp(fpkt, frm);
	}
}

/*
 * fcoei_logo_peer
 *	Send LOGO to the peer to emulate link offline event
 *
 * Input:
 *	arg - fcoei soft state set in fcoei_bind_port
 *
 * Returns:
 *	N/A
 *
 * Comments:
 *	N/A
 */
static void
fcoei_logo_peer(void *arg)
{
	fcoei_soft_state_t	*ss = (fcoei_soft_state_t *)arg;
	fc_packet_t		*fpkt;
	fcoei_exchange_t	*xch;
	la_els_logo_t		*els_logo;

	/*
	 * Allocate space for exchange
	 */
	xch = kmem_zalloc(sizeof (*xch), KM_SLEEP);

	/*
	 * Allocate space for fc_packet
	 */
	fpkt = kmem_zalloc(sizeof (fc_packet_t), KM_SLEEP);
	fpkt->pkt_cmdlen = 20;
	fpkt->pkt_cmd = kmem_zalloc(fpkt->pkt_cmdlen, KM_SLEEP);
	fpkt->pkt_rsplen = 20;
	fpkt->pkt_resp = kmem_zalloc(fpkt->pkt_rsplen, KM_SLEEP);

	/*
	 * Link them together
	 */
	fpkt->pkt_fca_private = xch;
	(void) fcoei_init_pkt(ss, fpkt, 0);

	/*
	 * Initialize FC frame header
	 */
	if (ss->ss_eport->eport_flags & EPORT_FLAG_IS_DIRECT_P2P) {
		fpkt->pkt_cmd_fhdr.d_id = ss->ss_p2p_info.d_id;
	} else {
		fpkt->pkt_cmd_fhdr.d_id = 0xFFFFFE;
	}

	fpkt->pkt_cmd_fhdr.s_id = ss->ss_p2p_info.fca_d_id;
	fpkt->pkt_cmd_fhdr.r_ctl = R_CTL_ELS_REQ;
	fpkt->pkt_cmd_fhdr.type = FC_TYPE_EXTENDED_LS;
	fpkt->pkt_cmd_fhdr.f_ctl = 0x290000;
	fpkt->pkt_timeout = 1;

	/*
	 * Initialize LOGO payload
	 */
	els_logo = (la_els_logo_t *)(void *)fpkt->pkt_cmd;
	els_logo->ls_code.ls_code = LA_ELS_LOGO;
	els_logo->nport_id.port_id = ss->ss_p2p_info.fca_d_id;
	bcopy(ss->ss_eport->eport_portwwn, &els_logo->nport_ww_name, 8);

	/*
	 * Set the completion function
	 */
	fpkt->pkt_comp = fcoei_fpkt_comp;
	if (fcoei_transport(ss, fpkt) != FC_SUCCESS) {
		FCOEI_LOG(__FUNCTION__, "fcoei_transport LOGO failed");
		fcoei_fpkt_comp(fpkt);
	}
}

/*
 * fcoei_fpkt_comp
 *	internal exchange completion
 *
 * Input:
 *	fpkt - fc_packet_t to be completed
 *
 * Returns:
 *	N/A
 *
 * Comments:
 *
 */
static void
fcoei_fpkt_comp(fc_packet_t *fpkt)
{
	fcoei_exchange_t	*xch = FPKT2XCH(fpkt);

	FCOEI_LOG(__FUNCTION__, "internal exchange is completed: %p", xch);

	(void) fcoei_un_init_pkt(xch->xch_ss, xch->xch_fpkt);
	kmem_free(xch->xch_fpkt->pkt_cmd, xch->xch_fpkt->pkt_cmdlen);
	kmem_free(xch->xch_fpkt->pkt_resp, xch->xch_fpkt->pkt_rsplen);
	kmem_free(xch->xch_fpkt, sizeof (fc_packet_t));
	kmem_free(xch, sizeof (fcoei_exchange_t));
}

/*
 * fcoei_xch_abort
 *	Prepare to abort the exchange
 *
 * Input:
 *	key = oxid/rxid of the exchange
 *	val = the exchange
 *	arg = the soft state
 *
 * Returns:
 *	MH_WALK_CONTINUE = continue to walk
 *
 * Comments:
 *	N/A
 */
/* ARGSUSED */
static uint32_t
fcoei_xch_abort(mod_hash_key_t key, mod_hash_val_t *val, void *arg)
{
	fcoei_exchange_t	*xch = (fcoei_exchange_t *)val;

	ASSERT(arg == xch->xch_ss);
	ASSERT(CMHK(key) != 0xFFFF);
	xch->xch_flags |= XCH_FLAG_ABORT;
	xch->xch_fpkt->pkt_state = FC_PKT_LOCAL_RJT;
	xch->xch_fpkt->pkt_reason = FC_REASON_OFFLINE;
	list_insert_tail(&xch->xch_ss->ss_comp_xch_list, xch);
	return (MH_WALK_CONTINUE);
}

/*
 * fcoei_init_fcatran_vectors
 *	Initialize fc_fca_tran vectors that are defined in this file
 *
 * Input:
 *	fcatran - fc_fca_tran of the soft state
 *
 * Returns:
 *	N/A
 *
 * Comments:
 *	N/A
 */
void
fcoei_init_fcatran_vectors(fc_fca_tran_t *fcatran)
{
	fcatran->fca_bind_port	 = fcoei_bind_port;
	fcatran->fca_unbind_port = fcoei_unbind_port;
	fcatran->fca_init_pkt	 = fcoei_init_pkt;
	fcatran->fca_un_init_pkt = fcoei_un_init_pkt;
	fcatran->fca_els_send	 = fcoei_els_send;
	fcatran->fca_get_cap	 = fcoei_get_cap;
	fcatran->fca_set_cap	 = fcoei_set_cap;
	fcatran->fca_getmap	 = fcoei_getmap;
	fcatran->fca_transport	 = fcoei_transport;
	fcatran->fca_ub_alloc	 = fcoei_ub_alloc;
	fcatran->fca_ub_free	 = fcoei_ub_free;
	fcatran->fca_ub_release	 = fcoei_ub_release;
	fcatran->fca_abort	 = fcoei_abort;
	fcatran->fca_reset	 = fcoei_reset;
	fcatran->fca_port_manage = fcoei_port_manage;
	fcatran->fca_get_device	 = fcoei_get_device;
	fcatran->fca_notify	 = fcoei_notify;
}

/*
 * fcoei_process_event_reset
 *	link reset phase II
 *
 * Input:
 *	arg - fcoei soft state set in fcoei_bind_port
 *
 * Returns:
 *	N/A
 *
 * Comments:
 *
 */
void
fcoei_process_event_reset(fcoei_event_t *ae)
{
	fcoei_soft_state_t	*ss = (fcoei_soft_state_t *)ae->ae_obj;

	ASSERT(!MUTEX_HELD(&ss->ss_watchdog_mutex));
	kmem_free(ae, sizeof (*ae));

	mod_hash_walk(ss->ss_sol_oxid_hash, fcoei_xch_abort, ss);
	mod_hash_walk(ss->ss_unsol_rxid_hash, fcoei_xch_abort, ss);
	fcoei_handle_comp_xch_list(ss);

	/*
	 * Notify LV that the link is up now
	 */
	ss->ss_eport->eport_ctl(ss->ss_eport, FCOE_CMD_PORT_ONLINE, 0);
}

/*
 * fcoei_process_event_exchange
 *	Process exchange in the single thread context
 *
 * Input:
 *	ae = the exchange event
 *
 * Returns:
 *	N/A
 *
 * Comments:
 *	N/A
 */
void
fcoei_process_event_exchange(fcoei_event_t *ae)
{
	fcoei_exchange_t	*xch  = (fcoei_exchange_t *)ae->ae_obj;
	fcoei_exchange_t	*xch_tmp;
	fc_packet_t		*fpkt = xch->xch_fpkt;

	/*
	 * These 4 elements need reset, pkt_state & pkt_reason will be set
	 */
	fpkt->pkt_action = 0;
	fpkt->pkt_expln = 0;
	fpkt->pkt_data_resid = 0;
	fpkt->pkt_resp_resid = 0;

	/*
	 * port state sanity checking
	 */
	if ((xch->xch_ss->ss_link_state != FC_STATE_ONLINE) ||
	    xch->xch_ss->ss_port_event_counter) {
		/*
		 * LV will retry it after one second
		 */
		fcoei_complete_xch(xch, NULL, FC_PKT_PORT_OFFLINE,
		    FC_REASON_OFFLINE);
		return;
	}

	switch (fpkt->pkt_cmd_fhdr.r_ctl) {
	case R_CTL_COMMAND:
		FCOEI_INIT_SOL_ID_HASH(xch, xch_tmp);
		fcoei_initiate_fcp_cmd(xch);
		break;

	case R_CTL_ELS_REQ:
		FCOEI_INIT_SOL_ID_HASH(xch, xch_tmp);
		fcoei_initiate_els_req(xch);
		break;

	case R_CTL_UNSOL_CONTROL:
		FCOEI_INIT_SOL_ID_HASH(xch, xch_tmp);
		fcoei_initiate_ct_req(xch);
		break;

	case R_CTL_ELS_RSP:
		/*
		 * Caution: in leadville, it still uses pkt_cmd_fhdr
		 * oxid & rxid have been decided when we get unsolicited frames.
		 * pkt_cmd_fhdr has contained the right oxid and rxid now.
		 */
		FCOEI_INIT_UNSOL_ID_HASH(xch);
		fcoei_initiate_els_resp(xch);
		break;

	default:
		fcoei_complete_xch(xch, NULL, FC_PKT_FAILURE,
		    FC_REASON_CMD_UNSUPPORTED);
	}
}