root/usr/src/uts/common/io/bnxe/577xx/drivers/common/lm/device/lm_mcp.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.
 *
 *
 * Module Description:
 *
 *
 * History:
 *    11/26/07 Alon Elhanani    Inception.
 ******************************************************************************/

#include "lm5710.h"
#include "license.h"
#include "mcp_shmem.h"
#include "debug.h"

#define MCP_EMUL_TIMEOUT 200000    /* 200 ms (in us) */
#define MCP_TIMEOUT      5000000   /* 5 seconds (in us) */
#define MCP_ONE_TIMEOUT  100000    /* 100 ms (in us) */

/**
 * Waits for MCP_ONE_TIMEOUT or MCP_ONE_TIMEOUT*10,
 * depending on the HW type.
 *
 * @param pdev
 */
static __inline void lm_mcp_wait_one (
    IN  struct _lm_device_t * pdev
    )
{
    /* special handling for emulation and FPGA,
       wait 10 times longer */
    if (CHIP_REV_IS_SLOW(pdev)) {
        mm_wait(pdev, MCP_ONE_TIMEOUT*10);
    } else {
        mm_wait(pdev, MCP_ONE_TIMEOUT);
    }
}


#if !defined(b710)

/**
 * Prepare CLP to MCP reset.
 *
 * @param pdev Device handle
 * @param magic_val Old value of `magic' bit.
 */
void lm_clp_reset_prep(
    IN  struct _lm_device_t * pdev,
    OUT u32_t               * magic_val
    )
{
    u32_t val = 0;
    u32_t offset;

#define SHARED_MF_CLP_MAGIC  0x80000000 /* `magic' bit */

    ASSERT_STATIC(sizeof(struct mf_cfg) % sizeof(u32_t) == 0);

    /* Do some magic... */
    offset = OFFSETOF(mf_cfg_t, shared_mf_config.clp_mb);
    LM_MFCFG_READ(pdev, offset, &val);
    *magic_val = val & SHARED_MF_CLP_MAGIC;
    LM_MFCFG_WRITE(pdev, offset, val | SHARED_MF_CLP_MAGIC);
}

/**
 * Restore the value of the `magic' bit.
 *
 * @param pdev Device handle.
 * @param magic_val Old value of the `magic' bit.
 */
void lm_clp_reset_done(
    IN  struct _lm_device_t * pdev,
    IN  u32_t                 magic_val
    )
{
    u32_t val = 0;
    u32_t offset;

    /* Restore the `magic' bit value... */
    offset = OFFSETOF(mf_cfg_t, shared_mf_config.clp_mb);
    LM_MFCFG_READ(pdev, offset, &val);
    LM_MFCFG_WRITE(pdev, offset, (val & (~SHARED_MF_CLP_MAGIC)) | magic_val);
}

#endif // !b710

u8_t lm_is_mcp_detected(
    IN struct _lm_device_t *pdev
    )
{
    return pdev->hw_info.mcp_detected;
}

/**
 * @Description
 *      Prepares for MCP reset: takes care of CLP configurations
 *      (saves it aside to resotre later) .
 *
 * @param pdev
 * @param magic_val Old value of 'magic' bit.
 */
lm_status_t lm_reset_mcp_prep(lm_device_t *pdev, u32_t * magic_val)
{
    u32_t shmem;
    u32_t validity_offset;

    /* Set `magic' bit in order to save MF config */
    if (!CHIP_IS_E1(pdev))
    {
        lm_clp_reset_prep(pdev, magic_val);
    }

    /* Get shmem offset */
    shmem = REG_RD(pdev, MISC_REG_SHARED_MEM_ADDR);
    validity_offset = OFFSETOF(shmem_region_t, validity_map[0]);

    /* Clear validity map flags */
    if( shmem > 0 )
    {
        REG_WR(pdev, shmem + validity_offset, 0);
    }

    return LM_STATUS_SUCCESS;
}

lm_status_t lm_reset_mcp_comp(lm_device_t *pdev, u32_t magic_val)
{
    lm_status_t lm_status         = LM_STATUS_SUCCESS;
    u32_t       shmem_sig_timeout = 0;
    u32_t       validity_offset   = 0;
    u32_t       shmem             = 0;
    u32_t       val               = 0;
    u32_t       cnt               = 0;

#ifdef _VBD_CMD_
    return LM_STATUS_SUCCESS;
#endif

    /* Get shmem offset */
    shmem = REG_RD(pdev, MISC_REG_SHARED_MEM_ADDR);
    if( shmem == 0 ) {
        DbgMessage(pdev, FATAL, "Shmem 0 return failure\n");
        lm_status = LM_STATUS_FAILURE;
        goto exit_lbl;
    }

    ASSERT_STATIC(0 != MCP_ONE_TIMEOUT);

    if (CHIP_REV_IS_EMUL(pdev))
        shmem_sig_timeout = MCP_EMUL_TIMEOUT / MCP_ONE_TIMEOUT; // 200ms
    else
        shmem_sig_timeout = MCP_TIMEOUT / MCP_ONE_TIMEOUT; // 5sec

    validity_offset = OFFSETOF(shmem_region_t, validity_map[0]);

    /* Wait for MCP to come up */
    for(cnt = 0; cnt < shmem_sig_timeout; cnt++)
    {
        /* TBD: its best to check validity map of last port. currently checks on port 0. */
        val = REG_RD(pdev, shmem + validity_offset);
        DbgMessage(pdev, INFORM, "shmem 0x%x validity map(0x%x)=0x%x\n", shmem, shmem + validity_offset, val);

        /* check that shared memory is valid. */
        if((val & (SHR_MEM_VALIDITY_DEV_INFO | SHR_MEM_VALIDITY_MB)) ==
           (SHR_MEM_VALIDITY_DEV_INFO|SHR_MEM_VALIDITY_MB)) {
            break;
        }

        lm_mcp_wait_one(pdev);
    }

    DbgMessage(pdev, INFORM , "Cnt=%d Shmem validity map 0x%x\n",cnt, val);

    /* Check that shared memory is valid. This indicates that MCP is up. */
    if((val & (SHR_MEM_VALIDITY_DEV_INFO | SHR_MEM_VALIDITY_MB)) !=
       (SHR_MEM_VALIDITY_DEV_INFO | SHR_MEM_VALIDITY_MB))
    {
        DbgMessage(pdev, FATAL, "Shmem signature not present. MCP is not up !!\n");
        lm_status = LM_STATUS_FAILURE;
        goto exit_lbl;
    }

exit_lbl:

    if (!CHIP_IS_E1(pdev))
    {
        /* Restore `magic' bit value */
        lm_clp_reset_done(pdev, magic_val);
    }

    return lm_status;
}

lm_status_t lm_reset_mcp(
    IN struct _lm_device_t *pdev
    )
{

    u32_t magic_val = 0;
    u32_t val, retries=0;
    lm_status_t lm_status = LM_STATUS_SUCCESS;

    DbgMessage(pdev, VERBOSE, "Entered lm_reset_mcp\n");

    lm_reset_mcp_prep(pdev, &magic_val);

    /* wait up to 3 seconds to get all locks. Whatsoever, reset mcp afterwards */
    do {
         REG_WR(pdev, MISC_REG_DRIVER_CONTROL_15 + 4, 0xffffffff);
         val = REG_RD(pdev, MISC_REG_DRIVER_CONTROL_15);
         mm_wait(pdev, 1);
    } while ((val != 0xffffffff) && (++retries < 3000000));

    /* Reset the MCP */
    REG_WR(pdev, GRCBASE_MISC+ MISC_REGISTERS_RESET_REG_2_CLEAR,
         MISC_REGISTERS_RESET_REG_2_RST_MCP_N_RESET_REG_HARD_CORE  |
         MISC_REGISTERS_RESET_REG_2_RST_MCP_N_HARD_CORE_RST_B      |
         MISC_REGISTERS_RESET_REG_2_RST_MCP_N_RESET_CMN_CPU        |
         MISC_REGISTERS_RESET_REG_2_RST_MCP_N_RESET_CMN_CORE);

    /* release the locks taken */
    REG_WR(pdev, MISC_REG_DRIVER_CONTROL_15, 0xffffffff);

    mm_wait(pdev, 100000);

    // No need to wait here a minimum time, since the mcp_comp will
    // returns only when mcp is ready.
    lm_status = lm_reset_mcp_comp(pdev, magic_val);

    return lm_status;
}

//acquire split MCP access lock register
lm_status_t
acquire_split_alr(
    lm_device_t *pdev)
{
    lm_status_t lm_status;
    u32_t j, cnt;
    u32_t val_wr, val_rd;

    DbgMessage(pdev, INFORM, "acquire_split_alr() - %d START!\n", FUNC_ID(pdev) );

    //Adjust timeout for our emulation needs
    cnt = 30000 * 100;
    val_wr = 1UL << 31;
    val_rd = 0;

    //acquire lock using mcpr_access_lock SPLIT register

    for(j = 0; j < cnt*10; j++)
    {
        REG_WR(pdev,  GRCBASE_MCP + 0x9c, val_wr);
        val_rd = REG_RD(pdev,  GRCBASE_MCP + 0x9c);
        if (val_rd & (1UL << 31))
        {
            break;
        }

        mm_wait(pdev, 5);
    }

    if(val_rd & (1UL << 31))
    {
        lm_status = LM_STATUS_SUCCESS;
    }
    else
    {
        DbgBreakMsg("Cannot get access to nvram interface.\n");

        lm_status = LM_STATUS_BUSY;
    }

    DbgMessage(pdev, INFORM, "acquire_split_alr() - %d END!\n", FUNC_ID(pdev) );

    return lm_status;
}

//Release split MCP access lock register
void
release_split_alr(
    lm_device_t *pdev)
{
    u32_t val = 0;

    DbgMessage(pdev, INFORM, "release_split_alr() - %d START!\n", FUNC_ID(pdev) );

    //This is only a sanity check, can remove later in free build.
    val= REG_RD(pdev, GRCBASE_MCP + 0x9c);
    DbgBreakIf(!(val & (1L << 31)));

    val = 0;

    //release mcpr_access_lock SPLIT register
    REG_WR(pdev,  GRCBASE_MCP + 0x9c, val);
    DbgMessage(pdev, INFORM, "release_split_alr() - %d END!\n", FUNC_ID(pdev) );
} /* release_nvram_lock */

/*******************************************************************************
 * Description:
 *         sends the mcp a keepalive to known registers
 * Return:
 ******************************************************************************/
lm_status_t lm_send_driver_pulse( lm_device_t* pdev )
{
    u32_t        msg_code   = 0;
    u32_t        drv_pulse  = 0;
    u32_t        mcp_pulse  = 0;

    if CHK_NULL(pdev)
    {
        return LM_STATUS_INVALID_PARAMETER ;
    }

    if GET_FLAGS(pdev->params.test_mode, TEST_MODE_NO_MCP)
    {
        return LM_STATUS_SUCCESS ;
    }

    ++pdev->vars.drv_pulse_wr_seq;
    msg_code = pdev->vars.drv_pulse_wr_seq & DRV_PULSE_SEQ_MASK;
    if (GET_FLAGS(pdev->params.test_mode, TEST_MODE_DRIVER_PULSE_ALWAYS_ALIVE)
        || IS_DRIVER_PULSE_ALWAYS_ALIVE(pdev))
    {
        SET_FLAGS( msg_code, DRV_PULSE_ALWAYS_ALIVE ) ;
    }

    drv_pulse = msg_code;

    LM_SHMEM_WRITE(pdev,
                   OFFSETOF(shmem_region_t,
                   func_mb[FUNC_MAILBOX_ID(pdev)].drv_pulse_mb),msg_code);
    LM_SHMEM_READ(pdev,
                  OFFSETOF(shmem_region_t,
                  func_mb[FUNC_MAILBOX_ID(pdev)].mcp_pulse_mb),
                  &mcp_pulse);

    mcp_pulse&= MCP_PULSE_SEQ_MASK ;
    /* The delta between driver pulse and mcp response
     * should be 1 (before mcp response) or 0 (after mcp response)
    */
    if ((drv_pulse != mcp_pulse) &&
        (drv_pulse != ((mcp_pulse + 1) & MCP_PULSE_SEQ_MASK)))
    {
        DbgMessage(pdev, FATAL, "drv_pulse (0x%x) != mcp_pulse (0x%x)\n", drv_pulse, mcp_pulse );
        return LM_STATUS_FAILURE ;
    }
    DbgMessage(pdev, INFORMi , "Sent driver pulse cmd to MCP\n");
    return LM_STATUS_SUCCESS ;
}
/*******************************************************************************
 * Description:
 *         Set driver pulse to MCP to always alive
 * Return:
 ******************************************************************************/
void lm_driver_pulse_always_alive(struct _lm_device_t* pdev)
{
    if CHK_NULL(pdev)
    {
        return;
    }
    if GET_FLAGS(pdev->params.test_mode, TEST_MODE_NO_MCP)
    {
        return ;
    }
    // Reset the MCP pulse to always alive
    LM_SHMEM_WRITE( pdev,
                    OFFSETOF(shmem_region_t,
                    func_mb[FUNC_MAILBOX_ID(pdev)].drv_pulse_mb),
                    DRV_PULSE_ALWAYS_ALIVE );
}
// entry that represents a function in the loader objcet
typedef struct _lm_loader_func_entry_t
{
    u8_t b_loaded ;   // does this function was loaded
} lm_loader_func_entry_t ;
// global object represents MCP - should be one per CHIP (boards)
typedef struct _lm_loader_path_obj_t
{
    u32_t*                   lock_ctx ;               // reserved - lock object context (currently not in use)
    lm_loader_func_entry_t   func_arr[E1H_FUNC_MAX] ; // array of function entries
} lm_loader_path_obj_t ;

typedef struct _lm_loader_obj_t
{
    u8_t                     lock_owner ;             // is a function acquire the lock? (1 based)
    lm_loader_path_obj_t path_arr[MAX_PATH_NUM] ;
} lm_loader_obj_t ;

lm_loader_obj_t g_lm_loader  = {0};

// TRUE if the function is first on the port
#define LM_LOADER_IS_FIRST_ON_PORT(_pdev,_path_idx,_port_idx) \
 ( (FALSE == g_lm_loader.path_arr[_path_idx].func_arr[_port_idx+0].b_loaded) && \
   (FALSE == g_lm_loader.path_arr[_path_idx].func_arr[_port_idx+2].b_loaded) && \
   (FALSE == g_lm_loader.path_arr[_path_idx].func_arr[_port_idx+4].b_loaded) && \
   (FALSE == g_lm_loader.path_arr[_path_idx].func_arr[_port_idx+6].b_loaded) )

// TRUE if the function is last on the port
#define LM_LOADER_IS_LAST_ON_PORT(_pdev,_path_idx,_port_idx) \
  ( ( ( FUNC_ID(_pdev) == (_port_idx+0) ) ? TRUE : (FALSE == g_lm_loader.path_arr[_path_idx].func_arr[(_port_idx+0)].b_loaded) ) && \
    ( ( FUNC_ID(_pdev) == (_port_idx+2) ) ? TRUE : (FALSE == g_lm_loader.path_arr[_path_idx].func_arr[(_port_idx+2)].b_loaded) ) && \
    ( ( FUNC_ID(_pdev) == (_port_idx+4) ) ? TRUE : (FALSE == g_lm_loader.path_arr[_path_idx].func_arr[(_port_idx+4)].b_loaded) ) && \
    ( ( FUNC_ID(_pdev) == (_port_idx+6) ) ? TRUE : (_port_idx == 0)?(FALSE == g_lm_loader.path_arr[_path_idx].func_arr[6].b_loaded):(FALSE == g_lm_loader.path_arr[_path_idx].func_arr[7].b_loaded) ) )


#define LM_LOADER_IS_FIRST_ON_COMMON(_pdev,_path_idx) (LM_LOADER_IS_FIRST_ON_PORT(_pdev,_path_idx,0) && LM_LOADER_IS_FIRST_ON_PORT(_pdev,_path_idx,1))
#define LM_LOADER_IS_LAST_ON_COMMON(_pdev,_path_idx)  (LM_LOADER_IS_LAST_ON_PORT(_pdev,_path_idx,0)  && LM_LOADER_IS_LAST_ON_PORT(_pdev,_path_idx,1))

#define LM_LOADER_IS_FIRST_ON_CHIP(_pdev) (LM_LOADER_IS_FIRST_ON_COMMON(_pdev,0) && LM_LOADER_IS_FIRST_ON_COMMON(_pdev,1))
#define LM_LOADER_IS_LAST_ON_CHIP(_pdev)  (LM_LOADER_IS_LAST_ON_COMMON(_pdev,0)  && LM_LOADER_IS_LAST_ON_COMMON(_pdev,1))

// Accessed only with lock!
// TRUE if any device is currently locked
#define LM_LOADER_IS_LOCKED(_chip_idx) ( (FALSE != g_lm_loader.lock_owner) )

/*
 *Function Name:lm_loader_opcode_to_mcp_msg
 *
 *Parameters:
 *      b_lock - true if it is lock false if unlock
 *Description:
 *      LM_LOADER_OPCODE_XXX-->DRV_MSG_CODE_XXX
 *Returns:
 *
 */
static u32_t lm_loader_opcode_to_mcp_msg( lm_loader_opcode opcode, u8_t b_lock )
{
    u32_t mcp_msg = 0xffffffff ;

    switch(opcode)
    {
    case LM_LOADER_OPCODE_LOAD:
        mcp_msg = b_lock ? DRV_MSG_CODE_LOAD_REQ : DRV_MSG_CODE_LOAD_DONE ;
        break;
    case LM_LOADER_OPCODE_UNLOAD_WOL_EN:
        mcp_msg = b_lock ? DRV_MSG_CODE_UNLOAD_REQ_WOL_EN : DRV_MSG_CODE_UNLOAD_DONE ;
        break;
    case LM_LOADER_OPCODE_UNLOAD_WOL_DIS:
        mcp_msg = b_lock ? DRV_MSG_CODE_UNLOAD_REQ_WOL_DIS : DRV_MSG_CODE_UNLOAD_DONE ;
        break;
    case LM_LOADER_OPCODE_UNLOAD_WOL_MCP:
        mcp_msg = b_lock ? DRV_MSG_CODE_UNLOAD_REQ_WOL_MCP : DRV_MSG_CODE_UNLOAD_DONE ;
        break;
    default:
        DbgBreakIf(1) ;
        break;
    }
    return mcp_msg ;
}
/*
 *Function Name:mcp_resp_to_lm_loader_resp
 *
 *Parameters:
 *
 *Description:
 *      Translates mcp response to loader response FW_MSG_CODE_DRV_XXX->LM_LOADER_RESPONSE_XX
 *Returns:
 *
 */
lm_loader_response mcp_resp_to_lm_loader_resp( u32_t mcp_resp )
{
    lm_loader_response resp = LM_LOADER_RESPONSE_INVALID ;
    switch(mcp_resp)
    {
    case FW_MSG_CODE_DRV_LOAD_COMMON:
        resp = LM_LOADER_RESPONSE_LOAD_COMMON ;
        break;
    case FW_MSG_CODE_DRV_LOAD_COMMON_CHIP:
        resp = LM_LOADER_RESPONSE_LOAD_COMMON_CHIP ;
        break;
    case FW_MSG_CODE_DRV_LOAD_PORT:
        resp = LM_LOADER_RESPONSE_LOAD_PORT ;
        break;
    case FW_MSG_CODE_DRV_LOAD_FUNCTION:
        resp = LM_LOADER_RESPONSE_LOAD_FUNCTION ;
        break;
    case FW_MSG_CODE_DRV_UNLOAD_COMMON:
        resp = LM_LOADER_RESPONSE_UNLOAD_COMMON ;
        break;
    case FW_MSG_CODE_DRV_UNLOAD_PORT:
        resp = LM_LOADER_RESPONSE_UNLOAD_PORT ;
        break;
    case FW_MSG_CODE_DRV_UNLOAD_FUNCTION:
        resp = LM_LOADER_RESPONSE_UNLOAD_FUNCTION ;
        break;
    case FW_MSG_CODE_DRV_LOAD_DONE:
        resp = LM_LOADER_RESPONSE_LOAD_DONE ;
        break;
    case FW_MSG_CODE_DRV_UNLOAD_DONE:
        resp = LM_LOADER_RESPONSE_UNLOAD_DONE ;
        break;
    default:
        DbgMessage(NULL, FATAL, "mcp_resp=0x%x\n", mcp_resp );
        DbgBreakIf(1) ;
        break;
    }
    return resp ;
}
// TBD - should it be the only indication??
#define IS_MCP_ON(_pdev) ( TEST_MODE_NO_MCP != GET_FLAGS(_pdev->params.test_mode, TEST_MODE_NO_MCP ) )

/*
 *Function Name:lm_loader_lock
 *
 *Parameters:
 *
 *Description:
 *     sync loading/unloading of port/funciton
 *Returns:
 *
 */
lm_loader_response lm_loader_lock( lm_device_t* pdev, lm_loader_opcode opcode )
{
    u32_t              mcp_msg        = 0;
    u32_t              param          = 0;
    u32_t              fw_resp        = 0;
    lm_loader_response resp           = LM_LOADER_RESPONSE_INVALID ;
    lm_status_t        lm_status      = LM_STATUS_SUCCESS ;
    u32_t              wait_cnt       = 0;
    u32_t              wait_cnt_limit = 5000;
    const u32_t        feature_flags  = mm_get_feature_flags( pdev );
    const u8_t         is_suspend     = opcode & LM_LOADER_OPCODE_UNLOAD_SUSPEND;

    opcode &= LM_LOADER_OPCODE_MASK;
    if( IS_MCP_ON(pdev) )
    {
        mcp_msg = lm_loader_opcode_to_mcp_msg( opcode, TRUE ) ;

        // in case it is load (and not unload)
        // send mfw LFA param
        if ( DRV_MSG_CODE_LOAD_REQ == mcp_msg )
        {
            SET_FLAGS(param, DRV_MSG_CODE_LOAD_REQ_WITH_LFA );

            // in case BFS, set FORCE_LFA flag on
            if( GET_FLAGS( feature_flags, FEATURE_ETH_BOOTMODE_PXE )   ||
                GET_FLAGS( feature_flags, FEATURE_ETH_BOOTMODE_ISCSI ) ||
                GET_FLAGS( feature_flags, FEATURE_ETH_BOOTMODE_FCOE ) )
            {
                SET_FLAGS( param, DRV_MSG_CODE_LOAD_REQ_FORCE_LFA );
            }

        }
        else if (is_suspend)
        {
            SET_FLAGS( param, DRV_MSG_CODE_UNLOAD_NON_D3_POWER ); //temporary
        }

        //we do this with no locks because acquiring the loader lock may take a long time (e.g in case another function takes a
        //long time to initialize we will only get a response from the MCP when it's done). We don't need a lock because interrupts
        //are disabled at this point and we won't get any IOCTLs.
        lm_status = lm_mcp_cmd_send_recieve_non_atomic( pdev, lm_mcp_mb_header, mcp_msg, param, MCP_CMD_DEFAULT_TIMEOUT, &fw_resp ) ;
        if ( LM_STATUS_SUCCESS == lm_status )
        {
            resp = mcp_resp_to_lm_loader_resp(  fw_resp ) ;
            pdev->vars.b_in_init_reset_flow = TRUE;
        }
    }
    else // MCP_SIM
    {
        if( ERR_IF(PORT_ID(pdev) > 1) || ERR_IF(( FUNC_ID(pdev)) >= ARRSIZE(g_lm_loader.path_arr[PATH_ID(pdev)].func_arr)) )
        {
            DbgBreakMsg("Invalid PORT_ID/FUNC_ID\n");
            return resp ;
        }
        do
        {
            MM_ACQUIRE_LOADER_LOCK();
            if( LM_LOADER_IS_LOCKED(PATH_ID(pdev)) )
            {
                MM_RELEASE_LOADER_LOCK();
                mm_wait(pdev,20) ;
                DbgBreakIfAll( ++wait_cnt > wait_cnt_limit ) ;
            }
            else
            {
                // we'll release the lock when we are finish the work
                break;
            }
        }while(1) ;
        // Verify no one hold the lock, if so - it's a bug!
        DbgBreakIf( 0 != g_lm_loader.lock_owner ) ;

        // mark our current function id as owner
        g_lm_loader.lock_owner = FUNC_ID(pdev)+1 ;

        switch( opcode )
        {
        case LM_LOADER_OPCODE_LOAD:
            if( LM_LOADER_IS_FIRST_ON_CHIP(pdev) )
            {
                resp = LM_LOADER_RESPONSE_LOAD_COMMON_CHIP;
            }
            else if( LM_LOADER_IS_FIRST_ON_COMMON(pdev,PATH_ID(pdev)) )
            {
                resp = LM_LOADER_RESPONSE_LOAD_COMMON ;
            }
            else if( LM_LOADER_IS_FIRST_ON_PORT( pdev, PATH_ID(pdev), PORT_ID(pdev) ) )
            {
                resp = LM_LOADER_RESPONSE_LOAD_PORT ;
            }
            else
            {
                resp = LM_LOADER_RESPONSE_LOAD_FUNCTION ;
            }
            break;
        case LM_LOADER_OPCODE_UNLOAD_WOL_EN:
        case LM_LOADER_OPCODE_UNLOAD_WOL_DIS:
        case LM_LOADER_OPCODE_UNLOAD_WOL_MCP:
            if( LM_LOADER_IS_LAST_ON_COMMON(pdev,PATH_ID(pdev)) )
            {
                resp = LM_LOADER_RESPONSE_UNLOAD_COMMON ;
            }
            else if( LM_LOADER_IS_LAST_ON_PORT( pdev, PATH_ID(pdev), PORT_ID(pdev) ) )
            {
                resp = LM_LOADER_RESPONSE_UNLOAD_PORT ;
            }
            else
            {
                resp = LM_LOADER_RESPONSE_UNLOAD_FUNCTION ;
            }
            break;
        default:
            DbgBreakIf(1) ;
            break;
        }  // switch
        pdev->vars.b_in_init_reset_flow = TRUE;
        MM_RELEASE_LOADER_LOCK();
    } // MCP_SIM
    return resp ;
}
/*
 *Function Name:lm_loader_unlock
 *
 *Parameters:
 *
 *Description:
 *      sync loading/unloading of port/funciton
 *Returns:
 *
 */
lm_loader_response lm_loader_unlock( struct _lm_device_t *pdev, lm_loader_opcode opcode, OPTIONAL const u32_t* IN p_param )
{
    u32_t              mcp_msg     = 0 ;
    u32_t              param       = p_param ? (*p_param) : 0 ;
    lm_loader_response resp        = LM_LOADER_RESPONSE_INVALID ;
    u32_t              fw_resp     = 0 ;
    lm_status_t        lm_status   = LM_STATUS_SUCCESS ;
    u8_t               b_new_state = 0xff ;
    if CHK_NULL(pdev)
    {
        return resp ;
    }
    opcode &= LM_LOADER_OPCODE_MASK;
    if( IS_MCP_ON(pdev) )
    {
        mcp_msg   = lm_loader_opcode_to_mcp_msg( opcode, FALSE );
        //we do this with no locks because acquiring the loader lock may take a long time (e.g in case another function takes a
        //long time to initialize we will only get a response from the MCP when it's done). We don't need a lock because interrupts
        //are disabled at this point and we won't get any IOCTLs.
        lm_status = lm_mcp_cmd_send_recieve_non_atomic(pdev, lm_mcp_mb_header, mcp_msg, param, MCP_CMD_DEFAULT_TIMEOUT, &fw_resp ) ;
        if ( LM_STATUS_SUCCESS == lm_status )
        {
            resp = mcp_resp_to_lm_loader_resp( fw_resp ) ;
            pdev->vars.b_in_init_reset_flow = FALSE;
        }
    }
    else // MCP_SIM
    {
        MM_ACQUIRE_LOADER_LOCK();

        // Verify current function id is the owner
        DbgBreakIf( g_lm_loader.lock_owner != FUNC_ID(pdev)+1 ) ;

        switch( opcode )
        {
        case LM_LOADER_OPCODE_LOAD:
            b_new_state = TRUE ;
            resp        = LM_LOADER_RESPONSE_LOAD_DONE ;
            break;
        case LM_LOADER_OPCODE_UNLOAD_WOL_EN:
        case LM_LOADER_OPCODE_UNLOAD_WOL_DIS:
        case LM_LOADER_OPCODE_UNLOAD_WOL_MCP:
            b_new_state = FALSE  ;
            resp        = LM_LOADER_RESPONSE_UNLOAD_DONE ;
            break;
        default:
            DbgBreakIf(1) ;
            break;
        }  // switch
        // verify new state differs than current
        DbgBreakIf(g_lm_loader.path_arr[PATH_ID(pdev)].func_arr[FUNC_ID(pdev)].b_loaded == b_new_state);

        // assign new state
        g_lm_loader.path_arr[PATH_ID(pdev)].func_arr[FUNC_ID(pdev)].b_loaded = b_new_state ;

        // mark we don't own the lock anymore
        g_lm_loader.lock_owner = FALSE ;

        pdev->vars.b_in_init_reset_flow = FALSE;
        MM_RELEASE_LOADER_LOCK();
    } // MCP_SIM
    return resp ;
}

/* Used for simulating a mcp reset where the mcp no longer knows the state of the uploaded drivers... */
void lm_loader_reset ( struct _lm_device_t *pdev )
{
    mm_memset(&g_lm_loader, 0, sizeof(g_lm_loader));
}

/*
 *Function Name:lm_mcp_cmd_init
 *
 *Parameters:
 *
 *Description:
 *      initiate sequence of mb + verify boot code version
 *Returns:
 *
 */
lm_status_t lm_mcp_cmd_init( struct _lm_device_t *pdev)
{
    u32_t val        = 0 ;
    u32_t bc_rev     = 0 ;
    u32_t offset     = 0 ;
    u8_t  func_mb_id = 0;

    DbgMessage(pdev, INFORMi , "### mcp_cmd_init\n");

    if CHK_NULL(pdev)
    {
        return LM_STATUS_FAILURE ;
    }

    // we are on NO_MCP mode - nothing to do
    if( 0 != GET_FLAGS(pdev->params.test_mode, TEST_MODE_NO_MCP ) )
    {
        return LM_STATUS_SUCCESS ;
    }

    //validtae bc version
    bc_rev = LM_GET_BC_REV_MAJOR(pdev);

    if (bc_rev < BC_REV_SUPPORTED)
    {
        DbgMessage(pdev, FATAL,"bc version is less than 0x%x equal to 0x%x.\n", BC_REV_SUPPORTED, bc_rev );
        DbgBreakMsg("Please upgrade the bootcode version.\n");
        // TODO add event log
        return LM_STATUS_INVALID_PARAMETER;
    }

    // enable optic module verification according to BC version
    if (bc_rev >= REQ_BC_VER_4_VRFY_FIRST_PHY_OPT_MDL)
    {
        SET_FLAGS(pdev->params.link.feature_config_flags, ELINK_FEATURE_CONFIG_BC_SUPPORTS_OPT_MDL_VRFY);
    }

    if (bc_rev >= REQ_BC_VER_4_VRFY_SPECIFIC_PHY_OPT_MDL)
    {
        SET_FLAGS(pdev->params.link.feature_config_flags, ELINK_FEATURE_CONFIG_BC_SUPPORTS_DUAL_PHY_OPT_MDL_VRFY);
    }

    if (bc_rev >= REQ_BC_VER_4_VRFY_AFEX_SUPPORTED)
    {
        SET_FLAGS(pdev->params.link.feature_config_flags, ELINK_FEATURE_CONFIG_BC_SUPPORTS_AFEX);
    }

    if (bc_rev >= REQ_BC_VER_4_SFP_TX_DISABLE_SUPPORTED)
    {
        SET_FLAGS(pdev->params.link.feature_config_flags, ELINK_FEATURE_CONFIG_BC_SUPPORTS_SFP_TX_DISABLED);
    }

    if (bc_rev >= REQ_BC_VER_4_MT_SUPPORTED)
    {
        SET_FLAGS(pdev->params.link.feature_config_flags, ELINK_FEATURE_CONFIG_MT_SUPPORT);
    }

    // regular MCP mode
    func_mb_id = pdev->params.pfunc_mb_id;

    // read first seq number from shared memory
    offset = OFFSETOF(shmem_region_t, func_mb[func_mb_id].drv_mb_header);
    LM_SHMEM_READ(pdev, offset, &val);
    pdev->vars.fw_wr_seq = (u16_t)(val & DRV_MSG_SEQ_NUMBER_MASK);

    // read current mcp_pulse value
    offset = OFFSETOF(shmem_region_t,func_mb[func_mb_id].mcp_pulse_mb) ;
    LM_SHMEM_READ(pdev, offset ,&val);
    pdev->vars.drv_pulse_wr_seq = (u16_t)(val & MCP_PULSE_SEQ_MASK);

    return LM_STATUS_SUCCESS;
}

lm_status_t lm_mcp_set_mf_bw(struct _lm_device_t *pdev, IN u8_t min_bw, IN u8_t max_bw)
{
    u32_t       minmax_param    = 0;
    u32_t       resp            = 0;
    lm_status_t lm_status       = LM_STATUS_SUCCESS;
    const u32_t bc_rev          = LM_GET_BC_REV_MAJOR(pdev);

    //if in no MCP mode, don't do anything
    if(!lm_is_mcp_detected(pdev))
    {
        DbgMessage(pdev, WARNmi, "No MCP detected.\n");
        return LM_STATUS_SUCCESS;
    }
    //if bootcode is less then REQ_BC_VER_4_SET_MF_BW, fail
    if( bc_rev < REQ_BC_VER_4_SET_MF_BW )
    {
        DbgMessage(pdev, WARNmi, "Invalid bootcode version.\n");
        return LM_STATUS_INVALID_PARAMETER;
    }
    //if not E2 or not MF mode, fail
    if(CHIP_IS_E1x(pdev) || !IS_MULTI_VNIC(pdev))
    {
        DbgMessage(pdev, WARNmi, "Device is E1/E1.5 or in SF mode.\n");
        return LM_STATUS_INVALID_PARAMETER;
    }
    //if the parameters are not valid, fail
    if (max_bw > 100)
    {
        DbgMessage(pdev, WARNmi, "Invalid parameters.\n");
        return LM_STATUS_INVALID_PARAMETER;
    }
    //build MCP command parameter from min_bw/max_bw
    //we use FUNC_MF_CFG_MIN_BW_SHIFT because the param structure is supposed to
    //be equivalent for this opcode and for the DCC opcode, but there is no define
    //for this opcode.
    ASSERT_STATIC(FUNC_MF_CFG_MIN_BW_MASK == DRV_MSG_CODE_SET_MF_BW_MIN_MASK);
    ASSERT_STATIC(FUNC_MF_CFG_MAX_BW_MASK == DRV_MSG_CODE_SET_MF_BW_MAX_MASK);
    minmax_param =  (min_bw << FUNC_MF_CFG_MIN_BW_SHIFT)|
                    (max_bw << FUNC_MF_CFG_MAX_BW_SHIFT);

    //call lm_mcp_cmd_send_recieve with DRV_MSG_CODE_SET_MF_BW opcode and the parameter
    lm_mcp_cmd_send_recieve(pdev, lm_mcp_mb_header, DRV_MSG_CODE_SET_MF_BW, minmax_param, MCP_CMD_DEFAULT_TIMEOUT, &resp);

    //make sure that the response is FW_MSG_CODE_SET_MF_BW_SENT
    if(resp != FW_MSG_CODE_SET_MF_BW_SENT)
    {
        DbgBreakIf(resp != FW_MSG_CODE_SET_MF_BW_SENT);
        return LM_STATUS_FAILURE;
    }

    //return what lm_mcp_cmd_send_recieve returned
    return lm_status;
}

/*
 *Function Name:lm_mcp_cmd_send
 *
 *Parameters:
 *
 *Description:
 *      send
 *Returns:
 *
 */
lm_status_t lm_mcp_cmd_send( struct _lm_device_t *pdev, lm_mcp_mb_type mb_type, u32_t drv_msg, u32_t param )
{
    u16_t*     p_seq      = NULL ;
    u32_t      offset     = 0 ;
    u32_t      drv_mask   = 0 ;
    const u8_t func_mb_id = pdev->params.pfunc_mb_id;

    DbgMessage(pdev, INFORMi , "### mcp_cmd_send mb_type=0x%x drv_msg=0x%x param=0x%x\n", mb_type, drv_msg, param );

    // we are on NO_MCP mode - nothing to do
    if( 0 != GET_FLAGS(pdev->params.test_mode, TEST_MODE_NO_MCP ) )
    {
        return LM_STATUS_SUCCESS ;
    }

    switch( mb_type )
    {
    case lm_mcp_mb_header:
        p_seq      = &pdev->vars.fw_wr_seq ;
        drv_mask   = DRV_MSG_SEQ_NUMBER_MASK ;
        offset     = OFFSETOF(shmem_region_t, func_mb[func_mb_id].drv_mb_header) ;
        /* Write the parameter to the mcp */
        if (p_seq)
        {
            LM_SHMEM_WRITE(pdev,OFFSETOF(shmem_region_t, func_mb[func_mb_id].drv_mb_param),param);
        }
        break;

    case lm_mcp_mb_pulse:
        p_seq      = &pdev->vars.drv_pulse_wr_seq ;
        drv_mask   = DRV_PULSE_SEQ_MASK ;
        offset     = OFFSETOF(shmem_region_t, func_mb[func_mb_id].mcp_pulse_mb) ;
        break;
    case lm_mcp_mb_param:
    default:
        break;
    }

    if CHK_NULL( p_seq )
    {
        return LM_STATUS_INVALID_PARAMETER ;
    }

    // incremant sequence
    ++(*p_seq);

    // prepare message
    drv_msg |= ( (*p_seq) & drv_mask );

    LM_SHMEM_WRITE(pdev,offset,drv_msg);

    DbgMessage(pdev, INFORMi , "mcp_cmd_send: Sent driver load cmd to MCP at 0x%x\n", drv_msg);

    return LM_STATUS_SUCCESS ;
}

/*
 *Function Name:lm_mcp_cmd_response
 *
 *Parameters:
 *              TBD - add timeout value
 *Description:
 *              assumption - only one request can be sent simultaneously
 *Returns:
 *
 */
lm_status_t lm_mcp_cmd_response( struct _lm_device_t *pdev,
                                 lm_mcp_mb_type       mcp_mb_type,
                                 u32_t                drv_msg,
                                 u32_t                timeout,
                                 OUT u32_t*           p_fw_resp )
{
    u16_t*      p_seq      = NULL ;
    u32_t       offset     = 0 ;
    u32_t       drv_mask   = 0 ;
    u32_t       fw_mask    = 0 ;
    u32_t       cnt        = 0 ;
    u32_t       wait_itr   = 0 ;
    u32_t       resp_mask  = 0xffffffff ;
    lm_status_t lm_status  = LM_STATUS_SUCCESS ;
    const u8_t  func_mb_id = pdev->params.pfunc_mb_id;

    UNREFERENCED_PARAMETER_(timeout);

    DbgMessage(pdev, INFORMi, "### mcp_cmd_response mb_type=0x%x drv_msg=0x%x\n", mcp_mb_type, drv_msg );

    if ( CHK_NULL(p_fw_resp) )
    {
        return LM_STATUS_FAILURE ;
    }

    switch( mcp_mb_type )
    {
    case lm_mcp_mb_header:
        p_seq      = &pdev->vars.fw_wr_seq ;
        drv_mask   = DRV_MSG_SEQ_NUMBER_MASK ;
        fw_mask    = FW_MSG_SEQ_NUMBER_MASK ;
        resp_mask  = FW_MSG_CODE_MASK ;
        offset     = OFFSETOF(shmem_region_t, func_mb[func_mb_id].fw_mb_header) ;
        break;

        // TBD - is it needed ??
    case lm_mcp_mb_pulse:
        p_seq      = &pdev->vars.drv_pulse_wr_seq ;
        drv_mask   = DRV_PULSE_SEQ_MASK ;
        fw_mask    = MCP_PULSE_SEQ_MASK ;
        offset     = OFFSETOF(shmem_region_t, func_mb[func_mb_id].mcp_pulse_mb) ;
        break;

    case lm_mcp_mb_param:
    default:
        break;
    }

    if CHK_NULL( p_seq )
    {
        return LM_STATUS_INVALID_PARAMETER ;
    }

    lm_status = LM_STATUS_TIMEOUT ;

    // Wait for reply 5 sec per unloading function
    //TODO exponential back off
    wait_itr = 240 * FW_ACK_NUM_OF_POLL * PORT_MAX * (u32_t)(IS_MULTI_VNIC(pdev) ? MAX_VNIC_NUM : 1);
    for(cnt = 0; cnt < wait_itr; cnt++)
    {
        mm_wait(pdev, FW_ACK_POLL_TIME_MS * 50);

        LM_SHMEM_READ(pdev, offset, p_fw_resp);

        if(( (*p_fw_resp) & fw_mask) == ( (*p_seq) & drv_mask))
        {
            lm_status = LM_STATUS_SUCCESS ;
            break;
        }
    }

    *p_fw_resp = (*p_fw_resp & resp_mask);

    return lm_status ;
}

lm_status_t lm_mcp_cmd_send_recieve_non_atomic( struct _lm_device_t *pdev,
                                             lm_mcp_mb_type       mcp_mb_type,
                                             u32_t                drv_msg,
                                             u32_t                param,
                                             u32_t                timeout,
                                             OUT u32_t*           p_fw_resp )
{
    lm_status_t lm_status = LM_STATUS_FAILURE;
    u32_t       val       = 0;

    lm_status = lm_mcp_cmd_send( pdev, mcp_mb_type, drv_msg, param) ;

    if( LM_STATUS_SUCCESS != lm_status )
    {
        val = lm_mcp_check(pdev);
        DbgMessage(pdev, FATAL, "mcp_cmd_send_and_recieve: mcp_cmd_send drv_msg=0x%x failed. lm_status=0x%x mcp_check=0x%x\n", drv_msg, lm_status, val);
        DbgBreakMsg("mcp_cmd_send_and_recieve: mcp_cmd_send failed!\n");
        return lm_status;
    }

    DbgMessage(pdev, INFORMi , "mcp_cmd_send_and_recieve: Sent driver cmd=0x%x to MCP\n",  drv_msg );

    lm_status = lm_mcp_cmd_response( pdev, mcp_mb_type, drv_msg, timeout, p_fw_resp ) ;

    if( LM_STATUS_SUCCESS != lm_status )
    {
        val = lm_mcp_check(pdev);
        DbgMessage(pdev, FATAL, "mcp_cmd_send_and_recieve: mcp_cmd_response drv_msg=0x%x failed. lm_status=0x%x mcp_check=0x%x\n", drv_msg, lm_status, val);
        DbgBreakMsg("mcp_cmd_send_and_recieve: mcp_cmd_response failed!\n");
        return lm_status;
    }

    DbgMessage(pdev, INFORMi , "mcp_cmd_send_and_recieve: Got response 0x%x from MCP\n", *p_fw_resp );

    return LM_STATUS_SUCCESS;
}

/*
 *Function Name:lm_mcp_cmd_send_recieve
 *
 *Parameters:
 *
 *Description:
 *
 *Returns: lm_status_t
 *
 */
lm_status_t lm_mcp_cmd_send_recieve( struct _lm_device_t *pdev,
                                     lm_mcp_mb_type       mcp_mb_type,
                                     u32_t                drv_msg,
                                     u32_t                param,
                                     u32_t                timeout,
                                     OUT u32_t*           p_fw_resp )
{
    lm_status_t lm_status = LM_STATUS_SUCCESS ;

    MM_ACQUIRE_MCP_LOCK(pdev);

    lm_status = lm_mcp_cmd_send_recieve_non_atomic(pdev, mcp_mb_type, drv_msg, param, timeout, p_fw_resp);

    MM_RELEASE_MCP_LOCK(pdev);

    return lm_status ;
}


// check if mcp program counter is advancing, In case it doesn't return the value in case it does, return 0
u32_t lm_mcp_check( struct _lm_device_t *pdev)
{
    static u32_t const offset = MCP_REG_MCPR_CPU_PROGRAM_COUNTER ;
    u32_t              reg    = 0 ;
    u32_t              i      = 0 ;

    reg = REG_RD(pdev, offset);

    for( i = 0; i<4; i++ )
    {
        if( REG_RD(pdev, offset) != reg )
        {
            return 0; // OK
        }
    }
    return reg; // mcp is hang on this value as program counter!
}

/**lm_mcp_cli_idx_to_drv_cap_flag
 * Get the flag to set in drv_capabilities_flag for a given LM
 * client.
 *
 * @param cli_id the LM client index.
 *
 * @return u32_t the appropriate flag for cli_id, or 0 if there
 *         is no matching flag.
 */
static u32_t lm_mcp_cli_idx_to_drv_cap_flag(IN const lm_cli_idx_t cli_id)
{
    switch(cli_id)
    {
    case LM_CLI_IDX_NDIS:
        return DRV_FLAGS_CAPABILITIES_LOADED_L2;
    case LM_CLI_IDX_ISCSI:
        return DRV_FLAGS_CAPABILITIES_LOADED_ISCSI;
    case LM_CLI_IDX_FCOE:
        return DRV_FLAGS_CAPABILITIES_LOADED_FCOE;
    case LM_CLI_IDX_MAX://may happen for UM clients that have no matching LM client, such as diag.
        return 0;
    case LM_CLI_IDX_FWD://fallthrough - this client has no bind/unbind flow and no matching UM client
    case LM_CLI_IDX_OOO://fallthrough - this client has no bind/unbind flow and no matching UM client
    default:
        DbgBreakMsg("Invalid client type");
        return 0;
    }
}

void lm_mcp_indicate_client_imp(struct _lm_device_t *pdev, IN const lm_cli_idx_t cli_id, IN const u8_t b_bind )
{
    const u32_t drv_cap_client = lm_mcp_cli_idx_to_drv_cap_flag(cli_id);
    const u32_t func_mb_id = FUNC_MAILBOX_ID(pdev);
    const u32_t shmem_offset = OFFSETOF(shmem2_region_t, drv_capabilities_flag[func_mb_id]);
    u32_t       drv_cap_shmem  = 0;

    if (CHIP_IS_E1x(pdev) ||
        !LM_SHMEM2_HAS(pdev, drv_capabilities_flag))
    {
        return;
    }

    if (0 == drv_cap_client)
    {
        //this is a client that does not require updating the SHMEM
        return;
    }

    LM_SHMEM2_READ(pdev, shmem_offset, &drv_cap_shmem);

    if( b_bind )
    {
        SET_FLAGS( drv_cap_shmem, drv_cap_client );
    }
    else
    {
        RESET_FLAGS( drv_cap_shmem, drv_cap_client );
    }

    LM_SHMEM2_WRITE(pdev, shmem_offset, drv_cap_shmem);
}

void lm_mcp_indicate_client_bind(struct _lm_device_t *pdev, IN const lm_cli_idx_t cli_id)
{
    lm_mcp_indicate_client_imp(pdev, cli_id, TRUE);
}

void lm_mcp_indicate_client_unbind(struct _lm_device_t *pdev, IN const lm_cli_idx_t cli_id)
{
    lm_mcp_indicate_client_imp(pdev, cli_id, FALSE);
}