Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  qed_mcp.c   Sprache: C

 
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
/* QLogic qed NIC Driver
 * Copyright (c) 2015-2017  QLogic Corporation
 * Copyright (c) 2019-2020 Marvell International Ltd.
 */


#include <linux/types.h>
#include <asm/byteorder.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/etherdevice.h>
#include "qed.h"
#include "qed_cxt.h"
#include "qed_dcbx.h"
#include "qed_hsi.h"
#include "qed_mfw_hsi.h"
#include "qed_hw.h"
#include "qed_mcp.h"
#include "qed_reg_addr.h"
#include "qed_sriov.h"

#define GRCBASE_MCP     0xe00000

#define QED_MCP_RESP_ITER_US 10

#define QED_DRV_MB_MAX_RETRIES (500 * 1000) /* Account for 5 sec */
#define QED_MCP_RESET_RETRIES (50 * 1000) /* Account for 500 msec */

#define DRV_INNER_WR(_p_hwfn, _p_ptt, _ptr, _offset, _val)      \
 qed_wr(_p_hwfn, _p_ptt, (_p_hwfn->mcp_info->_ptr + (_offset)), \
        _val)

#define DRV_INNER_RD(_p_hwfn, _p_ptt, _ptr, _offset) \
 qed_rd(_p_hwfn, _p_ptt, (_p_hwfn->mcp_info->_ptr + (_offset)))

#define DRV_MB_WR(_p_hwfn, _p_ptt, _field, _val)  \
 DRV_INNER_WR(p_hwfn, _p_ptt, drv_mb_addr, \
       offsetof(struct public_drv_mb, _field), _val)

#define DRV_MB_RD(_p_hwfn, _p_ptt, _field)    \
 DRV_INNER_RD(_p_hwfn, _p_ptt, drv_mb_addr, \
       offsetof(struct public_drv_mb, _field))

#define PDA_COMP (((FW_MAJOR_VERSION) + (FW_MINOR_VERSION << 8)) << \
    DRV_ID_PDA_COMP_VER_SHIFT)

#define MCP_BYTES_PER_MBIT_SHIFT 17

bool qed_mcp_is_init(struct qed_hwfn *p_hwfn)
{
 if (!p_hwfn->mcp_info || !p_hwfn->mcp_info->public_base)
  return false;
 return true;
}

void qed_mcp_cmd_port_init(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt)
{
 u32 addr = SECTION_OFFSIZE_ADDR(p_hwfn->mcp_info->public_base,
     PUBLIC_PORT);
 u32 mfw_mb_offsize = qed_rd(p_hwfn, p_ptt, addr);

 p_hwfn->mcp_info->port_addr = SECTION_ADDR(mfw_mb_offsize,
         MFW_PORT(p_hwfn));
 DP_VERBOSE(p_hwfn, QED_MSG_SP,
     "port_addr = 0x%x, port_id 0x%02x\n",
     p_hwfn->mcp_info->port_addr, MFW_PORT(p_hwfn));
}

void qed_mcp_read_mb(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt)
{
 u32 length = MFW_DRV_MSG_MAX_DWORDS(p_hwfn->mcp_info->mfw_mb_length);
 u32 tmp, i;

 if (!p_hwfn->mcp_info->public_base)
  return;

 for (i = 0; i < length; i++) {
  tmp = qed_rd(p_hwfn, p_ptt,
        p_hwfn->mcp_info->mfw_mb_addr +
        (i << 2) + sizeof(u32));

  /* The MB data is actually BE; Need to force it to cpu */
  ((u32 *)p_hwfn->mcp_info->mfw_mb_cur)[i] =
   be32_to_cpu((__force __be32)tmp);
 }
}

struct qed_mcp_cmd_elem {
 struct list_head list;
 struct qed_mcp_mb_params *p_mb_params;
 u16 expected_seq_num;
 bool b_is_completed;
};

/* Must be called while cmd_lock is acquired */
static struct qed_mcp_cmd_elem *
qed_mcp_cmd_add_elem(struct qed_hwfn *p_hwfn,
       struct qed_mcp_mb_params *p_mb_params,
       u16 expected_seq_num)
{
 struct qed_mcp_cmd_elem *p_cmd_elem = NULL;

 p_cmd_elem = kzalloc(sizeof(*p_cmd_elem), GFP_ATOMIC);
 if (!p_cmd_elem)
  goto out;

 p_cmd_elem->p_mb_params = p_mb_params;
 p_cmd_elem->expected_seq_num = expected_seq_num;
 list_add(&p_cmd_elem->list, &p_hwfn->mcp_info->cmd_list);
out:
 return p_cmd_elem;
}

/* Must be called while cmd_lock is acquired */
static void qed_mcp_cmd_del_elem(struct qed_hwfn *p_hwfn,
     struct qed_mcp_cmd_elem *p_cmd_elem)
{
 list_del(&p_cmd_elem->list);
 kfree(p_cmd_elem);
}

/* Must be called while cmd_lock is acquired */
static struct qed_mcp_cmd_elem *qed_mcp_cmd_get_elem(struct qed_hwfn *p_hwfn,
           u16 seq_num)
{
 struct qed_mcp_cmd_elem *p_cmd_elem = NULL;

 list_for_each_entry(p_cmd_elem, &p_hwfn->mcp_info->cmd_list, list) {
  if (p_cmd_elem->expected_seq_num == seq_num)
   return p_cmd_elem;
 }

 return NULL;
}

int qed_mcp_free(struct qed_hwfn *p_hwfn)
{
 if (p_hwfn->mcp_info) {
  struct qed_mcp_cmd_elem *p_cmd_elem = NULL, *p_tmp;

  kfree(p_hwfn->mcp_info->mfw_mb_cur);
  kfree(p_hwfn->mcp_info->mfw_mb_shadow);

  spin_lock_bh(&p_hwfn->mcp_info->cmd_lock);
  list_for_each_entry_safe(p_cmd_elem,
      p_tmp,
      &p_hwfn->mcp_info->cmd_list, list) {
   qed_mcp_cmd_del_elem(p_hwfn, p_cmd_elem);
  }
  spin_unlock_bh(&p_hwfn->mcp_info->cmd_lock);
 }

 kfree(p_hwfn->mcp_info);
 p_hwfn->mcp_info = NULL;

 return 0;
}

/* Maximum of 1 sec to wait for the SHMEM ready indication */
#define QED_MCP_SHMEM_RDY_MAX_RETRIES 20
#define QED_MCP_SHMEM_RDY_ITER_MS 50

static int qed_load_mcp_offsets(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt)
{
 struct qed_mcp_info *p_info = p_hwfn->mcp_info;
 u8 cnt = QED_MCP_SHMEM_RDY_MAX_RETRIES;
 u8 msec = QED_MCP_SHMEM_RDY_ITER_MS;
 u32 drv_mb_offsize, mfw_mb_offsize;
 u32 mcp_pf_id = MCP_PF_ID(p_hwfn);

 p_info->public_base = qed_rd(p_hwfn, p_ptt, MISC_REG_SHARED_MEM_ADDR);
 if (!p_info->public_base) {
  DP_NOTICE(p_hwfn,
     "The address of the MCP scratch-pad is not configured\n");
  return -EINVAL;
 }

 p_info->public_base |= GRCBASE_MCP;

 /* Get the MFW MB address and number of supported messages */
 mfw_mb_offsize = qed_rd(p_hwfn, p_ptt,
    SECTION_OFFSIZE_ADDR(p_info->public_base,
           PUBLIC_MFW_MB));
 p_info->mfw_mb_addr = SECTION_ADDR(mfw_mb_offsize, mcp_pf_id);
 p_info->mfw_mb_length = (u16)qed_rd(p_hwfn, p_ptt,
         p_info->mfw_mb_addr +
         offsetof(struct public_mfw_mb,
           sup_msgs));

 /* The driver can notify that there was an MCP reset, and might read the
 * SHMEM values before the MFW has completed initializing them.
 * To avoid this, the "sup_msgs" field in the MFW mailbox is used as a
 * data ready indication.
 */

 while (!p_info->mfw_mb_length && --cnt) {
  msleep(msec);
  p_info->mfw_mb_length =
   (u16)qed_rd(p_hwfn, p_ptt,
        p_info->mfw_mb_addr +
        offsetof(struct public_mfw_mb, sup_msgs));
 }

 if (!cnt) {
  DP_NOTICE(p_hwfn,
     "Failed to get the SHMEM ready notification after %d msec\n",
     QED_MCP_SHMEM_RDY_MAX_RETRIES * msec);
  return -EBUSY;
 }

 /* Calculate the driver and MFW mailbox address */
 drv_mb_offsize = qed_rd(p_hwfn, p_ptt,
    SECTION_OFFSIZE_ADDR(p_info->public_base,
           PUBLIC_DRV_MB));
 p_info->drv_mb_addr = SECTION_ADDR(drv_mb_offsize, mcp_pf_id);
 DP_VERBOSE(p_hwfn, QED_MSG_SP,
     "drv_mb_offsiz = 0x%x, drv_mb_addr = 0x%x mcp_pf_id = 0x%x\n",
     drv_mb_offsize, p_info->drv_mb_addr, mcp_pf_id);

 /* Get the current driver mailbox sequence before sending
 * the first command
 */

 p_info->drv_mb_seq = DRV_MB_RD(p_hwfn, p_ptt, drv_mb_header) &
        DRV_MSG_SEQ_NUMBER_MASK;

 /* Get current FW pulse sequence */
 p_info->drv_pulse_seq = DRV_MB_RD(p_hwfn, p_ptt, drv_pulse_mb) &
    DRV_PULSE_SEQ_MASK;

 p_info->mcp_hist = qed_rd(p_hwfn, p_ptt, MISCS_REG_GENERIC_POR_0);

 return 0;
}

int qed_mcp_cmd_init(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt)
{
 struct qed_mcp_info *p_info;
 u32 size;

 /* Allocate mcp_info structure */
 p_hwfn->mcp_info = kzalloc(sizeof(*p_hwfn->mcp_info), GFP_KERNEL);
 if (!p_hwfn->mcp_info)
  goto err;
 p_info = p_hwfn->mcp_info;

 /* Initialize the MFW spinlock */
 spin_lock_init(&p_info->cmd_lock);
 spin_lock_init(&p_info->link_lock);
 spin_lock_init(&p_info->unload_lock);

 INIT_LIST_HEAD(&p_info->cmd_list);

 if (qed_load_mcp_offsets(p_hwfn, p_ptt) != 0) {
  DP_NOTICE(p_hwfn, "MCP is not initialized\n");
  /* Do not free mcp_info here, since public_base indicate that
 * the MCP is not initialized
 */

  return 0;
 }

 size = MFW_DRV_MSG_MAX_DWORDS(p_info->mfw_mb_length) * sizeof(u32);
 p_info->mfw_mb_cur = kzalloc(size, GFP_KERNEL);
 p_info->mfw_mb_shadow = kzalloc(size, GFP_KERNEL);
 if (!p_info->mfw_mb_cur || !p_info->mfw_mb_shadow)
  goto err;

 return 0;

err:
 qed_mcp_free(p_hwfn);
 return -ENOMEM;
}

static void qed_mcp_reread_offsets(struct qed_hwfn *p_hwfn,
       struct qed_ptt *p_ptt)
{
 u32 generic_por_0 = qed_rd(p_hwfn, p_ptt, MISCS_REG_GENERIC_POR_0);

 /* Use MCP history register to check if MCP reset occurred between init
 * time and now.
 */

 if (p_hwfn->mcp_info->mcp_hist != generic_por_0) {
  DP_VERBOSE(p_hwfn,
      QED_MSG_SP,
      "Rereading MCP offsets [mcp_hist 0x%08x, generic_por_0 0x%08x]\n",
      p_hwfn->mcp_info->mcp_hist, generic_por_0);

  qed_load_mcp_offsets(p_hwfn, p_ptt);
  qed_mcp_cmd_port_init(p_hwfn, p_ptt);
 }
}

int qed_mcp_reset(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt)
{
 u32 org_mcp_reset_seq, seq, delay = QED_MCP_RESP_ITER_US, cnt = 0;
 int rc = 0;

 if (p_hwfn->mcp_info->b_block_cmd) {
  DP_NOTICE(p_hwfn,
     "The MFW is not responsive. Avoid sending MCP_RESET mailbox command.\n");
  return -EBUSY;
 }

 /* Ensure that only a single thread is accessing the mailbox */
 spin_lock_bh(&p_hwfn->mcp_info->cmd_lock);

 org_mcp_reset_seq = qed_rd(p_hwfn, p_ptt, MISCS_REG_GENERIC_POR_0);

 /* Set drv command along with the updated sequence */
 qed_mcp_reread_offsets(p_hwfn, p_ptt);
 seq = ++p_hwfn->mcp_info->drv_mb_seq;
 DRV_MB_WR(p_hwfn, p_ptt, drv_mb_header, (DRV_MSG_CODE_MCP_RESET | seq));

 do {
  /* Wait for MFW response */
  udelay(delay);
  /* Give the FW up to 500 second (50*1000*10usec) */
 } while ((org_mcp_reset_seq == qed_rd(p_hwfn, p_ptt,
           MISCS_REG_GENERIC_POR_0)) &&
   (cnt++ < QED_MCP_RESET_RETRIES));

 if (org_mcp_reset_seq !=
     qed_rd(p_hwfn, p_ptt, MISCS_REG_GENERIC_POR_0)) {
  DP_VERBOSE(p_hwfn, QED_MSG_SP,
      "MCP was reset after %d usec\n", cnt * delay);
 } else {
  DP_ERR(p_hwfn, "Failed to reset MCP\n");
  rc = -EAGAIN;
 }

 spin_unlock_bh(&p_hwfn->mcp_info->cmd_lock);

 return rc;
}

/* Must be called while cmd_lock is acquired */
static bool qed_mcp_has_pending_cmd(struct qed_hwfn *p_hwfn)
{
 struct qed_mcp_cmd_elem *p_cmd_elem;

 /* There is at most one pending command at a certain time, and if it
 * exists - it is placed at the HEAD of the list.
 */

 if (!list_empty(&p_hwfn->mcp_info->cmd_list)) {
  p_cmd_elem = list_first_entry(&p_hwfn->mcp_info->cmd_list,
           struct qed_mcp_cmd_elem, list);
  return !p_cmd_elem->b_is_completed;
 }

 return false;
}

/* Must be called while cmd_lock is acquired */
static int
qed_mcp_update_pending_cmd(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt)
{
 struct qed_mcp_mb_params *p_mb_params;
 struct qed_mcp_cmd_elem *p_cmd_elem;
 u32 mcp_resp;
 u16 seq_num;

 mcp_resp = DRV_MB_RD(p_hwfn, p_ptt, fw_mb_header);
 seq_num = (u16)(mcp_resp & FW_MSG_SEQ_NUMBER_MASK);

 /* Return if no new non-handled response has been received */
 if (seq_num != p_hwfn->mcp_info->drv_mb_seq)
  return -EAGAIN;

 p_cmd_elem = qed_mcp_cmd_get_elem(p_hwfn, seq_num);
 if (!p_cmd_elem) {
  DP_ERR(p_hwfn,
         "Failed to find a pending mailbox cmd that expects sequence number %d\n",
         seq_num);
  return -EINVAL;
 }

 p_mb_params = p_cmd_elem->p_mb_params;

 /* Get the MFW response along with the sequence number */
 p_mb_params->mcp_resp = mcp_resp;

 /* Get the MFW param */
 p_mb_params->mcp_param = DRV_MB_RD(p_hwfn, p_ptt, fw_mb_param);

 /* Get the union data */
 if (p_mb_params->p_data_dst && p_mb_params->data_dst_size) {
  u32 union_data_addr = p_hwfn->mcp_info->drv_mb_addr +
          offsetof(struct public_drv_mb,
            union_data);
  qed_memcpy_from(p_hwfn, p_ptt, p_mb_params->p_data_dst,
    union_data_addr, p_mb_params->data_dst_size);
 }

 p_cmd_elem->b_is_completed = true;

 return 0;
}

/* Must be called while cmd_lock is acquired */
static void __qed_mcp_cmd_and_union(struct qed_hwfn *p_hwfn,
        struct qed_ptt *p_ptt,
        struct qed_mcp_mb_params *p_mb_params,
        u16 seq_num)
{
 union drv_union_data union_data;
 u32 union_data_addr;

 /* Set the union data */
 union_data_addr = p_hwfn->mcp_info->drv_mb_addr +
     offsetof(struct public_drv_mb, union_data);
 memset(&union_data, 0, sizeof(union_data));
 if (p_mb_params->p_data_src && p_mb_params->data_src_size)
  memcpy(&union_data, p_mb_params->p_data_src,
         p_mb_params->data_src_size);
 qed_memcpy_to(p_hwfn, p_ptt, union_data_addr, &union_data,
        sizeof(union_data));

 /* Set the drv param */
 DRV_MB_WR(p_hwfn, p_ptt, drv_mb_param, p_mb_params->param);

 /* Set the drv command along with the sequence number */
 DRV_MB_WR(p_hwfn, p_ptt, drv_mb_header, (p_mb_params->cmd | seq_num));

 DP_VERBOSE(p_hwfn, QED_MSG_SP,
     "MFW mailbox: command 0x%08x param 0x%08x\n",
     (p_mb_params->cmd | seq_num), p_mb_params->param);
}

static void qed_mcp_cmd_set_blocking(struct qed_hwfn *p_hwfn, bool block_cmd)
{
 p_hwfn->mcp_info->b_block_cmd = block_cmd;

 DP_INFO(p_hwfn, "%s sending of mailbox commands to the MFW\n",
  block_cmd ? "Block" : "Unblock");
}

static void qed_mcp_print_cpu_info(struct qed_hwfn *p_hwfn,
       struct qed_ptt *p_ptt)
{
 u32 cpu_mode, cpu_state, cpu_pc_0, cpu_pc_1, cpu_pc_2;
 u32 delay = QED_MCP_RESP_ITER_US;

 cpu_mode = qed_rd(p_hwfn, p_ptt, MCP_REG_CPU_MODE);
 cpu_state = qed_rd(p_hwfn, p_ptt, MCP_REG_CPU_STATE);
 cpu_pc_0 = qed_rd(p_hwfn, p_ptt, MCP_REG_CPU_PROGRAM_COUNTER);
 udelay(delay);
 cpu_pc_1 = qed_rd(p_hwfn, p_ptt, MCP_REG_CPU_PROGRAM_COUNTER);
 udelay(delay);
 cpu_pc_2 = qed_rd(p_hwfn, p_ptt, MCP_REG_CPU_PROGRAM_COUNTER);

 DP_NOTICE(p_hwfn,
    "MCP CPU info: mode 0x%08x, state 0x%08x, pc {0x%08x, 0x%08x, 0x%08x}\n",
    cpu_mode, cpu_state, cpu_pc_0, cpu_pc_1, cpu_pc_2);
}

static int
_qed_mcp_cmd_and_union(struct qed_hwfn *p_hwfn,
         struct qed_ptt *p_ptt,
         struct qed_mcp_mb_params *p_mb_params)
{
 struct qed_mcp_cmd_elem *p_cmd_elem;
 u16 seq_num;
 u32 cnt = 0;
 int rc = 0;

 /* Wait until the mailbox is non-occupied */
 do {
  /* Exit the loop if there is no pending command, or if the
 * pending command is completed during this iteration.
 * The spinlock stays locked until the command is sent.
 */


  spin_lock_bh(&p_hwfn->mcp_info->cmd_lock);

  if (!qed_mcp_has_pending_cmd(p_hwfn))
   break;

  rc = qed_mcp_update_pending_cmd(p_hwfn, p_ptt);
  if (!rc)
   break;
  else if (rc != -EAGAIN)
   goto err;

  spin_unlock_bh(&p_hwfn->mcp_info->cmd_lock);

  if (QED_MB_FLAGS_IS_SET(p_mb_params, CAN_SLEEP))
   usleep_range(QED_MCP_RESP_ITER_US,
         QED_MCP_RESP_ITER_US * 2);
  else
   udelay(QED_MCP_RESP_ITER_US);
 } while (++cnt < QED_DRV_MB_MAX_RETRIES);

 if (cnt >= QED_DRV_MB_MAX_RETRIES) {
  DP_NOTICE(p_hwfn,
     "The MFW mailbox is occupied by an uncompleted command. Failed to send command 0x%08x [param 0x%08x].\n",
     p_mb_params->cmd, p_mb_params->param);
  return -EAGAIN;
 }

 /* Send the mailbox command */
 qed_mcp_reread_offsets(p_hwfn, p_ptt);
 seq_num = ++p_hwfn->mcp_info->drv_mb_seq;
 p_cmd_elem = qed_mcp_cmd_add_elem(p_hwfn, p_mb_params, seq_num);
 if (!p_cmd_elem) {
  rc = -ENOMEM;
  goto err;
 }

 __qed_mcp_cmd_and_union(p_hwfn, p_ptt, p_mb_params, seq_num);
 spin_unlock_bh(&p_hwfn->mcp_info->cmd_lock);

 /* Wait for the MFW response */
 do {
  /* Exit the loop if the command is already completed, or if the
 * command is completed during this iteration.
 * The spinlock stays locked until the list element is removed.
 */


  if (QED_MB_FLAGS_IS_SET(p_mb_params, CAN_SLEEP))
   usleep_range(QED_MCP_RESP_ITER_US,
         QED_MCP_RESP_ITER_US * 2);
  else
   udelay(QED_MCP_RESP_ITER_US);

  spin_lock_bh(&p_hwfn->mcp_info->cmd_lock);

  if (p_cmd_elem->b_is_completed)
   break;

  rc = qed_mcp_update_pending_cmd(p_hwfn, p_ptt);
  if (!rc)
   break;
  else if (rc != -EAGAIN)
   goto err;

  spin_unlock_bh(&p_hwfn->mcp_info->cmd_lock);
 } while (++cnt < QED_DRV_MB_MAX_RETRIES);

 if (cnt >= QED_DRV_MB_MAX_RETRIES) {
  DP_NOTICE(p_hwfn,
     "The MFW failed to respond to command 0x%08x [param 0x%08x].\n",
     p_mb_params->cmd, p_mb_params->param);
  qed_mcp_print_cpu_info(p_hwfn, p_ptt);

  spin_lock_bh(&p_hwfn->mcp_info->cmd_lock);
  qed_mcp_cmd_del_elem(p_hwfn, p_cmd_elem);
  spin_unlock_bh(&p_hwfn->mcp_info->cmd_lock);

  if (!QED_MB_FLAGS_IS_SET(p_mb_params, AVOID_BLOCK))
   qed_mcp_cmd_set_blocking(p_hwfn, true);

  qed_hw_err_notify(p_hwfn, p_ptt,
      QED_HW_ERR_MFW_RESP_FAIL, NULL);
  return -EAGAIN;
 }

 qed_mcp_cmd_del_elem(p_hwfn, p_cmd_elem);
 spin_unlock_bh(&p_hwfn->mcp_info->cmd_lock);

 DP_VERBOSE(p_hwfn,
     QED_MSG_SP,
     "MFW mailbox: response 0x%08x param 0x%08x [after %d.%03d ms]\n",
     p_mb_params->mcp_resp,
     p_mb_params->mcp_param,
     (cnt * QED_MCP_RESP_ITER_US) / 1000,
     (cnt * QED_MCP_RESP_ITER_US) % 1000);

 /* Clear the sequence number from the MFW response */
 p_mb_params->mcp_resp &= FW_MSG_CODE_MASK;

 return 0;

err:
 spin_unlock_bh(&p_hwfn->mcp_info->cmd_lock);
 return rc;
}

static int qed_mcp_cmd_and_union(struct qed_hwfn *p_hwfn,
     struct qed_ptt *p_ptt,
     struct qed_mcp_mb_params *p_mb_params)
{
 size_t union_data_size = sizeof(union drv_union_data);

 /* MCP not initialized */
 if (!qed_mcp_is_init(p_hwfn)) {
  DP_NOTICE(p_hwfn, "MFW is not initialized!\n");
  return -EBUSY;
 }

 if (p_hwfn->mcp_info->b_block_cmd) {
  DP_NOTICE(p_hwfn,
     "The MFW is not responsive. Avoid sending mailbox command 0x%08x [param 0x%08x].\n",
     p_mb_params->cmd, p_mb_params->param);
  return -EBUSY;
 }

 if (p_mb_params->data_src_size > union_data_size ||
     p_mb_params->data_dst_size > union_data_size) {
  DP_ERR(p_hwfn,
         "The provided size is larger than the union data size [src_size %u, dst_size %u, union_data_size %zu]\n",
         p_mb_params->data_src_size,
         p_mb_params->data_dst_size, union_data_size);
  return -EINVAL;
 }

 return _qed_mcp_cmd_and_union(p_hwfn, p_ptt, p_mb_params);
}

static int _qed_mcp_cmd(struct qed_hwfn *p_hwfn,
   struct qed_ptt *p_ptt,
   u32 cmd,
   u32 param,
   u32 *o_mcp_resp,
   u32 *o_mcp_param,
   bool can_sleep)
{
 struct qed_mcp_mb_params mb_params;
 int rc;

 memset(&mb_params, 0, sizeof(mb_params));
 mb_params.cmd = cmd;
 mb_params.param = param;
 mb_params.flags = can_sleep ? QED_MB_FLAG_CAN_SLEEP : 0;

 rc = qed_mcp_cmd_and_union(p_hwfn, p_ptt, &mb_params);
 if (rc)
  return rc;

 *o_mcp_resp = mb_params.mcp_resp;
 *o_mcp_param = mb_params.mcp_param;

 return 0;
}

int qed_mcp_cmd(struct qed_hwfn *p_hwfn,
  struct qed_ptt *p_ptt,
  u32 cmd,
  u32 param,
  u32 *o_mcp_resp,
  u32 *o_mcp_param)
{
 return (_qed_mcp_cmd(p_hwfn, p_ptt, cmd, param,
        o_mcp_resp, o_mcp_param, true));
}

int qed_mcp_cmd_nosleep(struct qed_hwfn *p_hwfn,
   struct qed_ptt *p_ptt,
   u32 cmd,
   u32 param,
   u32 *o_mcp_resp,
   u32 *o_mcp_param)
{
 return (_qed_mcp_cmd(p_hwfn, p_ptt, cmd, param,
        o_mcp_resp, o_mcp_param, false));
}

static int
qed_mcp_nvm_wr_cmd(struct qed_hwfn *p_hwfn,
     struct qed_ptt *p_ptt,
     u32 cmd,
     u32 param,
     u32 *o_mcp_resp,
     u32 *o_mcp_param, u32 i_txn_size, u32 *i_buf)
{
 struct qed_mcp_mb_params mb_params;
 int rc;

 memset(&mb_params, 0, sizeof(mb_params));
 mb_params.cmd = cmd;
 mb_params.param = param;
 mb_params.p_data_src = i_buf;
 mb_params.data_src_size = (u8)i_txn_size;
 rc = qed_mcp_cmd_and_union(p_hwfn, p_ptt, &mb_params);
 if (rc)
  return rc;

 *o_mcp_resp = mb_params.mcp_resp;
 *o_mcp_param = mb_params.mcp_param;

 /* nvm_info needs to be updated */
 p_hwfn->nvm_info.valid = false;

 return 0;
}

int qed_mcp_nvm_rd_cmd(struct qed_hwfn *p_hwfn,
         struct qed_ptt *p_ptt,
         u32 cmd,
         u32 param,
         u32 *o_mcp_resp,
         u32 *o_mcp_param,
         u32 *o_txn_size, u32 *o_buf, bool b_can_sleep)
{
 struct qed_mcp_mb_params mb_params;
 u8 raw_data[MCP_DRV_NVM_BUF_LEN];
 int rc;

 memset(&mb_params, 0, sizeof(mb_params));
 mb_params.cmd = cmd;
 mb_params.param = param;
 mb_params.p_data_dst = raw_data;

 /* Use the maximal value since the actual one is part of the response */
 mb_params.data_dst_size = MCP_DRV_NVM_BUF_LEN;
 if (b_can_sleep)
  mb_params.flags = QED_MB_FLAG_CAN_SLEEP;

 rc = qed_mcp_cmd_and_union(p_hwfn, p_ptt, &mb_params);
 if (rc)
  return rc;

 *o_mcp_resp = mb_params.mcp_resp;
 *o_mcp_param = mb_params.mcp_param;

 *o_txn_size = *o_mcp_param;
 memcpy(o_buf, raw_data, *o_txn_size);

 return 0;
}

static bool
qed_mcp_can_force_load(u8 drv_role,
         u8 exist_drv_role,
         enum qed_override_force_load override_force_load)
{
 bool can_force_load = false;

 switch (override_force_load) {
 case QED_OVERRIDE_FORCE_LOAD_ALWAYS:
  can_force_load = true;
  break;
 case QED_OVERRIDE_FORCE_LOAD_NEVER:
  can_force_load = false;
  break;
 default:
  can_force_load = (drv_role == DRV_ROLE_OS &&
      exist_drv_role == DRV_ROLE_PREBOOT) ||
     (drv_role == DRV_ROLE_KDUMP &&
      exist_drv_role == DRV_ROLE_OS);
  break;
 }

 return can_force_load;
}

static int qed_mcp_cancel_load_req(struct qed_hwfn *p_hwfn,
       struct qed_ptt *p_ptt)
{
 u32 resp = 0, param = 0;
 int rc;

 rc = qed_mcp_cmd(p_hwfn, p_ptt, DRV_MSG_CODE_CANCEL_LOAD_REQ, 0,
    &resp, ¶m);
 if (rc)
  DP_NOTICE(p_hwfn,
     "Failed to send cancel load request, rc = %d\n", rc);

 return rc;
}

#define BITMAP_IDX_FOR_CONFIG_QEDE BIT(0)
#define BITMAP_IDX_FOR_CONFIG_QED_SRIOV BIT(1)
#define BITMAP_IDX_FOR_CONFIG_QEDR BIT(2)
#define BITMAP_IDX_FOR_CONFIG_QEDF BIT(4)
#define BITMAP_IDX_FOR_CONFIG_QEDI BIT(5)
#define BITMAP_IDX_FOR_CONFIG_QED_LL2 BIT(6)

static u32 qed_get_config_bitmap(void)
{
 u32 config_bitmap = 0x0;

 if (IS_ENABLED(CONFIG_QEDE))
  config_bitmap |= BITMAP_IDX_FOR_CONFIG_QEDE;

 if (IS_ENABLED(CONFIG_QED_SRIOV))
  config_bitmap |= BITMAP_IDX_FOR_CONFIG_QED_SRIOV;

 if (IS_ENABLED(CONFIG_QED_RDMA))
  config_bitmap |= BITMAP_IDX_FOR_CONFIG_QEDR;

 if (IS_ENABLED(CONFIG_QED_FCOE))
  config_bitmap |= BITMAP_IDX_FOR_CONFIG_QEDF;

 if (IS_ENABLED(CONFIG_QED_ISCSI))
  config_bitmap |= BITMAP_IDX_FOR_CONFIG_QEDI;

 if (IS_ENABLED(CONFIG_QED_LL2))
  config_bitmap |= BITMAP_IDX_FOR_CONFIG_QED_LL2;

 return config_bitmap;
}

struct qed_load_req_in_params {
 u8 hsi_ver;
#define QED_LOAD_REQ_HSI_VER_DEFAULT 0
#define QED_LOAD_REQ_HSI_VER_1  1
 u32 drv_ver_0;
 u32 drv_ver_1;
 u32 fw_ver;
 u8 drv_role;
 u8 timeout_val;
 u8 force_cmd;
 bool avoid_eng_reset;
};

struct qed_load_req_out_params {
 u32 load_code;
 u32 exist_drv_ver_0;
 u32 exist_drv_ver_1;
 u32 exist_fw_ver;
 u8 exist_drv_role;
 u8 mfw_hsi_ver;
 bool drv_exists;
};

static int
__qed_mcp_load_req(struct qed_hwfn *p_hwfn,
     struct qed_ptt *p_ptt,
     struct qed_load_req_in_params *p_in_params,
     struct qed_load_req_out_params *p_out_params)
{
 struct qed_mcp_mb_params mb_params;
 struct load_req_stc load_req;
 struct load_rsp_stc load_rsp;
 u32 hsi_ver;
 int rc;

 memset(&load_req, 0, sizeof(load_req));
 load_req.drv_ver_0 = p_in_params->drv_ver_0;
 load_req.drv_ver_1 = p_in_params->drv_ver_1;
 load_req.fw_ver = p_in_params->fw_ver;
 QED_MFW_SET_FIELD(load_req.misc0, LOAD_REQ_ROLE, p_in_params->drv_role);
 QED_MFW_SET_FIELD(load_req.misc0, LOAD_REQ_LOCK_TO,
     p_in_params->timeout_val);
 QED_MFW_SET_FIELD(load_req.misc0, LOAD_REQ_FORCE,
     p_in_params->force_cmd);
 QED_MFW_SET_FIELD(load_req.misc0, LOAD_REQ_FLAGS0,
     p_in_params->avoid_eng_reset);

 hsi_ver = (p_in_params->hsi_ver == QED_LOAD_REQ_HSI_VER_DEFAULT) ?
    DRV_ID_MCP_HSI_VER_CURRENT :
    (p_in_params->hsi_ver << DRV_ID_MCP_HSI_VER_SHIFT);

 memset(&mb_params, 0, sizeof(mb_params));
 mb_params.cmd = DRV_MSG_CODE_LOAD_REQ;
 mb_params.param = PDA_COMP | hsi_ver | p_hwfn->cdev->drv_type;
 mb_params.p_data_src = &load_req;
 mb_params.data_src_size = sizeof(load_req);
 mb_params.p_data_dst = &load_rsp;
 mb_params.data_dst_size = sizeof(load_rsp);
 mb_params.flags = QED_MB_FLAG_CAN_SLEEP | QED_MB_FLAG_AVOID_BLOCK;

 DP_VERBOSE(p_hwfn, QED_MSG_SP,
     "Load Request: param 0x%08x [init_hw %d, drv_type %d, hsi_ver %d, pda 0x%04x]\n",
     mb_params.param,
     QED_MFW_GET_FIELD(mb_params.param, DRV_ID_DRV_INIT_HW),
     QED_MFW_GET_FIELD(mb_params.param, DRV_ID_DRV_TYPE),
     QED_MFW_GET_FIELD(mb_params.param, DRV_ID_MCP_HSI_VER),
     QED_MFW_GET_FIELD(mb_params.param, DRV_ID_PDA_COMP_VER));

 if (p_in_params->hsi_ver != QED_LOAD_REQ_HSI_VER_1) {
  DP_VERBOSE(p_hwfn, QED_MSG_SP,
      "Load Request: drv_ver 0x%08x_0x%08x, fw_ver 0x%08x, misc0 0x%08x [role %d, timeout %d, force %d, flags0 0x%x]\n",
      load_req.drv_ver_0,
      load_req.drv_ver_1,
      load_req.fw_ver,
      load_req.misc0,
      QED_MFW_GET_FIELD(load_req.misc0, LOAD_REQ_ROLE),
      QED_MFW_GET_FIELD(load_req.misc0,
          LOAD_REQ_LOCK_TO),
      QED_MFW_GET_FIELD(load_req.misc0, LOAD_REQ_FORCE),
      QED_MFW_GET_FIELD(load_req.misc0, LOAD_REQ_FLAGS0));
 }

 rc = qed_mcp_cmd_and_union(p_hwfn, p_ptt, &mb_params);
 if (rc) {
  DP_NOTICE(p_hwfn, "Failed to send load request, rc = %d\n", rc);
  return rc;
 }

 DP_VERBOSE(p_hwfn, QED_MSG_SP,
     "Load Response: resp 0x%08x\n", mb_params.mcp_resp);
 p_out_params->load_code = mb_params.mcp_resp;

 if (p_in_params->hsi_ver != QED_LOAD_REQ_HSI_VER_1 &&
     p_out_params->load_code != FW_MSG_CODE_DRV_LOAD_REFUSED_HSI_1) {
  DP_VERBOSE(p_hwfn,
      QED_MSG_SP,
      "Load Response: exist_drv_ver 0x%08x_0x%08x, exist_fw_ver 0x%08x, misc0 0x%08x [exist_role %d, mfw_hsi %d, flags0 0x%x]\n",
      load_rsp.drv_ver_0,
      load_rsp.drv_ver_1,
      load_rsp.fw_ver,
      load_rsp.misc0,
      QED_MFW_GET_FIELD(load_rsp.misc0, LOAD_RSP_ROLE),
      QED_MFW_GET_FIELD(load_rsp.misc0, LOAD_RSP_HSI),
      QED_MFW_GET_FIELD(load_rsp.misc0, LOAD_RSP_FLAGS0));

  p_out_params->exist_drv_ver_0 = load_rsp.drv_ver_0;
  p_out_params->exist_drv_ver_1 = load_rsp.drv_ver_1;
  p_out_params->exist_fw_ver = load_rsp.fw_ver;
  p_out_params->exist_drv_role =
      QED_MFW_GET_FIELD(load_rsp.misc0, LOAD_RSP_ROLE);
  p_out_params->mfw_hsi_ver =
      QED_MFW_GET_FIELD(load_rsp.misc0, LOAD_RSP_HSI);
  p_out_params->drv_exists =
      QED_MFW_GET_FIELD(load_rsp.misc0, LOAD_RSP_FLAGS0) &
      LOAD_RSP_FLAGS0_DRV_EXISTS;
 }

 return 0;
}

static int eocre_get_mfw_drv_role(struct qed_hwfn *p_hwfn,
      enum qed_drv_role drv_role,
      u8 *p_mfw_drv_role)
{
 switch (drv_role) {
 case QED_DRV_ROLE_OS:
  *p_mfw_drv_role = DRV_ROLE_OS;
  break;
 case QED_DRV_ROLE_KDUMP:
  *p_mfw_drv_role = DRV_ROLE_KDUMP;
  break;
 default:
  DP_ERR(p_hwfn, "Unexpected driver role %d\n", drv_role);
  return -EINVAL;
 }

 return 0;
}

enum qed_load_req_force {
 QED_LOAD_REQ_FORCE_NONE,
 QED_LOAD_REQ_FORCE_PF,
 QED_LOAD_REQ_FORCE_ALL,
};

static void qed_get_mfw_force_cmd(struct qed_hwfn *p_hwfn,
      enum qed_load_req_force force_cmd,
      u8 *p_mfw_force_cmd)
{
 switch (force_cmd) {
 case QED_LOAD_REQ_FORCE_NONE:
  *p_mfw_force_cmd = LOAD_REQ_FORCE_NONE;
  break;
 case QED_LOAD_REQ_FORCE_PF:
  *p_mfw_force_cmd = LOAD_REQ_FORCE_PF;
  break;
 case QED_LOAD_REQ_FORCE_ALL:
  *p_mfw_force_cmd = LOAD_REQ_FORCE_ALL;
  break;
 }
}

int qed_mcp_load_req(struct qed_hwfn *p_hwfn,
       struct qed_ptt *p_ptt,
       struct qed_load_req_params *p_params)
{
 struct qed_load_req_out_params out_params;
 struct qed_load_req_in_params in_params;
 u8 mfw_drv_role, mfw_force_cmd;
 int rc;

 memset(&in_params, 0, sizeof(in_params));
 in_params.hsi_ver = QED_LOAD_REQ_HSI_VER_DEFAULT;
 in_params.drv_ver_1 = qed_get_config_bitmap();
 in_params.fw_ver = STORM_FW_VERSION;
 rc = eocre_get_mfw_drv_role(p_hwfn, p_params->drv_role, &mfw_drv_role);
 if (rc)
  return rc;

 in_params.drv_role = mfw_drv_role;
 in_params.timeout_val = p_params->timeout_val;
 qed_get_mfw_force_cmd(p_hwfn,
         QED_LOAD_REQ_FORCE_NONE, &mfw_force_cmd);

 in_params.force_cmd = mfw_force_cmd;
 in_params.avoid_eng_reset = p_params->avoid_eng_reset;

 memset(&out_params, 0, sizeof(out_params));
 rc = __qed_mcp_load_req(p_hwfn, p_ptt, &in_params, &out_params);
 if (rc)
  return rc;

 /* First handle cases where another load request should/might be sent:
 * - MFW expects the old interface [HSI version = 1]
 * - MFW responds that a force load request is required
 */

 if (out_params.load_code == FW_MSG_CODE_DRV_LOAD_REFUSED_HSI_1) {
  DP_INFO(p_hwfn,
   "MFW refused a load request due to HSI > 1. Resending with HSI = 1\n");

  in_params.hsi_ver = QED_LOAD_REQ_HSI_VER_1;
  memset(&out_params, 0, sizeof(out_params));
  rc = __qed_mcp_load_req(p_hwfn, p_ptt, &in_params, &out_params);
  if (rc)
   return rc;
 } else if (out_params.load_code ==
     FW_MSG_CODE_DRV_LOAD_REFUSED_REQUIRES_FORCE) {
  if (qed_mcp_can_force_load(in_params.drv_role,
        out_params.exist_drv_role,
        p_params->override_force_load)) {
   DP_INFO(p_hwfn,
    "A force load is required [{role, fw_ver, drv_ver}: loading={%d, 0x%08x, x%08x_0x%08x}, existing={%d, 0x%08x, 0x%08x_0x%08x}]\n",
    in_params.drv_role, in_params.fw_ver,
    in_params.drv_ver_0, in_params.drv_ver_1,
    out_params.exist_drv_role,
    out_params.exist_fw_ver,
    out_params.exist_drv_ver_0,
    out_params.exist_drv_ver_1);

   qed_get_mfw_force_cmd(p_hwfn,
           QED_LOAD_REQ_FORCE_ALL,
           &mfw_force_cmd);

   in_params.force_cmd = mfw_force_cmd;
   memset(&out_params, 0, sizeof(out_params));
   rc = __qed_mcp_load_req(p_hwfn, p_ptt, &in_params,
      &out_params);
   if (rc)
    return rc;
  } else {
   DP_NOTICE(p_hwfn,
      "A force load is required [{role, fw_ver, drv_ver}: loading={%d, 0x%08x, x%08x_0x%08x}, existing={%d, 0x%08x, 0x%08x_0x%08x}] - Avoid\n",
      in_params.drv_role, in_params.fw_ver,
      in_params.drv_ver_0, in_params.drv_ver_1,
      out_params.exist_drv_role,
      out_params.exist_fw_ver,
      out_params.exist_drv_ver_0,
      out_params.exist_drv_ver_1);
   DP_NOTICE(p_hwfn,
      "Avoid sending a force load request to prevent disruption of active PFs\n");

   qed_mcp_cancel_load_req(p_hwfn, p_ptt);
   return -EBUSY;
  }
 }

 /* Now handle the other types of responses.
 * The "REFUSED_HSI_1" and "REFUSED_REQUIRES_FORCE" responses are not
 * expected here after the additional revised load requests were sent.
 */

 switch (out_params.load_code) {
 case FW_MSG_CODE_DRV_LOAD_ENGINE:
 case FW_MSG_CODE_DRV_LOAD_PORT:
 case FW_MSG_CODE_DRV_LOAD_FUNCTION:
  if (out_params.mfw_hsi_ver != QED_LOAD_REQ_HSI_VER_1 &&
      out_params.drv_exists) {
   /* The role and fw/driver version match, but the PF is
 * already loaded and has not been unloaded gracefully.
 */

   DP_NOTICE(p_hwfn,
      "PF is already loaded\n");
   return -EINVAL;
  }
  break;
 default:
  DP_NOTICE(p_hwfn,
     "Unexpected refusal to load request [resp 0x%08x]. Aborting.\n",
     out_params.load_code);
  return -EBUSY;
 }

 p_params->load_code = out_params.load_code;

 return 0;
}

int qed_mcp_load_done(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt)
{
 u32 resp = 0, param = 0;
 int rc;

 rc = qed_mcp_cmd(p_hwfn, p_ptt, DRV_MSG_CODE_LOAD_DONE, 0, &resp,
    ¶m);
 if (rc) {
  DP_NOTICE(p_hwfn,
     "Failed to send a LOAD_DONE command, rc = %d\n", rc);
  return rc;
 }

 /* Check if there is a DID mismatch between nvm-cfg/efuse */
 if (param & FW_MB_PARAM_LOAD_DONE_DID_EFUSE_ERROR)
  DP_NOTICE(p_hwfn,
     "warning: device configuration is not supported on this board type. The device may not function as expected.\n");

 return 0;
}

#define MFW_COMPLETION_MAX_ITER 5000
#define MFW_COMPLETION_INTERVAL_MS 1

int qed_mcp_unload_req(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt)
{
 struct qed_mcp_mb_params mb_params;
 u32 cnt = MFW_COMPLETION_MAX_ITER;
 u32 wol_param;
 int rc;

 switch (p_hwfn->cdev->wol_config) {
 case QED_OV_WOL_DISABLED:
  wol_param = DRV_MB_PARAM_UNLOAD_WOL_DISABLED;
  break;
 case QED_OV_WOL_ENABLED:
  wol_param = DRV_MB_PARAM_UNLOAD_WOL_ENABLED;
  break;
 default:
  DP_NOTICE(p_hwfn,
     "Unknown WoL configuration %02x\n",
     p_hwfn->cdev->wol_config);
  fallthrough;
 case QED_OV_WOL_DEFAULT:
  wol_param = DRV_MB_PARAM_UNLOAD_WOL_MCP;
 }

 memset(&mb_params, 0, sizeof(mb_params));
 mb_params.cmd = DRV_MSG_CODE_UNLOAD_REQ;
 mb_params.param = wol_param;
 mb_params.flags = QED_MB_FLAG_CAN_SLEEP | QED_MB_FLAG_AVOID_BLOCK;

 spin_lock_bh(&p_hwfn->mcp_info->unload_lock);
 set_bit(QED_MCP_BYPASS_PROC_BIT,
  &p_hwfn->mcp_info->mcp_handling_status);
 spin_unlock_bh(&p_hwfn->mcp_info->unload_lock);

 rc = qed_mcp_cmd_and_union(p_hwfn, p_ptt, &mb_params);

 while (test_bit(QED_MCP_IN_PROCESSING_BIT,
   &p_hwfn->mcp_info->mcp_handling_status) && --cnt)
  msleep(MFW_COMPLETION_INTERVAL_MS);

 if (!cnt)
  DP_NOTICE(p_hwfn,
     "Failed to wait MFW event completion after %d msec\n",
     MFW_COMPLETION_MAX_ITER * MFW_COMPLETION_INTERVAL_MS);

 return rc;
}

int qed_mcp_unload_done(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt)
{
 struct qed_mcp_mb_params mb_params;
 struct mcp_mac wol_mac;

 memset(&mb_params, 0, sizeof(mb_params));
 mb_params.cmd = DRV_MSG_CODE_UNLOAD_DONE;

 /* Set the primary MAC if WoL is enabled */
 if (p_hwfn->cdev->wol_config == QED_OV_WOL_ENABLED) {
  u8 *p_mac = p_hwfn->cdev->wol_mac;

  memset(&wol_mac, 0, sizeof(wol_mac));
  wol_mac.mac_upper = p_mac[0] << 8 | p_mac[1];
  wol_mac.mac_lower = p_mac[2] << 24 | p_mac[3] << 16 |
        p_mac[4] << 8 | p_mac[5];

  DP_VERBOSE(p_hwfn,
      (QED_MSG_SP | NETIF_MSG_IFDOWN),
      "Setting WoL MAC: %pM --> [%08x,%08x]\n",
      p_mac, wol_mac.mac_upper, wol_mac.mac_lower);

  mb_params.p_data_src = &wol_mac;
  mb_params.data_src_size = sizeof(wol_mac);
 }

 return qed_mcp_cmd_and_union(p_hwfn, p_ptt, &mb_params);
}

static void qed_mcp_handle_vf_flr(struct qed_hwfn *p_hwfn,
      struct qed_ptt *p_ptt)
{
 u32 addr = SECTION_OFFSIZE_ADDR(p_hwfn->mcp_info->public_base,
     PUBLIC_PATH);
 u32 mfw_path_offsize = qed_rd(p_hwfn, p_ptt, addr);
 u32 path_addr = SECTION_ADDR(mfw_path_offsize,
         QED_PATH_ID(p_hwfn));
 u32 disabled_vfs[VF_MAX_STATIC / 32];
 int i;

 DP_VERBOSE(p_hwfn,
     QED_MSG_SP,
     "Reading Disabled VF information from [offset %08x], path_addr %08x\n",
     mfw_path_offsize, path_addr);

 for (i = 0; i < (VF_MAX_STATIC / 32); i++) {
  disabled_vfs[i] = qed_rd(p_hwfn, p_ptt,
      path_addr +
      offsetof(struct public_path,
        mcp_vf_disabled) +
      sizeof(u32) * i);
  DP_VERBOSE(p_hwfn, (QED_MSG_SP | QED_MSG_IOV),
      "FLR-ed VFs [%08x,...,%08x] - %08x\n",
      i * 32, (i + 1) * 32 - 1, disabled_vfs[i]);
 }

 if (qed_iov_mark_vf_flr(p_hwfn, disabled_vfs))
  qed_schedule_iov(p_hwfn, QED_IOV_WQ_FLR_FLAG);
}

int qed_mcp_ack_vf_flr(struct qed_hwfn *p_hwfn,
         struct qed_ptt *p_ptt, u32 *vfs_to_ack)
{
 u32 addr = SECTION_OFFSIZE_ADDR(p_hwfn->mcp_info->public_base,
     PUBLIC_FUNC);
 u32 mfw_func_offsize = qed_rd(p_hwfn, p_ptt, addr);
 u32 func_addr = SECTION_ADDR(mfw_func_offsize,
         MCP_PF_ID(p_hwfn));
 struct qed_mcp_mb_params mb_params;
 int rc;
 int i;

 for (i = 0; i < (VF_MAX_STATIC / 32); i++)
  DP_VERBOSE(p_hwfn, (QED_MSG_SP | QED_MSG_IOV),
      "Acking VFs [%08x,...,%08x] - %08x\n",
      i * 32, (i + 1) * 32 - 1, vfs_to_ack[i]);

 memset(&mb_params, 0, sizeof(mb_params));
 mb_params.cmd = DRV_MSG_CODE_VF_DISABLED_DONE;
 mb_params.p_data_src = vfs_to_ack;
 mb_params.data_src_size = VF_MAX_STATIC / 8;
 rc = qed_mcp_cmd_and_union(p_hwfn, p_ptt, &mb_params);
 if (rc) {
  DP_NOTICE(p_hwfn, "Failed to pass ACK for VF flr to MFW\n");
  return -EBUSY;
 }

 /* Clear the ACK bits */
 for (i = 0; i < (VF_MAX_STATIC / 32); i++)
  qed_wr(p_hwfn, p_ptt,
         func_addr +
         offsetof(struct public_func, drv_ack_vf_disabled) +
         i * sizeof(u32), 0);

 return rc;
}

static void qed_mcp_handle_transceiver_change(struct qed_hwfn *p_hwfn,
           struct qed_ptt *p_ptt)
{
 u32 transceiver_state;

 transceiver_state = qed_rd(p_hwfn, p_ptt,
       p_hwfn->mcp_info->port_addr +
       offsetof(struct public_port,
         transceiver_data));

 DP_VERBOSE(p_hwfn,
     (NETIF_MSG_HW | QED_MSG_SP),
     "Received transceiver state update [0x%08x] from mfw [Addr 0x%x]\n",
     transceiver_state,
     (u32)(p_hwfn->mcp_info->port_addr +
     offsetof(struct public_port, transceiver_data)));

 transceiver_state = GET_FIELD(transceiver_state,
          ETH_TRANSCEIVER_STATE);

 if (transceiver_state == ETH_TRANSCEIVER_STATE_PRESENT)
  DP_NOTICE(p_hwfn, "Transceiver is present.\n");
 else
  DP_NOTICE(p_hwfn, "Transceiver is unplugged.\n");
}

static void qed_mcp_read_eee_config(struct qed_hwfn *p_hwfn,
        struct qed_ptt *p_ptt,
        struct qed_mcp_link_state *p_link)
{
 u32 eee_status, val;

 p_link->eee_adv_caps = 0;
 p_link->eee_lp_adv_caps = 0;
 eee_status = qed_rd(p_hwfn,
       p_ptt,
       p_hwfn->mcp_info->port_addr +
       offsetof(struct public_port, eee_status));
 p_link->eee_active = !!(eee_status & EEE_ACTIVE_BIT);
 val = (eee_status & EEE_LD_ADV_STATUS_MASK) >> EEE_LD_ADV_STATUS_OFFSET;
 if (val & EEE_1G_ADV)
  p_link->eee_adv_caps |= QED_EEE_1G_ADV;
 if (val & EEE_10G_ADV)
  p_link->eee_adv_caps |= QED_EEE_10G_ADV;
 val = (eee_status & EEE_LP_ADV_STATUS_MASK) >> EEE_LP_ADV_STATUS_OFFSET;
 if (val & EEE_1G_ADV)
  p_link->eee_lp_adv_caps |= QED_EEE_1G_ADV;
 if (val & EEE_10G_ADV)
  p_link->eee_lp_adv_caps |= QED_EEE_10G_ADV;
}

static u32 qed_mcp_get_shmem_func(struct qed_hwfn *p_hwfn,
      struct qed_ptt *p_ptt,
      struct public_func *p_data, int pfid)
{
 u32 addr = SECTION_OFFSIZE_ADDR(p_hwfn->mcp_info->public_base,
     PUBLIC_FUNC);
 u32 mfw_path_offsize = qed_rd(p_hwfn, p_ptt, addr);
 u32 func_addr;
 u32 i, size;

 func_addr = SECTION_ADDR(mfw_path_offsize, pfid);
 memset(p_data, 0, sizeof(*p_data));

 size = min_t(u32, sizeof(*p_data), QED_SECTION_SIZE(mfw_path_offsize));
 for (i = 0; i < size / sizeof(u32); i++)
  ((u32 *)p_data)[i] = qed_rd(p_hwfn, p_ptt,
         func_addr + (i << 2));
 return size;
}

static void qed_read_pf_bandwidth(struct qed_hwfn *p_hwfn,
      struct public_func *p_shmem_info)
{
 struct qed_mcp_function_info *p_info;

 p_info = &p_hwfn->mcp_info->func_info;

 p_info->bandwidth_min = QED_MFW_GET_FIELD(p_shmem_info->config,
        FUNC_MF_CFG_MIN_BW);
 if (p_info->bandwidth_min < 1 || p_info->bandwidth_min > 100) {
  DP_INFO(p_hwfn,
   "bandwidth minimum out of bounds [%02x]. Set to 1\n",
   p_info->bandwidth_min);
  p_info->bandwidth_min = 1;
 }

 p_info->bandwidth_max = QED_MFW_GET_FIELD(p_shmem_info->config,
        FUNC_MF_CFG_MAX_BW);
 if (p_info->bandwidth_max < 1 || p_info->bandwidth_max > 100) {
  DP_INFO(p_hwfn,
   "bandwidth maximum out of bounds [%02x]. Set to 100\n",
   p_info->bandwidth_max);
  p_info->bandwidth_max = 100;
 }
}

static void qed_mcp_handle_link_change(struct qed_hwfn *p_hwfn,
           struct qed_ptt *p_ptt, bool b_reset)
{
 struct qed_mcp_link_state *p_link;
 u8 max_bw, min_bw;
 u32 status = 0;

 /* Prevent SW/attentions from doing this at the same time */
 spin_lock_bh(&p_hwfn->mcp_info->link_lock);

 p_link = &p_hwfn->mcp_info->link_output;
 memset(p_link, 0, sizeof(*p_link));
 if (!b_reset) {
  status = qed_rd(p_hwfn, p_ptt,
    p_hwfn->mcp_info->port_addr +
    offsetof(struct public_port, link_status));
  DP_VERBOSE(p_hwfn, (NETIF_MSG_LINK | QED_MSG_SP),
      "Received link update [0x%08x] from mfw [Addr 0x%x]\n",
      status,
      (u32)(p_hwfn->mcp_info->port_addr +
     offsetof(struct public_port, link_status)));
 } else {
  DP_VERBOSE(p_hwfn, NETIF_MSG_LINK,
      "Resetting link indications\n");
  goto out;
 }

 if (p_hwfn->b_drv_link_init) {
  /* Link indication with modern MFW arrives as per-PF
 * indication.
 */

  if (p_hwfn->mcp_info->capabilities &
      FW_MB_PARAM_FEATURE_SUPPORT_VLINK) {
   struct public_func shmem_info;

   qed_mcp_get_shmem_func(p_hwfn, p_ptt, &shmem_info,
            MCP_PF_ID(p_hwfn));
   p_link->link_up = !!(shmem_info.status &
          FUNC_STATUS_VIRTUAL_LINK_UP);
   qed_read_pf_bandwidth(p_hwfn, &shmem_info);
   DP_VERBOSE(p_hwfn, NETIF_MSG_LINK,
       "Virtual link_up = %d\n", p_link->link_up);
  } else {
   p_link->link_up = !!(status & LINK_STATUS_LINK_UP);
   DP_VERBOSE(p_hwfn, NETIF_MSG_LINK,
       "Physical link_up = %d\n", p_link->link_up);
  }
 } else {
  p_link->link_up = false;
 }

 p_link->full_duplex = true;
 switch ((status & LINK_STATUS_SPEED_AND_DUPLEX_MASK)) {
 case LINK_STATUS_SPEED_AND_DUPLEX_100G:
  p_link->speed = 100000;
  break;
 case LINK_STATUS_SPEED_AND_DUPLEX_50G:
  p_link->speed = 50000;
  break;
 case LINK_STATUS_SPEED_AND_DUPLEX_40G:
  p_link->speed = 40000;
  break;
 case LINK_STATUS_SPEED_AND_DUPLEX_25G:
  p_link->speed = 25000;
  break;
 case LINK_STATUS_SPEED_AND_DUPLEX_20G:
  p_link->speed = 20000;
  break;
 case LINK_STATUS_SPEED_AND_DUPLEX_10G:
  p_link->speed = 10000;
  break;
 case LINK_STATUS_SPEED_AND_DUPLEX_1000THD:
  p_link->full_duplex = false;
  fallthrough;
 case LINK_STATUS_SPEED_AND_DUPLEX_1000TFD:
  p_link->speed = 1000;
  break;
 default:
  p_link->speed = 0;
  p_link->link_up = 0;
 }

 if (p_link->link_up && p_link->speed)
  p_link->line_speed = p_link->speed;
 else
  p_link->line_speed = 0;

 max_bw = p_hwfn->mcp_info->func_info.bandwidth_max;
 min_bw = p_hwfn->mcp_info->func_info.bandwidth_min;

 /* Max bandwidth configuration */
 __qed_configure_pf_max_bandwidth(p_hwfn, p_ptt, p_link, max_bw);

 /* Min bandwidth configuration */
 __qed_configure_pf_min_bandwidth(p_hwfn, p_ptt, p_link, min_bw);
 qed_configure_vp_wfq_on_link_change(p_hwfn->cdev, p_ptt,
         p_link->min_pf_rate);

 p_link->an = !!(status & LINK_STATUS_AUTO_NEGOTIATE_ENABLED);
 p_link->an_complete = !!(status &
     LINK_STATUS_AUTO_NEGOTIATE_COMPLETE);
 p_link->parallel_detection = !!(status &
     LINK_STATUS_PARALLEL_DETECTION_USED);
 p_link->pfc_enabled = !!(status & LINK_STATUS_PFC_ENABLED);

 p_link->partner_adv_speed |=
  (status & LINK_STATUS_LINK_PARTNER_1000TFD_CAPABLE) ?
  QED_LINK_PARTNER_SPEED_1G_FD : 0;
 p_link->partner_adv_speed |=
  (status & LINK_STATUS_LINK_PARTNER_1000THD_CAPABLE) ?
  QED_LINK_PARTNER_SPEED_1G_HD : 0;
 p_link->partner_adv_speed |=
  (status & LINK_STATUS_LINK_PARTNER_10G_CAPABLE) ?
  QED_LINK_PARTNER_SPEED_10G : 0;
 p_link->partner_adv_speed |=
  (status & LINK_STATUS_LINK_PARTNER_20G_CAPABLE) ?
  QED_LINK_PARTNER_SPEED_20G : 0;
 p_link->partner_adv_speed |=
  (status & LINK_STATUS_LINK_PARTNER_25G_CAPABLE) ?
  QED_LINK_PARTNER_SPEED_25G : 0;
 p_link->partner_adv_speed |=
  (status & LINK_STATUS_LINK_PARTNER_40G_CAPABLE) ?
  QED_LINK_PARTNER_SPEED_40G : 0;
 p_link->partner_adv_speed |=
  (status & LINK_STATUS_LINK_PARTNER_50G_CAPABLE) ?
  QED_LINK_PARTNER_SPEED_50G : 0;
 p_link->partner_adv_speed |=
  (status & LINK_STATUS_LINK_PARTNER_100G_CAPABLE) ?
  QED_LINK_PARTNER_SPEED_100G : 0;

 p_link->partner_tx_flow_ctrl_en =
  !!(status & LINK_STATUS_TX_FLOW_CONTROL_ENABLED);
 p_link->partner_rx_flow_ctrl_en =
  !!(status & LINK_STATUS_RX_FLOW_CONTROL_ENABLED);

 switch (status & LINK_STATUS_LINK_PARTNER_FLOW_CONTROL_MASK) {
 case LINK_STATUS_LINK_PARTNER_SYMMETRIC_PAUSE:
  p_link->partner_adv_pause = QED_LINK_PARTNER_SYMMETRIC_PAUSE;
  break;
 case LINK_STATUS_LINK_PARTNER_ASYMMETRIC_PAUSE:
  p_link->partner_adv_pause = QED_LINK_PARTNER_ASYMMETRIC_PAUSE;
  break;
 case LINK_STATUS_LINK_PARTNER_BOTH_PAUSE:
  p_link->partner_adv_pause = QED_LINK_PARTNER_BOTH_PAUSE;
  break;
 default:
  p_link->partner_adv_pause = 0;
 }

 p_link->sfp_tx_fault = !!(status & LINK_STATUS_SFP_TX_FAULT);

 if (p_hwfn->mcp_info->capabilities & FW_MB_PARAM_FEATURE_SUPPORT_EEE)
  qed_mcp_read_eee_config(p_hwfn, p_ptt, p_link);

 if (p_hwfn->mcp_info->capabilities &
     FW_MB_PARAM_FEATURE_SUPPORT_FEC_CONTROL) {
  switch (status & LINK_STATUS_FEC_MODE_MASK) {
  case LINK_STATUS_FEC_MODE_NONE:
   p_link->fec_active = QED_FEC_MODE_NONE;
   break;
  case LINK_STATUS_FEC_MODE_FIRECODE_CL74:
   p_link->fec_active = QED_FEC_MODE_FIRECODE;
   break;
  case LINK_STATUS_FEC_MODE_RS_CL91:
   p_link->fec_active = QED_FEC_MODE_RS;
   break;
  default:
   p_link->fec_active = QED_FEC_MODE_AUTO;
  }
 } else {
  p_link->fec_active = QED_FEC_MODE_UNSUPPORTED;
 }

 qed_link_update(p_hwfn, p_ptt);
out:
 spin_unlock_bh(&p_hwfn->mcp_info->link_lock);
}

int qed_mcp_set_link(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt, bool b_up)
{
 struct qed_mcp_link_params *params = &p_hwfn->mcp_info->link_input;
 struct qed_mcp_mb_params mb_params;
 struct eth_phy_cfg phy_cfg;
 u32 cmd, fec_bit = 0;
 u32 val, ext_speed;
 int rc = 0;

 /* Set the shmem configuration according to params */
 memset(&phy_cfg, 0, sizeof(phy_cfg));
 cmd = b_up ? DRV_MSG_CODE_INIT_PHY : DRV_MSG_CODE_LINK_RESET;
 if (!params->speed.autoneg)
  phy_cfg.speed = params->speed.forced_speed;
 phy_cfg.pause |= (params->pause.autoneg) ? ETH_PAUSE_AUTONEG : 0;
 phy_cfg.pause |= (params->pause.forced_rx) ? ETH_PAUSE_RX : 0;
 phy_cfg.pause |= (params->pause.forced_tx) ? ETH_PAUSE_TX : 0;
 phy_cfg.adv_speed = params->speed.advertised_speeds;
 phy_cfg.loopback_mode = params->loopback_mode;

 /* There are MFWs that share this capability regardless of whether
 * this is feasible or not. And given that at the very least adv_caps
 * would be set internally by qed, we want to make sure LFA would
 * still work.
 */

 if ((p_hwfn->mcp_info->capabilities &
      FW_MB_PARAM_FEATURE_SUPPORT_EEE) && params->eee.enable) {
  phy_cfg.eee_cfg |= EEE_CFG_EEE_ENABLED;
  if (params->eee.tx_lpi_enable)
   phy_cfg.eee_cfg |= EEE_CFG_TX_LPI;
  if (params->eee.adv_caps & QED_EEE_1G_ADV)
   phy_cfg.eee_cfg |= EEE_CFG_ADV_SPEED_1G;
  if (params->eee.adv_caps & QED_EEE_10G_ADV)
   phy_cfg.eee_cfg |= EEE_CFG_ADV_SPEED_10G;
  phy_cfg.eee_cfg |= (params->eee.tx_lpi_timer <<
        EEE_TX_TIMER_USEC_OFFSET) &
       EEE_TX_TIMER_USEC_MASK;
 }

 if (p_hwfn->mcp_info->capabilities &
     FW_MB_PARAM_FEATURE_SUPPORT_FEC_CONTROL) {
  if (params->fec & QED_FEC_MODE_NONE)
   fec_bit |= FEC_FORCE_MODE_NONE;
  else if (params->fec & QED_FEC_MODE_FIRECODE)
   fec_bit |= FEC_FORCE_MODE_FIRECODE;
  else if (params->fec & QED_FEC_MODE_RS)
   fec_bit |= FEC_FORCE_MODE_RS;
  else if (params->fec & QED_FEC_MODE_AUTO)
   fec_bit |= FEC_FORCE_MODE_AUTO;

  SET_MFW_FIELD(phy_cfg.fec_mode, FEC_FORCE_MODE, fec_bit);
 }

 if (p_hwfn->mcp_info->capabilities &
     FW_MB_PARAM_FEATURE_SUPPORT_EXT_SPEED_FEC_CONTROL) {
  ext_speed = 0;
  if (params->ext_speed.autoneg)
   ext_speed |= ETH_EXT_SPEED_NONE;

  val = params->ext_speed.forced_speed;
  if (val & QED_EXT_SPEED_1G)
   ext_speed |= ETH_EXT_SPEED_1G;
  if (val & QED_EXT_SPEED_10G)
   ext_speed |= ETH_EXT_SPEED_10G;
  if (val & QED_EXT_SPEED_25G)
   ext_speed |= ETH_EXT_SPEED_25G;
  if (val & QED_EXT_SPEED_40G)
   ext_speed |= ETH_EXT_SPEED_40G;
  if (val & QED_EXT_SPEED_50G_R)
   ext_speed |= ETH_EXT_SPEED_50G_BASE_R;
  if (val & QED_EXT_SPEED_50G_R2)
   ext_speed |= ETH_EXT_SPEED_50G_BASE_R2;
  if (val & QED_EXT_SPEED_100G_R2)
   ext_speed |= ETH_EXT_SPEED_100G_BASE_R2;
  if (val & QED_EXT_SPEED_100G_R4)
   ext_speed |= ETH_EXT_SPEED_100G_BASE_R4;
  if (val & QED_EXT_SPEED_100G_P4)
   ext_speed |= ETH_EXT_SPEED_100G_BASE_P4;

  SET_MFW_FIELD(phy_cfg.extended_speed, ETH_EXT_SPEED,
         ext_speed);

  ext_speed = 0;

  val = params->ext_speed.advertised_speeds;
  if (val & QED_EXT_SPEED_MASK_1G)
   ext_speed |= ETH_EXT_ADV_SPEED_1G;
  if (val & QED_EXT_SPEED_MASK_10G)
   ext_speed |= ETH_EXT_ADV_SPEED_10G;
  if (val & QED_EXT_SPEED_MASK_25G)
   ext_speed |= ETH_EXT_ADV_SPEED_25G;
  if (val & QED_EXT_SPEED_MASK_40G)
   ext_speed |= ETH_EXT_ADV_SPEED_40G;
  if (val & QED_EXT_SPEED_MASK_50G_R)
   ext_speed |= ETH_EXT_ADV_SPEED_50G_BASE_R;
  if (val & QED_EXT_SPEED_MASK_50G_R2)
   ext_speed |= ETH_EXT_ADV_SPEED_50G_BASE_R2;
  if (val & QED_EXT_SPEED_MASK_100G_R2)
   ext_speed |= ETH_EXT_ADV_SPEED_100G_BASE_R2;
  if (val & QED_EXT_SPEED_MASK_100G_R4)
   ext_speed |= ETH_EXT_ADV_SPEED_100G_BASE_R4;
  if (val & QED_EXT_SPEED_MASK_100G_P4)
   ext_speed |= ETH_EXT_ADV_SPEED_100G_BASE_P4;

  phy_cfg.extended_speed |= ext_speed;

  SET_MFW_FIELD(phy_cfg.fec_mode, FEC_EXTENDED_MODE,
         params->ext_fec_mode);
 }

 p_hwfn->b_drv_link_init = b_up;

 if (b_up) {
  DP_VERBOSE(p_hwfn, NETIF_MSG_LINK,
      "Configuring Link: Speed 0x%08x, Pause 0x%08x, Adv. Speed 0x%08x, Loopback 0x%08x, FEC 0x%08x, Ext. Speed 0x%08x\n",
      phy_cfg.speed, phy_cfg.pause, phy_cfg.adv_speed,
      phy_cfg.loopback_mode, phy_cfg.fec_mode,
      phy_cfg.extended_speed);
 } else {
  DP_VERBOSE(p_hwfn, NETIF_MSG_LINK, "Resetting link\n");
 }

 memset(&mb_params, 0, sizeof(mb_params));
 mb_params.cmd = cmd;
 mb_params.p_data_src = &phy_cfg;
 mb_params.data_src_size = sizeof(phy_cfg);
 rc = qed_mcp_cmd_and_union(p_hwfn, p_ptt, &mb_params);

 /* if mcp fails to respond we must abort */
 if (rc) {
  DP_ERR(p_hwfn, "MCP response failure, aborting\n");
  return rc;
 }

 /* Mimic link-change attention, done for several reasons:
 *  - On reset, there's no guarantee MFW would trigger
 *    an attention.
 *  - On initialization, older MFWs might not indicate link change
 *    during LFA, so we'll never get an UP indication.
 */

 qed_mcp_handle_link_change(p_hwfn, p_ptt, !b_up);

 return 0;
}

u32 qed_get_process_kill_counter(struct qed_hwfn *p_hwfn,
     struct qed_ptt *p_ptt)
{
 u32 path_offsize_addr, path_offsize, path_addr, proc_kill_cnt;

 if (IS_VF(p_hwfn->cdev))
  return -EINVAL;

 path_offsize_addr = SECTION_OFFSIZE_ADDR(p_hwfn->mcp_info->public_base,
       PUBLIC_PATH);
 path_offsize = qed_rd(p_hwfn, p_ptt, path_offsize_addr);
 path_addr = SECTION_ADDR(path_offsize, QED_PATH_ID(p_hwfn));

 proc_kill_cnt = qed_rd(p_hwfn, p_ptt,
          path_addr +
          offsetof(struct public_path, process_kill)) &
   PROCESS_KILL_COUNTER_MASK;

 return proc_kill_cnt;
}

static void qed_mcp_handle_process_kill(struct qed_hwfn *p_hwfn,
     struct qed_ptt *p_ptt)
{
 struct qed_dev *cdev = p_hwfn->cdev;
 u32 proc_kill_cnt;

 /* Prevent possible attentions/interrupts during the recovery handling
 * and till its load phase, during which they will be re-enabled.
 */

 qed_int_igu_disable_int(p_hwfn, p_ptt);

 DP_NOTICE(p_hwfn, "Received a process kill indication\n");

 /* The following operations should be done once, and thus in CMT mode
 * are carried out by only the first HW function.
 */

 if (p_hwfn != QED_LEADING_HWFN(cdev))
  return;

 if (cdev->recov_in_prog) {
  DP_NOTICE(p_hwfn,
     "Ignoring the indication since a recovery process is already in progress\n");
  return;
 }

 cdev->recov_in_prog = true;

 proc_kill_cnt = qed_get_process_kill_counter(p_hwfn, p_ptt);
 DP_NOTICE(p_hwfn, "Process kill counter: %d\n", proc_kill_cnt);

 qed_schedule_recovery_handler(p_hwfn);
}

static void qed_mcp_send_protocol_stats(struct qed_hwfn *p_hwfn,
     struct qed_ptt *p_ptt,
     enum MFW_DRV_MSG_TYPE type)
{
 enum qed_mcp_protocol_type stats_type;
 union qed_mcp_protocol_stats stats;
 struct qed_mcp_mb_params mb_params;
 u32 hsi_param;

 switch (type) {
 case MFW_DRV_MSG_GET_LAN_STATS:
  stats_type = QED_MCP_LAN_STATS;
  hsi_param = DRV_MSG_CODE_STATS_TYPE_LAN;
  break;
 case MFW_DRV_MSG_GET_FCOE_STATS:
  stats_type = QED_MCP_FCOE_STATS;
  hsi_param = DRV_MSG_CODE_STATS_TYPE_FCOE;
  break;
 case MFW_DRV_MSG_GET_ISCSI_STATS:
  stats_type = QED_MCP_ISCSI_STATS;
  hsi_param = DRV_MSG_CODE_STATS_TYPE_ISCSI;
  break;
 case MFW_DRV_MSG_GET_RDMA_STATS:
  stats_type = QED_MCP_RDMA_STATS;
  hsi_param = DRV_MSG_CODE_STATS_TYPE_RDMA;
  break;
 default:
  DP_NOTICE(p_hwfn, "Invalid protocol type %d\n", type);
  return;
 }

 qed_get_protocol_stats(p_hwfn->cdev, stats_type, &stats);

 memset(&mb_params, 0, sizeof(mb_params));
 mb_params.cmd = DRV_MSG_CODE_GET_STATS;
 mb_params.param = hsi_param;
 mb_params.p_data_src = &stats;
 mb_params.data_src_size = sizeof(stats);
 qed_mcp_cmd_and_union(p_hwfn, p_ptt, &mb_params);
}

static void qed_mcp_update_bw(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt)
{
 struct qed_mcp_function_info *p_info;
 struct public_func shmem_info;
 u32 resp = 0, param = 0;

 qed_mcp_get_shmem_func(p_hwfn, p_ptt, &shmem_info, MCP_PF_ID(p_hwfn));

 qed_read_pf_bandwidth(p_hwfn, &shmem_info);

 p_info = &p_hwfn->mcp_info->func_info;

 qed_configure_pf_min_bandwidth(p_hwfn->cdev, p_info->bandwidth_min);
 qed_configure_pf_max_bandwidth(p_hwfn->cdev, p_info->bandwidth_max);

 /* Acknowledge the MFW */
 qed_mcp_cmd_nosleep(p_hwfn, p_ptt, DRV_MSG_CODE_BW_UPDATE_ACK, 0, &resp,
       ¶m);
}

static void qed_mcp_update_stag(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt)
{
 struct public_func shmem_info;
 u32 resp = 0, param = 0;

 qed_mcp_get_shmem_func(p_hwfn, p_ptt, &shmem_info, MCP_PF_ID(p_hwfn));

 p_hwfn->mcp_info->func_info.ovlan = (u16)shmem_info.ovlan_stag &
       FUNC_MF_CFG_OV_STAG_MASK;
 p_hwfn->hw_info.ovlan = p_hwfn->mcp_info->func_info.ovlan;
 if (test_bit(QED_MF_OVLAN_CLSS, &p_hwfn->cdev->mf_bits)) {
  if (p_hwfn->hw_info.ovlan != QED_MCP_VLAN_UNSET) {
   qed_wr(p_hwfn, p_ptt, NIG_REG_LLH_FUNC_TAG_VALUE,
          p_hwfn->hw_info.ovlan);
   qed_wr(p_hwfn, p_ptt, NIG_REG_LLH_FUNC_TAG_EN, 1);

   /* Configure DB to add external vlan to EDPM packets */
   qed_wr(p_hwfn, p_ptt, DORQ_REG_TAG1_OVRD_MODE, 1);
   qed_wr(p_hwfn, p_ptt, DORQ_REG_PF_EXT_VID_BB_K2,
          p_hwfn->hw_info.ovlan);
  } else {
   qed_wr(p_hwfn, p_ptt, NIG_REG_LLH_FUNC_TAG_EN, 0);
   qed_wr(p_hwfn, p_ptt, NIG_REG_LLH_FUNC_TAG_VALUE, 0);
   qed_wr(p_hwfn, p_ptt, DORQ_REG_TAG1_OVRD_MODE, 0);
   qed_wr(p_hwfn, p_ptt, DORQ_REG_PF_EXT_VID_BB_K2, 0);
  }

  qed_sp_pf_update_stag(p_hwfn);
 }

 DP_VERBOSE(p_hwfn, QED_MSG_SP, "ovlan = %d hw_mode = 0x%x\n",
     p_hwfn->mcp_info->func_info.ovlan, p_hwfn->hw_info.hw_mode);

 /* Acknowledge the MFW */
 qed_mcp_cmd_nosleep(p_hwfn, p_ptt, DRV_MSG_CODE_S_TAG_UPDATE_ACK, 0,
       &resp, ¶m);
}

static void qed_mcp_handle_fan_failure(struct qed_hwfn *p_hwfn,
           struct qed_ptt *p_ptt)
{
 /* A single notification should be sent to upper driver in CMT mode */
 if (p_hwfn != QED_LEADING_HWFN(p_hwfn->cdev))
  return;

 qed_hw_err_notify(p_hwfn, p_ptt, QED_HW_ERR_FAN_FAIL,
     "Fan failure was detected on the network interface card and it's going to be shut down.\n");
}

struct qed_mdump_cmd_params {
 u32 cmd;
 void *p_data_src;
 u8 data_src_size;
 void *p_data_dst;
 u8 data_dst_size;
 u32 mcp_resp;
};

static int
qed_mcp_mdump_cmd(struct qed_hwfn *p_hwfn,
    struct qed_ptt *p_ptt,
    struct qed_mdump_cmd_params *p_mdump_cmd_params)
{
 struct qed_mcp_mb_params mb_params;
 int rc;

 memset(&mb_params, 0, sizeof(mb_params));
 mb_params.cmd = DRV_MSG_CODE_MDUMP_CMD;
 mb_params.param = p_mdump_cmd_params->cmd;
 mb_params.p_data_src = p_mdump_cmd_params->p_data_src;
 mb_params.data_src_size = p_mdump_cmd_params->data_src_size;
 mb_params.p_data_dst = p_mdump_cmd_params->p_data_dst;
 mb_params.data_dst_size = p_mdump_cmd_params->data_dst_size;
 rc = qed_mcp_cmd_and_union(p_hwfn, p_ptt, &mb_params);
 if (rc)
  return rc;

 p_mdump_cmd_params->mcp_resp = mb_params.mcp_resp;

 if (p_mdump_cmd_params->mcp_resp == FW_MSG_CODE_MDUMP_INVALID_CMD) {
  DP_INFO(p_hwfn,
   "The mdump sub command is unsupported by the MFW [mdump_cmd 0x%x]\n",
   p_mdump_cmd_params->cmd);
  rc = -EOPNOTSUPP;
 } else if (p_mdump_cmd_params->mcp_resp == FW_MSG_CODE_UNSUPPORTED) {
  DP_INFO(p_hwfn,
   "The mdump command is not supported by the MFW\n");
  rc = -EOPNOTSUPP;
 }

 return rc;
}

static int qed_mcp_mdump_ack(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt)
{
 struct qed_mdump_cmd_params mdump_cmd_params;

 memset(&mdump_cmd_params, 0, sizeof(mdump_cmd_params));
 mdump_cmd_params.cmd = DRV_MSG_CODE_MDUMP_ACK;

 return qed_mcp_mdump_cmd(p_hwfn, p_ptt, &mdump_cmd_params);
}

int
qed_mcp_mdump_get_retain(struct qed_hwfn *p_hwfn,
    struct qed_ptt *p_ptt,
    struct mdump_retain_data_stc *p_mdump_retain)
{
 struct qed_mdump_cmd_params mdump_cmd_params;
 int rc;

 memset(&mdump_cmd_params, 0, sizeof(mdump_cmd_params));
 mdump_cmd_params.cmd = DRV_MSG_CODE_MDUMP_GET_RETAIN;
 mdump_cmd_params.p_data_dst = p_mdump_retain;
 mdump_cmd_params.data_dst_size = sizeof(*p_mdump_retain);

 rc = qed_mcp_mdump_cmd(p_hwfn, p_ptt, &mdump_cmd_params);
 if (rc)
  return rc;

 if (mdump_cmd_params.mcp_resp != FW_MSG_CODE_OK) {
  DP_INFO(p_hwfn,
   "Failed to get the mdump retained data [mcp_resp 0x%x]\n",
   mdump_cmd_params.mcp_resp);
  return -EINVAL;
 }

 return 0;
}

static void qed_mcp_handle_critical_error(struct qed_hwfn *p_hwfn,
       struct qed_ptt *p_ptt)
{
 struct mdump_retain_data_stc mdump_retain;
 int rc;

 /* In CMT mode - no need for more than a single acknowledgment to the
 * MFW, and no more than a single notification to the upper driver.
 */

 if (p_hwfn != QED_LEADING_HWFN(p_hwfn->cdev))
  return;

 rc = qed_mcp_mdump_get_retain(p_hwfn, p_ptt, &mdump_retain);
 if (rc == 0 && mdump_retain.valid)
  DP_NOTICE(p_hwfn,
     "The MFW notified that a critical error occurred in the device [epoch 0x%08x, pf 0x%x, status 0x%08x]\n",
     mdump_retain.epoch,
     mdump_retain.pf, mdump_retain.status);
 else
  DP_NOTICE(p_hwfn,
     "The MFW notified that a critical error occurred in the device\n");

 DP_NOTICE(p_hwfn,
    "Acknowledging the notification to not allow the MFW crash dump [driver debug data collection is preferable]\n");
 qed_mcp_mdump_ack(p_hwfn, p_ptt);

 qed_hw_err_notify(p_hwfn, p_ptt, QED_HW_ERR_HW_ATTN, NULL);
}

void qed_mcp_read_ufp_config(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt)
{
 struct public_func shmem_info;
 u32 port_cfg, val;

 if (!test_bit(QED_MF_UFP_SPECIFIC, &p_hwfn->cdev->mf_bits))
  return;

 memset(&p_hwfn->ufp_info, 0, sizeof(p_hwfn->ufp_info));
 port_cfg = qed_rd(p_hwfn, p_ptt, p_hwfn->mcp_info->port_addr +
     offsetof(struct public_port, oem_cfg_port));
 val = (port_cfg & OEM_CFG_CHANNEL_TYPE_MASK) >>
  OEM_CFG_CHANNEL_TYPE_OFFSET;
 if (val != OEM_CFG_CHANNEL_TYPE_STAGGED)
  DP_NOTICE(p_hwfn,
     "Incorrect UFP Channel type %d port_id 0x%02x\n",
     val, MFW_PORT(p_hwfn));

 val = (port_cfg & OEM_CFG_SCHED_TYPE_MASK) >> OEM_CFG_SCHED_TYPE_OFFSET;
 if (val == OEM_CFG_SCHED_TYPE_ETS) {
  p_hwfn->ufp_info.mode = QED_UFP_MODE_ETS;
 } else if (val == OEM_CFG_SCHED_TYPE_VNIC_BW) {
  p_hwfn->ufp_info.mode = QED_UFP_MODE_VNIC_BW;
 } else {
  p_hwfn->ufp_info.mode = QED_UFP_MODE_UNKNOWN;
  DP_NOTICE(p_hwfn,
     "Unknown UFP scheduling mode %d port_id 0x%02x\n",
     val, MFW_PORT(p_hwfn));
 }

 qed_mcp_get_shmem_func(p_hwfn, p_ptt, &shmem_info, MCP_PF_ID(p_hwfn));
 val = (shmem_info.oem_cfg_func & OEM_CFG_FUNC_TC_MASK) >>
  OEM_CFG_FUNC_TC_OFFSET;
 p_hwfn->ufp_info.tc = (u8)val;
 val = (shmem_info.oem_cfg_func & OEM_CFG_FUNC_HOST_PRI_CTRL_MASK) >>
  OEM_CFG_FUNC_HOST_PRI_CTRL_OFFSET;
 if (val == OEM_CFG_FUNC_HOST_PRI_CTRL_VNIC) {
  p_hwfn->ufp_info.pri_type = QED_UFP_PRI_VNIC;
 } else if (val == OEM_CFG_FUNC_HOST_PRI_CTRL_OS) {
  p_hwfn->ufp_info.pri_type = QED_UFP_PRI_OS;
 } else {
  p_hwfn->ufp_info.pri_type = QED_UFP_PRI_UNKNOWN;
  DP_NOTICE(p_hwfn,
     "Unknown Host priority control %d port_id 0x%02x\n",
     val, MFW_PORT(p_hwfn));
 }

 DP_NOTICE(p_hwfn,
    "UFP shmem config: mode = %d tc = %d pri_type = %d port_id 0x%02x\n",
    p_hwfn->ufp_info.mode, p_hwfn->ufp_info.tc,
    p_hwfn->ufp_info.pri_type, MFW_PORT(p_hwfn));
}

static int
qed_mcp_handle_ufp_event(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt)
{
 qed_mcp_read_ufp_config(p_hwfn, p_ptt);

 if (p_hwfn->ufp_info.mode == QED_UFP_MODE_VNIC_BW) {
  p_hwfn->qm_info.ooo_tc = p_hwfn->ufp_info.tc;
  qed_hw_info_set_offload_tc(&p_hwfn->hw_info,
        p_hwfn->ufp_info.tc);

  qed_qm_reconf(p_hwfn, p_ptt);
 } else if (p_hwfn->ufp_info.mode == QED_UFP_MODE_ETS) {
  /* Merge UFP TC with the dcbx TC data */
  qed_dcbx_mib_update_event(p_hwfn, p_ptt,
       QED_DCBX_OPERATIONAL_MIB);
 } else {
  DP_ERR(p_hwfn, "Invalid sched type, discard the UFP config\n");
  return -EINVAL;
 }

 /* update storm FW with negotiation results */
 qed_sp_pf_update_ufp(p_hwfn);

 /* update stag pcp value */
 qed_sp_pf_update_stag(p_hwfn);

 return 0;
}

int qed_mcp_handle_events(struct qed_hwfn *p_hwfn,
     struct qed_ptt *p_ptt)
{
 struct qed_mcp_info *info = p_hwfn->mcp_info;
 int rc = 0;
 bool found = false;
 u16 i;

 DP_VERBOSE(p_hwfn, QED_MSG_SP, "Received message from MFW\n");

 /* Read Messages from MFW */
 qed_mcp_read_mb(p_hwfn, p_ptt);

 /* Compare current messages to old ones */
 for (i = 0; i < info->mfw_mb_length; i++) {
  if (info->mfw_mb_cur[i] == info->mfw_mb_shadow[i])
   continue;

  found = true;

  DP_VERBOSE(p_hwfn, NETIF_MSG_LINK,
      "Msg [%d] - old CMD 0x%02x, new CMD 0x%02x\n",
      i, info->mfw_mb_shadow[i], info->mfw_mb_cur[i]);

  spin_lock_bh(&p_hwfn->mcp_info->unload_lock);
  if (test_bit(QED_MCP_BYPASS_PROC_BIT,
        &p_hwfn->mcp_info->mcp_handling_status)) {
   spin_unlock_bh(&p_hwfn->mcp_info->unload_lock);
   DP_INFO(p_hwfn,
    "Msg [%d] is bypassed on unload flow\n", i);
   continue;
  }

  set_bit(QED_MCP_IN_PROCESSING_BIT,
   &p_hwfn->mcp_info->mcp_handling_status);
  spin_unlock_bh(&p_hwfn->mcp_info->unload_lock);

  switch (i) {
  case MFW_DRV_MSG_LINK_CHANGE:
   qed_mcp_handle_link_change(p_hwfn, p_ptt, false);
   break;
  case MFW_DRV_MSG_VF_DISABLED:
   qed_mcp_handle_vf_flr(p_hwfn, p_ptt);
   break;
  case MFW_DRV_MSG_LLDP_DATA_UPDATED:
   qed_dcbx_mib_update_event(p_hwfn, p_ptt,
        QED_DCBX_REMOTE_LLDP_MIB);
   break;
  case MFW_DRV_MSG_DCBX_REMOTE_MIB_UPDATED:
   qed_dcbx_mib_update_event(p_hwfn, p_ptt,
        QED_DCBX_REMOTE_MIB);
   break;
  case MFW_DRV_MSG_DCBX_OPERATIONAL_MIB_UPDATED:
   qed_dcbx_mib_update_event(p_hwfn, p_ptt,
        QED_DCBX_OPERATIONAL_MIB);
   break;
  case MFW_DRV_MSG_OEM_CFG_UPDATE:
   qed_mcp_handle_ufp_event(p_hwfn, p_ptt);
   break;
  case MFW_DRV_MSG_TRANSCEIVER_STATE_CHANGE:
   qed_mcp_handle_transceiver_change(p_hwfn, p_ptt);
   break;
  case MFW_DRV_MSG_ERROR_RECOVERY:
   qed_mcp_handle_process_kill(p_hwfn, p_ptt);
   break;
  case MFW_DRV_MSG_GET_LAN_STATS:
  case MFW_DRV_MSG_GET_FCOE_STATS:
  case MFW_DRV_MSG_GET_ISCSI_STATS:
  case MFW_DRV_MSG_GET_RDMA_STATS:
   qed_mcp_send_protocol_stats(p_hwfn, p_ptt, i);
   break;
  case MFW_DRV_MSG_BW_UPDATE:
   qed_mcp_update_bw(p_hwfn, p_ptt);
   break;
  case MFW_DRV_MSG_S_TAG_UPDATE:
   qed_mcp_update_stag(p_hwfn, p_ptt);
   break;
  case MFW_DRV_MSG_FAILURE_DETECTED:
   qed_mcp_handle_fan_failure(p_hwfn, p_ptt);
   break;
  case MFW_DRV_MSG_CRITICAL_ERROR_OCCURRED:
   qed_mcp_handle_critical_error(p_hwfn, p_ptt);
   break;
  case MFW_DRV_MSG_GET_TLV_REQ:
   qed_mfw_tlv_req(p_hwfn);
   break;
  default:
   DP_INFO(p_hwfn, "Unimplemented MFW message %d\n", i);
   rc = -EINVAL;
  }

  clear_bit(QED_MCP_IN_PROCESSING_BIT,
     &p_hwfn->mcp_info->mcp_handling_status);
 }

 /* ACK everything */
 for (i = 0; i < MFW_DRV_MSG_MAX_DWORDS(info->mfw_mb_length); i++) {
  __be32 val = cpu_to_be32(((u32 *)info->mfw_mb_cur)[i]);

  /* MFW expect answer in BE, so we force write in that format */
  qed_wr(p_hwfn, p_ptt,
         info->mfw_mb_addr + sizeof(u32) +
         MFW_DRV_MSG_MAX_DWORDS(info->mfw_mb_length) *
         sizeof(u32) + i * sizeof(u32),
         (__force u32)val);
 }

 if (!found) {
  DP_NOTICE(p_hwfn,
     "Received an MFW message indication but no new message!\n");
  rc = -EINVAL;
 }

 /* Copy the new mfw messages into the shadow */
 memcpy(info->mfw_mb_shadow, info->mfw_mb_cur, info->mfw_mb_length);

 return rc;
}

int qed_mcp_get_mfw_ver(struct qed_hwfn *p_hwfn,
   struct qed_ptt *p_ptt,
   u32 *p_mfw_ver, u32 *p_running_bundle_id)
{
 u32 global_offsize, public_base;

 if (IS_VF(p_hwfn->cdev)) {
  if (p_hwfn->vf_iov_info) {
   struct pfvf_acquire_resp_tlv *p_resp;

   p_resp = &p_hwfn->vf_iov_info->acquire_resp;
   *p_mfw_ver = p_resp->pfdev_info.mfw_ver;
   return 0;
  } else {
   DP_VERBOSE(p_hwfn,
       QED_MSG_IOV,
       "VF requested MFW version prior to ACQUIRE\n");
   return -EINVAL;
  }
 }

 public_base = p_hwfn->mcp_info->public_base;
 global_offsize = qed_rd(p_hwfn, p_ptt,
    SECTION_OFFSIZE_ADDR(public_base,
           PUBLIC_GLOBAL));
 *p_mfw_ver =
     qed_rd(p_hwfn, p_ptt,
     SECTION_ADDR(global_offsize,
    0) + offsetof(struct public_global, mfw_ver));

 if (p_running_bundle_id) {
  *p_running_bundle_id = qed_rd(p_hwfn, p_ptt,
           SECTION_ADDR(global_offsize, 0) +
           offsetof(struct public_global,
             running_bundle_id));
 }

 return 0;
}

int qed_mcp_get_mbi_ver(struct qed_hwfn *p_hwfn,
   struct qed_ptt *p_ptt, u32 *p_mbi_ver)
{
 u32 nvm_cfg_addr, nvm_cfg1_offset, mbi_ver_addr;

 if (IS_VF(p_hwfn->cdev))
  return -EINVAL;

 /* Read the address of the nvm_cfg */
 nvm_cfg_addr = qed_rd(p_hwfn, p_ptt, MISC_REG_GEN_PURP_CR0);
 if (!nvm_cfg_addr) {
  DP_NOTICE(p_hwfn, "Shared memory not initialized\n");
  return -EINVAL;
 }

 /* Read the offset of nvm_cfg1 */
 nvm_cfg1_offset = qed_rd(p_hwfn, p_ptt, nvm_cfg_addr + 4);

 mbi_ver_addr = MCP_REG_SCRATCH + nvm_cfg1_offset +
         offsetof(struct nvm_cfg1, glob) +
         offsetof(struct nvm_cfg1_glob, mbi_version);
 *p_mbi_ver = qed_rd(p_hwfn, p_ptt,
       mbi_ver_addr) &
       (NVM_CFG1_GLOB_MBI_VERSION_0_MASK |
        NVM_CFG1_GLOB_MBI_VERSION_1_MASK |
        NVM_CFG1_GLOB_MBI_VERSION_2_MASK);

 return 0;
}

int qed_mcp_get_media_type(struct qed_hwfn *p_hwfn,
      struct qed_ptt *p_ptt, u32 *p_media_type)
{
 *p_media_type = MEDIA_UNSPECIFIED;

 if (IS_VF(p_hwfn->cdev))
  return -EINVAL;

 if (!qed_mcp_is_init(p_hwfn)) {
  DP_NOTICE(p_hwfn, "MFW is not initialized!\n");
  return -EBUSY;
 }

 if (!p_ptt) {
  *p_media_type = MEDIA_UNSPECIFIED;
  return -EINVAL;
 }

 *p_media_type = qed_rd(p_hwfn, p_ptt,
          p_hwfn->mcp_info->port_addr +
          offsetof(struct public_port,
     media_type));

 return 0;
}

int qed_mcp_get_transceiver_data(struct qed_hwfn *p_hwfn,
     struct qed_ptt *p_ptt,
     u32 *p_transceiver_state,
     u32 *p_transceiver_type)
{
 u32 transceiver_info;

 *p_transceiver_type = ETH_TRANSCEIVER_TYPE_NONE;
 *p_transceiver_state = ETH_TRANSCEIVER_STATE_UPDATING;

 if (IS_VF(p_hwfn->cdev))
  return -EINVAL;

 if (!qed_mcp_is_init(p_hwfn)) {
  DP_NOTICE(p_hwfn, "MFW is not initialized!\n");
  return -EBUSY;
 }

--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=96 H=97 G=96

¤ Dauer der Verarbeitung: 0.24 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge