root/usr/src/uts/common/io/fibre-channel/fca/fcoei/fcoei_lv.c
/*
 * 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_inc_32(xch->xch_cnt);

        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_inc_32(xch->xch_cnt);

        /*
         * 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_inc_32(xch->xch_cnt);

        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_inc_32(xch->xch_cnt);

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

        uint16_t prli_flags = ((fcp_spp->orig_process_assoc_valid << 15) |
            (fcp_spp->resp_process_assoc_valid << 14) |
            (fcp_spp->establish_image_pair << 13));
        FCOE_V2B_2(prli_flags, 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);
        }
}