Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/net/ethernet/mellanox/mlxsw/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 95 kB image not shown  

Quelle  core.c   Sprache: C

 
// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
/* Copyright (c) 2015-2018 Mellanox Technologies. All rights reserved */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/export.h>
#include <linux/err.h>
#include <linux/if_link.h>
#include <linux/netdevice.h>
#include <linux/completion.h>
#include <linux/skbuff.h>
#include <linux/etherdevice.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/gfp.h>
#include <linux/random.h>
#include <linux/jiffies.h>
#include <linux/mutex.h>
#include <linux/rcupdate.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/firmware.h>
#include <asm/byteorder.h>
#include <net/devlink.h>
#include <trace/events/devlink.h>

#include "core.h"
#include "core_env.h"
#include "item.h"
#include "cmd.h"
#include "port.h"
#include "trap.h"
#include "emad.h"
#include "reg.h"
#include "resources.h"
#include "../mlxfw/mlxfw.h"
#include "txheader.h"

static LIST_HEAD(mlxsw_core_driver_list);
static DEFINE_SPINLOCK(mlxsw_core_driver_list_lock);

static const char mlxsw_core_driver_name[] = "mlxsw_core";

static struct workqueue_struct *mlxsw_wq;
static struct workqueue_struct *mlxsw_owq;

struct mlxsw_core_port {
 struct devlink_port devlink_port;
 void *port_driver_priv;
 u16 local_port;
 struct mlxsw_linecard *linecard;
};

void *mlxsw_core_port_driver_priv(struct mlxsw_core_port *mlxsw_core_port)
{
 return mlxsw_core_port->port_driver_priv;
}
EXPORT_SYMBOL(mlxsw_core_port_driver_priv);

static bool mlxsw_core_port_check(struct mlxsw_core_port *mlxsw_core_port)
{
 return mlxsw_core_port->port_driver_priv != NULL;
}

struct mlxsw_core {
 struct mlxsw_driver *driver;
 const struct mlxsw_bus *bus;
 void *bus_priv;
 const struct mlxsw_bus_info *bus_info;
 struct workqueue_struct *emad_wq;
 struct list_head rx_listener_list;
 struct list_head event_listener_list;
 struct list_head irq_event_handler_list;
 struct mutex irq_event_handler_lock; /* Locks access to handlers list */
 struct {
  atomic64_t tid;
  struct list_head trans_list;
  spinlock_t trans_list_lock; /* protects trans_list writes */
  bool use_emad;
  bool enable_string_tlv;
  bool enable_latency_tlv;
 } emad;
 struct {
  u16 *mapping; /* lag_id+port_index to local_port mapping */
 } lag;
 struct mlxsw_res res;
 struct mlxsw_hwmon *hwmon;
 struct mlxsw_thermal *thermal;
 struct mlxsw_linecards *linecards;
 struct mlxsw_core_port *ports;
 unsigned int max_ports;
 atomic_t active_ports_count;
 bool fw_flash_in_progress;
 struct {
  struct devlink_health_reporter *fw_fatal;
 } health;
 struct mlxsw_env *env;
 unsigned long driver_priv[];
 /* driver_priv has to be always the last item */
};

struct mlxsw_linecards *mlxsw_core_linecards(struct mlxsw_core *mlxsw_core)
{
 return mlxsw_core->linecards;
}

void mlxsw_core_linecards_set(struct mlxsw_core *mlxsw_core,
         struct mlxsw_linecards *linecards)
{
 mlxsw_core->linecards = linecards;
}

#define MLXSW_PORT_MAX_PORTS_DEFAULT 0x40

static u64 mlxsw_ports_occ_get(void *priv)
{
 struct mlxsw_core *mlxsw_core = priv;

 return atomic_read(&mlxsw_core->active_ports_count);
}

static int mlxsw_core_resources_ports_register(struct mlxsw_core *mlxsw_core)
{
 struct devlink *devlink = priv_to_devlink(mlxsw_core);
 struct devlink_resource_size_params ports_num_params;
 u32 max_ports;

 max_ports = mlxsw_core->max_ports - 1;
 devlink_resource_size_params_init(&ports_num_params, max_ports,
       max_ports, 1,
       DEVLINK_RESOURCE_UNIT_ENTRY);

 return devl_resource_register(devlink,
          DEVLINK_RESOURCE_GENERIC_NAME_PORTS,
          max_ports, MLXSW_CORE_RESOURCE_PORTS,
          DEVLINK_RESOURCE_ID_PARENT_TOP,
          &ports_num_params);
}

static int mlxsw_ports_init(struct mlxsw_core *mlxsw_core, bool reload)
{
 struct devlink *devlink = priv_to_devlink(mlxsw_core);
 int err;

 /* Switch ports are numbered from 1 to queried value */
 if (MLXSW_CORE_RES_VALID(mlxsw_core, MAX_SYSTEM_PORT))
  mlxsw_core->max_ports = MLXSW_CORE_RES_GET(mlxsw_core,
          MAX_SYSTEM_PORT) + 1;
 else
  mlxsw_core->max_ports = MLXSW_PORT_MAX_PORTS_DEFAULT + 1;

 mlxsw_core->ports = kcalloc(mlxsw_core->max_ports,
        sizeof(struct mlxsw_core_port), GFP_KERNEL);
 if (!mlxsw_core->ports)
  return -ENOMEM;

 if (!reload) {
  err = mlxsw_core_resources_ports_register(mlxsw_core);
  if (err)
   goto err_resources_ports_register;
 }
 atomic_set(&mlxsw_core->active_ports_count, 0);
 devl_resource_occ_get_register(devlink, MLXSW_CORE_RESOURCE_PORTS,
           mlxsw_ports_occ_get, mlxsw_core);

 return 0;

err_resources_ports_register:
 kfree(mlxsw_core->ports);
 return err;
}

static void mlxsw_ports_fini(struct mlxsw_core *mlxsw_core, bool reload)
{
 struct devlink *devlink = priv_to_devlink(mlxsw_core);

 devl_resource_occ_get_unregister(devlink, MLXSW_CORE_RESOURCE_PORTS);
 if (!reload)
  devl_resources_unregister(priv_to_devlink(mlxsw_core));

 kfree(mlxsw_core->ports);
}

unsigned int mlxsw_core_max_ports(const struct mlxsw_core *mlxsw_core)
{
 return mlxsw_core->max_ports;
}
EXPORT_SYMBOL(mlxsw_core_max_ports);

int mlxsw_core_max_lag(struct mlxsw_core *mlxsw_core, u16 *p_max_lag)
{
 struct mlxsw_driver *driver = mlxsw_core->driver;

 if (driver->profile->used_max_lag) {
  *p_max_lag = driver->profile->max_lag;
  return 0;
 }

 if (!MLXSW_CORE_RES_VALID(mlxsw_core, MAX_LAG))
  return -EIO;

 *p_max_lag = MLXSW_CORE_RES_GET(mlxsw_core, MAX_LAG);
 return 0;
}
EXPORT_SYMBOL(mlxsw_core_max_lag);

enum mlxsw_cmd_mbox_config_profile_lag_mode
mlxsw_core_lag_mode(struct mlxsw_core *mlxsw_core)
{
 return mlxsw_core->bus->lag_mode(mlxsw_core->bus_priv);
}
EXPORT_SYMBOL(mlxsw_core_lag_mode);

enum mlxsw_cmd_mbox_config_profile_flood_mode
mlxsw_core_flood_mode(struct mlxsw_core *mlxsw_core)
{
 return mlxsw_core->bus->flood_mode(mlxsw_core->bus_priv);
}
EXPORT_SYMBOL(mlxsw_core_flood_mode);

void *mlxsw_core_driver_priv(struct mlxsw_core *mlxsw_core)
{
 return mlxsw_core->driver_priv;
}
EXPORT_SYMBOL(mlxsw_core_driver_priv);

bool
mlxsw_core_fw_rev_minor_subminor_validate(const struct mlxsw_fw_rev *rev,
       const struct mlxsw_fw_rev *req_rev)
{
 return rev->minor > req_rev->minor ||
        (rev->minor == req_rev->minor &&
  rev->subminor >= req_rev->subminor);
}
EXPORT_SYMBOL(mlxsw_core_fw_rev_minor_subminor_validate);

struct mlxsw_rx_listener_item {
 struct list_head list;
 struct mlxsw_rx_listener rxl;
 void *priv;
 bool enabled;
};

struct mlxsw_event_listener_item {
 struct list_head list;
 struct mlxsw_core *mlxsw_core;
 struct mlxsw_event_listener el;
 void *priv;
};

static const u8 mlxsw_core_trap_groups[] = {
 MLXSW_REG_HTGT_TRAP_GROUP_EMAD,
 MLXSW_REG_HTGT_TRAP_GROUP_CORE_EVENT,
};

static int mlxsw_core_trap_groups_set(struct mlxsw_core *mlxsw_core)
{
 char htgt_pl[MLXSW_REG_HTGT_LEN];
 int err;
 int i;

 if (!(mlxsw_core->bus->features & MLXSW_BUS_F_TXRX))
  return 0;

 for (i = 0; i < ARRAY_SIZE(mlxsw_core_trap_groups); i++) {
  mlxsw_reg_htgt_pack(htgt_pl, mlxsw_core_trap_groups[i],
        MLXSW_REG_HTGT_INVALID_POLICER,
        MLXSW_REG_HTGT_DEFAULT_PRIORITY,
        MLXSW_REG_HTGT_DEFAULT_TC);
  err = mlxsw_reg_write(mlxsw_core, MLXSW_REG(htgt), htgt_pl);
  if (err)
   return err;
 }
 return 0;
}

/******************
 * EMAD processing
 ******************/


/* emad_eth_hdr_dmac
 * Destination MAC in EMAD's Ethernet header.
 * Must be set to 01:02:c9:00:00:01
 */

MLXSW_ITEM_BUF(emad, eth_hdr, dmac, 0x00, 6);

/* emad_eth_hdr_smac
 * Source MAC in EMAD's Ethernet header.
 * Must be set to 00:02:c9:01:02:03
 */

MLXSW_ITEM_BUF(emad, eth_hdr, smac, 0x06, 6);

/* emad_eth_hdr_ethertype
 * Ethertype in EMAD's Ethernet header.
 * Must be set to 0x8932
 */

MLXSW_ITEM32(emad, eth_hdr, ethertype, 0x0C, 16, 16);

/* emad_eth_hdr_mlx_proto
 * Mellanox protocol.
 * Must be set to 0x0.
 */

MLXSW_ITEM32(emad, eth_hdr, mlx_proto, 0x0C, 8, 8);

/* emad_eth_hdr_ver
 * Mellanox protocol version.
 * Must be set to 0x0.
 */

MLXSW_ITEM32(emad, eth_hdr, ver, 0x0C, 4, 4);

/* emad_op_tlv_type
 * Type of the TLV.
 * Must be set to 0x1 (operation TLV).
 */

MLXSW_ITEM32(emad, op_tlv, type, 0x00, 27, 5);

/* emad_op_tlv_len
 * Length of the operation TLV in u32.
 * Must be set to 0x4.
 */

MLXSW_ITEM32(emad, op_tlv, len, 0x00, 16, 11);

/* emad_op_tlv_dr
 * Direct route bit. Setting to 1 indicates the EMAD is a direct route
 * EMAD. DR TLV must follow.
 *
 * Note: Currently not supported and must not be set.
 */

MLXSW_ITEM32(emad, op_tlv, dr, 0x00, 15, 1);

/* emad_op_tlv_status
 * Returned status in case of EMAD response. Must be set to 0 in case
 * of EMAD request.
 * 0x0 - success
 * 0x1 - device is busy. Requester should retry
 * 0x2 - Mellanox protocol version not supported
 * 0x3 - unknown TLV
 * 0x4 - register not supported
 * 0x5 - operation class not supported
 * 0x6 - EMAD method not supported
 * 0x7 - bad parameter (e.g. port out of range)
 * 0x8 - resource not available
 * 0x9 - message receipt acknowledgment. Requester should retry
 * 0x70 - internal error
 */

MLXSW_ITEM32(emad, op_tlv, status, 0x00, 8, 7);

/* emad_op_tlv_register_id
 * Register ID of register within register TLV.
 */

MLXSW_ITEM32(emad, op_tlv, register_id, 0x04, 16, 16);

/* emad_op_tlv_r
 * Response bit. Setting to 1 indicates Response, otherwise request.
 */

MLXSW_ITEM32(emad, op_tlv, r, 0x04, 15, 1);

/* emad_op_tlv_method
 * EMAD method type.
 * 0x1 - query
 * 0x2 - write
 * 0x3 - send (currently not supported)
 * 0x4 - event
 */

MLXSW_ITEM32(emad, op_tlv, method, 0x04, 8, 7);

/* emad_op_tlv_class
 * EMAD operation class. Must be set to 0x1 (REG_ACCESS).
 */

MLXSW_ITEM32(emad, op_tlv, class, 0x04, 0, 8);

/* emad_op_tlv_tid
 * EMAD transaction ID. Used for pairing request and response EMADs.
 */

MLXSW_ITEM64(emad, op_tlv, tid, 0x08, 0, 64);

/* emad_string_tlv_type
 * Type of the TLV.
 * Must be set to 0x2 (string TLV).
 */

MLXSW_ITEM32(emad, string_tlv, type, 0x00, 27, 5);

/* emad_string_tlv_len
 * Length of the string TLV in u32.
 */

MLXSW_ITEM32(emad, string_tlv, len, 0x00, 16, 11);

#define MLXSW_EMAD_STRING_TLV_STRING_LEN 128

/* emad_string_tlv_string
 * String provided by the device's firmware in case of erroneous register access
 */

MLXSW_ITEM_BUF(emad, string_tlv, string, 0x04,
        MLXSW_EMAD_STRING_TLV_STRING_LEN);

/* emad_latency_tlv_type
 * Type of the TLV.
 * Must be set to 0x4 (latency TLV).
 */

MLXSW_ITEM32(emad, latency_tlv, type, 0x00, 27, 5);

/* emad_latency_tlv_len
 * Length of the latency TLV in u32.
 */

MLXSW_ITEM32(emad, latency_tlv, len, 0x00, 16, 11);

/* emad_latency_tlv_latency_time
 * EMAD latency time in units of uSec.
 */

MLXSW_ITEM32(emad, latency_tlv, latency_time, 0x04, 0, 32);

/* emad_reg_tlv_type
 * Type of the TLV.
 * Must be set to 0x3 (register TLV).
 */

MLXSW_ITEM32(emad, reg_tlv, type, 0x00, 27, 5);

/* emad_reg_tlv_len
 * Length of the operation TLV in u32.
 */

MLXSW_ITEM32(emad, reg_tlv, len, 0x00, 16, 11);

/* emad_end_tlv_type
 * Type of the TLV.
 * Must be set to 0x0 (end TLV).
 */

MLXSW_ITEM32(emad, end_tlv, type, 0x00, 27, 5);

/* emad_end_tlv_len
 * Length of the end TLV in u32.
 * Must be set to 1.
 */

MLXSW_ITEM32(emad, end_tlv, len, 0x00, 16, 11);

enum mlxsw_core_reg_access_type {
 MLXSW_CORE_REG_ACCESS_TYPE_QUERY,
 MLXSW_CORE_REG_ACCESS_TYPE_WRITE,
};

static inline const char *
mlxsw_core_reg_access_type_str(enum mlxsw_core_reg_access_type type)
{
 switch (type) {
 case MLXSW_CORE_REG_ACCESS_TYPE_QUERY:
  return "query";
 case MLXSW_CORE_REG_ACCESS_TYPE_WRITE:
  return "write";
 }
 BUG();
}

static void mlxsw_emad_pack_end_tlv(char *end_tlv)
{
 mlxsw_emad_end_tlv_type_set(end_tlv, MLXSW_EMAD_TLV_TYPE_END);
 mlxsw_emad_end_tlv_len_set(end_tlv, MLXSW_EMAD_END_TLV_LEN);
}

static void mlxsw_emad_pack_reg_tlv(char *reg_tlv,
        const struct mlxsw_reg_info *reg,
        char *payload)
{
 mlxsw_emad_reg_tlv_type_set(reg_tlv, MLXSW_EMAD_TLV_TYPE_REG);
 mlxsw_emad_reg_tlv_len_set(reg_tlv, reg->len / sizeof(u32) + 1);
 memcpy(reg_tlv + sizeof(u32), payload, reg->len);
}

static void mlxsw_emad_pack_string_tlv(char *string_tlv)
{
 mlxsw_emad_string_tlv_type_set(string_tlv, MLXSW_EMAD_TLV_TYPE_STRING);
 mlxsw_emad_string_tlv_len_set(string_tlv, MLXSW_EMAD_STRING_TLV_LEN);
}

static void mlxsw_emad_pack_op_tlv(char *op_tlv,
       const struct mlxsw_reg_info *reg,
       enum mlxsw_core_reg_access_type type,
       u64 tid)
{
 mlxsw_emad_op_tlv_type_set(op_tlv, MLXSW_EMAD_TLV_TYPE_OP);
 mlxsw_emad_op_tlv_len_set(op_tlv, MLXSW_EMAD_OP_TLV_LEN);
 mlxsw_emad_op_tlv_dr_set(op_tlv, 0);
 mlxsw_emad_op_tlv_status_set(op_tlv, 0);
 mlxsw_emad_op_tlv_register_id_set(op_tlv, reg->id);
 mlxsw_emad_op_tlv_r_set(op_tlv, MLXSW_EMAD_OP_TLV_REQUEST);
 if (type == MLXSW_CORE_REG_ACCESS_TYPE_QUERY)
  mlxsw_emad_op_tlv_method_set(op_tlv,
          MLXSW_EMAD_OP_TLV_METHOD_QUERY);
 else
  mlxsw_emad_op_tlv_method_set(op_tlv,
          MLXSW_EMAD_OP_TLV_METHOD_WRITE);
 mlxsw_emad_op_tlv_class_set(op_tlv,
        MLXSW_EMAD_OP_TLV_CLASS_REG_ACCESS);
 mlxsw_emad_op_tlv_tid_set(op_tlv, tid);
}

static void mlxsw_emad_pack_latency_tlv(char *latency_tlv)
{
 mlxsw_emad_latency_tlv_type_set(latency_tlv, MLXSW_EMAD_TLV_TYPE_LATENCY);
 mlxsw_emad_latency_tlv_len_set(latency_tlv, MLXSW_EMAD_LATENCY_TLV_LEN);
}

static int mlxsw_emad_construct_eth_hdr(struct sk_buff *skb)
{
 char *eth_hdr = skb_push(skb, MLXSW_EMAD_ETH_HDR_LEN);

 mlxsw_emad_eth_hdr_dmac_memcpy_to(eth_hdr, MLXSW_EMAD_EH_DMAC);
 mlxsw_emad_eth_hdr_smac_memcpy_to(eth_hdr, MLXSW_EMAD_EH_SMAC);
 mlxsw_emad_eth_hdr_ethertype_set(eth_hdr, MLXSW_EMAD_EH_ETHERTYPE);
 mlxsw_emad_eth_hdr_mlx_proto_set(eth_hdr, MLXSW_EMAD_EH_MLX_PROTO);
 mlxsw_emad_eth_hdr_ver_set(eth_hdr, MLXSW_EMAD_EH_PROTO_VERSION);

 skb_reset_mac_header(skb);

 return 0;
}

static void mlxsw_emad_construct(const struct mlxsw_core *mlxsw_core,
     struct sk_buff *skb,
     const struct mlxsw_reg_info *reg,
     char *payload,
     enum mlxsw_core_reg_access_type type, u64 tid)
{
 char *buf;

 buf = skb_push(skb, MLXSW_EMAD_END_TLV_LEN * sizeof(u32));
 mlxsw_emad_pack_end_tlv(buf);

 buf = skb_push(skb, reg->len + sizeof(u32));
 mlxsw_emad_pack_reg_tlv(buf, reg, payload);

 if (mlxsw_core->emad.enable_latency_tlv) {
  buf = skb_push(skb, MLXSW_EMAD_LATENCY_TLV_LEN * sizeof(u32));
  mlxsw_emad_pack_latency_tlv(buf);
 }

 if (mlxsw_core->emad.enable_string_tlv) {
  buf = skb_push(skb, MLXSW_EMAD_STRING_TLV_LEN * sizeof(u32));
  mlxsw_emad_pack_string_tlv(buf);
 }

 buf = skb_push(skb, MLXSW_EMAD_OP_TLV_LEN * sizeof(u32));
 mlxsw_emad_pack_op_tlv(buf, reg, type, tid);

 mlxsw_emad_construct_eth_hdr(skb);
}

struct mlxsw_emad_tlv_offsets {
 u16 op_tlv;
 u16 string_tlv;
 u16 latency_tlv;
 u16 reg_tlv;
};

static bool mlxsw_emad_tlv_is_string_tlv(const char *tlv)
{
 u8 tlv_type = mlxsw_emad_string_tlv_type_get(tlv);

 return tlv_type == MLXSW_EMAD_TLV_TYPE_STRING;
}

static bool mlxsw_emad_tlv_is_latency_tlv(const char *tlv)
{
 u8 tlv_type = mlxsw_emad_latency_tlv_type_get(tlv);

 return tlv_type == MLXSW_EMAD_TLV_TYPE_LATENCY;
}

static void mlxsw_emad_tlv_parse(struct sk_buff *skb)
{
 struct mlxsw_emad_tlv_offsets *offsets =
  (struct mlxsw_emad_tlv_offsets *) skb->cb;

 offsets->op_tlv = MLXSW_EMAD_ETH_HDR_LEN;
 offsets->string_tlv = 0;
 offsets->latency_tlv = 0;

 offsets->reg_tlv = MLXSW_EMAD_ETH_HDR_LEN +
      MLXSW_EMAD_OP_TLV_LEN * sizeof(u32);

 /* If string TLV is present, it must come after the operation TLV. */
 if (mlxsw_emad_tlv_is_string_tlv(skb->data + offsets->reg_tlv)) {
  offsets->string_tlv = offsets->reg_tlv;
  offsets->reg_tlv += MLXSW_EMAD_STRING_TLV_LEN * sizeof(u32);
 }

 if (mlxsw_emad_tlv_is_latency_tlv(skb->data + offsets->reg_tlv)) {
  offsets->latency_tlv = offsets->reg_tlv;
  offsets->reg_tlv += MLXSW_EMAD_LATENCY_TLV_LEN * sizeof(u32);
 }
}

static char *mlxsw_emad_op_tlv(const struct sk_buff *skb)
{
 struct mlxsw_emad_tlv_offsets *offsets =
  (struct mlxsw_emad_tlv_offsets *) skb->cb;

 return ((char *) (skb->data + offsets->op_tlv));
}

static char *mlxsw_emad_string_tlv(const struct sk_buff *skb)
{
 struct mlxsw_emad_tlv_offsets *offsets =
  (struct mlxsw_emad_tlv_offsets *) skb->cb;

 if (!offsets->string_tlv)
  return NULL;

 return ((char *) (skb->data + offsets->string_tlv));
}

static char *mlxsw_emad_reg_tlv(const struct sk_buff *skb)
{
 struct mlxsw_emad_tlv_offsets *offsets =
  (struct mlxsw_emad_tlv_offsets *) skb->cb;

 return ((char *) (skb->data + offsets->reg_tlv));
}

static char *mlxsw_emad_reg_payload(const char *reg_tlv)
{
 return ((char *) (reg_tlv + sizeof(u32)));
}

static char *mlxsw_emad_reg_payload_cmd(const char *mbox)
{
 return ((char *) (mbox + (MLXSW_EMAD_OP_TLV_LEN + 1) * sizeof(u32)));
}

static u64 mlxsw_emad_get_tid(const struct sk_buff *skb)
{
 char *op_tlv;

 op_tlv = mlxsw_emad_op_tlv(skb);
 return mlxsw_emad_op_tlv_tid_get(op_tlv);
}

static bool mlxsw_emad_is_resp(const struct sk_buff *skb)
{
 char *op_tlv;

 op_tlv = mlxsw_emad_op_tlv(skb);
 return (mlxsw_emad_op_tlv_r_get(op_tlv) == MLXSW_EMAD_OP_TLV_RESPONSE);
}

static int mlxsw_emad_process_status(char *op_tlv,
         enum mlxsw_emad_op_tlv_status *p_status)
{
 *p_status = mlxsw_emad_op_tlv_status_get(op_tlv);

 switch (*p_status) {
 case MLXSW_EMAD_OP_TLV_STATUS_SUCCESS:
  return 0;
 case MLXSW_EMAD_OP_TLV_STATUS_BUSY:
 case MLXSW_EMAD_OP_TLV_STATUS_MESSAGE_RECEIPT_ACK:
  return -EAGAIN;
 case MLXSW_EMAD_OP_TLV_STATUS_VERSION_NOT_SUPPORTED:
 case MLXSW_EMAD_OP_TLV_STATUS_UNKNOWN_TLV:
 case MLXSW_EMAD_OP_TLV_STATUS_REGISTER_NOT_SUPPORTED:
 case MLXSW_EMAD_OP_TLV_STATUS_CLASS_NOT_SUPPORTED:
 case MLXSW_EMAD_OP_TLV_STATUS_METHOD_NOT_SUPPORTED:
 case MLXSW_EMAD_OP_TLV_STATUS_BAD_PARAMETER:
 case MLXSW_EMAD_OP_TLV_STATUS_RESOURCE_NOT_AVAILABLE:
 case MLXSW_EMAD_OP_TLV_STATUS_INTERNAL_ERROR:
 default:
  return -EIO;
 }
}

static int
mlxsw_emad_process_status_skb(struct sk_buff *skb,
         enum mlxsw_emad_op_tlv_status *p_status)
{
 return mlxsw_emad_process_status(mlxsw_emad_op_tlv(skb), p_status);
}

struct mlxsw_reg_trans {
 struct list_head list;
 struct list_head bulk_list;
 struct mlxsw_core *core;
 struct sk_buff *tx_skb;
 struct mlxsw_txhdr_info txhdr_info;
 struct delayed_work timeout_dw;
 unsigned int retries;
 u64 tid;
 struct completion completion;
 atomic_t active;
 mlxsw_reg_trans_cb_t *cb;
 unsigned long cb_priv;
 const struct mlxsw_reg_info *reg;
 enum mlxsw_core_reg_access_type type;
 int err;
 char *emad_err_string;
 enum mlxsw_emad_op_tlv_status emad_status;
 struct rcu_head rcu;
};

static void mlxsw_emad_process_string_tlv(const struct sk_buff *skb,
       struct mlxsw_reg_trans *trans)
{
 char *string_tlv;
 char *string;

 string_tlv = mlxsw_emad_string_tlv(skb);
 if (!string_tlv)
  return;

 trans->emad_err_string = kzalloc(MLXSW_EMAD_STRING_TLV_STRING_LEN,
      GFP_ATOMIC);
 if (!trans->emad_err_string)
  return;

 string = mlxsw_emad_string_tlv_string_data(string_tlv);
 strscpy(trans->emad_err_string, string,
  MLXSW_EMAD_STRING_TLV_STRING_LEN);
}

#define MLXSW_EMAD_TIMEOUT_DURING_FW_FLASH_MS 3000
#define MLXSW_EMAD_TIMEOUT_MS   200

static void mlxsw_emad_trans_timeout_schedule(struct mlxsw_reg_trans *trans)
{
 unsigned long timeout = msecs_to_jiffies(MLXSW_EMAD_TIMEOUT_MS);

 if (trans->core->fw_flash_in_progress)
  timeout = msecs_to_jiffies(MLXSW_EMAD_TIMEOUT_DURING_FW_FLASH_MS);

 queue_delayed_work(trans->core->emad_wq, &trans->timeout_dw,
      timeout << trans->retries);
}

static int mlxsw_emad_transmit(struct mlxsw_core *mlxsw_core,
          struct mlxsw_reg_trans *trans)
{
 struct sk_buff *skb;
 int err;

 skb = skb_clone(trans->tx_skb, GFP_KERNEL);
 if (!skb)
  return -ENOMEM;

 trace_devlink_hwmsg(priv_to_devlink(mlxsw_core), false, 0, skb->data,
       skb->len);

 atomic_set(&trans->active, 1);
 err = mlxsw_core_skb_transmit(mlxsw_core, skb, &trans->txhdr_info);
 if (err) {
  dev_kfree_skb(skb);
  return err;
 }
 mlxsw_emad_trans_timeout_schedule(trans);
 return 0;
}

static void mlxsw_emad_trans_finish(struct mlxsw_reg_trans *trans, int err)
{
 struct mlxsw_core *mlxsw_core = trans->core;

 dev_kfree_skb(trans->tx_skb);
 spin_lock_bh(&mlxsw_core->emad.trans_list_lock);
 list_del_rcu(&trans->list);
 spin_unlock_bh(&mlxsw_core->emad.trans_list_lock);
 trans->err = err;
 complete(&trans->completion);
}

static void mlxsw_emad_transmit_retry(struct mlxsw_core *mlxsw_core,
          struct mlxsw_reg_trans *trans)
{
 int err;

 if (trans->retries < MLXSW_EMAD_MAX_RETRY) {
  trans->retries++;
  err = mlxsw_emad_transmit(trans->core, trans);
  if (err == 0)
   return;

  if (!atomic_dec_and_test(&trans->active))
   return;
 } else {
  err = -EIO;
 }
 mlxsw_emad_trans_finish(trans, err);
}

static void mlxsw_emad_trans_timeout_work(struct work_struct *work)
{
 struct mlxsw_reg_trans *trans = container_of(work,
           struct mlxsw_reg_trans,
           timeout_dw.work);

 if (!atomic_dec_and_test(&trans->active))
  return;

 mlxsw_emad_transmit_retry(trans->core, trans);
}

static void mlxsw_emad_process_response(struct mlxsw_core *mlxsw_core,
     struct mlxsw_reg_trans *trans,
     struct sk_buff *skb)
{
 int err;

 if (!atomic_dec_and_test(&trans->active))
  return;

 err = mlxsw_emad_process_status_skb(skb, &trans->emad_status);
 if (err == -EAGAIN) {
  mlxsw_emad_transmit_retry(mlxsw_core, trans);
 } else {
  if (err == 0) {
   char *reg_tlv = mlxsw_emad_reg_tlv(skb);

   if (trans->cb)
    trans->cb(mlxsw_core,
       mlxsw_emad_reg_payload(reg_tlv),
       trans->reg->len, trans->cb_priv);
  } else {
   mlxsw_emad_process_string_tlv(skb, trans);
  }
  mlxsw_emad_trans_finish(trans, err);
 }
}

/* called with rcu read lock held */
static void mlxsw_emad_rx_listener_func(struct sk_buff *skb, u16 local_port,
     void *priv)
{
 struct mlxsw_core *mlxsw_core = priv;
 struct mlxsw_reg_trans *trans;

 trace_devlink_hwmsg(priv_to_devlink(mlxsw_core), true, 0,
       skb->data, skb->len);

 mlxsw_emad_tlv_parse(skb);

 if (!mlxsw_emad_is_resp(skb))
  goto free_skb;

 list_for_each_entry_rcu(trans, &mlxsw_core->emad.trans_list, list) {
  if (mlxsw_emad_get_tid(skb) == trans->tid) {
   mlxsw_emad_process_response(mlxsw_core, trans, skb);
   break;
  }
 }

free_skb:
 dev_kfree_skb(skb);
}

static const struct mlxsw_listener mlxsw_emad_rx_listener =
 MLXSW_RXL(mlxsw_emad_rx_listener_func, ETHEMAD, TRAP_TO_CPU, false,
    EMAD, FORWARD);

static int mlxsw_emad_tlv_enable(struct mlxsw_core *mlxsw_core)
{
 char mgir_pl[MLXSW_REG_MGIR_LEN];
 bool string_tlv, latency_tlv;
 int err;

 mlxsw_reg_mgir_pack(mgir_pl);
 err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mgir), mgir_pl);
 if (err)
  return err;

 string_tlv = mlxsw_reg_mgir_fw_info_string_tlv_get(mgir_pl);
 mlxsw_core->emad.enable_string_tlv = string_tlv;

 latency_tlv = mlxsw_reg_mgir_fw_info_latency_tlv_get(mgir_pl);
 mlxsw_core->emad.enable_latency_tlv = latency_tlv;

 return 0;
}

static void mlxsw_emad_tlv_disable(struct mlxsw_core *mlxsw_core)
{
 mlxsw_core->emad.enable_latency_tlv = false;
 mlxsw_core->emad.enable_string_tlv = false;
}

static int mlxsw_emad_init(struct mlxsw_core *mlxsw_core)
{
 struct workqueue_struct *emad_wq;
 u64 tid;
 int err;

 if (!(mlxsw_core->bus->features & MLXSW_BUS_F_TXRX))
  return 0;

 emad_wq = alloc_workqueue("mlxsw_core_emad", 0, 0);
 if (!emad_wq)
  return -ENOMEM;
 mlxsw_core->emad_wq = emad_wq;

 /* Set the upper 32 bits of the transaction ID field to a random
 * number. This allows us to discard EMADs addressed to other
 * devices.
 */

 get_random_bytes(&tid, 4);
 tid <<= 32;
 atomic64_set(&mlxsw_core->emad.tid, tid);

 INIT_LIST_HEAD(&mlxsw_core->emad.trans_list);
 spin_lock_init(&mlxsw_core->emad.trans_list_lock);

 err = mlxsw_core_trap_register(mlxsw_core, &mlxsw_emad_rx_listener,
           mlxsw_core);
 if (err)
  goto err_trap_register;

 err = mlxsw_emad_tlv_enable(mlxsw_core);
 if (err)
  goto err_emad_tlv_enable;

 mlxsw_core->emad.use_emad = true;

 return 0;

err_emad_tlv_enable:
 mlxsw_core_trap_unregister(mlxsw_core, &mlxsw_emad_rx_listener,
       mlxsw_core);
err_trap_register:
 destroy_workqueue(mlxsw_core->emad_wq);
 return err;
}

static void mlxsw_emad_fini(struct mlxsw_core *mlxsw_core)
{

 if (!(mlxsw_core->bus->features & MLXSW_BUS_F_TXRX))
  return;

 mlxsw_core->emad.use_emad = false;
 mlxsw_emad_tlv_disable(mlxsw_core);
 mlxsw_core_trap_unregister(mlxsw_core, &mlxsw_emad_rx_listener,
       mlxsw_core);
 destroy_workqueue(mlxsw_core->emad_wq);
}

static struct sk_buff *mlxsw_emad_alloc(const struct mlxsw_core *mlxsw_core,
     u16 reg_len)
{
 struct sk_buff *skb;
 u16 emad_len;

 emad_len = (reg_len + sizeof(u32) + MLXSW_EMAD_ETH_HDR_LEN +
      (MLXSW_EMAD_OP_TLV_LEN + MLXSW_EMAD_END_TLV_LEN) *
      sizeof(u32) + MLXSW_TXHDR_LEN);
 if (mlxsw_core->emad.enable_string_tlv)
  emad_len += MLXSW_EMAD_STRING_TLV_LEN * sizeof(u32);
 if (mlxsw_core->emad.enable_latency_tlv)
  emad_len +=  MLXSW_EMAD_LATENCY_TLV_LEN * sizeof(u32);
 if (emad_len > MLXSW_EMAD_MAX_FRAME_LEN)
  return NULL;

 skb = netdev_alloc_skb(NULL, emad_len);
 if (!skb)
  return NULL;
 memset(skb->data, 0, emad_len);
 skb_reserve(skb, emad_len);

 return skb;
}

static int mlxsw_emad_reg_access(struct mlxsw_core *mlxsw_core,
     const struct mlxsw_reg_info *reg,
     char *payload,
     enum mlxsw_core_reg_access_type type,
     struct mlxsw_reg_trans *trans,
     struct list_head *bulk_list,
     mlxsw_reg_trans_cb_t *cb,
     unsigned long cb_priv, u64 tid)
{
 struct sk_buff *skb;
 int err;

 dev_dbg(mlxsw_core->bus_info->dev, "EMAD reg access (tid=%llx,reg_id=%x(%s),type=%s)\n",
  tid, reg->id, mlxsw_reg_id_str(reg->id),
  mlxsw_core_reg_access_type_str(type));

 skb = mlxsw_emad_alloc(mlxsw_core, reg->len);
 if (!skb)
  return -ENOMEM;

 list_add_tail(&trans->bulk_list, bulk_list);
 trans->core = mlxsw_core;
 trans->tx_skb = skb;
 trans->txhdr_info.tx_info.local_port = MLXSW_PORT_CPU_PORT;
 trans->txhdr_info.tx_info.is_emad = true;
 INIT_DELAYED_WORK(&trans->timeout_dw, mlxsw_emad_trans_timeout_work);
 trans->tid = tid;
 init_completion(&trans->completion);
 trans->cb = cb;
 trans->cb_priv = cb_priv;
 trans->reg = reg;
 trans->type = type;

 mlxsw_emad_construct(mlxsw_core, skb, reg, payload, type, trans->tid);

 spin_lock_bh(&mlxsw_core->emad.trans_list_lock);
 list_add_tail_rcu(&trans->list, &mlxsw_core->emad.trans_list);
 spin_unlock_bh(&mlxsw_core->emad.trans_list_lock);
 err = mlxsw_emad_transmit(mlxsw_core, trans);
 if (err)
  goto err_out;
 return 0;

err_out:
 spin_lock_bh(&mlxsw_core->emad.trans_list_lock);
 list_del_rcu(&trans->list);
 spin_unlock_bh(&mlxsw_core->emad.trans_list_lock);
 list_del(&trans->bulk_list);
 dev_kfree_skb(trans->tx_skb);
 return err;
}

/*****************
 * Core functions
 *****************/


int mlxsw_core_driver_register(struct mlxsw_driver *mlxsw_driver)
{
 spin_lock(&mlxsw_core_driver_list_lock);
 list_add_tail(&mlxsw_driver->list, &mlxsw_core_driver_list);
 spin_unlock(&mlxsw_core_driver_list_lock);
 return 0;
}
EXPORT_SYMBOL(mlxsw_core_driver_register);

void mlxsw_core_driver_unregister(struct mlxsw_driver *mlxsw_driver)
{
 spin_lock(&mlxsw_core_driver_list_lock);
 list_del(&mlxsw_driver->list);
 spin_unlock(&mlxsw_core_driver_list_lock);
}
EXPORT_SYMBOL(mlxsw_core_driver_unregister);

static struct mlxsw_driver *__driver_find(const char *kind)
{
 struct mlxsw_driver *mlxsw_driver;

 list_for_each_entry(mlxsw_driver, &mlxsw_core_driver_list, list) {
  if (strcmp(mlxsw_driver->kind, kind) == 0)
   return mlxsw_driver;
 }
 return NULL;
}

static struct mlxsw_driver *mlxsw_core_driver_get(const char *kind)
{
 struct mlxsw_driver *mlxsw_driver;

 spin_lock(&mlxsw_core_driver_list_lock);
 mlxsw_driver = __driver_find(kind);
 spin_unlock(&mlxsw_core_driver_list_lock);
 return mlxsw_driver;
}

int mlxsw_core_fw_flash(struct mlxsw_core *mlxsw_core,
   struct mlxfw_dev *mlxfw_dev,
   const struct firmware *firmware,
   struct netlink_ext_ack *extack)
{
 int err;

 mlxsw_core->fw_flash_in_progress = true;
 err = mlxfw_firmware_flash(mlxfw_dev, firmware, extack);
 mlxsw_core->fw_flash_in_progress = false;

 return err;
}

struct mlxsw_core_fw_info {
 struct mlxfw_dev mlxfw_dev;
 struct mlxsw_core *mlxsw_core;
};

static int mlxsw_core_fw_component_query(struct mlxfw_dev *mlxfw_dev,
      u16 component_index, u32 *p_max_size,
      u8 *p_align_bits, u16 *p_max_write_size)
{
 struct mlxsw_core_fw_info *mlxsw_core_fw_info =
  container_of(mlxfw_dev, struct mlxsw_core_fw_info, mlxfw_dev);
 struct mlxsw_core *mlxsw_core = mlxsw_core_fw_info->mlxsw_core;
 char mcqi_pl[MLXSW_REG_MCQI_LEN];
 int err;

 mlxsw_reg_mcqi_pack(mcqi_pl, component_index);
 err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mcqi), mcqi_pl);
 if (err)
  return err;
 mlxsw_reg_mcqi_unpack(mcqi_pl, p_max_size, p_align_bits, p_max_write_size);

 *p_align_bits = max_t(u8, *p_align_bits, 2);
 *p_max_write_size = min_t(u16, *p_max_write_size, MLXSW_REG_MCDA_MAX_DATA_LEN);
 return 0;
}

static int mlxsw_core_fw_fsm_lock(struct mlxfw_dev *mlxfw_dev, u32 *fwhandle)
{
 struct mlxsw_core_fw_info *mlxsw_core_fw_info =
  container_of(mlxfw_dev, struct mlxsw_core_fw_info, mlxfw_dev);
 struct mlxsw_core *mlxsw_core = mlxsw_core_fw_info->mlxsw_core;
 char mcc_pl[MLXSW_REG_MCC_LEN];
 u8 control_state;
 int err;

 mlxsw_reg_mcc_pack(mcc_pl, 0, 0, 0, 0);
 err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mcc), mcc_pl);
 if (err)
  return err;

 mlxsw_reg_mcc_unpack(mcc_pl, fwhandle, NULL, &control_state);
 if (control_state != MLXFW_FSM_STATE_IDLE)
  return -EBUSY;

 mlxsw_reg_mcc_pack(mcc_pl, MLXSW_REG_MCC_INSTRUCTION_LOCK_UPDATE_HANDLE, 0, *fwhandle, 0);
 return mlxsw_reg_write(mlxsw_core, MLXSW_REG(mcc), mcc_pl);
}

static int mlxsw_core_fw_fsm_component_update(struct mlxfw_dev *mlxfw_dev, u32 fwhandle,
           u16 component_index, u32 component_size)
{
 struct mlxsw_core_fw_info *mlxsw_core_fw_info =
  container_of(mlxfw_dev, struct mlxsw_core_fw_info, mlxfw_dev);
 struct mlxsw_core *mlxsw_core = mlxsw_core_fw_info->mlxsw_core;
 char mcc_pl[MLXSW_REG_MCC_LEN];

 mlxsw_reg_mcc_pack(mcc_pl, MLXSW_REG_MCC_INSTRUCTION_UPDATE_COMPONENT,
      component_index, fwhandle, component_size);
 return mlxsw_reg_write(mlxsw_core, MLXSW_REG(mcc), mcc_pl);
}

static int mlxsw_core_fw_fsm_block_download(struct mlxfw_dev *mlxfw_dev, u32 fwhandle,
         u8 *data, u16 size, u32 offset)
{
 struct mlxsw_core_fw_info *mlxsw_core_fw_info =
  container_of(mlxfw_dev, struct mlxsw_core_fw_info, mlxfw_dev);
 struct mlxsw_core *mlxsw_core = mlxsw_core_fw_info->mlxsw_core;
 char mcda_pl[MLXSW_REG_MCDA_LEN];

 mlxsw_reg_mcda_pack(mcda_pl, fwhandle, offset, size, data);
 return mlxsw_reg_write(mlxsw_core, MLXSW_REG(mcda), mcda_pl);
}

static int mlxsw_core_fw_fsm_component_verify(struct mlxfw_dev *mlxfw_dev, u32 fwhandle,
           u16 component_index)
{
 struct mlxsw_core_fw_info *mlxsw_core_fw_info =
  container_of(mlxfw_dev, struct mlxsw_core_fw_info, mlxfw_dev);
 struct mlxsw_core *mlxsw_core = mlxsw_core_fw_info->mlxsw_core;
 char mcc_pl[MLXSW_REG_MCC_LEN];

 mlxsw_reg_mcc_pack(mcc_pl, MLXSW_REG_MCC_INSTRUCTION_VERIFY_COMPONENT,
      component_index, fwhandle, 0);
 return mlxsw_reg_write(mlxsw_core, MLXSW_REG(mcc), mcc_pl);
}

static int mlxsw_core_fw_fsm_activate(struct mlxfw_dev *mlxfw_dev, u32 fwhandle)
{
 struct mlxsw_core_fw_info *mlxsw_core_fw_info =
  container_of(mlxfw_dev, struct mlxsw_core_fw_info, mlxfw_dev);
 struct mlxsw_core *mlxsw_core = mlxsw_core_fw_info->mlxsw_core;
 char mcc_pl[MLXSW_REG_MCC_LEN];

 mlxsw_reg_mcc_pack(mcc_pl, MLXSW_REG_MCC_INSTRUCTION_ACTIVATE, 0, fwhandle, 0);
 return mlxsw_reg_write(mlxsw_core, MLXSW_REG(mcc), mcc_pl);
}

static int mlxsw_core_fw_fsm_query_state(struct mlxfw_dev *mlxfw_dev, u32 fwhandle,
      enum mlxfw_fsm_state *fsm_state,
      enum mlxfw_fsm_state_err *fsm_state_err)
{
 struct mlxsw_core_fw_info *mlxsw_core_fw_info =
  container_of(mlxfw_dev, struct mlxsw_core_fw_info, mlxfw_dev);
 struct mlxsw_core *mlxsw_core = mlxsw_core_fw_info->mlxsw_core;
 char mcc_pl[MLXSW_REG_MCC_LEN];
 u8 control_state;
 u8 error_code;
 int err;

 mlxsw_reg_mcc_pack(mcc_pl, 0, 0, fwhandle, 0);
 err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mcc), mcc_pl);
 if (err)
  return err;

 mlxsw_reg_mcc_unpack(mcc_pl, NULL, &error_code, &control_state);
 *fsm_state = control_state;
 *fsm_state_err = min_t(enum mlxfw_fsm_state_err, error_code, MLXFW_FSM_STATE_ERR_MAX);
 return 0;
}

static void mlxsw_core_fw_fsm_cancel(struct mlxfw_dev *mlxfw_dev, u32 fwhandle)
{
 struct mlxsw_core_fw_info *mlxsw_core_fw_info =
  container_of(mlxfw_dev, struct mlxsw_core_fw_info, mlxfw_dev);
 struct mlxsw_core *mlxsw_core = mlxsw_core_fw_info->mlxsw_core;
 char mcc_pl[MLXSW_REG_MCC_LEN];

 mlxsw_reg_mcc_pack(mcc_pl, MLXSW_REG_MCC_INSTRUCTION_CANCEL, 0, fwhandle, 0);
 mlxsw_reg_write(mlxsw_core, MLXSW_REG(mcc), mcc_pl);
}

static void mlxsw_core_fw_fsm_release(struct mlxfw_dev *mlxfw_dev, u32 fwhandle)
{
 struct mlxsw_core_fw_info *mlxsw_core_fw_info =
  container_of(mlxfw_dev, struct mlxsw_core_fw_info, mlxfw_dev);
 struct mlxsw_core *mlxsw_core = mlxsw_core_fw_info->mlxsw_core;
 char mcc_pl[MLXSW_REG_MCC_LEN];

 mlxsw_reg_mcc_pack(mcc_pl, MLXSW_REG_MCC_INSTRUCTION_RELEASE_UPDATE_HANDLE, 0, fwhandle, 0);
 mlxsw_reg_write(mlxsw_core, MLXSW_REG(mcc), mcc_pl);
}

static const struct mlxfw_dev_ops mlxsw_core_fw_mlxsw_dev_ops = {
 .component_query = mlxsw_core_fw_component_query,
 .fsm_lock  = mlxsw_core_fw_fsm_lock,
 .fsm_component_update = mlxsw_core_fw_fsm_component_update,
 .fsm_block_download = mlxsw_core_fw_fsm_block_download,
 .fsm_component_verify = mlxsw_core_fw_fsm_component_verify,
 .fsm_activate  = mlxsw_core_fw_fsm_activate,
 .fsm_query_state = mlxsw_core_fw_fsm_query_state,
 .fsm_cancel  = mlxsw_core_fw_fsm_cancel,
 .fsm_release  = mlxsw_core_fw_fsm_release,
};

static int mlxsw_core_dev_fw_flash(struct mlxsw_core *mlxsw_core,
       const struct firmware *firmware,
       struct netlink_ext_ack *extack)
{
 struct mlxsw_core_fw_info mlxsw_core_fw_info = {
  .mlxfw_dev = {
   .ops = &mlxsw_core_fw_mlxsw_dev_ops,
   .psid = mlxsw_core->bus_info->psid,
   .psid_size = strlen(mlxsw_core->bus_info->psid),
   .devlink = priv_to_devlink(mlxsw_core),
  },
  .mlxsw_core = mlxsw_core
 };

 return mlxsw_core_fw_flash(mlxsw_core, &mlxsw_core_fw_info.mlxfw_dev,
       firmware, extack);
}

static int mlxsw_core_fw_rev_validate(struct mlxsw_core *mlxsw_core,
          const struct mlxsw_bus_info *mlxsw_bus_info,
          const struct mlxsw_fw_rev *req_rev,
          const char *filename)
{
 const struct mlxsw_fw_rev *rev = &mlxsw_bus_info->fw_rev;
 union devlink_param_value value;
 const struct firmware *firmware;
 int err;

 /* Don't check if driver does not require it */
 if (!req_rev || !filename)
  return 0;

 /* Don't check if devlink 'fw_load_policy' param is 'flash' */
 err = devl_param_driverinit_value_get(priv_to_devlink(mlxsw_core),
           DEVLINK_PARAM_GENERIC_ID_FW_LOAD_POLICY,
           &value);
 if (err)
  return err;
 if (value.vu8 == DEVLINK_PARAM_FW_LOAD_POLICY_VALUE_FLASH)
  return 0;

 /* Validate driver & FW are compatible */
 if (rev->major != req_rev->major) {
  WARN(1, "Mismatch in major FW version [%d:%d] is never expected; Please contact support\n",
       rev->major, req_rev->major);
  return -EINVAL;
 }
 if (mlxsw_core_fw_rev_minor_subminor_validate(rev, req_rev))
  return 0;

 dev_err(mlxsw_bus_info->dev, "The firmware version %d.%d.%d is incompatible with the driver (required >= %d.%d.%d)\n",
  rev->major, rev->minor, rev->subminor, req_rev->major,
  req_rev->minor, req_rev->subminor);
 dev_info(mlxsw_bus_info->dev, "Flashing firmware using file %s\n", filename);

 err = request_firmware_direct(&firmware, filename, mlxsw_bus_info->dev);
 if (err) {
  dev_err(mlxsw_bus_info->dev, "Could not request firmware file %s\n", filename);
  return err;
 }

 err = mlxsw_core_dev_fw_flash(mlxsw_core, firmware, NULL);
 release_firmware(firmware);
 if (err)
  dev_err(mlxsw_bus_info->dev, "Could not upgrade firmware\n");

 /* On FW flash success, tell the caller FW reset is needed
 * if current FW supports it.
 */

 if (rev->minor >= req_rev->can_reset_minor)
  return err ? err : -EAGAIN;
 else
  return 0;
}

static int mlxsw_core_fw_flash_update(struct mlxsw_core *mlxsw_core,
          struct devlink_flash_update_params *params,
          struct netlink_ext_ack *extack)
{
 return mlxsw_core_dev_fw_flash(mlxsw_core, params->fw, extack);
}

static int mlxsw_core_devlink_param_fw_load_policy_validate(struct devlink *devlink, u32 id,
           union devlink_param_value val,
           struct netlink_ext_ack *extack)
{
 if (val.vu8 != DEVLINK_PARAM_FW_LOAD_POLICY_VALUE_DRIVER &&
     val.vu8 != DEVLINK_PARAM_FW_LOAD_POLICY_VALUE_FLASH) {
  NL_SET_ERR_MSG_MOD(extack, "'fw_load_policy' must be 'driver' or 'flash'");
  return -EINVAL;
 }

 return 0;
}

static const struct devlink_param mlxsw_core_fw_devlink_params[] = {
 DEVLINK_PARAM_GENERIC(FW_LOAD_POLICY, BIT(DEVLINK_PARAM_CMODE_DRIVERINIT), NULL, NULL,
         mlxsw_core_devlink_param_fw_load_policy_validate),
};

static int mlxsw_core_fw_params_register(struct mlxsw_core *mlxsw_core)
{
 struct devlink *devlink = priv_to_devlink(mlxsw_core);
 union devlink_param_value value;
 int err;

 err = devl_params_register(devlink, mlxsw_core_fw_devlink_params,
       ARRAY_SIZE(mlxsw_core_fw_devlink_params));
 if (err)
  return err;

 value.vu8 = DEVLINK_PARAM_FW_LOAD_POLICY_VALUE_DRIVER;
 devl_param_driverinit_value_set(devlink,
     DEVLINK_PARAM_GENERIC_ID_FW_LOAD_POLICY,
     value);
 return 0;
}

static void mlxsw_core_fw_params_unregister(struct mlxsw_core *mlxsw_core)
{
 devl_params_unregister(priv_to_devlink(mlxsw_core), mlxsw_core_fw_devlink_params,
          ARRAY_SIZE(mlxsw_core_fw_devlink_params));
}

static void *__dl_port(struct devlink_port *devlink_port)
{
 return container_of(devlink_port, struct mlxsw_core_port, devlink_port);
}

static int mlxsw_devlink_port_split(struct devlink *devlink,
        struct devlink_port *port,
        unsigned int count,
        struct netlink_ext_ack *extack)
{
 struct mlxsw_core_port *mlxsw_core_port = __dl_port(port);
 struct mlxsw_core *mlxsw_core = devlink_priv(devlink);

 if (!mlxsw_core->driver->port_split)
  return -EOPNOTSUPP;
 return mlxsw_core->driver->port_split(mlxsw_core,
           mlxsw_core_port->local_port,
           count, extack);
}

static int mlxsw_devlink_port_unsplit(struct devlink *devlink,
          struct devlink_port *port,
          struct netlink_ext_ack *extack)
{
 struct mlxsw_core_port *mlxsw_core_port = __dl_port(port);
 struct mlxsw_core *mlxsw_core = devlink_priv(devlink);

 if (!mlxsw_core->driver->port_unsplit)
  return -EOPNOTSUPP;
 return mlxsw_core->driver->port_unsplit(mlxsw_core,
      mlxsw_core_port->local_port,
      extack);
}

static int
mlxsw_devlink_sb_pool_get(struct devlink *devlink,
     unsigned int sb_index, u16 pool_index,
     struct devlink_sb_pool_info *pool_info)
{
 struct mlxsw_core *mlxsw_core = devlink_priv(devlink);
 struct mlxsw_driver *mlxsw_driver = mlxsw_core->driver;

 if (!mlxsw_driver->sb_pool_get)
  return -EOPNOTSUPP;
 return mlxsw_driver->sb_pool_get(mlxsw_core, sb_index,
      pool_index, pool_info);
}

static int
mlxsw_devlink_sb_pool_set(struct devlink *devlink,
     unsigned int sb_index, u16 pool_index, u32 size,
     enum devlink_sb_threshold_type threshold_type,
     struct netlink_ext_ack *extack)
{
 struct mlxsw_core *mlxsw_core = devlink_priv(devlink);
 struct mlxsw_driver *mlxsw_driver = mlxsw_core->driver;

 if (!mlxsw_driver->sb_pool_set)
  return -EOPNOTSUPP;
 return mlxsw_driver->sb_pool_set(mlxsw_core, sb_index,
      pool_index, size, threshold_type,
      extack);
}

static int mlxsw_devlink_sb_port_pool_get(struct devlink_port *devlink_port,
       unsigned int sb_index, u16 pool_index,
       u32 *p_threshold)
{
 struct mlxsw_core *mlxsw_core = devlink_priv(devlink_port->devlink);
 struct mlxsw_driver *mlxsw_driver = mlxsw_core->driver;
 struct mlxsw_core_port *mlxsw_core_port = __dl_port(devlink_port);

 if (!mlxsw_driver->sb_port_pool_get ||
     !mlxsw_core_port_check(mlxsw_core_port))
  return -EOPNOTSUPP;
 return mlxsw_driver->sb_port_pool_get(mlxsw_core_port, sb_index,
           pool_index, p_threshold);
}

static int mlxsw_devlink_sb_port_pool_set(struct devlink_port *devlink_port,
       unsigned int sb_index, u16 pool_index,
       u32 threshold,
       struct netlink_ext_ack *extack)
{
 struct mlxsw_core *mlxsw_core = devlink_priv(devlink_port->devlink);
 struct mlxsw_driver *mlxsw_driver = mlxsw_core->driver;
 struct mlxsw_core_port *mlxsw_core_port = __dl_port(devlink_port);

 if (!mlxsw_driver->sb_port_pool_set ||
     !mlxsw_core_port_check(mlxsw_core_port))
  return -EOPNOTSUPP;
 return mlxsw_driver->sb_port_pool_set(mlxsw_core_port, sb_index,
           pool_index, threshold, extack);
}

static int
mlxsw_devlink_sb_tc_pool_bind_get(struct devlink_port *devlink_port,
      unsigned int sb_index, u16 tc_index,
      enum devlink_sb_pool_type pool_type,
      u16 *p_pool_index, u32 *p_threshold)
{
 struct mlxsw_core *mlxsw_core = devlink_priv(devlink_port->devlink);
 struct mlxsw_driver *mlxsw_driver = mlxsw_core->driver;
 struct mlxsw_core_port *mlxsw_core_port = __dl_port(devlink_port);

 if (!mlxsw_driver->sb_tc_pool_bind_get ||
     !mlxsw_core_port_check(mlxsw_core_port))
  return -EOPNOTSUPP;
 return mlxsw_driver->sb_tc_pool_bind_get(mlxsw_core_port, sb_index,
       tc_index, pool_type,
       p_pool_index, p_threshold);
}

static int
mlxsw_devlink_sb_tc_pool_bind_set(struct devlink_port *devlink_port,
      unsigned int sb_index, u16 tc_index,
      enum devlink_sb_pool_type pool_type,
      u16 pool_index, u32 threshold,
      struct netlink_ext_ack *extack)
{
 struct mlxsw_core *mlxsw_core = devlink_priv(devlink_port->devlink);
 struct mlxsw_driver *mlxsw_driver = mlxsw_core->driver;
 struct mlxsw_core_port *mlxsw_core_port = __dl_port(devlink_port);

 if (!mlxsw_driver->sb_tc_pool_bind_set ||
     !mlxsw_core_port_check(mlxsw_core_port))
  return -EOPNOTSUPP;
 return mlxsw_driver->sb_tc_pool_bind_set(mlxsw_core_port, sb_index,
       tc_index, pool_type,
       pool_index, threshold, extack);
}

static int mlxsw_devlink_sb_occ_snapshot(struct devlink *devlink,
      unsigned int sb_index)
{
 struct mlxsw_core *mlxsw_core = devlink_priv(devlink);
 struct mlxsw_driver *mlxsw_driver = mlxsw_core->driver;

 if (!mlxsw_driver->sb_occ_snapshot)
  return -EOPNOTSUPP;
 return mlxsw_driver->sb_occ_snapshot(mlxsw_core, sb_index);
}

static int mlxsw_devlink_sb_occ_max_clear(struct devlink *devlink,
       unsigned int sb_index)
{
 struct mlxsw_core *mlxsw_core = devlink_priv(devlink);
 struct mlxsw_driver *mlxsw_driver = mlxsw_core->driver;

 if (!mlxsw_driver->sb_occ_max_clear)
  return -EOPNOTSUPP;
 return mlxsw_driver->sb_occ_max_clear(mlxsw_core, sb_index);
}

static int
mlxsw_devlink_sb_occ_port_pool_get(struct devlink_port *devlink_port,
       unsigned int sb_index, u16 pool_index,
       u32 *p_cur, u32 *p_max)
{
 struct mlxsw_core *mlxsw_core = devlink_priv(devlink_port->devlink);
 struct mlxsw_driver *mlxsw_driver = mlxsw_core->driver;
 struct mlxsw_core_port *mlxsw_core_port = __dl_port(devlink_port);

 if (!mlxsw_driver->sb_occ_port_pool_get ||
     !mlxsw_core_port_check(mlxsw_core_port))
  return -EOPNOTSUPP;
 return mlxsw_driver->sb_occ_port_pool_get(mlxsw_core_port, sb_index,
        pool_index, p_cur, p_max);
}

static int
mlxsw_devlink_sb_occ_tc_port_bind_get(struct devlink_port *devlink_port,
          unsigned int sb_index, u16 tc_index,
          enum devlink_sb_pool_type pool_type,
          u32 *p_cur, u32 *p_max)
{
 struct mlxsw_core *mlxsw_core = devlink_priv(devlink_port->devlink);
 struct mlxsw_driver *mlxsw_driver = mlxsw_core->driver;
 struct mlxsw_core_port *mlxsw_core_port = __dl_port(devlink_port);

 if (!mlxsw_driver->sb_occ_tc_port_bind_get ||
     !mlxsw_core_port_check(mlxsw_core_port))
  return -EOPNOTSUPP;
 return mlxsw_driver->sb_occ_tc_port_bind_get(mlxsw_core_port,
           sb_index, tc_index,
           pool_type, p_cur, p_max);
}

static int
mlxsw_devlink_info_get(struct devlink *devlink, struct devlink_info_req *req,
         struct netlink_ext_ack *extack)
{
 struct mlxsw_core *mlxsw_core = devlink_priv(devlink);
 char fw_info_psid[MLXSW_REG_MGIR_FW_INFO_PSID_SIZE];
 u32 hw_rev, fw_major, fw_minor, fw_sub_minor;
 char mgir_pl[MLXSW_REG_MGIR_LEN];
 char buf[32];
 int err;

 mlxsw_reg_mgir_pack(mgir_pl);
 err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mgir), mgir_pl);
 if (err)
  return err;
 mlxsw_reg_mgir_unpack(mgir_pl, &hw_rev, fw_info_psid, &fw_major,
         &fw_minor, &fw_sub_minor);

 sprintf(buf, "%X", hw_rev);
 err = devlink_info_version_fixed_put(req, "hw.revision", buf);
 if (err)
  return err;

 err = devlink_info_version_fixed_put(req,
          DEVLINK_INFO_VERSION_GENERIC_FW_PSID,
          fw_info_psid);
 if (err)
  return err;

 sprintf(buf, "%d.%d.%d", fw_major, fw_minor, fw_sub_minor);
 err = devlink_info_version_running_put(req, "fw.version", buf);
 if (err)
  return err;

 return devlink_info_version_running_put(req,
      DEVLINK_INFO_VERSION_GENERIC_FW,
      buf);
}

static int
mlxsw_devlink_core_bus_device_reload_down(struct devlink *devlink,
       bool netns_change, enum devlink_reload_action action,
       enum devlink_reload_limit limit,
       struct netlink_ext_ack *extack)
{
 struct mlxsw_core *mlxsw_core = devlink_priv(devlink);

 if (!(mlxsw_core->bus->features & MLXSW_BUS_F_RESET))
  return -EOPNOTSUPP;

 mlxsw_core_bus_device_unregister(mlxsw_core, true);
 return 0;
}

static int
mlxsw_devlink_core_bus_device_reload_up(struct devlink *devlink, enum devlink_reload_action action,
     enum devlink_reload_limit limit, u32 *actions_performed,
     struct netlink_ext_ack *extack)
{
 struct mlxsw_core *mlxsw_core = devlink_priv(devlink);
 int err;

 *actions_performed = BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT) |
        BIT(DEVLINK_RELOAD_ACTION_FW_ACTIVATE);
 err = mlxsw_core_bus_device_register(mlxsw_core->bus_info,
          mlxsw_core->bus,
          mlxsw_core->bus_priv, true,
          devlink, extack);
 return err;
}

static int mlxsw_devlink_flash_update(struct devlink *devlink,
          struct devlink_flash_update_params *params,
          struct netlink_ext_ack *extack)
{
 struct mlxsw_core *mlxsw_core = devlink_priv(devlink);

 return mlxsw_core_fw_flash_update(mlxsw_core, params, extack);
}

static int mlxsw_devlink_trap_init(struct devlink *devlink,
       const struct devlink_trap *trap,
       void *trap_ctx)
{
 struct mlxsw_core *mlxsw_core = devlink_priv(devlink);
 struct mlxsw_driver *mlxsw_driver = mlxsw_core->driver;

 if (!mlxsw_driver->trap_init)
  return -EOPNOTSUPP;
 return mlxsw_driver->trap_init(mlxsw_core, trap, trap_ctx);
}

static void mlxsw_devlink_trap_fini(struct devlink *devlink,
        const struct devlink_trap *trap,
        void *trap_ctx)
{
 struct mlxsw_core *mlxsw_core = devlink_priv(devlink);
 struct mlxsw_driver *mlxsw_driver = mlxsw_core->driver;

 if (!mlxsw_driver->trap_fini)
  return;
 mlxsw_driver->trap_fini(mlxsw_core, trap, trap_ctx);
}

static int mlxsw_devlink_trap_action_set(struct devlink *devlink,
      const struct devlink_trap *trap,
      enum devlink_trap_action action,
      struct netlink_ext_ack *extack)
{
 struct mlxsw_core *mlxsw_core = devlink_priv(devlink);
 struct mlxsw_driver *mlxsw_driver = mlxsw_core->driver;

 if (!mlxsw_driver->trap_action_set)
  return -EOPNOTSUPP;
 return mlxsw_driver->trap_action_set(mlxsw_core, trap, action, extack);
}

static int
mlxsw_devlink_trap_group_init(struct devlink *devlink,
         const struct devlink_trap_group *group)
{
 struct mlxsw_core *mlxsw_core = devlink_priv(devlink);
 struct mlxsw_driver *mlxsw_driver = mlxsw_core->driver;

 if (!mlxsw_driver->trap_group_init)
  return -EOPNOTSUPP;
 return mlxsw_driver->trap_group_init(mlxsw_core, group);
}

static int
mlxsw_devlink_trap_group_set(struct devlink *devlink,
        const struct devlink_trap_group *group,
        const struct devlink_trap_policer *policer,
        struct netlink_ext_ack *extack)
{
 struct mlxsw_core *mlxsw_core = devlink_priv(devlink);
 struct mlxsw_driver *mlxsw_driver = mlxsw_core->driver;

 if (!mlxsw_driver->trap_group_set)
  return -EOPNOTSUPP;
 return mlxsw_driver->trap_group_set(mlxsw_core, group, policer, extack);
}

static int
mlxsw_devlink_trap_policer_init(struct devlink *devlink,
    const struct devlink_trap_policer *policer)
{
 struct mlxsw_core *mlxsw_core = devlink_priv(devlink);
 struct mlxsw_driver *mlxsw_driver = mlxsw_core->driver;

 if (!mlxsw_driver->trap_policer_init)
  return -EOPNOTSUPP;
 return mlxsw_driver->trap_policer_init(mlxsw_core, policer);
}

static void
mlxsw_devlink_trap_policer_fini(struct devlink *devlink,
    const struct devlink_trap_policer *policer)
{
 struct mlxsw_core *mlxsw_core = devlink_priv(devlink);
 struct mlxsw_driver *mlxsw_driver = mlxsw_core->driver;

 if (!mlxsw_driver->trap_policer_fini)
  return;
 mlxsw_driver->trap_policer_fini(mlxsw_core, policer);
}

static int
mlxsw_devlink_trap_policer_set(struct devlink *devlink,
          const struct devlink_trap_policer *policer,
          u64 rate, u64 burst,
          struct netlink_ext_ack *extack)
{
 struct mlxsw_core *mlxsw_core = devlink_priv(devlink);
 struct mlxsw_driver *mlxsw_driver = mlxsw_core->driver;

 if (!mlxsw_driver->trap_policer_set)
  return -EOPNOTSUPP;
 return mlxsw_driver->trap_policer_set(mlxsw_core, policer, rate, burst,
           extack);
}

static int
mlxsw_devlink_trap_policer_counter_get(struct devlink *devlink,
           const struct devlink_trap_policer *policer,
           u64 *p_drops)
{
 struct mlxsw_core *mlxsw_core = devlink_priv(devlink);
 struct mlxsw_driver *mlxsw_driver = mlxsw_core->driver;

 if (!mlxsw_driver->trap_policer_counter_get)
  return -EOPNOTSUPP;
 return mlxsw_driver->trap_policer_counter_get(mlxsw_core, policer,
            p_drops);
}

static const struct devlink_ops mlxsw_devlink_ops = {
 .reload_actions  = BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT) |
      BIT(DEVLINK_RELOAD_ACTION_FW_ACTIVATE),
 .reload_down  = mlxsw_devlink_core_bus_device_reload_down,
 .reload_up  = mlxsw_devlink_core_bus_device_reload_up,
 .sb_pool_get   = mlxsw_devlink_sb_pool_get,
 .sb_pool_set   = mlxsw_devlink_sb_pool_set,
 .sb_port_pool_get  = mlxsw_devlink_sb_port_pool_get,
 .sb_port_pool_set  = mlxsw_devlink_sb_port_pool_set,
 .sb_tc_pool_bind_get  = mlxsw_devlink_sb_tc_pool_bind_get,
 .sb_tc_pool_bind_set  = mlxsw_devlink_sb_tc_pool_bind_set,
 .sb_occ_snapshot  = mlxsw_devlink_sb_occ_snapshot,
 .sb_occ_max_clear  = mlxsw_devlink_sb_occ_max_clear,
 .sb_occ_port_pool_get  = mlxsw_devlink_sb_occ_port_pool_get,
 .sb_occ_tc_port_bind_get = mlxsw_devlink_sb_occ_tc_port_bind_get,
 .info_get   = mlxsw_devlink_info_get,
 .flash_update   = mlxsw_devlink_flash_update,
 .trap_init   = mlxsw_devlink_trap_init,
 .trap_fini   = mlxsw_devlink_trap_fini,
 .trap_action_set  = mlxsw_devlink_trap_action_set,
 .trap_group_init  = mlxsw_devlink_trap_group_init,
 .trap_group_set   = mlxsw_devlink_trap_group_set,
 .trap_policer_init  = mlxsw_devlink_trap_policer_init,
 .trap_policer_fini  = mlxsw_devlink_trap_policer_fini,
 .trap_policer_set  = mlxsw_devlink_trap_policer_set,
 .trap_policer_counter_get = mlxsw_devlink_trap_policer_counter_get,
};

static int mlxsw_core_params_register(struct mlxsw_core *mlxsw_core)
{
 return mlxsw_core_fw_params_register(mlxsw_core);
}

static void mlxsw_core_params_unregister(struct mlxsw_core *mlxsw_core)
{
 mlxsw_core_fw_params_unregister(mlxsw_core);
}

struct mlxsw_core_health_event {
 struct mlxsw_core *mlxsw_core;
 char mfde_pl[MLXSW_REG_MFDE_LEN];
 struct work_struct work;
};

static void mlxsw_core_health_event_work(struct work_struct *work)
{
 struct mlxsw_core_health_event *event;
 struct mlxsw_core *mlxsw_core;

 event = container_of(work, struct mlxsw_core_health_event, work);
 mlxsw_core = event->mlxsw_core;
 devlink_health_report(mlxsw_core->health.fw_fatal, "FW fatal event occurred",
         event->mfde_pl);
 kfree(event);
}

static void mlxsw_core_health_listener_func(const struct mlxsw_reg_info *reg,
         char *mfde_pl, void *priv)
{
 struct mlxsw_core_health_event *event;
 struct mlxsw_core *mlxsw_core = priv;

 event = kmalloc(sizeof(*event), GFP_ATOMIC);
 if (!event)
  return;
 event->mlxsw_core = mlxsw_core;
 memcpy(event->mfde_pl, mfde_pl, sizeof(event->mfde_pl));
 INIT_WORK(&event->work, mlxsw_core_health_event_work);
 mlxsw_core_schedule_work(&event->work);
}

static const struct mlxsw_listener mlxsw_core_health_listener =
 MLXSW_CORE_EVENTL(mlxsw_core_health_listener_func, MFDE);

static void
mlxsw_core_health_fw_fatal_dump_fatal_cause(const char *mfde_pl,
         struct devlink_fmsg *fmsg)
{
 u32 val, tile_v;

 val = mlxsw_reg_mfde_fatal_cause_id_get(mfde_pl);
 devlink_fmsg_u32_pair_put(fmsg, "cause_id", val);
 tile_v = mlxsw_reg_mfde_fatal_cause_tile_v_get(mfde_pl);
 if (tile_v) {
  val = mlxsw_reg_mfde_fatal_cause_tile_index_get(mfde_pl);
  devlink_fmsg_u8_pair_put(fmsg, "tile_index", val);
 }
}

static void
mlxsw_core_health_fw_fatal_dump_fw_assert(const char *mfde_pl,
       struct devlink_fmsg *fmsg)
{
 u32 val, tile_v;

 val = mlxsw_reg_mfde_fw_assert_var0_get(mfde_pl);
 devlink_fmsg_u32_pair_put(fmsg, "var0", val);
 val = mlxsw_reg_mfde_fw_assert_var1_get(mfde_pl);
 devlink_fmsg_u32_pair_put(fmsg, "var1", val);
 val = mlxsw_reg_mfde_fw_assert_var2_get(mfde_pl);
 devlink_fmsg_u32_pair_put(fmsg, "var2", val);
 val = mlxsw_reg_mfde_fw_assert_var3_get(mfde_pl);
 devlink_fmsg_u32_pair_put(fmsg, "var3", val);
 val = mlxsw_reg_mfde_fw_assert_var4_get(mfde_pl);
 devlink_fmsg_u32_pair_put(fmsg, "var4", val);
 val = mlxsw_reg_mfde_fw_assert_existptr_get(mfde_pl);
 devlink_fmsg_u32_pair_put(fmsg, "existptr", val);
 val = mlxsw_reg_mfde_fw_assert_callra_get(mfde_pl);
 devlink_fmsg_u32_pair_put(fmsg, "callra", val);
 val = mlxsw_reg_mfde_fw_assert_oe_get(mfde_pl);
 devlink_fmsg_bool_pair_put(fmsg, "old_event", val);
 tile_v = mlxsw_reg_mfde_fw_assert_tile_v_get(mfde_pl);
 if (tile_v) {
  val = mlxsw_reg_mfde_fw_assert_tile_index_get(mfde_pl);
  devlink_fmsg_u8_pair_put(fmsg, "tile_index", val);
 }
 val = mlxsw_reg_mfde_fw_assert_ext_synd_get(mfde_pl);
 devlink_fmsg_u32_pair_put(fmsg, "ext_synd", val);
}

static void
mlxsw_core_health_fw_fatal_dump_kvd_im_stop(const char *mfde_pl,
         struct devlink_fmsg *fmsg)
{
 u32 val;

 val = mlxsw_reg_mfde_kvd_im_stop_oe_get(mfde_pl);
 devlink_fmsg_bool_pair_put(fmsg, "old_event", val);
 val = mlxsw_reg_mfde_kvd_im_stop_pipes_mask_get(mfde_pl);
 devlink_fmsg_u32_pair_put(fmsg, "pipes_mask", val);
}

static void
mlxsw_core_health_fw_fatal_dump_crspace_to(const char *mfde_pl,
        struct devlink_fmsg *fmsg)
{
 u32 val;

 val = mlxsw_reg_mfde_crspace_to_log_address_get(mfde_pl);
 devlink_fmsg_u32_pair_put(fmsg, "log_address", val);
 val = mlxsw_reg_mfde_crspace_to_oe_get(mfde_pl);
 devlink_fmsg_bool_pair_put(fmsg, "old_event", val);
 val = mlxsw_reg_mfde_crspace_to_log_id_get(mfde_pl);
 devlink_fmsg_u8_pair_put(fmsg, "log_irisc_id", val);
 val = mlxsw_reg_mfde_crspace_to_log_ip_get(mfde_pl);
 devlink_fmsg_u64_pair_put(fmsg, "log_ip", val);
}

static int mlxsw_core_health_fw_fatal_dump(struct devlink_health_reporter *reporter,
        struct devlink_fmsg *fmsg, void *priv_ctx,
        struct netlink_ext_ack *extack)
{
 char *mfde_pl = priv_ctx;
 char *val_str;
 u8 event_id;
 u32 val;

 if (!priv_ctx)
  /* User-triggered dumps are not possible */
  return -EOPNOTSUPP;

 val = mlxsw_reg_mfde_irisc_id_get(mfde_pl);
 devlink_fmsg_u8_pair_put(fmsg, "irisc_id", val);

 devlink_fmsg_arr_pair_nest_start(fmsg, "event");
 event_id = mlxsw_reg_mfde_event_id_get(mfde_pl);
 devlink_fmsg_u32_pair_put(fmsg, "id", event_id);
 switch (event_id) {
 case MLXSW_REG_MFDE_EVENT_ID_CRSPACE_TO:
  val_str = "CR space timeout";
  break;
 case MLXSW_REG_MFDE_EVENT_ID_KVD_IM_STOP:
  val_str = "KVD insertion machine stopped";
  break;
 case MLXSW_REG_MFDE_EVENT_ID_TEST:
  val_str = "Test";
  break;
 case MLXSW_REG_MFDE_EVENT_ID_FW_ASSERT:
  val_str = "FW assert";
  break;
 case MLXSW_REG_MFDE_EVENT_ID_FATAL_CAUSE:
  val_str = "Fatal cause";
  break;
 default:
  val_str = NULL;
 }
 if (val_str)
  devlink_fmsg_string_pair_put(fmsg, "desc", val_str);
 devlink_fmsg_arr_pair_nest_end(fmsg);

 devlink_fmsg_arr_pair_nest_start(fmsg, "severity");
 val = mlxsw_reg_mfde_severity_get(mfde_pl);
 devlink_fmsg_u8_pair_put(fmsg, "id", val);
 switch (val) {
 case MLXSW_REG_MFDE_SEVERITY_FATL:
  val_str = "Fatal";
  break;
 case MLXSW_REG_MFDE_SEVERITY_NRML:
  val_str = "Normal";
  break;
 case MLXSW_REG_MFDE_SEVERITY_INTR:
  val_str = "Debug";
  break;
 default:
  val_str = NULL;
 }
 if (val_str)
  devlink_fmsg_string_pair_put(fmsg, "desc", val_str);
 devlink_fmsg_arr_pair_nest_end(fmsg);

 val = mlxsw_reg_mfde_method_get(mfde_pl);
 switch (val) {
 case MLXSW_REG_MFDE_METHOD_QUERY:
  val_str = "query";
  break;
 case MLXSW_REG_MFDE_METHOD_WRITE:
  val_str = "write";
  break;
 default:
  val_str = NULL;
 }
 if (val_str)
  devlink_fmsg_string_pair_put(fmsg, "method", val_str);

 val = mlxsw_reg_mfde_long_process_get(mfde_pl);
 devlink_fmsg_bool_pair_put(fmsg, "long_process", val);

 val = mlxsw_reg_mfde_command_type_get(mfde_pl);
 switch (val) {
 case MLXSW_REG_MFDE_COMMAND_TYPE_MAD:
  val_str = "mad";
  break;
 case MLXSW_REG_MFDE_COMMAND_TYPE_EMAD:
  val_str = "emad";
  break;
 case MLXSW_REG_MFDE_COMMAND_TYPE_CMDIF:
  val_str = "cmdif";
  break;
 default:
  val_str = NULL;
 }
 if (val_str)
  devlink_fmsg_string_pair_put(fmsg, "command_type", val_str);

 val = mlxsw_reg_mfde_reg_attr_id_get(mfde_pl);
 devlink_fmsg_u32_pair_put(fmsg, "reg_attr_id", val);

 switch (event_id) {
 case MLXSW_REG_MFDE_EVENT_ID_CRSPACE_TO:
  mlxsw_core_health_fw_fatal_dump_crspace_to(mfde_pl, fmsg);
  break;
 case MLXSW_REG_MFDE_EVENT_ID_KVD_IM_STOP:
  mlxsw_core_health_fw_fatal_dump_kvd_im_stop(mfde_pl, fmsg);
  break;
 case MLXSW_REG_MFDE_EVENT_ID_FW_ASSERT:
  mlxsw_core_health_fw_fatal_dump_fw_assert(mfde_pl, fmsg);
  break;
 case MLXSW_REG_MFDE_EVENT_ID_FATAL_CAUSE:
  mlxsw_core_health_fw_fatal_dump_fatal_cause(mfde_pl, fmsg);
  break;
 }

 return 0;
}

static int
mlxsw_core_health_fw_fatal_test(struct devlink_health_reporter *reporter,
    struct netlink_ext_ack *extack)
{
 struct mlxsw_core *mlxsw_core = devlink_health_reporter_priv(reporter);
 char mfgd_pl[MLXSW_REG_MFGD_LEN];
 int err;

 /* Read the register first to make sure no other bits are changed. */
 err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mfgd), mfgd_pl);
 if (err)
  return err;
 mlxsw_reg_mfgd_trigger_test_set(mfgd_pl, true);
 return mlxsw_reg_write(mlxsw_core, MLXSW_REG(mfgd), mfgd_pl);
}

static const struct devlink_health_reporter_ops
mlxsw_core_health_fw_fatal_ops = {
 .name = "fw_fatal",
 .dump = mlxsw_core_health_fw_fatal_dump,
 .test = mlxsw_core_health_fw_fatal_test,
};

static int mlxsw_core_health_fw_fatal_config(struct mlxsw_core *mlxsw_core,
          bool enable)
{
 char mfgd_pl[MLXSW_REG_MFGD_LEN];
 int err;

 /* Read the register first to make sure no other bits are changed. */
 err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mfgd), mfgd_pl);
 if (err)
  return err;
 mlxsw_reg_mfgd_fatal_event_mode_set(mfgd_pl, enable);
 return mlxsw_reg_write(mlxsw_core, MLXSW_REG(mfgd), mfgd_pl);
}

static int mlxsw_core_health_init(struct mlxsw_core *mlxsw_core)
{
 struct devlink *devlink = priv_to_devlink(mlxsw_core);
 struct devlink_health_reporter *fw_fatal;
 int err;

 if (!(mlxsw_core->bus->features & MLXSW_BUS_F_TXRX))
  return 0;

 fw_fatal = devl_health_reporter_create(devlink, &mlxsw_core_health_fw_fatal_ops,
            0, mlxsw_core);
 if (IS_ERR(fw_fatal)) {
  dev_err(mlxsw_core->bus_info->dev, "Failed to create fw fatal reporter");
  return PTR_ERR(fw_fatal);
 }
 mlxsw_core->health.fw_fatal = fw_fatal;

 err = mlxsw_core_trap_register(mlxsw_core, &mlxsw_core_health_listener, mlxsw_core);
 if (err)
  goto err_trap_register;

 err = mlxsw_core_health_fw_fatal_config(mlxsw_core, true);
 if (err)
  goto err_fw_fatal_config;

 return 0;

err_fw_fatal_config:
 mlxsw_core_trap_unregister(mlxsw_core, &mlxsw_core_health_listener, mlxsw_core);
err_trap_register:
 devl_health_reporter_destroy(mlxsw_core->health.fw_fatal);
 return err;
}

static void mlxsw_core_health_fini(struct mlxsw_core *mlxsw_core)
{
 if (!(mlxsw_core->bus->features & MLXSW_BUS_F_TXRX))
  return;

 mlxsw_core_health_fw_fatal_config(mlxsw_core, false);
 mlxsw_core_trap_unregister(mlxsw_core, &mlxsw_core_health_listener, mlxsw_core);
 /* Make sure there is no more event work scheduled */
 mlxsw_core_flush_owq();
 devl_health_reporter_destroy(mlxsw_core->health.fw_fatal);
}

static void mlxsw_core_irq_event_handler_init(struct mlxsw_core *mlxsw_core)
{
 INIT_LIST_HEAD(&mlxsw_core->irq_event_handler_list);
 mutex_init(&mlxsw_core->irq_event_handler_lock);
}

static void mlxsw_core_irq_event_handler_fini(struct mlxsw_core *mlxsw_core)
{
 mutex_destroy(&mlxsw_core->irq_event_handler_lock);
 WARN_ON(!list_empty(&mlxsw_core->irq_event_handler_list));
}

static int
__mlxsw_core_bus_device_register(const struct mlxsw_bus_info *mlxsw_bus_info,
     const struct mlxsw_bus *mlxsw_bus,
     void *bus_priv, bool reload,
     struct devlink *devlink,
     struct netlink_ext_ack *extack)
{
 const char *device_kind = mlxsw_bus_info->device_kind;
 struct mlxsw_core *mlxsw_core;
 struct mlxsw_driver *mlxsw_driver;
 size_t alloc_size;
 u16 max_lag;
 int err;

 mlxsw_driver = mlxsw_core_driver_get(device_kind);
 if (!mlxsw_driver)
  return -EINVAL;

 if (!reload) {
  alloc_size = sizeof(*mlxsw_core) + mlxsw_driver->priv_size;
  devlink = devlink_alloc(&mlxsw_devlink_ops, alloc_size,
     mlxsw_bus_info->dev);
  if (!devlink) {
   err = -ENOMEM;
   goto err_devlink_alloc;
  }
  devl_lock(devlink);
  devl_register(devlink);
 }

 mlxsw_core = devlink_priv(devlink);
 INIT_LIST_HEAD(&mlxsw_core->rx_listener_list);
 INIT_LIST_HEAD(&mlxsw_core->event_listener_list);
 mlxsw_core->driver = mlxsw_driver;
 mlxsw_core->bus = mlxsw_bus;
 mlxsw_core->bus_priv = bus_priv;
 mlxsw_core->bus_info = mlxsw_bus_info;
 mlxsw_core_irq_event_handler_init(mlxsw_core);

 err = mlxsw_bus->init(bus_priv, mlxsw_core, mlxsw_driver->profile,
         &mlxsw_core->res);
 if (err)
  goto err_bus_init;

 if (mlxsw_driver->resources_register && !reload) {
  err = mlxsw_driver->resources_register(mlxsw_core);
  if (err)
   goto err_register_resources;
 }

 err = mlxsw_ports_init(mlxsw_core, reload);
 if (err)
  goto err_ports_init;

 err = mlxsw_core_max_lag(mlxsw_core, &max_lag);
 if (!err && MLXSW_CORE_RES_VALID(mlxsw_core, MAX_LAG_MEMBERS)) {
  alloc_size = sizeof(*mlxsw_core->lag.mapping) * max_lag *
   MLXSW_CORE_RES_GET(mlxsw_core, MAX_LAG_MEMBERS);
  mlxsw_core->lag.mapping = kzalloc(alloc_size, GFP_KERNEL);
  if (!mlxsw_core->lag.mapping) {
   err = -ENOMEM;
   goto err_alloc_lag_mapping;
  }
 }

 err = mlxsw_core_trap_groups_set(mlxsw_core);
 if (err)
  goto err_trap_groups_set;

 err = mlxsw_emad_init(mlxsw_core);
 if (err)
  goto err_emad_init;

 if (!reload) {
  err = mlxsw_core_params_register(mlxsw_core);
  if (err)
   goto err_register_params;
 }

 err = mlxsw_core_fw_rev_validate(mlxsw_core, mlxsw_bus_info, mlxsw_driver->fw_req_rev,
      mlxsw_driver->fw_filename);
 if (err)
  goto err_fw_rev_validate;

 err = mlxsw_linecards_init(mlxsw_core, mlxsw_bus_info);
 if (err)
  goto err_linecards_init;

 err = mlxsw_core_health_init(mlxsw_core);
 if (err)
  goto err_health_init;

 err = mlxsw_hwmon_init(mlxsw_core, mlxsw_bus_info, &mlxsw_core->hwmon);
 if (err)
  goto err_hwmon_init;

 err = mlxsw_thermal_init(mlxsw_core, mlxsw_bus_info,
     &mlxsw_core->thermal);
 if (err)
  goto err_thermal_init;

 err = mlxsw_env_init(mlxsw_core, mlxsw_bus_info, &mlxsw_core->env);
 if (err)
  goto err_env_init;

 if (mlxsw_driver->init) {
  err = mlxsw_driver->init(mlxsw_core, mlxsw_bus_info, extack);
  if (err)
   goto err_driver_init;
 }

 if (!reload)
  devl_unlock(devlink);
 return 0;

err_driver_init:
 mlxsw_env_fini(mlxsw_core->env);
err_env_init:
 mlxsw_thermal_fini(mlxsw_core->thermal);
err_thermal_init:
 mlxsw_hwmon_fini(mlxsw_core->hwmon);
err_hwmon_init:
 mlxsw_core_health_fini(mlxsw_core);
err_health_init:
 mlxsw_linecards_fini(mlxsw_core);
err_linecards_init:
err_fw_rev_validate:
 if (!reload)
  mlxsw_core_params_unregister(mlxsw_core);
err_register_params:
 mlxsw_emad_fini(mlxsw_core);
err_emad_init:
err_trap_groups_set:
 kfree(mlxsw_core->lag.mapping);
err_alloc_lag_mapping:
 mlxsw_ports_fini(mlxsw_core, reload);
err_ports_init:
 if (!reload)
  devl_resources_unregister(devlink);
err_register_resources:
 mlxsw_bus->fini(bus_priv);
err_bus_init:
 mlxsw_core_irq_event_handler_fini(mlxsw_core);
 if (!reload) {
  devl_unregister(devlink);
  devl_unlock(devlink);
  devlink_free(devlink);
 }
err_devlink_alloc:
 return err;
}

int mlxsw_core_bus_device_register(const struct mlxsw_bus_info *mlxsw_bus_info,
       const struct mlxsw_bus *mlxsw_bus,
       void *bus_priv, bool reload,
       struct devlink *devlink,
       struct netlink_ext_ack *extack)
{
 bool called_again = false;
 int err;

again:
 err = __mlxsw_core_bus_device_register(mlxsw_bus_info, mlxsw_bus,
            bus_priv, reload,
            devlink, extack);
 /* -EAGAIN is returned in case the FW was updated. FW needs
 * a reset, so lets try to call __mlxsw_core_bus_device_register()
 * again.
 */

 if (err == -EAGAIN && !called_again) {
  called_again = true;
  goto again;
 }

 return err;
--> --------------------

--> maximum size reached

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

Messung V0.5
C=98 H=94 G=95

¤ Dauer der Verarbeitung: 0.11 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.