root/usr/src/uts/common/io/bnxe/bnxe_fcoe.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 2014 QLogic Corporation
 * The contents of this file are subject to the terms of the
 * QLogic End User License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the License at
 * http://www.qlogic.com/Resources/Documents/DriverDownloadHelp/
 * QLogic_End_User_Software_License.txt
 * See the License for the specific language governing permissions
 * and limitations under the License.
 */

#include "bnxe.h"


#define VERIFY_FCOE_BINDING(pUM)                                  \
    if (!BNXE_FCOE(pUM))                                          \
    {                                                             \
        BnxeLogWarn((pUM), "FCoE not supported on this device!"); \
        return B_FALSE;                                           \
    }                                                             \
    if (!(CLIENT_BOUND(pUM, LM_CLI_IDX_FCOE)))                    \
    {                                                             \
        BnxeLogWarn((pUM), "FCoE client not bound!");             \
        return B_FALSE;                                           \
    }


void BnxeFcoeFreeResc(um_device_t *   pUM,
                      BnxeFcoeState * pFcoeState)
{
    BNXE_LOCK_ENTER_OFFLOAD(pUM);
    lm_fc_del_fcoe_state(&pUM->lm_dev, &pFcoeState->lm_fcoe);
    BNXE_LOCK_EXIT_OFFLOAD(pUM);

    lm_fc_free_con_resc(&pUM->lm_dev, &pFcoeState->lm_fcoe);

    kmem_free(pFcoeState, sizeof(BnxeFcoeState));
}


static boolean_t BnxeFcoeCqeIndicate(um_device_t * pUM,
                                     void *        pData,
                                     u32_t         dataLen)
{
    struct fcoe_kcqe * kcqe = (struct fcoe_kcqe *)pData;

    if (dataLen != (sizeof(*kcqe)))
    {
        BnxeLogWarn(pUM, "Invalid FCoE CQE");
        return B_FALSE;
    }

    /* XXX
     * Need to add a mutex or reference count to ensure that bnxef isn't
     * unloaded underneath this taskq dispatch routine.
     */

    ASSERT(CLIENT_BOUND(pUM, LM_CLI_IDX_FCOE));
    pUM->fcoe.bind.cliIndicateCqes(pUM->fcoe.pDev,
                                   (void **)&kcqe, 1);

    /* XXX release mutex or decrement reference count */

    return B_TRUE;
}


static void BnxeFcoeInitCqeWork(um_device_t * pUM,
                                void *        pData,
                                u32_t         dataLen)
{
    if (!BnxeFcoeCqeIndicate(pUM, pData, dataLen))
    {
        pUM->fcoe.stats.initCqeRxErr++;
    }
    else
    {
        pUM->fcoe.stats.initCqeRx++;
    }
}


boolean_t BnxeFcoeInitCqe(um_device_t *      pUM,
                          struct fcoe_kcqe * kcqe)
{
    struct fcoe_kcqe tmp_kcqe = {0};

    tmp_kcqe.op_code = FCOE_KCQE_OPCODE_INIT_FUNC;

    tmp_kcqe.flags |=
        (FCOE_KWQE_LAYER_CODE << FCOE_KWQE_HEADER_LAYER_CODE_SHIFT);

    tmp_kcqe.completion_status =
        mm_cpu_to_le32((mm_le32_to_cpu(kcqe->completion_status) == 0) ?
                           FCOE_KCQE_COMPLETION_STATUS_SUCCESS :
                           FCOE_KCQE_COMPLETION_STATUS_NIC_ERROR);

    return BnxeWorkQueueAdd(pUM, BnxeFcoeInitCqeWork,
                            &tmp_kcqe, sizeof(tmp_kcqe));
}


static void BnxeFcoeInitWqeWork(um_device_t * pUM,
                                void *        pData,
                                u32_t         dataLen)
{
    union fcoe_kwqe * kwqe = (union fcoe_kwqe *)pData;
    struct fcoe_kcqe  kcqe  = {0};

    if (dataLen != (3 * sizeof(*kwqe)))
    {
        BnxeLogWarn(pUM, "Invalid FCoE Init WQE");
        pUM->fcoe.stats.initWqeTxErr++;
        return;
    }

    if (kwqe[1].init2.hsi_major_version != FCOE_HSI_MAJOR_VERSION)
    {
        BnxeLogWarn(pUM, "ERROR: Invalid FCoE HSI major version (L5=%d vs FW=%d)",
                    kwqe[1].init2.hsi_major_version,
                    FCOE_HSI_MAJOR_VERSION);
        kcqe.completion_status = FCOE_KCQE_COMPLETION_STATUS_WRONG_HSI_VERSION;
        goto BnxeFcoeInitWqeWork_error;
    }

    if (lm_fc_init(&pUM->lm_dev,
                   &kwqe[0].init1,
                   &kwqe[1].init2,
                   &kwqe[2].init3) != LM_STATUS_SUCCESS)
    {
        BnxeLogWarn(pUM, "Failed to post FCoE Init WQE");
        kcqe.completion_status = FCOE_KCQE_COMPLETION_STATUS_ERROR;
        goto BnxeFcoeInitWqeWork_error;
    }

    pUM->fcoe.stats.initWqeTx++;

    return;

BnxeFcoeInitWqeWork_error:

    pUM->fcoe.stats.initWqeTxErr++;

    kcqe.op_code = FCOE_KCQE_OPCODE_INIT_FUNC;
    kcqe.flags |= (FCOE_KWQE_LAYER_CODE << FCOE_KWQE_HEADER_LAYER_CODE_SHIFT);
    kcqe.completion_status = mm_cpu_to_le32(kcqe.completion_status);
    kcqe.fcoe_conn_id = kwqe[1].conn_offload1.fcoe_conn_id;

    /* call here directly (for error case) */

    /* XXX
     * Need to add a mutex or reference count to ensure that bnxef isn't
     * unloaded underneath this taskq dispatch routine.
     */

    {
        struct fcoe_kcqe * pKcqe = &kcqe;
        ASSERT(CLIENT_BOUND(pUM, LM_CLI_IDX_FCOE));
        pUM->fcoe.bind.cliIndicateCqes(pUM->fcoe.pDev,
                                       (void **)&pKcqe, 1);
    }

    /* XXX release mutex or decrement reference count */
}


static boolean_t BnxeFcoeInitWqe(um_device_t *      pUM,
                                 union fcoe_kwqe ** kwqes)
{
    union fcoe_kwqe wqe[3];

    wqe[0] =*(kwqes[0]);
    wqe[1] =*(kwqes[1]);
    wqe[2] =*(kwqes[2]);

    return BnxeWorkQueueAdd(pUM, BnxeFcoeInitWqeWork, wqe, sizeof(wqe));
}


static void BnxeFcoeOffloadConnCqeWork(um_device_t * pUM,
                                       void *        pData,
                                       u32_t         dataLen)
{
    if (!BnxeFcoeCqeIndicate(pUM, pData, dataLen))
    {
        pUM->fcoe.stats.offloadConnCqeRxErr++;
    }
    else
    {
        pUM->fcoe.stats.offloadConnCqeRx++;
    }
}


boolean_t BnxeFcoeOffloadConnCqe(um_device_t *      pUM,
                                 BnxeFcoeState *    pFcoeState,
                                 struct fcoe_kcqe * kcqe)
{
    struct fcoe_kcqe tmp_kcqe = {0};

    tmp_kcqe.op_code = FCOE_KCQE_OPCODE_OFFLOAD_CONN;

    tmp_kcqe.flags |=
        (FCOE_KWQE_LAYER_CODE << FCOE_KWQE_HEADER_LAYER_CODE_SHIFT);

    tmp_kcqe.fcoe_conn_context_id = kcqe->fcoe_conn_context_id;
    tmp_kcqe.fcoe_conn_id         = kcqe->fcoe_conn_id;

    tmp_kcqe.completion_status =
        mm_cpu_to_le32((mm_le32_to_cpu(kcqe->completion_status) == 0) ?
                           FCOE_KCQE_COMPLETION_STATUS_SUCCESS :
                           FCOE_KCQE_COMPLETION_STATUS_CTX_ALLOC_FAILURE);

    if (pFcoeState != NULL)
    {
        pFcoeState->lm_fcoe.hdr.status =
            (mm_le32_to_cpu(kcqe->completion_status) == 0) ?
                STATE_STATUS_NORMAL :
                STATE_STATUS_INIT_OFFLOAD_ERR;
    }

    return BnxeWorkQueueAdd(pUM, BnxeFcoeOffloadConnCqeWork,
                            &tmp_kcqe, sizeof(tmp_kcqe));
}


static void BnxeFcoeOffloadConnWqeWork(um_device_t * pUM,
                                       void *        pData,
                                       u32_t         dataLen)
{
    union fcoe_kwqe * kwqe = (union fcoe_kwqe *)pData;
    struct fcoe_kcqe  kcqe = {0};
    BnxeFcoeState *   pFcoeState;
    lm_status_t       rc;

    if (dataLen != (4 * sizeof(*kwqe)))
    {
        BnxeLogWarn(pUM, "Invalid FCoE Offload Conn WQE");
        pUM->fcoe.stats.offloadConnWqeTxErr++;
        return;
    }

    if ((pFcoeState = kmem_zalloc(sizeof(BnxeFcoeState),
                                  KM_NOSLEEP)) == NULL)
    {
        BnxeLogWarn(pUM, "Failed to allocate memory for FCoE state");
        goto BnxeFcoeOffloadConnWqeWork_error;
    }

    BNXE_LOCK_ENTER_OFFLOAD(pUM);
    rc = lm_fc_init_fcoe_state(&pUM->lm_dev,
                               &pUM->lm_dev.fcoe_info.run_time.state_blk,
                               &pFcoeState->lm_fcoe);
    BNXE_LOCK_EXIT_OFFLOAD(pUM);

    if (rc != LM_STATUS_SUCCESS)
    {
        kmem_free(pFcoeState, sizeof(BnxeFcoeState));

        BnxeLogWarn(pUM, "Failed to initialize FCoE state");
        goto BnxeFcoeOffloadConnWqeWork_error;
    }

    pFcoeState->lm_fcoe.ofld1 = kwqe[0].conn_offload1;
    pFcoeState->lm_fcoe.ofld2 = kwqe[1].conn_offload2;
    pFcoeState->lm_fcoe.ofld3 = kwqe[2].conn_offload3;
    pFcoeState->lm_fcoe.ofld4 = kwqe[3].conn_offload4;

    rc = lm_fc_alloc_con_resc(&pUM->lm_dev, &pFcoeState->lm_fcoe);

    if (rc == LM_STATUS_SUCCESS)
    {
        lm_fc_init_fcoe_context(&pUM->lm_dev, &pFcoeState->lm_fcoe);
        lm_fc_post_offload_ramrod(&pUM->lm_dev, &pFcoeState->lm_fcoe);
    }
    else if (rc == LM_STATUS_PENDING)
    {
        /*
         * the cid is pending - its too soon to initialize the context, it will
         * be initialized from the recycle cid callback and completed as well.
         */
        BnxeLogInfo(pUM, "lm_fc_alloc_con_resc returned pending?");
    }
    else
    {
        BnxeFcoeFreeResc(pUM, pFcoeState);
        BnxeLogInfo(pUM, "lm_fc_alloc_con_resc failed (%d)", rc);
        goto BnxeFcoeOffloadConnWqeWork_error;
    }

    pUM->fcoe.stats.offloadConnWqeTx++;

    return;

BnxeFcoeOffloadConnWqeWork_error:

    pUM->fcoe.stats.offloadConnWqeTxErr++;

    kcqe.op_code = FCOE_KCQE_OPCODE_OFFLOAD_CONN;
    kcqe.flags |= (FCOE_KWQE_LAYER_CODE << FCOE_KWQE_HEADER_LAYER_CODE_SHIFT);
    kcqe.completion_status = mm_cpu_to_le32(FCOE_KCQE_COMPLETION_STATUS_CTX_ALLOC_FAILURE);
    kcqe.fcoe_conn_id = kwqe[0].conn_offload1.fcoe_conn_id;

    /* call here directly (for error case) */

    /* XXX
     * Need to add a mutex or reference count to ensure that bnxef isn't
     * unloaded underneath this taskq dispatch routine.
     */

    {
        struct fcoe_kcqe * pKcqe = &kcqe;
        ASSERT(CLIENT_BOUND(pUM, LM_CLI_IDX_FCOE));
        pUM->fcoe.bind.cliIndicateCqes(pUM->fcoe.pDev,
                                       (void **)&pKcqe, 1);
    }

    /* XXX release mutex or decrement reference count */
}


static boolean_t BnxeFcoeOffloadConnWqe(um_device_t *      pUM,
                                        union fcoe_kwqe ** kwqes)
{
    union fcoe_kwqe wqe[4];

    wqe[0] =*(kwqes[0]);
    wqe[1] =*(kwqes[1]);
    wqe[2] =*(kwqes[2]);
    wqe[3] =*(kwqes[3]);

    return BnxeWorkQueueAdd(pUM, BnxeFcoeOffloadConnWqeWork,
                            wqe, sizeof(wqe));
}


static void BnxeFcoeEnableConnCqeWork(um_device_t * pUM,
                                      void *        pData,
                                      u32_t         dataLen)
{
    if (!BnxeFcoeCqeIndicate(pUM, pData, dataLen))
    {
        pUM->fcoe.stats.enableConnCqeRxErr++;
    }
    else
    {
        pUM->fcoe.stats.enableConnCqeRx++;
    }
}


boolean_t BnxeFcoeEnableConnCqe(um_device_t *      pUM,
                                BnxeFcoeState *    pFcoeState,
                                struct fcoe_kcqe * kcqe)
{
    struct fcoe_kcqe tmp_kcqe = {0};

    tmp_kcqe.op_code = FCOE_KCQE_OPCODE_ENABLE_CONN;

    tmp_kcqe.flags |=
        (FCOE_KWQE_LAYER_CODE << FCOE_KWQE_HEADER_LAYER_CODE_SHIFT);

    tmp_kcqe.fcoe_conn_context_id = kcqe->fcoe_conn_context_id;
    tmp_kcqe.fcoe_conn_id         = kcqe->fcoe_conn_id;

    tmp_kcqe.completion_status =
        mm_cpu_to_le32((mm_le32_to_cpu(kcqe->completion_status) == 0) ?
                           FCOE_KCQE_COMPLETION_STATUS_SUCCESS :
                           FCOE_KCQE_COMPLETION_STATUS_CTX_ALLOC_FAILURE);

    if (pFcoeState != NULL)
    {
        pFcoeState->lm_fcoe.hdr.status =
            (mm_le32_to_cpu(kcqe->completion_status) == 0) ?
                STATE_STATUS_NORMAL :
                STATE_STATUS_INIT_OFFLOAD_ERR;
    }

    return BnxeWorkQueueAdd(pUM, BnxeFcoeEnableConnCqeWork,
                            &tmp_kcqe, sizeof(tmp_kcqe));
}


static void BnxeFcoeEnableConnWqeWork(um_device_t * pUM,
                                      void *        pData,
                                      u32_t         dataLen)
{
    union fcoe_kwqe * kwqe = (union fcoe_kwqe *)pData;
    struct fcoe_kcqe  kcqe = {0};
    BnxeFcoeState *   pFcoeState;

    if (dataLen != sizeof(*kwqe))
    {
        BnxeLogWarn(pUM, "Invalid FCoE Enable Conn WQE");
        pUM->fcoe.stats.enableConnWqeTxErr++;
        return;
    }

    pFcoeState =
        lm_cid_cookie(&pUM->lm_dev,
                      FCOE_CONNECTION_TYPE,
                      SW_CID(mm_le32_to_cpu(kwqe->conn_enable_disable.context_id)));

    if (pFcoeState == NULL)
    {
        goto BnxeFcoeEnableConnWqeWork_error;
    }

    if (lm_fc_post_enable_ramrod(&pUM->lm_dev,
                                 &pFcoeState->lm_fcoe,
                                 &kwqe->conn_enable_disable) !=
        LM_STATUS_SUCCESS)
    {
        goto BnxeFcoeEnableConnWqeWork_error;
    }

    pUM->fcoe.stats.enableConnWqeTx++;

    return;

BnxeFcoeEnableConnWqeWork_error:

    pUM->fcoe.stats.enableConnWqeTxErr++;

    BnxeLogWarn(pUM, "Failed to post FCoE Enable Conn WQE");

    kcqe.op_code = FCOE_KCQE_OPCODE_ENABLE_CONN;
    kcqe.flags |= (FCOE_KWQE_LAYER_CODE << FCOE_KWQE_HEADER_LAYER_CODE_SHIFT);
    kcqe.fcoe_conn_context_id = kwqe->conn_enable_disable.context_id;
    kcqe.completion_status = mm_cpu_to_le32(FCOE_KCQE_COMPLETION_STATUS_NIC_ERROR);

    /* call here directly (for error case) */

    /* XXX
     * Need to add a mutex or reference count to ensure that bnxef isn't
     * unloaded underneath this taskq dispatch routine.
     */

    {
        struct fcoe_kcqe * pKcqe = &kcqe;
        ASSERT(CLIENT_BOUND(pUM, LM_CLI_IDX_FCOE));
        pUM->fcoe.bind.cliIndicateCqes(pUM->fcoe.pDev,
                                       (void **)&pKcqe, 1);
    }

    /* XXX release mutex or decrement reference count */
}


static boolean_t BnxeFcoeEnableConnWqe(um_device_t *      pUM,
                                       union fcoe_kwqe ** kwqes)
{
    return BnxeWorkQueueAdd(pUM, BnxeFcoeEnableConnWqeWork,
                            kwqes[0], sizeof(*(kwqes[0])));
}


static void BnxeFcoeDisableConnCqeWork(um_device_t * pUM,
                                       void *        pData,
                                       u32_t         dataLen)
{
    if (!BnxeFcoeCqeIndicate(pUM, pData, dataLen))
    {
        pUM->fcoe.stats.disableConnCqeRxErr++;
    }
    else
    {
        pUM->fcoe.stats.disableConnCqeRx++;
    }
}


boolean_t BnxeFcoeDisableConnCqe(um_device_t *      pUM,
                                 BnxeFcoeState *    pFcoeState,
                                 struct fcoe_kcqe * kcqe)
{
    struct fcoe_kcqe tmp_kcqe = {0};

    tmp_kcqe.op_code = FCOE_KCQE_OPCODE_DISABLE_CONN;

    tmp_kcqe.flags |=
        (FCOE_KWQE_LAYER_CODE << FCOE_KWQE_HEADER_LAYER_CODE_SHIFT);

    tmp_kcqe.fcoe_conn_context_id = kcqe->fcoe_conn_context_id;
    tmp_kcqe.fcoe_conn_id         = kcqe->fcoe_conn_id;

    tmp_kcqe.completion_status =
        mm_cpu_to_le32((mm_le32_to_cpu(kcqe->completion_status) == 0) ?
                           FCOE_KCQE_COMPLETION_STATUS_SUCCESS :
                           FCOE_KCQE_COMPLETION_STATUS_NIC_ERROR);

    if (pFcoeState != NULL)
    {
        pFcoeState->lm_fcoe.hdr.status =
            (mm_le32_to_cpu(kcqe->completion_status) == 0) ?
                STATE_STATUS_NORMAL :
                STATE_STATUS_INIT_OFFLOAD_ERR;
    }

    return BnxeWorkQueueAdd(pUM, BnxeFcoeDisableConnCqeWork,
                            &tmp_kcqe, sizeof(tmp_kcqe));
}


static void BnxeFcoeDisableConnWqeWork(um_device_t * pUM,
                                       void *        pData,
                                       u32_t         dataLen)
{
    union fcoe_kwqe * kwqe = (union fcoe_kwqe *)pData;
    struct fcoe_kcqe  kcqe = {0};
    BnxeFcoeState *   pFcoeState;

    if (dataLen != sizeof(*kwqe))
    {
        BnxeLogWarn(pUM, "Invalid FCoE Disable Conn WQE");
        pUM->fcoe.stats.disableConnWqeTxErr++;
        return;
    }

    pFcoeState =
        lm_cid_cookie(&pUM->lm_dev,
                      FCOE_CONNECTION_TYPE,
                      SW_CID(mm_le32_to_cpu(kwqe->conn_enable_disable.context_id)));

    if (pFcoeState == NULL)
    {
        goto BnxeFcoeDisableConnWqeWork_error;
    }

    if (lm_fc_post_disable_ramrod(&pUM->lm_dev,
                                  &pFcoeState->lm_fcoe,
                                  &kwqe->conn_enable_disable) !=
        LM_STATUS_SUCCESS)
    {
        goto BnxeFcoeDisableConnWqeWork_error;
    }

    pUM->fcoe.stats.disableConnWqeTx++;

    return;

BnxeFcoeDisableConnWqeWork_error:

    pUM->fcoe.stats.disableConnWqeTxErr++;

    BnxeLogWarn(pUM, "Failed to post FCoE Disable Conn WQE");

    kcqe.op_code = FCOE_KCQE_OPCODE_DISABLE_CONN;
    kcqe.flags |= (FCOE_KWQE_LAYER_CODE << FCOE_KWQE_HEADER_LAYER_CODE_SHIFT);
    kcqe.fcoe_conn_context_id = kwqe->conn_enable_disable.context_id;
    kcqe.completion_status = mm_cpu_to_le32(FCOE_KCQE_COMPLETION_STATUS_NIC_ERROR);

    /* call here directly (for error case) */

    /* XXX
     * Need to add a mutex or reference count to ensure that bnxef isn't
     * unloaded underneath this taskq dispatch routine.
     */

    {
        struct fcoe_kcqe * pKcqe = &kcqe;
        ASSERT(CLIENT_BOUND(pUM, LM_CLI_IDX_FCOE));
        pUM->fcoe.bind.cliIndicateCqes(pUM->fcoe.pDev,
                                       (void **)&pKcqe, 1);
    }

    /* XXX release mutex or decrement reference count */
}


static boolean_t BnxeFcoeDisableConnWqe(um_device_t *      pUM,
                                       union fcoe_kwqe ** kwqes)
{
    return BnxeWorkQueueAdd(pUM, BnxeFcoeDisableConnWqeWork,
                            kwqes[0], sizeof(*(kwqes[0])));
}


static void BnxeFcoeDestroyConnCqeWork(um_device_t * pUM,
                                       void *        pData,
                                       u32_t         dataLen)
{
    struct fcoe_kcqe * kcqe = (struct fcoe_kcqe *)pData;
    BnxeFcoeState *    pFcoeState;

    if (dataLen != (sizeof(*kcqe)))
    {
        BnxeLogWarn(pUM, "Invalid FCoE Destroy Conn CQE");
        pUM->fcoe.stats.destroyConnCqeRxErr++;
        return;
    }

    pFcoeState =
        lm_cid_cookie(&pUM->lm_dev,
                      FCOE_CONNECTION_TYPE,
                      SW_CID(mm_le32_to_cpu(kcqe->fcoe_conn_context_id)));

    BnxeFcoeFreeResc(pUM, pFcoeState);

    if (!BnxeFcoeCqeIndicate(pUM, pData, dataLen))
    {
        pUM->fcoe.stats.destroyConnCqeRxErr++;
    }
    else
    {
        pUM->fcoe.stats.destroyConnCqeRx++;
    }
}


boolean_t BnxeFcoeDestroyConnCqe(um_device_t *      pUM,
                                 BnxeFcoeState *    pFcoeState,
                                 struct fcoe_kcqe * kcqe)
{
    struct fcoe_kcqe tmp_kcqe = {0};

    tmp_kcqe.op_code = FCOE_KCQE_OPCODE_DESTROY_CONN;

    tmp_kcqe.flags |=
        (FCOE_KWQE_LAYER_CODE << FCOE_KWQE_HEADER_LAYER_CODE_SHIFT);

    tmp_kcqe.fcoe_conn_context_id = kcqe->fcoe_conn_context_id;
    tmp_kcqe.fcoe_conn_id         = kcqe->fcoe_conn_id;

    tmp_kcqe.completion_status =
        mm_cpu_to_le32((mm_le32_to_cpu(kcqe->completion_status) == 0) ?
                           FCOE_KCQE_COMPLETION_STATUS_SUCCESS :
                           FCOE_KCQE_COMPLETION_STATUS_NIC_ERROR);

    if (pFcoeState != NULL)
    {
        pFcoeState->lm_fcoe.hdr.status =
            (mm_le32_to_cpu(kcqe->completion_status) == 0) ?
                STATE_STATUS_NORMAL :
                STATE_STATUS_INIT_OFFLOAD_ERR;
    }

    return BnxeWorkQueueAdd(pUM, BnxeFcoeDestroyConnCqeWork,
                            &tmp_kcqe, sizeof(tmp_kcqe));
}


static void BnxeFcoeDestroyConnWqeWork(um_device_t * pUM,
                                       void *        pData,
                                       u32_t         dataLen)
{
    union fcoe_kwqe * kwqe = (union fcoe_kwqe *)pData;
    struct fcoe_kcqe  kcqe = {0};
    BnxeFcoeState *   pFcoeState;

    if (dataLen != sizeof(*kwqe))
    {
        BnxeLogWarn(pUM, "Invalid FCoE Destroy Conn WQE");
        pUM->fcoe.stats.destroyConnWqeTxErr++;
        return;
    }

    pFcoeState =
        lm_cid_cookie(&pUM->lm_dev,
                      FCOE_CONNECTION_TYPE,
                      SW_CID(mm_le32_to_cpu(kwqe->conn_destroy.context_id)));

    if (pFcoeState == NULL)
    {
        goto BnxeFcoeDestroyConnWqeWork_error;
    }

    if (lm_fc_post_terminate_ramrod(&pUM->lm_dev,
                                    &pFcoeState->lm_fcoe) !=
        LM_STATUS_SUCCESS)
    {
        goto BnxeFcoeDestroyConnWqeWork_error;
    }

    pUM->fcoe.stats.destroyConnWqeTx++;

    return;

BnxeFcoeDestroyConnWqeWork_error:

    pUM->fcoe.stats.destroyConnWqeTxErr++;

    BnxeLogWarn(pUM, "Failed to post FCoE Destroy Conn WQE");

    kcqe.op_code = FCOE_KCQE_OPCODE_DESTROY_CONN;
    kcqe.flags |= (FCOE_KWQE_LAYER_CODE << FCOE_KWQE_HEADER_LAYER_CODE_SHIFT);
    kcqe.fcoe_conn_context_id = kwqe->conn_destroy.context_id;
    kcqe.fcoe_conn_id         = kwqe->conn_destroy.conn_id;
    kcqe.completion_status = mm_cpu_to_le32(FCOE_KCQE_COMPLETION_STATUS_NIC_ERROR);

    /* call here directly (for error case) */

    /* XXX
     * Need to add a mutex or reference count to ensure that bnxef isn't
     * unloaded underneath this taskq dispatch routine.
     */

    {
        struct fcoe_kcqe * pKcqe = &kcqe;
        ASSERT(CLIENT_BOUND(pUM, LM_CLI_IDX_FCOE));
        pUM->fcoe.bind.cliIndicateCqes(pUM->fcoe.pDev,
                                       (void **)&pKcqe, 1);
    }

    /* XXX release mutex or decrement reference count */
}


static boolean_t BnxeFcoeDestroyConnWqe(um_device_t *      pUM,
                                       union fcoe_kwqe ** kwqes)
{
    return BnxeWorkQueueAdd(pUM, BnxeFcoeDestroyConnWqeWork,
                            kwqes[0], sizeof(*(kwqes[0])));
}


static void BnxeFcoeDestroyCqeWork(um_device_t * pUM,
                                   void *        pData,
                                   u32_t         dataLen)
{
    if (!BnxeFcoeCqeIndicate(pUM, pData, dataLen))
    {
        pUM->fcoe.stats.destroyCqeRxErr++;
    }
    else
    {
        pUM->fcoe.stats.destroyCqeRx++;
    }
}


boolean_t BnxeFcoeDestroyCqe(um_device_t *      pUM,
                             struct fcoe_kcqe * kcqe)
{
    struct fcoe_kcqe tmp_kcqe = {0};

    tmp_kcqe.op_code = FCOE_KCQE_OPCODE_DESTROY_FUNC;

    tmp_kcqe.flags |=
        (FCOE_KWQE_LAYER_CODE << FCOE_KWQE_HEADER_LAYER_CODE_SHIFT);

    tmp_kcqe.completion_status =
        mm_le32_to_cpu((mm_le32_to_cpu(kcqe->completion_status) == 0) ?
                           FCOE_KCQE_COMPLETION_STATUS_SUCCESS :
                           FCOE_KCQE_COMPLETION_STATUS_NIC_ERROR);

    return BnxeWorkQueueAdd(pUM, BnxeFcoeDestroyCqeWork,
                            &tmp_kcqe, sizeof(tmp_kcqe));
}


static void BnxeFcoeDestroyWqeWork(um_device_t * pUM,
                                   void *        pData,
                                   u32_t         dataLen)
{
    union fcoe_kwqe * kwqe = (union fcoe_kwqe *)pData;
    struct fcoe_kcqe  kcqe = {0};
    BnxeFcoeState *   pFcoeState;

    if (dataLen != sizeof(*kwqe))
    {
        BnxeLogWarn(pUM, "Invalid FCoE Destroy WQE");
        pUM->fcoe.stats.destroyWqeTxErr++;
        return;
    }

    if (lm_fc_post_destroy_ramrod(&pUM->lm_dev) == LM_STATUS_SUCCESS)
    {
        pUM->fcoe.stats.destroyWqeTx++;
        return;
    }

    pUM->fcoe.stats.destroyWqeTxErr++;

    BnxeLogWarn(pUM, "Failed to post FCoE Destroy WQE");

    kcqe.op_code = FCOE_KCQE_OPCODE_DESTROY_FUNC;
    kcqe.flags |= (FCOE_KWQE_LAYER_CODE << FCOE_KWQE_HEADER_LAYER_CODE_SHIFT);
    kcqe.completion_status = mm_cpu_to_le32(FCOE_KCQE_COMPLETION_STATUS_NIC_ERROR);

    /* call here directly (for error case) */

    /* XXX
     * Need to add a mutex or reference count to ensure that bnxef isn't
     * unloaded underneath this taskq dispatch routine.
     */

    {
        struct fcoe_kcqe * pKcqe = &kcqe;
        ASSERT(CLIENT_BOUND(pUM, LM_CLI_IDX_FCOE));
        pUM->fcoe.bind.cliIndicateCqes(pUM->fcoe.pDev,
                                       (void **)&pKcqe, 1);
    }

    /* XXX release mutex or decrement reference count */
}


static boolean_t BnxeFcoeDestroyWqe(um_device_t *      pUM,
                                    union fcoe_kwqe ** kwqes)
{
    return BnxeWorkQueueAdd(pUM, BnxeFcoeDestroyWqeWork,
                            kwqes[0], sizeof(*(kwqes[0])));
}


static void BnxeFcoeStatCqeWork(um_device_t * pUM,
                                void *        pData,
                                u32_t         dataLen)
{
    if (!BnxeFcoeCqeIndicate(pUM, pData, dataLen))
    {
        pUM->fcoe.stats.statCqeRxErr++;
    }
    else
    {
        pUM->fcoe.stats.statCqeRx++;
    }
}


boolean_t BnxeFcoeStatCqe(um_device_t *      pUM,
                          struct fcoe_kcqe * kcqe)
{
    struct fcoe_kcqe tmp_kcqe = {0};

    tmp_kcqe.op_code = FCOE_KCQE_OPCODE_STAT_FUNC;

    tmp_kcqe.flags |=
        (FCOE_KWQE_LAYER_CODE << FCOE_KWQE_HEADER_LAYER_CODE_SHIFT);

    tmp_kcqe.completion_status =
        mm_cpu_to_le32((mm_le32_to_cpu(kcqe->completion_status) == 0) ?
                           FCOE_KCQE_COMPLETION_STATUS_SUCCESS :
                           FCOE_KCQE_COMPLETION_STATUS_NIC_ERROR);

    return BnxeWorkQueueAdd(pUM, BnxeFcoeStatCqeWork,
                            &tmp_kcqe, sizeof(tmp_kcqe));
}


static void BnxeFcoeStatWqeWork(um_device_t * pUM,
                                void *        pData,
                                u32_t         dataLen)
{
    union fcoe_kwqe * kwqe = (union fcoe_kwqe *)pData;
    struct fcoe_kcqe  kcqe = {0};

    if (dataLen != sizeof(*kwqe))
    {
        BnxeLogWarn(pUM, "Invalid FCoE Stat WQE");
        pUM->fcoe.stats.statWqeTxErr++;
        return;
    }

    if (lm_fc_post_stat_ramrod(&pUM->lm_dev,
                               &kwqe->statistics) == LM_STATUS_SUCCESS)
    {
        pUM->fcoe.stats.statWqeTx++;
        return;
    }

    pUM->fcoe.stats.statWqeTxErr++;

    BnxeLogWarn(pUM, "Failed to post FCoE Stat WQE");

    kcqe.op_code = FCOE_KCQE_OPCODE_STAT_FUNC;
    kcqe.flags |= (FCOE_KWQE_LAYER_CODE << FCOE_KWQE_HEADER_LAYER_CODE_SHIFT);
    kcqe.completion_status = mm_cpu_to_le32(FCOE_KCQE_COMPLETION_STATUS_NIC_ERROR);

    /* call here directly (for error case) */

    /* XXX
     * Need to add a mutex or reference count to ensure that bnxef isn't
     * unloaded underneath this taskq dispatch routine.
     */

    {
        struct fcoe_kcqe * pKcqe = &kcqe;
        ASSERT(CLIENT_BOUND(pUM, LM_CLI_IDX_FCOE));
        pUM->fcoe.bind.cliIndicateCqes(pUM->fcoe.pDev,
                                       (void **)&pKcqe, 1);
    }

    /* XXX release mutex or decrement reference count */
}


static boolean_t BnxeFcoeStatWqe(um_device_t *      pUM,
                                 union fcoe_kwqe ** kwqes)
{
    return BnxeWorkQueueAdd(pUM, BnxeFcoeStatWqeWork,
                            kwqes[0], sizeof(*(kwqes[0])));
}


#define KCQE_LIMIT 64

static void BnxeFcoeCompRequestCqeWork(um_device_t * pUM,
                                       void *        pData,
                                       u32_t         dataLen)
{
    struct fcoe_kcqe * kcqe_arr = (struct fcoe_kcqe *)pData;
    struct fcoe_kcqe * kcqes[KCQE_LIMIT];
    u32_t              num_kcqes;
    int i;

    if ((dataLen % (sizeof(*kcqe_arr))) != 0)
    {
        BnxeLogWarn(pUM, "Invalid FCoE Comp Request CQE array");
        pUM->fcoe.stats.compRequestCqeRxErr++;
        return;
    }

    num_kcqes = (dataLen / (sizeof(*kcqe_arr)));

    /* init the kcqe pointer array */

    for (i = 0; i < num_kcqes; i++)
    {
        kcqes[i] = &kcqe_arr[i];
    }

    ASSERT(CLIENT_BOUND(pUM, LM_CLI_IDX_FCOE));

    if (!pUM->fcoe.bind.cliIndicateCqes(pUM->fcoe.pDev,
                                        (void **)kcqes,
                                        num_kcqes))
    {
        pUM->fcoe.stats.compRequestCqeRxErr++;
    }
    else
    {
        pUM->fcoe.stats.compRequestCqeRx += num_kcqes;
    }
}


boolean_t BnxeFcoeCompRequestCqe(um_device_t *      pUM,
                                 struct fcoe_kcqe * kcqes,
                                 u32_t              num_kcqes)
{
    u32_t kcqesIdx = 0;
    u32_t kcqesLimit = 0;
    u32_t numUp;

    /* Send up KCQE_LIMIT kcqes at a time... */

    while (kcqesIdx < num_kcqes)
    {
        if (num_kcqes - kcqesIdx > KCQE_LIMIT)
        {
            kcqesLimit += KCQE_LIMIT;
        }
        else
        {
            kcqesLimit = num_kcqes;
        }

        numUp = (kcqesLimit % KCQE_LIMIT == 0) ? KCQE_LIMIT :
                                                 (kcqesLimit % KCQE_LIMIT);

#if 0
        if (!BnxeWorkQueueAdd(pUM, BnxeFcoeCompRequestCqeWork,
                              kcqes + kcqesIdx,
                              (sizeof(struct fcoe_kcqe) * numUp)))
        {
            return B_FALSE;
        }
#else
        BnxeFcoeCompRequestCqeWork(pUM,
                                   kcqes + kcqesIdx,
                                   (sizeof(struct fcoe_kcqe) * numUp));
#endif

        kcqesIdx += (kcqesLimit - kcqesIdx);
    }

    return B_TRUE;
}


boolean_t BnxeFcoePrvCtl(dev_info_t * pDev,
                         int          cmd,
                         void *       pData,
                         int          dataLen)
{
    um_device_t *  pUM = (um_device_t *)ddi_get_driver_private(pDev);
    BnxeFcoeInfo * pFcoeInfo;
    int rc, i;

    /* sanity check */
    if (pUM == NULL || pUM->pDev != pDev)
    {
        BnxeLogWarn(NULL, "%s: dev_info_t match failed", __func__);
        return B_FALSE;
    }

    BnxeLogDbg(pUM, "*** %s ***", __func__);

    VERIFY_FCOE_BINDING(pUM);

    switch (cmd)
    {
    case PRV_CTL_GET_MAC_ADDR:

        if (dataLen < ETHERNET_ADDRESS_SIZE)
        {
            BnxeLogWarn(pUM, "Invalid MAC Address buffer length for get (%d)",
                        dataLen);
            return B_FALSE;
        }

        if (!pData)
        {
            BnxeLogWarn(pUM, "NULL MAC Address buffer for get");
            return B_FALSE;
        }

        COPY_ETH_ADDRESS(pUM->lm_dev.hw_info.fcoe_mac_addr, pData);

        return B_TRUE;

    case PRV_CTL_SET_MAC_ADDR:

        if (dataLen < ETHERNET_ADDRESS_SIZE)
        {
            BnxeLogWarn(pUM, "Invalid MAC Address length for set (%d)",
                        dataLen);
            return B_FALSE;
        }

        if (!pData)
        {
            BnxeLogWarn(pUM, "NULL MAC Address buffer for set");
            return B_FALSE;
        }

        /* Validate MAC address */
        if (IS_ETH_MULTICAST(pData))
        {
            BnxeLogWarn(pUM, "Cannot program a mcast/bcast address as an MAC Address.");
            return B_FALSE;
        }

        BNXE_LOCK_ENTER_HWINIT(pUM);

        /* XXX wrong? (overwriting fcoe hw programmed address!) */
        COPY_ETH_ADDRESS(pData, pUM->lm_dev.hw_info.fcoe_mac_addr);

        rc = BnxeMacAddress(pUM, LM_CLI_IDX_FCOE, B_TRUE,
                            pUM->lm_dev.hw_info.fcoe_mac_addr);

        BNXE_LOCK_EXIT_HWINIT(pUM);

        return (rc < 0) ? B_FALSE : B_TRUE;

    case PRV_CTL_QUERY_PARAMS:

        if (dataLen != sizeof(BnxeFcoeInfo))
        {
            BnxeLogWarn(pUM, "Invalid query buffer for FCoE (%d)",
                        dataLen);
            return B_FALSE;
        }

        if (!pData)
        {
            BnxeLogWarn(pUM, "Invalid query buffer for FCoE");
            return B_FALSE;
        }

        pFcoeInfo = (BnxeFcoeInfo *)pData;

        pFcoeInfo->flags = 0;

        /*
         * Always set the FORCE_LOAD flags which tells bnxef to perform any
         * necessary delays needed when bringing up targets. This allows ample
         * time for bnxef to come up and finish for FCoE boot. If we don't
         * force a delay then there is a race condition and the kernel won't
         * find the root disk.
         */
        pFcoeInfo->flags |= FCOE_INFO_FLAG_FORCE_LOAD;

        switch (pUM->lm_dev.params.mf_mode)
        {
        case SINGLE_FUNCTION:
            pFcoeInfo->flags |= FCOE_INFO_FLAG_MF_MODE_SF;
            break;
        case MULTI_FUNCTION_SD:
            pFcoeInfo->flags |= FCOE_INFO_FLAG_MF_MODE_SD;
            break;
        case MULTI_FUNCTION_SI:
            pFcoeInfo->flags |= FCOE_INFO_FLAG_MF_MODE_SI;
            break;
        case MULTI_FUNCTION_AFEX:
            pFcoeInfo->flags |= FCOE_INFO_FLAG_MF_MODE_AFEX;
            break;
        default:
            break;
        }

        pFcoeInfo->max_fcoe_conn      = pUM->lm_dev.params.max_func_fcoe_cons;
        pFcoeInfo->max_fcoe_exchanges = pUM->lm_dev.params.max_fcoe_task;

        memcpy(&pFcoeInfo->wwn, &pUM->fcoe.wwn, sizeof(BnxeWwnInfo));

        return B_TRUE;

    case PRV_CTL_DISABLE_INTR:

        BnxeIntrIguSbDisable(pUM, FCOE_CID(&pUM->lm_dev), B_FALSE);
        return B_TRUE;

    case PRV_CTL_ENABLE_INTR:

        BnxeIntrIguSbEnable(pUM, FCOE_CID(&pUM->lm_dev), B_FALSE);
        return B_TRUE;

    case PRV_CTL_MBA_BOOT:

        if (dataLen != sizeof(boolean_t))
        {
            BnxeLogWarn(pUM, "Invalid MBA boot check buffer for FCoE (%d)",
                        dataLen);
            return B_FALSE;
        }

        if (!pData)
        {
            BnxeLogWarn(pUM, "Invalid MBA boot check buffer for FCoE");
            return B_FALSE;
        }

        *((boolean_t *)pData) =
            (pUM->iscsiInfo.signature != 0) ? B_TRUE : B_FALSE;

        return B_TRUE;

    case PRV_CTL_LINK_STATE:

        if (dataLen != sizeof(boolean_t))
        {
            BnxeLogWarn(pUM, "Invalid link state buffer for FCoE (%d)",
                        dataLen);
            return B_FALSE;
        }

        if (!pData)
        {
            BnxeLogWarn(pUM, "Invalid link state buffer for FCoE");
            return B_FALSE;
        }

        *((boolean_t *)pData) =
            (pUM->devParams.lastIndLink == LM_STATUS_LINK_ACTIVE) ?
                B_TRUE : B_FALSE;

        return B_TRUE;

    case PRV_CTL_BOARD_TYPE:

        if (!pData || (dataLen <= 0))
        {
            BnxeLogWarn(pUM, "Invalid board type buffer for FCoE");
            return B_FALSE;
        }

        snprintf((char *)pData, dataLen, "%s", pUM->chipName);

        return B_TRUE;

    case PRV_CTL_BOARD_SERNUM:

        if (!pData || (dataLen <= 0))
        {
            BnxeLogWarn(pUM, "Invalid board serial number buffer for FCoE");
            return B_FALSE;
        }

        snprintf((char *)pData, dataLen,
                 "%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c",
                 pUM->lm_dev.hw_info.board_num[0],
                 pUM->lm_dev.hw_info.board_num[1],
                 pUM->lm_dev.hw_info.board_num[2],
                 pUM->lm_dev.hw_info.board_num[3],
                 pUM->lm_dev.hw_info.board_num[4],
                 pUM->lm_dev.hw_info.board_num[5],
                 pUM->lm_dev.hw_info.board_num[6],
                 pUM->lm_dev.hw_info.board_num[7],
                 pUM->lm_dev.hw_info.board_num[8],
                 pUM->lm_dev.hw_info.board_num[9],
                 pUM->lm_dev.hw_info.board_num[10],
                 pUM->lm_dev.hw_info.board_num[11],
                 pUM->lm_dev.hw_info.board_num[12],
                 pUM->lm_dev.hw_info.board_num[13],
                 pUM->lm_dev.hw_info.board_num[14],
                 pUM->lm_dev.hw_info.board_num[15]);

        return B_TRUE;

    case PRV_CTL_BOOTCODE_VERSION:

        if (!pData || (dataLen <= 0))
        {
            BnxeLogWarn(pUM, "Invalid boot code version buffer for FCoE");
            return B_FALSE;
        }

        snprintf((char *)pData, dataLen, "%s", pUM->versionBC);

        return B_TRUE;

    case PRV_CTL_REPORT_FCOE_STATS:

        if (!pData ||
            (dataLen !=
             sizeof(pUM->lm_dev.vars.stats.stats_mirror.
                                     stats_drv.drv_info_to_mfw.fcoe_stats)))
        {
            BnxeLogWarn(pUM, "Invalid stats reporting buffer for FCoE");
            return B_FALSE;
        }

        memcpy(&pUM->lm_dev.vars.stats.stats_mirror.
                                stats_drv.drv_info_to_mfw.fcoe_stats,
               (fcoe_stats_info_t *)pData,
               sizeof(fcoe_stats_info_t));

        return B_TRUE;

    case PRV_CTL_SET_CAPS:

        if (!pData || (dataLen != sizeof(struct fcoe_capabilities)))
        {
            BnxeLogWarn(pUM, "Invalid capabilities buffer for FCoE");
            return B_FALSE;
        }

        memcpy(&pUM->lm_dev.vars.stats.stats_mirror.stats_drv.
                                 drv_info_to_shmem.fcoe_capabilities,
               pData,
               sizeof(pUM->lm_dev.vars.stats.stats_mirror.stats_drv.
                                       drv_info_to_shmem.fcoe_capabilities));

        lm_ncsi_fcoe_cap_to_scratchpad(&pUM->lm_dev);

        return B_TRUE;

    default:

        BnxeLogWarn(pUM, "Unknown provider command %d", cmd);
        return B_FALSE;
    }
}


mblk_t * BnxeFcoePrvTx(dev_info_t * pDev,
                       mblk_t *     pMblk,
                       u32_t        flags,
                       u16_t        vlan_tag)
{
    um_device_t * pUM = (um_device_t *)ddi_get_driver_private(pDev);
    lm_device_t * pLM = &pUM->lm_dev;
    mblk_t *      pNextMblk = NULL;
    int           txCount = 0;
    int rc;

    /* sanity check */
    if (pUM == NULL || pUM->pDev != pDev)
    {
        BnxeLogWarn(NULL, "%s: dev_info_t match failed", __func__);
        return pMblk;
    }

    VERIFY_FCOE_BINDING(pUM);

    BnxeLogDbg(pUM, "*** %s ***", __func__);

    while (pMblk)
    {
        txCount++;

        pNextMblk = pMblk->b_next;
        pMblk->b_next = NULL;

        rc = BnxeTxSendMblk(pUM, FCOE_CID(pLM), pMblk, flags, vlan_tag);

        if (rc == BNXE_TX_GOODXMIT)
        {
            pMblk = pNextMblk;
            continue;
        }
        else if (rc == BNXE_TX_DEFERPKT)
        {
            pMblk = pNextMblk;
        }
        else
        {
            pMblk->b_next = pNextMblk;
        }

        break;
    }

    return pMblk;
}


boolean_t BnxeFcoePrvPoll(dev_info_t * pDev)
{
    um_device_t * pUM  = (um_device_t *)ddi_get_driver_private(pDev);
    RxQueue *     pRxQ = &pUM->rxq[FCOE_CID(&pUM->lm_dev)];
    u32_t         idx  = pRxQ->idx;

    /* sanity check */
    if (pUM == NULL || pUM->pDev != pDev)
    {
        BnxeLogWarn(NULL, "%s: dev_info_t match failed", __func__);
        return B_FALSE;
    }

    VERIFY_FCOE_BINDING(pUM);

    BnxeLogDbg(pUM, "*** %s ***", __func__);

    if (pRxQ->inPollMode == B_FALSE)
    {
        BnxeLogWarn(pUM, "Polling on FCoE ring %d when NOT in poll mode!", idx);
        return B_FALSE;
    }

    pRxQ->pollCnt++;

    BnxePollRxRingFCOE(pUM);

    return B_TRUE;
}


boolean_t BnxeFcoePrvSendWqes(dev_info_t * pDev,
                              void *       wqes[],
                              int          wqeCnt)
{
    union fcoe_kwqe ** kwqes = (union fcoe_kwqe **)wqes;
    int                kwqeCnt = 0;

    um_device_t * pUM = (um_device_t *)ddi_get_driver_private(pDev);

    /* sanity check */
    if (pUM == NULL || pUM->pDev != pDev)
    {
        BnxeLogWarn(NULL, "%s: dev_info_t match failed", __func__);
        return B_FALSE;
    }

    VERIFY_FCOE_BINDING(pUM);

    if ((kwqes == NULL) || (kwqes[0] == NULL))
    {
        BnxeLogWarn(pUM, "Invalid WQE array");
        return B_FALSE;
    }

    BnxeLogDbg(pUM, "*** %s ***", __func__);

    while (kwqeCnt < wqeCnt)
    {
        switch (kwqes[kwqeCnt]->init1.hdr.op_code)
        {
        case FCOE_KWQE_OPCODE_INIT1:

            BnxeLogDbg(pUM, "*** %s - FCOE_KWQE_OPCODE_INIT", __func__);

            if ((wqeCnt <= kwqeCnt + 2) ||
                (kwqes[kwqeCnt + 1] == NULL) ||
                (kwqes[kwqeCnt + 2] == NULL) ||
                (kwqes[kwqeCnt + 1]->init2.hdr.op_code != FCOE_KWQE_OPCODE_INIT2) ||
                (kwqes[kwqeCnt + 2]->init3.hdr.op_code != FCOE_KWQE_OPCODE_INIT3))
            {
                BnxeLogWarn(pUM, "FCoE Init kwqes error");
                pUM->fcoe.stats.initWqeTxErr++;
                return B_FALSE;
            }

            if (!BnxeFcoeInitWqe(pUM, &kwqes[kwqeCnt]))
            {
                BnxeLogWarn(pUM, "Failed to init FCoE Init WQE work");
                return B_FALSE;
            }

            kwqeCnt += 3;

            break;

        case FCOE_KWQE_OPCODE_OFFLOAD_CONN1:

            BnxeLogDbg(pUM, "*** %s - FCOE_KWQE_OPCODE_OFFLOAD_CONN1", __func__);

            if ((wqeCnt <= kwqeCnt + 3) ||
                (kwqes[kwqeCnt + 1] == NULL) ||
                (kwqes[kwqeCnt + 2] == NULL) ||
                (kwqes[kwqeCnt + 3] == NULL) ||
                (kwqes[kwqeCnt + 1]->conn_offload2.hdr.op_code != FCOE_KWQE_OPCODE_OFFLOAD_CONN2) ||
                (kwqes[kwqeCnt + 2]->conn_offload3.hdr.op_code != FCOE_KWQE_OPCODE_OFFLOAD_CONN3) ||
                (kwqes[kwqeCnt + 3]->conn_offload4.hdr.op_code != FCOE_KWQE_OPCODE_OFFLOAD_CONN4))
            {
                BnxeLogWarn(pUM, "FCoE Offload Conn kwqes error");
                pUM->fcoe.stats.offloadConnWqeTxErr++;
                return B_FALSE;
            }

            if (!BnxeFcoeOffloadConnWqe(pUM, &kwqes[kwqeCnt]))
            {
                BnxeLogWarn(pUM, "Failed to init FCoE Offload Conn WQE work");
                return B_FALSE;
            }

            kwqeCnt += 4;

            break;

        case FCOE_KWQE_OPCODE_ENABLE_CONN:

            BnxeLogDbg(pUM, "*** %s - FCOE_KWQE_OPCODE_ENABLE_CONN", __func__);

            if (!BnxeFcoeEnableConnWqe(pUM, &kwqes[kwqeCnt]))
            {
                BnxeLogWarn(pUM, "Failed to init FCoE Enable Conn WQE work");
                return B_FALSE;
            }

            kwqeCnt += 1;

            break;

        case FCOE_KWQE_OPCODE_DISABLE_CONN:

            BnxeLogDbg(pUM, "*** %s - FCOE_KWQE_OPCODE_DISABLE_CONN", __func__);

            if (!BnxeFcoeDisableConnWqe(pUM, &kwqes[kwqeCnt]))
            {
                BnxeLogWarn(pUM, "Failed to init FCoE Disable Conn WQE work");
                return B_FALSE;
            }

            kwqeCnt += 1;

            break;

        case FCOE_KWQE_OPCODE_DESTROY_CONN:

            BnxeLogDbg(pUM, "*** %s - FCOE_KWQE_OPCODE_DESTROY_CONN", __func__);

            if (!BnxeFcoeDestroyConnWqe(pUM, &kwqes[kwqeCnt]))
            {
                BnxeLogWarn(pUM, "Failed to init FCoE Destroy Conn WQE work");
                return B_FALSE;
            }

            kwqeCnt += 1;

            break;

        case FCOE_KWQE_OPCODE_DESTROY:

            BnxeLogDbg(pUM, "*** %s - FCOE_KWQE_OPCODE_DESTROY", __func__);

            if (!BnxeFcoeDestroyWqe(pUM, &kwqes[kwqeCnt]))
            {
                BnxeLogWarn(pUM, "Failed to init FCoE Destroy WQE work");
                return B_FALSE;
            }

            kwqeCnt += 1;

            break;

        case FCOE_KWQE_OPCODE_STAT:

            BnxeLogDbg(pUM, "*** %s - FCOE_KWQE_OPCODE_STAT", __func__);

            if (!BnxeFcoeStatWqe(pUM, &kwqes[kwqeCnt]))
            {
                BnxeLogWarn(pUM, "Failed to init FCoE Stat WQE work");
                return B_FALSE;
            }

            kwqeCnt += 1;

            break;

        default:

            BnxeDbgBreakMsg(pUM, "Invalid KWQE opcode");
            return B_FALSE;
        }
    }

    return B_TRUE;
}


boolean_t BnxeFcoePrvMapMailboxq(dev_info_t *       pDev,
                                 u32_t              cid,
                                 void **            ppMap,
                                 ddi_acc_handle_t * pAccHandle)
{
    um_device_t * pUM = (um_device_t *)ddi_get_driver_private(pDev);

    /* sanity check */
    if (pUM == NULL || pUM->pDev != pDev)
    {
        BnxeLogWarn(NULL, "%s: dev_info_t match failed", __func__);
        return B_FALSE;
    }

    VERIFY_FCOE_BINDING(pUM);

    BnxeLogDbg(pUM, "*** %s ***", __func__);

    /* get the right offset from the mapped bar */

    *ppMap = (void *)((u8_t *)pUM->lm_dev.context_info->array[SW_CID(cid)].cid_resc.mapped_cid_bar_addr + DPM_TRIGER_TYPE);
    *pAccHandle = pUM->lm_dev.context_info->array[SW_CID(cid)].cid_resc.reg_handle;

    if (!(*ppMap) || !(*pAccHandle))
    {
        BnxeLogWarn(pUM, "Cannot map mailboxq base address for FCoE");
        return B_FALSE;
    }

    return B_TRUE;
}


boolean_t BnxeFcoePrvUnmapMailboxq(dev_info_t *     pDev,
                                   u32_t            cid,
                                   void *           pMap,
                                   ddi_acc_handle_t accHandle)
{
    um_device_t *    pUM = (um_device_t *)ddi_get_driver_private(pDev);
    void *           pTmp;
    ddi_acc_handle_t tmpAcc;

    /* sanity check */
    if (pUM == NULL || pUM->pDev != pDev)
    {
        BnxeLogWarn(NULL, "%s: dev_info_t match failed", __func__);
        return B_FALSE;
    }

    VERIFY_FCOE_BINDING(pUM);

    BnxeLogDbg(pUM, "*** %s ***", __func__);

    /* verify the mapped bar address */
    pTmp = (void *)((u8_t *)pUM->lm_dev.context_info->array[SW_CID(cid)].cid_resc.mapped_cid_bar_addr + DPM_TRIGER_TYPE);
    tmpAcc = pUM->lm_dev.context_info->array[SW_CID(cid)].cid_resc.reg_handle;

    if ((pMap != pTmp) || (accHandle != tmpAcc))
    {
        BnxeLogWarn(pUM, "Invalid map info for FCoE (%p)", pMap);
        return B_FALSE;
    }

    return B_TRUE;
}


int BnxeFcoeInit(um_device_t * pUM)
{
    char * pCompat[2] = { BNXEF_NAME, NULL };
    char   name[256];
    int    rc;

    BnxeLogInfo(pUM, "Starting FCoE");

    if (!BNXE_FCOE(pUM))
    {
        BnxeLogWarn(pUM, "FCoE not supported on this device");
        return ENOTSUP;
    }

    //if (CLIENT_DEVI(pUM, LM_CLI_IDX_FCOE))
    if (pUM->fcoe.pDev)
    {
        BnxeLogWarn(pUM, "FCoE child node already initialized");
        return EEXIST;
    }

    if (ndi_devi_alloc(pUM->pDev,
                       BNXEF_NAME,
                       DEVI_PSEUDO_NODEID,
                       &pUM->fcoe.pDev) != NDI_SUCCESS)
    {
        BnxeLogWarn(pUM, "Failed to allocate a child node for FCoE");
        pUM->fcoe.pDev = NULL;
        return ENOMEM;
    }

    if (ndi_prop_update_string_array(DDI_DEV_T_NONE,
                                     pUM->fcoe.pDev,
                                     "name",
                                     pCompat,
                                     1) != DDI_PROP_SUCCESS)
    {
        BnxeLogWarn(pUM, "Failed to set the name string for FCoE");
        /* XXX see other call to ndi_devi_free below */
        //ndi_devi_free(pUM->fcoe.pDev);
        pUM->fcoe.pDev = NULL;
        return ENOENT;
    }

    CLIENT_DEVI_SET(pUM, LM_CLI_IDX_FCOE);

    /*
     * XXX If/when supporting custom wwn's then prime them
     * here in so they will be passed to bnxef during BINDING.
     * Ideally custom wwn's will be set via the driver .conf
     * file and via a private driver property.
     */
    memset(&pUM->fcoe.wwn, 0, sizeof(BnxeWwnInfo));
    pUM->fcoe.wwn.fcp_pwwn_provided = B_TRUE;
    memcpy(pUM->fcoe.wwn.fcp_pwwn, pUM->lm_dev.hw_info.fcoe_wwn_port_name,
           BNXE_FCOE_WWN_SIZE);
    pUM->fcoe.wwn.fcp_nwwn_provided = B_TRUE;
    memcpy(pUM->fcoe.wwn.fcp_nwwn, pUM->lm_dev.hw_info.fcoe_wwn_node_name,
           BNXE_FCOE_WWN_SIZE);

    BnxeLogInfo(pUM, "Created the FCoE child node %s@%s",
                BNXEF_NAME, ddi_get_name_addr(pUM->pDev));

    if ((rc = ndi_devi_online(pUM->fcoe.pDev, NDI_ONLINE_ATTACH)) !=
        NDI_SUCCESS)
    {
        /* XXX
         * ndi_devi_free will cause a panic. Don't know why and we've
         * verified that Sun's FCoE driver does not free it either.
         */
        //ndi_devi_free(pUM->fcoe.pDev);
        CLIENT_DEVI_RESET(pUM, LM_CLI_IDX_FCOE);
        pUM->fcoe.pDev = NULL;
        BnxeLogInfo(pUM, "Unable to bind the QLogic FCoE driver (%d)", rc);
        return ECHILD;
    }

#if 0
    /* bring bnxef online and attach it */
    if (ndi_devi_bind_driver(pUM->fcoe.pDev, 0) != NDI_SUCCESS)
    {
        BnxeLogInfo(pUM, "Unable to bind the QLogic FCoE driver");
    }
#endif

    return 0;
}


int BnxeFcoeFini(um_device_t * pUM)
{
    int rc = 0;
    int nullDev = B_FALSE; /* false = wait for bnxef UNBIND */

    BnxeLogInfo(pUM, "Stopping FCoE");

    if (!BNXE_FCOE(pUM))
    {
        BnxeLogWarn(pUM, "FCoE not supported on this device");
        return ENOTSUP;
    }

    if (CLIENT_BOUND(pUM, LM_CLI_IDX_FCOE))
    {
        if (pUM->fcoe.pDev == NULL)
        {
            BnxeLogWarn(pUM, "FCoE Client bound and pDev is NULL, FINI failed! %s@%s",
                        BNXEF_NAME, ddi_get_name_addr(pUM->pDev));
            return ENOENT;
        }
        else if (pUM->fcoe.bind.cliCtl == NULL)
        {
            BnxeLogWarn(pUM, "FCoE Client bound and cliCtl is NULL, FINI failed! %s@%s",
                        BNXEF_NAME, ddi_get_name_addr(pUM->pDev));
            return ENOENT;
        }
        else if (pUM->fcoe.bind.cliCtl(pUM->fcoe.pDev,
                                       CLI_CTL_UNLOAD,
                                       NULL,
                                       0) == B_FALSE)
        {
            BnxeLogWarn(pUM, "FCoE Client bound and UNLOAD failed! %s@%s",
                        BNXEF_NAME, ddi_get_name_addr(pUM->pDev));
            return ENOMSG; /* no graceful unload with bnxef */
        }
    }
    else
    {
        rc = ENODEV;
        nullDev = B_TRUE;
    }

    /*
     * There are times when delete-port doesn't fully work and bnxef is unable
     * to detach and never calls UNBIND.  So here we'll just make sure that
     * the child dev node is not NULL which semi-gaurantees the UNBIND hasn't
     * been called yet.  Multiple offline calls will hopefully kick bnxef...
     */
    //if (CLIENT_DEVI(pUM, LM_CLI_IDX_FCOE))
    if (pUM->fcoe.pDev)
    {
        CLIENT_DEVI_RESET(pUM, LM_CLI_IDX_FCOE);

        BnxeLogWarn(pUM, "Bringing down QLogic FCoE driver %s@%s",
                    BNXEF_NAME, ddi_get_name_addr(pUM->pDev));

#if 1
        if (ndi_devi_offline(pUM->fcoe.pDev, NDI_DEVI_REMOVE) != NDI_SUCCESS)
        {
            BnxeLogWarn(pUM, "Failed to bring the QLogic FCoE driver offline %s@%s",
                        BNXEF_NAME, ddi_get_name_addr(pUM->pDev));
            return EBUSY;
        }
#else
        ndi_devi_offline(pUM->fcoe.pDev, NDI_DEVI_REMOVE);
        if (nullDev) pUM->fcoe.pDev = NULL;
#endif

        memset(&pUM->fcoe.wwn, 0, sizeof(BnxeWwnInfo));

        BnxeLogInfo(pUM, "Destroyed the FCoE child node %s@%s",
                    BNXEF_NAME, ddi_get_name_addr(pUM->pDev));
    }

    return rc;
}


void BnxeFcoeStartStop(um_device_t * pUM)
{
    int rc;

    if (!BNXE_FCOE(pUM))
    {
        BnxeLogWarn(pUM, "FCoE is not supported on this device");
        return;
    }

    if (pUM->devParams.fcoeEnable)
    {
        BnxeFcoeInit(pUM);
    }
    else
    {
        BnxeFcoeFini(pUM);
    }
}