root/usr/src/uts/common/io/fibre-channel/fca/fcoei/fcoei_eth.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
 */

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

/*
 * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * This file defines interface functions between fcoe and fcoei driver.
 */

#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 header files
 */
#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 internal functions
 */
static void fcoei_process_unsol_els_req(fcoe_frame_t *frm);
static void fcoei_process_sol_els_rsp(fcoe_frame_t *frm);
static void fcoei_process_unsol_abts_req(fcoe_frame_t *frame);
static void fcoei_process_sol_abts_acc(fcoe_frame_t *frame);
static void fcoei_process_sol_abts_rjt(fcoe_frame_t *frame);
static void fcoei_process_sol_ct_rsp(fcoe_frame_t *frame);
static void fcoei_process_unsol_xfer_rdy(fcoe_frame_t *frame);
static void fcoei_process_sol_fcp_resp(fcoe_frame_t *frm);

static void fcoei_fill_fcp_resp(uint8_t *src, uint8_t *dest, int size);
static void fcoei_fill_els_fpkt_resp(fcoe_frame_t *frm, fcoei_exchange_t *xch,
    int size);

/*
 * fcoei_rx_frame
 *      Unsolicited frame is received
 *
 * Input:
 *      frame = unsolicited frame that is received
 *
 * Return:
 *      N/A
 *
 * Comment:
 *      N/A
 */
static void
fcoei_rx_frame(fcoe_frame_t *frm)
{
        if (!(FRM2SS(frm)->ss_flags & SS_FLAG_LV_BOUND)) {
                /*
                 * Release the frame and netb
                 */
                FCOEI_LOG(__FUNCTION__, "not bound now");
                frm->frm_eport->eport_free_netb(frm->frm_netb);
                frm->frm_eport->eport_release_frame(frm);
                return;
        }

        FRM2IFM(frm)->ifm_ae.ae_type = AE_EVENT_UNSOL_FRAME;
        FRM2IFM(frm)->ifm_ae.ae_obj = frm;

        mutex_enter(&FRM2SS(frm)->ss_watchdog_mutex);
        list_insert_tail(&FRM2SS(frm)->ss_event_list, &FRM2IFM(frm)->ifm_ae);
        if (FRM2SS(frm)->ss_flags & SS_FLAG_WATCHDOG_IDLE) {
                cv_signal(&FRM2SS(frm)->ss_watchdog_cv);
        }
        mutex_exit(&FRM2SS(frm)->ss_watchdog_mutex);
}

/*
 * fcoei_release_sol_frame
 *      Release the solicited frame that has just been sent out
 *
 * Input:
 *      frame = solicited frame that has been sent out
 *
 * Returns:
 *      N/A
 *
 * Comments:
 *      After FCOE sends solicited frames out, it will call this to notify
 *      FCOEI of the completion.
 */
static void
fcoei_release_sol_frame(fcoe_frame_t *frm)
{
        /*
         * For request-type frames, it's safe to be handled out of
         * watchdog, because it needn't update anything
         */
        switch (FRM2IFM(frm)->ifm_rctl) {
        case R_CTL_SOLICITED_DATA:
        case R_CTL_COMMAND:
        case R_CTL_ELS_REQ:
        case R_CTL_UNSOL_CONTROL:
        case R_CTL_LS_ABTS:
                FRM2SS(frm)->ss_eport->eport_release_frame(frm);
                break;

        default:
                FRM2IFM(frm)->ifm_ae.ae_type = AE_EVENT_SOL_FRAME;
                FRM2IFM(frm)->ifm_ae.ae_obj = frm;

                mutex_enter(&FRM2SS(frm)->ss_watchdog_mutex);
                list_insert_tail(&FRM2SS(frm)->ss_event_list,
                    &FRM2IFM(frm)->ifm_ae);
                if (FRM2SS(frm)->ss_flags & SS_FLAG_WATCHDOG_IDLE) {
                        cv_signal(&FRM2SS(frm)->ss_watchdog_cv);
                }
                mutex_exit(&FRM2SS(frm)->ss_watchdog_mutex);
                break;
        }
}

/*
 * fcoei_process_unsol_xfer_rdy
 *      XFER_RDY is received
 *
 * Input:
 *      frm = XFER_RDY frame
 *
 * Returns:
 *      N/A
 *
 * Comments:
 *      N/A
 */
static void
fcoei_process_unsol_xfer_rdy(fcoe_frame_t *frm)
{
        uint16_t                 sol_oxid;
        fcoei_exchange_t        *xch;
        int                      rcv_buf_size;
        int                      offset;
        int                      left_size;
        int                      data_size;
        int                      frm_num;
        int                      idx;
        fcoe_frame_t            *nfrm;

        sol_oxid = FRM_OXID(frm);
        if (mod_hash_find(FRM2SS(frm)->ss_sol_oxid_hash,
            FMHK(sol_oxid), (mod_hash_val_t *)&xch) != 0) {
                return;
        }

        /*
         * rcv_buf_size is the total size of data that should be transferred
         * in this sequence.
         * offset is based on the exchange not the sequence.
         */
        xch->xch_rxid = FRM_RXID(frm);
        rcv_buf_size = FCOE_B2V_4(frm->frm_payload + 4);
        offset = FCOE_B2V_4(frm->frm_payload);
        ASSERT(xch->xch_resid >= rcv_buf_size);

        /*
         * Local variables initialization
         */
        left_size = rcv_buf_size;
        data_size = FRM2SS(frm)->ss_fcp_data_payload_size;
        frm_num = (rcv_buf_size + data_size - 1) / data_size;

        for (idx = 0; idx < frm_num - 1; idx++) {
                /*
                 * The first (frm_num -1) frames are always full
                 */
                nfrm = FRM2SS(frm)->ss_eport->eport_alloc_frame(
                    FRM2SS(frm)->ss_eport, data_size + FCFH_SIZE, NULL);
                if (nfrm == NULL) {
                        FCOEI_LOG(__FUNCTION__, "can't alloc frame");
                        return;
                }

                /*
                 * Copy the data payload that will  be transferred
                 */
                bcopy(offset + (uint8_t *)xch->xch_fpkt->pkt_data,
                    nfrm->frm_payload, nfrm->frm_payload_size);

                FFM_R_CTL(R_CTL_SOLICITED_DATA, nfrm);
                FFM_TYPE(FC_TYPE_SCSI_FCP, nfrm);
                FFM_F_CTL(0x010008, nfrm);
                FFM_OXID(xch->xch_oxid, nfrm);
                FFM_RXID(xch->xch_rxid, nfrm);
                FFM_S_ID(FRM_D_ID(frm), nfrm);
                FFM_D_ID(FRM_S_ID(frm), nfrm);
                FFM_SEQ_CNT(idx, nfrm);
                FFM_PARAM(offset, nfrm);
                fcoei_init_ifm(nfrm, xch);

                /*
                 * Submit the frame
                 */
                xch->xch_ss->ss_eport->eport_tx_frame(nfrm);

                /*
                 * Update offset and left_size
                 */
                offset += data_size;
                left_size -= data_size;
        }

        /*
         * Send the last data frame of this sequence
         */
        data_size = left_size;
        nfrm = xch->xch_ss->ss_eport->eport_alloc_frame(
            xch->xch_ss->ss_eport, data_size + FCFH_SIZE, NULL);
        if (nfrm != NULL) {
                fcoei_init_ifm(nfrm, xch);
        } else {
                ASSERT(0);
                return;
        }

        /*
         * Copy the data payload that will be transferred
         */
        bcopy(offset + (uint8_t *)xch->xch_fpkt->pkt_data,
            nfrm->frm_payload, nfrm->frm_payload_size);

        /*
         * Set ifm_rctl for fcoei_handle_sol_frame_done
         */
        FRM2IFM(nfrm)->ifm_rctl = R_CTL_SOLICITED_DATA;

        /*
         * FFM
         */
        FFM_R_CTL(R_CTL_SOLICITED_DATA, nfrm);
        FFM_TYPE(FC_TYPE_SCSI_FCP, nfrm);
        FFM_F_CTL(0x090008, nfrm);
        FFM_OXID(xch->xch_oxid, nfrm);
        FFM_RXID(xch->xch_rxid, nfrm);
        FFM_S_ID(FRM_D_ID(frm), nfrm);
        FFM_D_ID(FRM_S_ID(frm), nfrm);
        FFM_SEQ_CNT(idx, nfrm);
        FFM_PARAM(offset, nfrm);

        /*
         * Submit the frame
         */
        xch->xch_ss->ss_eport->eport_tx_frame(nfrm);

        /*
         * Sequence is a transaction, so we need only update
         * xch_remained_bytes in the end.
         */
        xch->xch_resid -= rcv_buf_size;
}

/*
 * fcoei_process_unsol_els_req
 *      els req frame is received
 *
 * Input:
 *      frm = ELS request frame
 *
 * Returns:
 *      N/A
 *
 * Comments:
 *      We will not create exchange data structure at this time,
 *      and we should create unsolicited buffer, which will only
 *      contain the exchange's request payload.
 */
static void
fcoei_process_unsol_els_req(fcoe_frame_t *frm)
{
        fc_unsol_buf_t          *ub;
        fc_rscn_t               *rscn;
        uint32_t                 offset;
        fcoei_exchange_t        *xch_tmp;

        /*
         * Get the unsol rxid first
         */
        FCOEI_SET_UNSOL_FRM_RXID(frm, xch_tmp);

        /*
         * Do proper ub initialization
         */
        ub = (fc_unsol_buf_t *)kmem_zalloc(sizeof (fc_unsol_buf_t), KM_SLEEP);
        ub->ub_class = FC_TRAN_CLASS3;
        ub->ub_bufsize = frm->frm_payload_size;
        ub->ub_buffer = kmem_alloc(frm->frm_payload_size, KM_SLEEP);
        ub->ub_port_handle = FRM2SS(frm);
        ub->ub_token = (uint64_t)(long)ub;

        /*
         * header conversion
         * Caution: ub_buffer is big endian, but ub_frame should be host-format
         * RSCN is one exception.
         */
        FCOEI_FRM2FHDR(frm, &ub->ub_frame);

        /*
         * If it's FLOGI, and our FLOGI failed last time,
         * then we post online event
         */
        if ((FRM2SS(frm)->ss_flags & SS_FLAG_FLOGI_FAILED) &&
            (frm->frm_payload[0] == LA_ELS_FLOGI)) {
                frm->frm_eport->eport_flags |=
                    EPORT_FLAG_IS_DIRECT_P2P;
                FRM2SS(frm)->ss_bind_info.port_statec_cb(FRM2SS(frm)->ss_port,
                    FC_STATE_ONLINE);
        }

        switch (frm->frm_payload[0]) {
        case LA_ELS_RSCN:
                /*
                 * Only RSCN need byte swapping
                 */
                rscn = (fc_rscn_t *)(void *)ub->ub_buffer;
                rscn->rscn_code = frm->frm_payload[0];
                rscn->rscn_len = frm->frm_payload[1];
                rscn->rscn_payload_len =
                    FCOE_B2V_2(frm->frm_payload + 2);

                offset = 4;
                for (int i = 0; i < rscn->rscn_payload_len - 4; i += 4) {
                        *(uint32_t *)((intptr_t)(uint8_t *)ub->ub_buffer +
                            offset) = FCOE_B2V_4(frm->frm_payload + offset);
                        offset += 4;
                }
                break;

        default:
                bcopy(frm->frm_payload, ub->ub_buffer, frm->frm_payload_size);
                break;
        }

        /*
         * Pass this unsol ELS up to Leadville
         */
        FRM2SS(frm)->ss_bind_info.port_unsol_cb(FRM2SS(frm)->ss_port, ub, 0);
}

/*
 * fcoei_search_abort_xch
 *      Find the exchange that should be aborted
 *
 * Input:
 *      key = oxid of the exchange
 *      val = the exchange
 *      arg = the soft state
 *
 * Returns:
 *      MH_WALK_TERMINATE = found it, terminate the walk
 *      MH_WALK_CONTINUE = not found, continue the walk
 *
 * Comments:
 *      N/A
 */
/* ARGSUSED */
static uint32_t
fcoei_search_abort_xch(mod_hash_key_t key, mod_hash_val_t *val, void *arg)
{
        fcoei_walk_arg_t        *wa = (fcoei_walk_arg_t *)arg;
        fcoei_exchange_t        *xch = (fcoei_exchange_t *)val;

        if (xch->xch_oxid == wa->wa_oxid) {
                wa->wa_xch = xch;
                ASSERT(xch->xch_oxid == CMHK(key));
                return (MH_WALK_TERMINATE);
        }

        return (MH_WALK_CONTINUE);
}

/*
 * fcoei_process_unsol_abts_req
 *      ABTS request is received
 *
 * Input:
 *      frm = ABTS request frame
 *
 * Returns:
 *      N/A
 *
 * Comments:
 *      The remote side wants to abort one unsolicited exchange.
 */
static void
fcoei_process_unsol_abts_req(fcoe_frame_t *frm)
{
        fcoei_exchange_t        *xch = NULL;
        fcoe_frame_t            *nfrm;
        int                      payload_size;
        fcoei_walk_arg_t         walk_arg;

        /*
         * According to spec, the responder could want to ABTS xch too
         */
        if (FRM_SENDER_IS_XCH_RESPONDER(frm)) {
                uint16_t sol_oxid = FRM_OXID(frm);
                (void) mod_hash_find(FRM2SS(frm)->ss_sol_oxid_hash,
                    FMHK(sol_oxid), (mod_hash_val_t *)&xch);
        } else {
                /*
                 * it's a unsolicited exchange, and we need find it out from
                 * unsolicited hash table. But at this time, RXID in frame could
                 * still be 0xFFFF in most cases, so we need do exaustive search
                 */
                walk_arg.wa_xch = NULL;
                walk_arg.wa_oxid = FRM_OXID(frm);
                mod_hash_walk(FRM2SS(frm)->ss_unsol_rxid_hash,
                    fcoei_search_abort_xch, &walk_arg);
                xch = walk_arg.wa_xch;
        }

        if (xch == NULL) {
                payload_size = 4;
                nfrm = FRM2SS(frm)->ss_eport->eport_alloc_frame(
                    FRM2SS(frm)->ss_eport,
                    payload_size + FCFH_SIZE, NULL);
                if (nfrm == NULL) {
                        FCOEI_LOG(__FUNCTION__, "can't alloc frame");
                        return;
                }

                bzero(nfrm->frm_payload, nfrm->frm_payload_size);
                nfrm->frm_payload[1] = 0x05;
                nfrm->frm_payload[3] = 0xAA;
                FFM_R_CTL(R_CTL_LS_BA_RJT, nfrm);
                fcoei_init_ifm(nfrm, xch);
        } else {
                /*
                 * We should complete the exchange with frm as NULL,
                 * and we don't care its success or failure
                 */
                fcoei_complete_xch(xch, NULL, FC_PKT_FAILURE, FC_REASON_ABTX);

                /*
                 * Construct ABTS ACC frame
                 */
                payload_size = 12;
                nfrm = FRM2SS(frm)->ss_eport->eport_alloc_frame(
                    FRM2SS(frm)->ss_eport, payload_size + FCFH_SIZE, NULL);
                if (nfrm == NULL) {
                        FCOEI_LOG(__FUNCTION__, "can't alloc frame");
                        return;
                }

                bzero(nfrm->frm_payload, nfrm->frm_payload_size);
                nfrm->frm_payload[4] = 0xFF & (xch->xch_oxid >> 8);
                nfrm->frm_payload[5] = 0xFF & (xch->xch_oxid);
                nfrm->frm_payload[6] = 0xFF & (xch->xch_rxid >> 8);
                nfrm->frm_payload[7] = 0xFF & (xch->xch_rxid);
                nfrm->frm_payload[10] = 0xFF;
                nfrm->frm_payload[11] = 0xFF;

                FFM_R_CTL(R_CTL_LS_BA_ACC, nfrm);
                fcoei_init_ifm(nfrm, xch);
        }

        FFM_D_ID(FRM_S_ID(frm), nfrm);
        FFM_S_ID(FRM_D_ID(frm), nfrm);
        FFM_TYPE(FRM_TYPE(frm), nfrm);
        FFM_F_CTL(FRM_F_CTL(frm), nfrm);
        FFM_OXID(FRM_OXID(frm), nfrm);
        FFM_RXID(FRM_RXID(frm), nfrm);
        FRM2SS(frm)->ss_eport->eport_tx_frame(nfrm);
}

/*
 * fcoei_process_sol_fcp_resp
 *      FCP response is received
 *
 * Input:
 *      frm = FCP response frame
 *
 * Returns:
 *      N/A
 *
 * Comments:
 *      N/A
 */
static void
fcoei_process_sol_fcp_resp(fcoe_frame_t *frm)
{
        uint16_t                 sol_oxid;
        uint32_t                 actual_size;
        fcoei_exchange_t        *xch  = NULL;
        fc_packet_t             *fpkt = NULL;
        mod_hash_val_t           val;
        uint32_t                 i_fcp_status;

        /*
         * Firstly, we search the related exchange
         */
        sol_oxid = FRM_OXID(frm);
        if (mod_hash_find(FRM2SS(frm)->ss_sol_oxid_hash,
            FMHK(sol_oxid), (mod_hash_val_t *)&xch) != 0) {
                PRT_FRM_HDR(__FUNCTION__, frm);
                FCOEI_LOG(__FUNCTION__, "can't find the corresponding xch: "
                    "oxid/%x %lu - %lu", sol_oxid,
                    CURRENT_CLOCK, frm->frm_clock);
                return;
        } else {
                fpkt = xch->xch_fpkt;
        }

        /*
         * Decide the actual response length
         */
        actual_size = fpkt->pkt_rsplen;
        if (actual_size > frm->frm_payload_size) {
                actual_size = frm->frm_payload_size;
        }

        /*
         * Update the exchange and hash table
         */
        (void) mod_hash_remove(FRM2SS(frm)->ss_sol_oxid_hash,
            FMHK(xch->xch_oxid), &val);
        ASSERT((fcoei_exchange_t *)val == xch);
        xch->xch_flags &= ~XCH_FLAG_IN_SOL_HASH;

        /*
         * Upate fpkt related elements
         */
        FCOEI_FRM2FHDR(frm, &fpkt->pkt_resp_fhdr);

        /*
         * we should set pkt_reason and pkt_state carefully
         */
        fpkt->pkt_state = FC_PKT_SUCCESS;
        fpkt->pkt_reason = 0;

        /*
         * First we zero the first 12 byte of dest
         */
        bzero(xch->xch_fpkt->pkt_resp, 12);
        i_fcp_status = BE_IN32(frm->frm_payload + 8);
        if (i_fcp_status != 0) {
                fcoei_fill_fcp_resp(frm->frm_payload,
                    (uint8_t *)xch->xch_fpkt->pkt_resp, actual_size);
        }

        /*
         * Update pkt_resp_resid
         */
        fpkt->pkt_data_resid = xch->xch_resid;
        if ((xch->xch_resid != 0) && ((xch->xch_resid % 0x200) == 0) &&
            ((xch->xch_fpkt->pkt_datalen % 0x200) == 0) &&
            (i_fcp_status == 0)) {
                FCOEI_LOG(__FUNCTION__, "frame lost no pause ? %x/%x",
                    xch->xch_resid, xch->xch_fpkt->pkt_datalen);
                fpkt->pkt_state = FC_PKT_LOCAL_RJT;
                fpkt->pkt_reason = FC_REASON_UNDERRUN;
        }

        /*
         * Notify LV it's over
         */
        if (fpkt->pkt_tran_flags & FC_TRAN_NO_INTR) {
                FCOEI_LOG(__FUNCTION__, "BEFORE WAKEUP: %p-%p", fpkt, xch);
                sema_v(&xch->xch_sema);
                FCOEI_LOG(__FUNCTION__, "AFTERE WAKEUP: %p-%p", fpkt, xch);
        } else {
                xch->xch_fpkt->pkt_comp(xch->xch_fpkt);
        }
}

/*
 * fcoei_process_sol_els_rsp
 *      ELS response is received
 *
 * Input:
 *      frm = ELS response frame
 *
 * Returns:
 *      N/A
 *
 * Comments:
 *      N/A
 */
static void
fcoei_process_sol_els_rsp(fcoe_frame_t *frm)
{
        uint16_t                 sol_oxid    = 0;
        uint32_t                 actual_size = 0;
        fcoei_exchange_t        *xch;
        fc_packet_t             *fpkt;

        /*
         * Look for the related exchange
         */
        xch = NULL;
        fpkt = NULL;
        sol_oxid = FRM_OXID(frm);
        if (mod_hash_find(FRM2SS(frm)->ss_sol_oxid_hash,
            FMHK(sol_oxid), (mod_hash_val_t *)&xch) != 0) {
                PRT_FRM_HDR(__FUNCTION__, frm);
                FCOEI_LOG(__FUNCTION__, "can't find the "
                    "corresponding xch: oxid/%x", sol_oxid);
                return;
        }

        xch->xch_rxid = FRM_RXID(frm);
        fpkt = xch->xch_fpkt;

        /*
         * Decide the actual response length
         */
        actual_size = frm->frm_payload_size;
        if (actual_size > fpkt->pkt_rsplen) {
                FCOEI_LOG(__FUNCTION__, "pkt_rsplen is smaller"
                    "0x(%x - %x)", actual_size, fpkt->pkt_rsplen);
                actual_size = fpkt->pkt_rsplen;
        }

        /*
         * Upate fpkt related elements
         */
        FCOEI_FRM2FHDR(frm, &fpkt->pkt_resp_fhdr);
        fcoei_fill_els_fpkt_resp(frm, xch, actual_size);

        /*
         * we should set pkt_reason and pkt_state carefully now
         * Need to analyze pkt_reason according to the response.
         * Leave it untouched now.
         */
        if (((ls_code_t *)(void *)xch->xch_fpkt->pkt_resp)->ls_code ==
            LA_ELS_RJT) {
                fcoei_complete_xch(xch, NULL, FC_PKT_FABRIC_RJT,
                    FC_REASON_INVALID_PARAM);
        } else {
                fcoei_complete_xch(xch, NULL, FC_PKT_SUCCESS, 0);
        }
}

/*
 * fcoei_process_sol_ct_rsp
 *      CT response is received
 *
 * Input:
 *      frm = CT response frame
 *
 * Returns:
 *      N/A
 *
 * Comments:
 *      N/A
 */
static void
fcoei_process_sol_ct_rsp(fcoe_frame_t *frm)
{
        uint16_t                 sol_oxid    = 0;
        uint32_t                 actual_size = 0;
        fcoei_exchange_t        *xch;
        fc_packet_t             *fpkt;

        /*
         * Look for the related exchange
         */
        xch = NULL;
        fpkt = NULL;
        sol_oxid = FRM_OXID(frm);
        if (mod_hash_find(FRM2SS(frm)->ss_sol_oxid_hash,
            FMHK(sol_oxid), (mod_hash_val_t *)&xch) != 0) {
                FCOEI_LOG(__FUNCTION__, "can't find the "
                    "corresponding xch: oxid/%x", sol_oxid);
                return;
        }

        xch->xch_rxid = FRM_RXID(frm);
        fpkt = xch->xch_fpkt;

        /*
         * Decide the actual response length
         */
        actual_size = fpkt->pkt_rsplen;
        if (actual_size > frm->frm_payload_size) {
                FCOEI_LOG(__FUNCTION__, "payload is smaller"
                    "0x(%x - %x)", actual_size, frm->frm_payload_size);
                actual_size = frm->frm_payload_size;
        }

        /*
         * Update fpkt related elements
         * Caution: we needn't do byte swapping for CT response
         */
        FCOEI_FRM2FHDR(frm, &fpkt->pkt_resp_fhdr);
        bcopy(FPLD, (uint8_t *)xch->xch_fpkt->pkt_resp, actual_size);

        /*
         * Complete it with frm as NULL
         */
        fcoei_complete_xch(xch, NULL, FC_PKT_SUCCESS, 0);
}

/*
 * fcoei_process_sol_abts_acc
 *      ABTS accpet is received
 *
 * Input:
 *      frm = ABTS accept frame
 *
 * Returns:
 *      N/A
 *
 * Comments:
 *      We will always finish the abortion of solicited exchanges,
 *      so we will not depend on the response from the remote side.
 *      We just log one message.
 */
static void
fcoei_process_sol_abts_acc(fcoe_frame_t *frm)
{
        FCOEI_LOG(__FUNCTION__, "the remote side has agreed to "
            "abort the exchange: oxid-%x, rxid-%x",
            FCOE_B2V_2(frm->frm_payload + 4),
            FCOE_B2V_2(frm->frm_payload + 6));
}

/*
 * fcoei_process_sol_abts_rjt
 *      ABTS reject is received
 *
 * Input:
 *      frm = ABTS reject frame
 *
 * Returns:
 *      N/A
 *
 * Comments:
 *      We will alwayas finish the abortion of solicited exchanges,
 *      so we will not depend on the response from the remote side.
 *      We just log one message.
 */
static void
fcoei_process_sol_abts_rjt(fcoe_frame_t *frm)
{
        FCOEI_LOG(__FUNCTION__, "the remote side rejected "
            "our request to abort one exchange.: %p", frm);
}

/*
 * fcoei_fill_els_fpkt_resp
 *      Fill fpkt ELS response in host format according frm payload
 *
 * Input:
 *      src = frm payload in link format
 *      dest = fpkt ELS response in host format
 *      size = Maximum conversion size
 *      els_op = ELS opcode
 *
 * Returns:
 *      N/A
 *
 * Comments:
 *      fpkt->pkt_resp must be mapped to one data structure, and it's
 *      different from the content in the raw frame
 */
static void
fcoei_fill_els_fpkt_resp(fcoe_frame_t *frm, fcoei_exchange_t *xch, int size)
{
        uint8_t                 *src       = frm->frm_payload;
        uint8_t                 *dest      = (uint8_t *)xch->xch_fpkt->pkt_resp;
        ls_code_t               *els_code  = (ls_code_t *)(void *)dest;
        la_els_logi_t           *els_logi  = (la_els_logi_t *)(void *)dest;
        la_els_adisc_t          *els_adisc = (la_els_adisc_t *)(void *)dest;
        la_els_rls_acc_t        *els_rls;
        la_els_rnid_acc_t       *els_rnid;
        struct fcp_prli_acc     *prli_acc;
        int                      offset;

        els_code->ls_code = FCOE_B2V_1(src);
        if (els_code->ls_code == LA_ELS_RJT) {
                FCOEI_LOG(__FUNCTION__, "size :%d", size);
                return;
        }

        switch (((ls_code_t *)(void *)xch->xch_fpkt->pkt_cmd)->ls_code) {
        case LA_ELS_FLOGI:
                bcopy((char *)frm->frm_hdr - 22,
                    frm->frm_eport->eport_efh_dst, ETHERADDRL);
                if (frm->frm_payload[8] & 0x10) {
                        /*
                         * We are in fabric p2p mode
                         */
                        uint8_t src_addr[ETHERADDRL];
                        frm->frm_eport->eport_flags &=
                            ~EPORT_FLAG_IS_DIRECT_P2P;
                        FCOE_SET_DEFAULT_OUI(src_addr);
                        bcopy(frm->frm_hdr->hdr_d_id, src_addr + 3, 3);
                        frm->frm_eport->eport_set_mac_address(
                            frm->frm_eport, src_addr, 1);
                } else {
                        /*
                         * We are in direct p2p mode
                         */
                        frm->frm_eport->eport_flags |=
                            EPORT_FLAG_IS_DIRECT_P2P;
                }

                if (!(FRM2SS(frm)->ss_eport->eport_flags &
                    EPORT_FLAG_IS_DIRECT_P2P)) {
                        FRM2SS(frm)->ss_p2p_info.fca_d_id = FRM_D_ID(frm);
                }

                /* FALLTHROUGH */

        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_D_ID(frm);
                        FRM2SS(frm)->ss_p2p_info.d_id = FRM_S_ID(frm);
                }

                offset = offsetof(la_els_logi_t, common_service);
                els_logi->common_service.fcph_version = FCOE_B2V_2(src +
                    offset);
                offset += 2;
                els_logi->common_service.btob_credit = FCOE_B2V_2(src +
                    offset);
                offset += 2;
                els_logi->common_service.cmn_features = FCOE_B2V_2(src +
                    offset);
                offset += 2;
                els_logi->common_service.rx_bufsize = FCOE_B2V_2(src +
                    offset);
                offset += 2;
                els_logi->common_service.conc_sequences = FCOE_B2V_2(src +
                    offset);
                offset += 2;
                els_logi->common_service.relative_offset = FCOE_B2V_2(src +
                    offset);
                offset += 2;
                els_logi->common_service.e_d_tov = FCOE_B2V_4(src +
                    offset);

                /*
                 * port/node WWN
                 */
                offset = offsetof(la_els_logi_t, nport_ww_name);
                bcopy(src + offset, &els_logi->nport_ww_name, 8);
                offset = offsetof(la_els_logi_t, node_ww_name);
                bcopy(src + offset, &els_logi->node_ww_name, 8);

                /*
                 * class_3
                 */
                offset = offsetof(la_els_logi_t, class_3);
                els_logi->class_3.class_opt = FCOE_B2V_2(src + offset);
                offset += 2;
                els_logi->class_3.initiator_ctl = FCOE_B2V_2(src + offset);
                offset += 2;
                els_logi->class_3.recipient_ctl = FCOE_B2V_2(src + offset);
                offset += 2;
                els_logi->class_3.rcv_size = FCOE_B2V_2(src + offset);
                offset += 2;
                els_logi->class_3.conc_sequences = FCOE_B2V_2(src + offset);
                offset += 2;
                els_logi->class_3.n_port_e_to_e_credit = FCOE_B2V_2(src +
                    offset);
                offset += 2;
                els_logi->class_3.open_seq_per_xchng = FCOE_B2V_2(src + offset);

                break;

        case LA_ELS_PRLI:
                /*
                 * PRLI service parameter response page
                 *
                 * fcp_prli_acc doesn't include ls_code, don't use offsetof
                 */
                offset = 4;
                prli_acc = (struct fcp_prli_acc *)(void *)(dest + offset);
                prli_acc->type = FCOE_B2V_1(src + offset);
                /*
                 * Type code extension
                 */
                offset += 1;
                /*
                 * PRLI response flags
                 */
                offset += 1;
                prli_acc->orig_process_assoc_valid =
                    (FCOE_B2V_2(src + offset) & BIT_15) ? 1 : 0;
                prli_acc->resp_process_assoc_valid =
                    (FCOE_B2V_2(src + offset) & BIT_14) ? 1 : 0;
                prli_acc->image_pair_established =
                    (FCOE_B2V_2(src + offset) & BIT_13) ? 1 : 0;
                prli_acc->accept_response_code =
                    (FCOE_B2V_2(src + offset) & 0x0F00) >> 8;
                /*
                 * process associator
                 */
                offset += 2;
                prli_acc->orig_process_associator = FCOE_B2V_4(src + offset);
                offset += 4;
                prli_acc->resp_process_associator = FCOE_B2V_4(src + offset);
                /*
                 * FC-4 type
                 */
                offset += 4;
                prli_acc->initiator_fn =
                    (FCOE_B2V_4(src + offset) & BIT_5) ? 1 : 0;
                prli_acc->target_fn =
                    (FCOE_B2V_4(src + offset) & BIT_4) ? 1 : 0;
                prli_acc->cmd_data_mixed =
                    (FCOE_B2V_4(src + offset) & BIT_3) ? 1 : 0;
                prli_acc->data_resp_mixed =
                    (FCOE_B2V_4(src + offset) & BIT_2) ? 1 : 0;
                prli_acc->read_xfer_rdy_disabled =
                    (FCOE_B2V_4(src + offset) & BIT_1) ? 1 : 0;
                prli_acc->write_xfer_rdy_disabled =
                    (FCOE_B2V_4(src + offset) & BIT_0) ? 1 : 0;

                break;

        case LA_ELS_LOGO:
                /*
                 * could only be LS_ACC, no additional information
                 */
                els_code->ls_code = FCOE_B2V_1(src);
                break;

        case LA_ELS_SCR:
                /*
                 * LS_ACC/LS_RJT, no additional information
                 */
                els_code->ls_code = FCOE_B2V_1(src);
                break;

        case LA_ELS_ADISC:
                offset = 5;
                els_adisc->hard_addr.hard_addr = FCOE_B2V_3(src + offset);
                offset = offsetof(la_els_adisc_t, port_wwn);
                bcopy(src + offset, &els_adisc->port_wwn, 8);
                offset = offsetof(la_els_adisc_t, node_wwn);
                bcopy(src + offset, &els_adisc->node_wwn, 8);
                offset += 9;
                els_adisc->nport_id.port_id = FCOE_B2V_3(src + offset);
                break;
        case LA_ELS_RLS:
                els_rls = (la_els_rls_acc_t *)(void *)dest;
                els_rls->ls_code.ls_code = FCOE_B2V_1(src);
                offset = 4;
                els_rls->rls_link_params.rls_link_fail =
                    FCOE_B2V_4(src + offset);
                offset = 8;
                els_rls->rls_link_params.rls_sync_loss =
                    FCOE_B2V_4(src + offset);
                offset = 12;
                els_rls->rls_link_params.rls_sig_loss =
                    FCOE_B2V_4(src + offset);
                offset = 16;
                els_rls->rls_link_params.rls_prim_seq_err =
                    FCOE_B2V_4(src + offset);
                offset = 20;
                els_rls->rls_link_params.rls_invalid_word =
                    FCOE_B2V_4(src + offset);
                offset = 24;
                els_rls->rls_link_params.rls_invalid_crc =
                    FCOE_B2V_4(src + offset);
                break;
        case LA_ELS_RNID:
                els_rnid = (la_els_rnid_acc_t *)(void *)dest;
                els_rnid->ls_code.ls_code = FCOE_B2V_1(src);
                offset = 4;
                bcopy(src + offset, &els_rnid->hdr.data_format, 1);
                offset = 5;
                bcopy(src + offset, &els_rnid->hdr.cmn_len, 1);
                offset = 7;
                bcopy(src + offset, &els_rnid->hdr.specific_len, 1);
                offset = 8;
                bcopy(src + offset, els_rnid->data, FCIO_RNID_MAX_DATA_LEN);
                break;
        default:
                FCOEI_LOG(__FUNCTION__, "unsupported R_CTL");
                break;
        }
}

/*
 * fcoei_fill_fcp_resp
 *      Fill fpkt FCP response in host format according to frm payload
 *
 * Input:
 *      src - frm payload in link format
 *      dest - fpkt FCP response in host format
 *      size - Maximum conversion size
 *
 * Returns:
 *      N/A
 *
 * Comments:
 *      This is called only for SCSI response with non good status
 */
static void
fcoei_fill_fcp_resp(uint8_t *src, uint8_t *dest, int size)
{
        fcp_rsp_t       *fcp_rsp_iu = (fcp_rsp_t *)(void *)dest;
        int              offset;

        /*
         * set fcp_status
         */
        offset = offsetof(fcp_rsp_t, fcp_u);
        offset += 2;
        fcp_rsp_iu->fcp_u.fcp_status.resid_under =
            (FCOE_B2V_1(src + offset) & BIT_3) ? 1 : 0;
        fcp_rsp_iu->fcp_u.fcp_status.resid_over =
            (FCOE_B2V_1(src + offset) & BIT_2) ? 1 : 0;
        fcp_rsp_iu->fcp_u.fcp_status.sense_len_set =
            (FCOE_B2V_1(src + offset) & BIT_1) ? 1 : 0;
        fcp_rsp_iu->fcp_u.fcp_status.rsp_len_set =
            (FCOE_B2V_1(src + offset) & BIT_0) ? 1 : 0;
        offset += 1;
        fcp_rsp_iu->fcp_u.fcp_status.scsi_status = FCOE_B2V_1(src + offset);
        /*
         * fcp_resid/fcp_sense_len/fcp_response_len
         */
        offset = offsetof(fcp_rsp_t, fcp_resid);
        fcp_rsp_iu->fcp_resid = FCOE_B2V_4(src + offset);
        offset = offsetof(fcp_rsp_t, fcp_sense_len);
        fcp_rsp_iu->fcp_sense_len = FCOE_B2V_4(src + offset);
        offset = offsetof(fcp_rsp_t, fcp_response_len);
        fcp_rsp_iu->fcp_response_len = FCOE_B2V_4(src + offset);
        /*
         * sense or response
         */
        offset += 4;
        if (fcp_rsp_iu->fcp_sense_len) {
                if ((offset + fcp_rsp_iu->fcp_sense_len) > size) {
                        FCOEI_LOG(__FUNCTION__, "buffer too small - sens");
                        return;
                }
                bcopy(src + offset, dest + offset, fcp_rsp_iu->fcp_sense_len);
                offset += fcp_rsp_iu->fcp_sense_len;
        }

        if (fcp_rsp_iu->fcp_response_len) {
                if ((offset + fcp_rsp_iu->fcp_response_len) > size) {
                        FCOEI_LOG(__FUNCTION__, "buffer too small - resp");
                        return;
                }
                bcopy(src + offset, dest + offset,
                    fcp_rsp_iu->fcp_response_len);
        }
}

void
fcoei_init_ect_vectors(fcoe_client_t *ect)
{
        ect->ect_rx_frame          = fcoei_rx_frame;
        ect->ect_port_event        = fcoei_port_event;
        ect->ect_release_sol_frame = fcoei_release_sol_frame;
}

/*
 * fcoei_process_unsol_frame
 *      Unsolicited frame is received
 *
 * Input:
 *      frame = unsolicited frame that is received
 *
 * Returns:
 *      N/A
 *
 * Comments:
 *      watchdog will call this to process unsolicited frames that we
 *      just received fcoei_process_xx is used to handle different
 *      unsolicited frames
 */
void
fcoei_process_unsol_frame(fcoe_frame_t *frm)
{
        fcoei_exchange_t        *xch;
        uint16_t                 sol_oxid;

        switch (FRM_R_CTL(frm)) {
        case R_CTL_SOLICITED_DATA:
                /*
                 * READ data phase frame
                 * Find the associated exchange
                 */
                sol_oxid = FRM_OXID(frm);
                if (mod_hash_find(FRM2SS(frm)->ss_sol_oxid_hash,
                    FMHK(sol_oxid), (mod_hash_val_t *)&xch) != 0) {
                        PRT_FRM_HDR(__FUNCTION__, frm);
                        FCOEI_LOG(__FUNCTION__, "associated xch not found: "
                            "oxid/%x %lu - %lu", sol_oxid,
                            CURRENT_CLOCK, frm->frm_clock);
                        break;
                }

                /*
                 * Copy data into fpkt data buffer, and update the counter
                 */
                bcopy(frm->frm_payload, (uint8_t *)xch->xch_fpkt->pkt_data +
                    FRM_PARAM(frm), frm->frm_payload_size);
                xch->xch_resid -= frm->frm_payload_size;
                xch->xch_rxid = FRM_RXID(frm);
                break;

        case R_CTL_XFER_RDY:
                fcoei_process_unsol_xfer_rdy(frm);
                break;

        case R_CTL_STATUS:
                fcoei_process_sol_fcp_resp(frm);
                break;

        case R_CTL_ELS_REQ:
                fcoei_process_unsol_els_req(frm);
                break;

        case R_CTL_LS_ABTS:
                fcoei_process_unsol_abts_req(frm);
                break;

        case R_CTL_ELS_RSP:
                fcoei_process_sol_els_rsp(frm);
                break;

        case R_CTL_SOLICITED_CONTROL:
                fcoei_process_sol_ct_rsp(frm);
                break;

        case R_CTL_LS_BA_ACC:
                fcoei_process_sol_abts_acc(frm);
                break;

        case R_CTL_LS_BA_RJT:
                fcoei_process_sol_abts_rjt(frm);
                break;

        default:
                /*
                 * Unsupported frame
                 */
                PRT_FRM_HDR("Unsupported unsol frame: ", frm);
        }

        /*
         * Release the frame and netb
         */
        frm->frm_eport->eport_free_netb(frm->frm_netb);
        frm->frm_eport->eport_release_frame(frm);
}

/*
 * fcoei_handle_sol_frame_done
 *      solicited frame is just sent out
 *
 * Input:
 *      frame = solicited frame that has been sent out
 *
 * Returns:
 *      N/A
 *
 * Comments:
 *      watchdog will call this to handle solicited frames that FCOEI
 *      has sent out Non-request frame post handling
 */
void
fcoei_handle_sol_frame_done(fcoe_frame_t *frm)
{
        /*
         * the corresponding xch could be NULL at this time
         */
        fcoei_exchange_t        *xch  = FRM2IFM(frm)->ifm_xch;

        switch (FRM2IFM(frm)->ifm_rctl) {
        case R_CTL_ELS_RSP:
                /*
                 * Complete it with frm as NULL
                 */
                fcoei_complete_xch(xch, NULL, FC_PKT_SUCCESS, 0);
                break;

        case R_CTL_LS_BA_ACC:
                FCOEI_LOG(__FUNCTION__,  "BA_ACC out: xch-%p, frm-%p",
                    xch, frm);
                PRT_FRM_HDR("LS_BA_ACC", frm);
                break;

        case R_CTL_LS_BA_RJT:
                FCOEI_LOG(__FUNCTION__,  "BA_RJT out: xch-%p, frm-%p",
                    xch, frm);
                PRT_FRM_HDR("LS_BA_RJT", frm);
                break;

        default:
                /*
                 * Unsupported frame
                 */
                PRT_FRM_HDR("Unsupported sol frame: ", frm);
        }

        /*
         * We should release only the frame, and we don't care its netb
         */
        FRM2SS(frm)->ss_eport->eport_release_frame(frm);
}

/*
 * fcoei_port_event
 *      link/port state changed
 *
 * Input:
 *      eport = to indicate which port has changed
 *      event = what change
 *
 * Returns:
 *      N/A
 *
 * Comments:
 *      refer fctl.h for ss_link_state value
 */
void
fcoei_port_event(fcoe_port_t *eport, uint32_t event)
{
        fcoei_event_t   *ae;

        if (!(EPORT2SS(eport)->ss_flags & SS_FLAG_LV_BOUND)) {
                FCOEI_LOG(__FUNCTION__, "not bound now");
                return;
        }

        mutex_enter(&EPORT2SS(eport)->ss_watchdog_mutex);
        switch (event) {
        case FCOE_NOTIFY_EPORT_LINK_DOWN:
                EPORT2SS(eport)->ss_link_state = FC_STATE_OFFLINE;
                cmn_err(CE_NOTE, "%02x%02x%02x%02x%02x%02x%02x%02x Link down",
                    eport->eport_portwwn[0], eport->eport_portwwn[1],
                    eport->eport_portwwn[2], eport->eport_portwwn[3],
                    eport->eport_portwwn[4], eport->eport_portwwn[5],
                    eport->eport_portwwn[6], eport->eport_portwwn[7]);
                break;

        case FCOE_NOTIFY_EPORT_LINK_UP:
                if (eport->eport_mtu >= 2200) {
                        EPORT2SS(eport)->ss_fcp_data_payload_size =
                            FCOE_DEFAULT_FCP_DATA_PAYLOAD_SIZE;
                } else {
                        FCOEI_LOG(__FUNCTION__, "fcoei: MTU is not big enough. "
                            "we will use 1K frames in FCP data phase.");
                        EPORT2SS(eport)->ss_fcp_data_payload_size =
                            FCOE_MIN_FCP_DATA_PAYLOAD_SIZE;
                }

                cmn_err(CE_NOTE, "%02x%02x%02x%02x%02x%02x%02x%02x Link up",
                    eport->eport_portwwn[0], eport->eport_portwwn[1],
                    eport->eport_portwwn[2], eport->eport_portwwn[3],
                    eport->eport_portwwn[4], eport->eport_portwwn[5],
                    eport->eport_portwwn[6], eport->eport_portwwn[7]);
                EPORT2SS(eport)->ss_link_state = FC_STATE_ONLINE;
                break;

        default:
                FCOEI_LOG(__FUNCTION__, "unsupported event");
                mutex_exit(&EPORT2SS(eport)->ss_watchdog_mutex);

                return;
        }

        EPORT2SS(eport)->ss_port_event_counter++;
        ae = (fcoei_event_t *)kmem_zalloc(sizeof (fcoei_event_t), KM_SLEEP);
        ae->ae_type = AE_EVENT_PORT;
        ae->ae_obj = EPORT2SS(eport);
        ae->ae_specific = EPORT2SS(eport)->ss_link_state;
        list_insert_tail(&EPORT2SS(eport)->ss_event_list, ae);
        mutex_exit(&EPORT2SS(eport)->ss_watchdog_mutex);
}

/*
 * fcoei_process_event_port
 *      link/port state changed
 *
 * Input:
 *      ae = link fcoei_event
 *
 * Returns:
 *      N/A
 *
 * Comments:
 *      asynchronous events from FCOE
 */
void
fcoei_process_event_port(fcoei_event_t *ae)
{
        fcoei_soft_state_t      *ss = (fcoei_soft_state_t *)ae->ae_obj;

        if (ss->ss_eport->eport_link_speed == FCOE_PORT_SPEED_1G) {
                ae->ae_specific |= FC_STATE_1GBIT_SPEED;
        } else if (ss->ss_eport->eport_link_speed ==
            FCOE_PORT_SPEED_10G) {
                ae->ae_specific |= FC_STATE_10GBIT_SPEED;
        }

        if (ss->ss_flags & SS_FLAG_LV_BOUND) {
                ss->ss_bind_info.port_statec_cb(ss->ss_port,
                    (uint32_t)ae->ae_specific);
        } else {
                FCOEI_LOG(__FUNCTION__, "ss %p not bound now", ss);
        }

        atomic_dec_32(&ss->ss_port_event_counter);
        kmem_free(ae, sizeof (fcoei_event_t));
}