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

Impressum fbnic_ethtool.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) Meta Platforms, Inc. and affiliates. */

#include <linux/ethtool.h>
#include <linux/netdevice.h>
#include <linux/pci.h>
#include <net/ipv6.h>

#include "fbnic.h"
#include "fbnic_netdev.h"
#include "fbnic_tlv.h"

struct fbnic_stat {
 u8 string[ETH_GSTRING_LEN];
 unsigned int size;
 unsigned int offset;
};

#define FBNIC_STAT_FIELDS(type, name, stat) { \
 .string = name, \
 .size = sizeof_field(struct type, stat), \
 .offset = offsetof(struct type, stat), \
}

/* Hardware statistics not captured in rtnl_link_stats */
#define FBNIC_HW_STAT(name, stat) \
 FBNIC_STAT_FIELDS(fbnic_hw_stats, name, stat)

static const struct fbnic_stat fbnic_gstrings_hw_stats[] = {
 /* TTI */
 FBNIC_HW_STAT("tti_cm_drop_frames", tti.cm_drop.frames),
 FBNIC_HW_STAT("tti_cm_drop_bytes", tti.cm_drop.bytes),
 FBNIC_HW_STAT("tti_frame_drop_frames", tti.frame_drop.frames),
 FBNIC_HW_STAT("tti_frame_drop_bytes", tti.frame_drop.bytes),
 FBNIC_HW_STAT("tti_tbi_drop_frames", tti.tbi_drop.frames),
 FBNIC_HW_STAT("tti_tbi_drop_bytes", tti.tbi_drop.bytes),

 /* TMI */
 FBNIC_HW_STAT("ptp_illegal_req", tmi.ptp_illegal_req),
 FBNIC_HW_STAT("ptp_good_ts", tmi.ptp_good_ts),
 FBNIC_HW_STAT("ptp_bad_ts", tmi.ptp_bad_ts),

 /* RPC */
 FBNIC_HW_STAT("rpc_unkn_etype", rpc.unkn_etype),
 FBNIC_HW_STAT("rpc_unkn_ext_hdr", rpc.unkn_ext_hdr),
 FBNIC_HW_STAT("rpc_ipv4_frag", rpc.ipv4_frag),
 FBNIC_HW_STAT("rpc_ipv6_frag", rpc.ipv6_frag),
 FBNIC_HW_STAT("rpc_ipv4_esp", rpc.ipv4_esp),
 FBNIC_HW_STAT("rpc_ipv6_esp", rpc.ipv6_esp),
 FBNIC_HW_STAT("rpc_tcp_opt_err", rpc.tcp_opt_err),
 FBNIC_HW_STAT("rpc_out_of_hdr_err", rpc.out_of_hdr_err),
};

#define FBNIC_HW_FIXED_STATS_LEN ARRAY_SIZE(fbnic_gstrings_hw_stats)

#define FBNIC_RXB_ENQUEUE_STAT(name, stat) \
 FBNIC_STAT_FIELDS(fbnic_rxb_enqueue_stats, name, stat)

static const struct fbnic_stat fbnic_gstrings_rxb_enqueue_stats[] = {
 FBNIC_RXB_ENQUEUE_STAT("rxb_integrity_err%u", integrity_err),
 FBNIC_RXB_ENQUEUE_STAT("rxb_mac_err%u", mac_err),
 FBNIC_RXB_ENQUEUE_STAT("rxb_parser_err%u", parser_err),
 FBNIC_RXB_ENQUEUE_STAT("rxb_frm_err%u", frm_err),

 FBNIC_RXB_ENQUEUE_STAT("rxb_drbo%u_frames", drbo.frames),
 FBNIC_RXB_ENQUEUE_STAT("rxb_drbo%u_bytes", drbo.bytes),
};

#define FBNIC_HW_RXB_ENQUEUE_STATS_LEN \
 ARRAY_SIZE(fbnic_gstrings_rxb_enqueue_stats)

#define FBNIC_RXB_FIFO_STAT(name, stat) \
 FBNIC_STAT_FIELDS(fbnic_rxb_fifo_stats, name, stat)

static const struct fbnic_stat fbnic_gstrings_rxb_fifo_stats[] = {
 FBNIC_RXB_FIFO_STAT("rxb_fifo%u_drop", trans_drop),
 FBNIC_RXB_FIFO_STAT("rxb_fifo%u_dropped_frames", drop.frames),
 FBNIC_RXB_FIFO_STAT("rxb_fifo%u_ecn", trans_ecn),
 FBNIC_RXB_FIFO_STAT("rxb_fifo%u_level", level),
};

#define FBNIC_HW_RXB_FIFO_STATS_LEN ARRAY_SIZE(fbnic_gstrings_rxb_fifo_stats)

#define FBNIC_RXB_DEQUEUE_STAT(name, stat) \
 FBNIC_STAT_FIELDS(fbnic_rxb_dequeue_stats, name, stat)

static const struct fbnic_stat fbnic_gstrings_rxb_dequeue_stats[] = {
 FBNIC_RXB_DEQUEUE_STAT("rxb_intf%u_frames", intf.frames),
 FBNIC_RXB_DEQUEUE_STAT("rxb_intf%u_bytes", intf.bytes),
 FBNIC_RXB_DEQUEUE_STAT("rxb_pbuf%u_frames", pbuf.frames),
 FBNIC_RXB_DEQUEUE_STAT("rxb_pbuf%u_bytes", pbuf.bytes),
};

#define FBNIC_HW_RXB_DEQUEUE_STATS_LEN \
 ARRAY_SIZE(fbnic_gstrings_rxb_dequeue_stats)

#define FBNIC_HW_Q_STAT(name, stat) \
 FBNIC_STAT_FIELDS(fbnic_hw_q_stats, name, stat.value)

static const struct fbnic_stat fbnic_gstrings_hw_q_stats[] = {
 FBNIC_HW_Q_STAT("rde_%u_pkt_err", rde_pkt_err),
 FBNIC_HW_Q_STAT("rde_%u_pkt_cq_drop", rde_pkt_cq_drop),
 FBNIC_HW_Q_STAT("rde_%u_pkt_bdq_drop", rde_pkt_bdq_drop),
};

#define FBNIC_HW_Q_STATS_LEN ARRAY_SIZE(fbnic_gstrings_hw_q_stats)
#define FBNIC_HW_STATS_LEN \
 (FBNIC_HW_FIXED_STATS_LEN + \
  FBNIC_HW_RXB_ENQUEUE_STATS_LEN * FBNIC_RXB_ENQUEUE_INDICES + \
  FBNIC_HW_RXB_FIFO_STATS_LEN * FBNIC_RXB_FIFO_INDICES + \
  FBNIC_HW_RXB_DEQUEUE_STATS_LEN * FBNIC_RXB_DEQUEUE_INDICES + \
  FBNIC_HW_Q_STATS_LEN * FBNIC_MAX_QUEUES)

static void
fbnic_get_drvinfo(struct net_device *netdev, struct ethtool_drvinfo *drvinfo)
{
 struct fbnic_net *fbn = netdev_priv(netdev);
 struct fbnic_dev *fbd = fbn->fbd;

 fbnic_get_fw_ver_commit_str(fbd, drvinfo->fw_version,
        sizeof(drvinfo->fw_version));
}

static int fbnic_get_regs_len(struct net_device *netdev)
{
 struct fbnic_net *fbn = netdev_priv(netdev);

 return fbnic_csr_regs_len(fbn->fbd) * sizeof(u32);
}

static void fbnic_get_regs(struct net_device *netdev,
      struct ethtool_regs *regs, void *data)
{
 struct fbnic_net *fbn = netdev_priv(netdev);

 fbnic_csr_get_regs(fbn->fbd, data, ®s->version);
}

static struct fbnic_net *fbnic_clone_create(struct fbnic_net *orig)
{
 struct fbnic_net *clone;

 clone = kmemdup(orig, sizeof(*orig), GFP_KERNEL);
 if (!clone)
  return NULL;

 memset(clone->tx, 0, sizeof(clone->tx));
 memset(clone->rx, 0, sizeof(clone->rx));
 memset(clone->napi, 0, sizeof(clone->napi));
 return clone;
}

static void fbnic_clone_swap_cfg(struct fbnic_net *orig,
     struct fbnic_net *clone)
{
 swap(clone->rcq_size, orig->rcq_size);
 swap(clone->hpq_size, orig->hpq_size);
 swap(clone->ppq_size, orig->ppq_size);
 swap(clone->txq_size, orig->txq_size);
 swap(clone->num_rx_queues, orig->num_rx_queues);
 swap(clone->num_tx_queues, orig->num_tx_queues);
 swap(clone->num_napi, orig->num_napi);
}

static void fbnic_aggregate_vector_counters(struct fbnic_net *fbn,
         struct fbnic_napi_vector *nv)
{
 int i, j;

 for (i = 0; i < nv->txt_count; i++) {
  fbnic_aggregate_ring_tx_counters(fbn, &nv->qt[i].sub0);
  fbnic_aggregate_ring_tx_counters(fbn, &nv->qt[i].sub1);
  fbnic_aggregate_ring_tx_counters(fbn, &nv->qt[i].cmpl);
 }

 for (j = 0; j < nv->rxt_count; j++, i++) {
  fbnic_aggregate_ring_rx_counters(fbn, &nv->qt[i].sub0);
  fbnic_aggregate_ring_rx_counters(fbn, &nv->qt[i].sub1);
  fbnic_aggregate_ring_rx_counters(fbn, &nv->qt[i].cmpl);
 }
}

static void fbnic_clone_swap(struct fbnic_net *orig,
        struct fbnic_net *clone)
{
 struct fbnic_dev *fbd = orig->fbd;
 unsigned int i;

 for (i = 0; i < max(clone->num_napi, orig->num_napi); i++)
  fbnic_synchronize_irq(fbd, FBNIC_NON_NAPI_VECTORS + i);
 for (i = 0; i < orig->num_napi; i++)
  fbnic_aggregate_vector_counters(orig, orig->napi[i]);

 fbnic_clone_swap_cfg(orig, clone);

 for (i = 0; i < ARRAY_SIZE(orig->napi); i++)
  swap(clone->napi[i], orig->napi[i]);
 for (i = 0; i < ARRAY_SIZE(orig->tx); i++)
  swap(clone->tx[i], orig->tx[i]);
 for (i = 0; i < ARRAY_SIZE(orig->rx); i++)
  swap(clone->rx[i], orig->rx[i]);
}

static void fbnic_clone_free(struct fbnic_net *clone)
{
 kfree(clone);
}

static int fbnic_get_coalesce(struct net_device *netdev,
         struct ethtool_coalesce *ec,
         struct kernel_ethtool_coalesce *kernel_coal,
         struct netlink_ext_ack *extack)
{
 struct fbnic_net *fbn = netdev_priv(netdev);

 ec->tx_coalesce_usecs = fbn->tx_usecs;
 ec->rx_coalesce_usecs = fbn->rx_usecs;
 ec->rx_max_coalesced_frames = fbn->rx_max_frames;

 return 0;
}

static int fbnic_set_coalesce(struct net_device *netdev,
         struct ethtool_coalesce *ec,
         struct kernel_ethtool_coalesce *kernel_coal,
         struct netlink_ext_ack *extack)
{
 struct fbnic_net *fbn = netdev_priv(netdev);

 /* Verify against hardware limits */
 if (ec->rx_coalesce_usecs > FIELD_MAX(FBNIC_INTR_CQ_REARM_RCQ_TIMEOUT)) {
  NL_SET_ERR_MSG_MOD(extack, "rx_usecs is above device max");
  return -EINVAL;
 }
 if (ec->tx_coalesce_usecs > FIELD_MAX(FBNIC_INTR_CQ_REARM_TCQ_TIMEOUT)) {
  NL_SET_ERR_MSG_MOD(extack, "tx_usecs is above device max");
  return -EINVAL;
 }
 if (ec->rx_max_coalesced_frames >
     FIELD_MAX(FBNIC_QUEUE_RIM_THRESHOLD_RCD_MASK) /
     FBNIC_MIN_RXD_PER_FRAME) {
  NL_SET_ERR_MSG_MOD(extack, "rx_frames is above device max");
  return -EINVAL;
 }

 fbn->tx_usecs = ec->tx_coalesce_usecs;
 fbn->rx_usecs = ec->rx_coalesce_usecs;
 fbn->rx_max_frames = ec->rx_max_coalesced_frames;

 if (netif_running(netdev)) {
  int i;

  for (i = 0; i < fbn->num_napi; i++) {
   struct fbnic_napi_vector *nv = fbn->napi[i];

   fbnic_config_txrx_usecs(nv, 0);
   fbnic_config_rx_frames(nv);
  }
 }

 return 0;
}

static void
fbnic_get_ringparam(struct net_device *netdev, struct ethtool_ringparam *ring,
      struct kernel_ethtool_ringparam *kernel_ring,
      struct netlink_ext_ack *extack)
{
 struct fbnic_net *fbn = netdev_priv(netdev);

 ring->rx_max_pending = FBNIC_QUEUE_SIZE_MAX;
 ring->rx_mini_max_pending = FBNIC_QUEUE_SIZE_MAX;
 ring->rx_jumbo_max_pending = FBNIC_QUEUE_SIZE_MAX;
 ring->tx_max_pending = FBNIC_QUEUE_SIZE_MAX;

 ring->rx_pending = fbn->rcq_size;
 ring->rx_mini_pending = fbn->hpq_size;
 ring->rx_jumbo_pending = fbn->ppq_size;
 ring->tx_pending = fbn->txq_size;
}

static void fbnic_set_rings(struct fbnic_net *fbn,
       struct ethtool_ringparam *ring)
{
 fbn->rcq_size = ring->rx_pending;
 fbn->hpq_size = ring->rx_mini_pending;
 fbn->ppq_size = ring->rx_jumbo_pending;
 fbn->txq_size = ring->tx_pending;
}

static int
fbnic_set_ringparam(struct net_device *netdev, struct ethtool_ringparam *ring,
      struct kernel_ethtool_ringparam *kernel_ring,
      struct netlink_ext_ack *extack)

{
 struct fbnic_net *fbn = netdev_priv(netdev);
 struct fbnic_net *clone;
 int err;

 ring->rx_pending = roundup_pow_of_two(ring->rx_pending);
 ring->rx_mini_pending = roundup_pow_of_two(ring->rx_mini_pending);
 ring->rx_jumbo_pending = roundup_pow_of_two(ring->rx_jumbo_pending);
 ring->tx_pending = roundup_pow_of_two(ring->tx_pending);

 /* These are absolute minimums allowing the device and driver to operate
 * but not necessarily guarantee reasonable performance. Settings below
 * Rx queue size of 128 and BDQs smaller than 64 are likely suboptimal
 * at best.
 */

 if (ring->rx_pending < max(FBNIC_QUEUE_SIZE_MIN, FBNIC_RX_DESC_MIN) ||
     ring->rx_mini_pending < FBNIC_QUEUE_SIZE_MIN ||
     ring->rx_jumbo_pending < FBNIC_QUEUE_SIZE_MIN ||
     ring->tx_pending < max(FBNIC_QUEUE_SIZE_MIN, FBNIC_TX_DESC_MIN)) {
  NL_SET_ERR_MSG_MOD(extack, "requested ring size too small");
  return -EINVAL;
 }

 if (!netif_running(netdev)) {
  fbnic_set_rings(fbn, ring);
  return 0;
 }

 clone = fbnic_clone_create(fbn);
 if (!clone)
  return -ENOMEM;

 fbnic_set_rings(clone, ring);

 err = fbnic_alloc_napi_vectors(clone);
 if (err)
  goto err_free_clone;

 err = fbnic_alloc_resources(clone);
 if (err)
  goto err_free_napis;

 fbnic_down_noidle(fbn);
 err = fbnic_wait_all_queues_idle(fbn->fbd, true);
 if (err)
  goto err_start_stack;

 err = fbnic_set_netif_queues(clone);
 if (err)
  goto err_start_stack;

 /* Nothing can fail past this point */
 fbnic_flush(fbn);

 fbnic_clone_swap(fbn, clone);

 fbnic_up(fbn);

 fbnic_free_resources(clone);
 fbnic_free_napi_vectors(clone);
 fbnic_clone_free(clone);

 return 0;

err_start_stack:
 fbnic_flush(fbn);
 fbnic_up(fbn);
 fbnic_free_resources(clone);
err_free_napis:
 fbnic_free_napi_vectors(clone);
err_free_clone:
 fbnic_clone_free(clone);
 return err;
}

static void fbnic_get_rxb_enqueue_strings(u8 **data, unsigned int idx)
{
 const struct fbnic_stat *stat;
 int i;

 stat = fbnic_gstrings_rxb_enqueue_stats;
 for (i = 0; i < FBNIC_HW_RXB_ENQUEUE_STATS_LEN; i++, stat++)
  ethtool_sprintf(data, stat->string, idx);
}

static void fbnic_get_rxb_fifo_strings(u8 **data, unsigned int idx)
{
 const struct fbnic_stat *stat;
 int i;

 stat = fbnic_gstrings_rxb_fifo_stats;
 for (i = 0; i < FBNIC_HW_RXB_FIFO_STATS_LEN; i++, stat++)
  ethtool_sprintf(data, stat->string, idx);
}

static void fbnic_get_rxb_dequeue_strings(u8 **data, unsigned int idx)
{
 const struct fbnic_stat *stat;
 int i;

 stat = fbnic_gstrings_rxb_dequeue_stats;
 for (i = 0; i < FBNIC_HW_RXB_DEQUEUE_STATS_LEN; i++, stat++)
  ethtool_sprintf(data, stat->string, idx);
}

static void fbnic_get_strings(struct net_device *dev, u32 sset, u8 *data)
{
 const struct fbnic_stat *stat;
 int i, idx;

 switch (sset) {
 case ETH_SS_STATS:
  for (i = 0; i < FBNIC_HW_FIXED_STATS_LEN; i++)
   ethtool_puts(&data, fbnic_gstrings_hw_stats[i].string);

  for (i = 0; i < FBNIC_RXB_ENQUEUE_INDICES; i++)
   fbnic_get_rxb_enqueue_strings(&data, i);

  for (i = 0; i < FBNIC_RXB_FIFO_INDICES; i++)
   fbnic_get_rxb_fifo_strings(&data, i);

  for (i = 0; i < FBNIC_RXB_DEQUEUE_INDICES; i++)
   fbnic_get_rxb_dequeue_strings(&data, i);

  for (idx = 0; idx < FBNIC_MAX_QUEUES; idx++) {
   stat = fbnic_gstrings_hw_q_stats;

   for (i = 0; i < FBNIC_HW_Q_STATS_LEN; i++, stat++)
    ethtool_sprintf(&data, stat->string, idx);
  }
  break;
 }
}

static void fbnic_report_hw_stats(const struct fbnic_stat *stat,
      const void *base, int len, u64 **data)
{
 while (len--) {
  u8 *curr = (u8 *)base + stat->offset;

  **data = *(u64 *)curr;

  stat++;
  (*data)++;
 }
}

static void fbnic_get_ethtool_stats(struct net_device *dev,
        struct ethtool_stats *stats, u64 *data)
{
 struct fbnic_net *fbn = netdev_priv(dev);
 struct fbnic_dev *fbd = fbn->fbd;
 int i;

 fbnic_get_hw_stats(fbn->fbd);

 spin_lock(&fbd->hw_stats_lock);
 fbnic_report_hw_stats(fbnic_gstrings_hw_stats, &fbd->hw_stats,
         FBNIC_HW_FIXED_STATS_LEN, &data);

 for (i = 0; i < FBNIC_RXB_ENQUEUE_INDICES; i++) {
  const struct fbnic_rxb_enqueue_stats *enq;

  enq = &fbd->hw_stats.rxb.enq[i];
  fbnic_report_hw_stats(fbnic_gstrings_rxb_enqueue_stats,
          enq, FBNIC_HW_RXB_ENQUEUE_STATS_LEN,
          &data);
 }

 for (i = 0; i < FBNIC_RXB_FIFO_INDICES; i++) {
  const struct fbnic_rxb_fifo_stats *fifo;

  fifo = &fbd->hw_stats.rxb.fifo[i];
  fbnic_report_hw_stats(fbnic_gstrings_rxb_fifo_stats,
          fifo, FBNIC_HW_RXB_FIFO_STATS_LEN,
          &data);
 }

 for (i = 0; i < FBNIC_RXB_DEQUEUE_INDICES; i++) {
  const struct fbnic_rxb_dequeue_stats *deq;

  deq = &fbd->hw_stats.rxb.deq[i];
  fbnic_report_hw_stats(fbnic_gstrings_rxb_dequeue_stats,
          deq, FBNIC_HW_RXB_DEQUEUE_STATS_LEN,
          &data);
 }

 for (i  = 0; i < FBNIC_MAX_QUEUES; i++) {
  const struct fbnic_hw_q_stats *hw_q = &fbd->hw_stats.hw_q[i];

  fbnic_report_hw_stats(fbnic_gstrings_hw_q_stats, hw_q,
          FBNIC_HW_Q_STATS_LEN, &data);
 }
 spin_unlock(&fbd->hw_stats_lock);
}

static int fbnic_get_sset_count(struct net_device *dev, int sset)
{
 switch (sset) {
 case ETH_SS_STATS:
  return FBNIC_HW_STATS_LEN;
 default:
  return -EOPNOTSUPP;
 }
}

static int fbnic_get_rss_hash_idx(u32 flow_type)
{
 switch (flow_type & ~(FLOW_EXT | FLOW_MAC_EXT | FLOW_RSS)) {
 case TCP_V4_FLOW:
  return FBNIC_TCP4_HASH_OPT;
 case TCP_V6_FLOW:
  return FBNIC_TCP6_HASH_OPT;
 case UDP_V4_FLOW:
  return FBNIC_UDP4_HASH_OPT;
 case UDP_V6_FLOW:
  return FBNIC_UDP6_HASH_OPT;
 case AH_V4_FLOW:
 case ESP_V4_FLOW:
 case AH_ESP_V4_FLOW:
 case SCTP_V4_FLOW:
 case IPV4_FLOW:
 case IPV4_USER_FLOW:
  return FBNIC_IPV4_HASH_OPT;
 case AH_V6_FLOW:
 case ESP_V6_FLOW:
 case AH_ESP_V6_FLOW:
 case SCTP_V6_FLOW:
 case IPV6_FLOW:
 case IPV6_USER_FLOW:
  return FBNIC_IPV6_HASH_OPT;
 case ETHER_FLOW:
  return FBNIC_ETHER_HASH_OPT;
 }

 return -1;
}

static int fbnic_get_cls_rule_all(struct fbnic_net *fbn,
      struct ethtool_rxnfc *cmd,
      u32 *rule_locs)
{
 struct fbnic_dev *fbd = fbn->fbd;
 int i, cnt = 0;

 /* Report maximum rule count */
 cmd->data = FBNIC_RPC_ACT_TBL_NFC_ENTRIES;

 for (i = 0; i < FBNIC_RPC_ACT_TBL_NFC_ENTRIES; i++) {
  int idx = i + FBNIC_RPC_ACT_TBL_NFC_OFFSET;
  struct fbnic_act_tcam *act_tcam;

  act_tcam = &fbd->act_tcam[idx];
  if (act_tcam->state != FBNIC_TCAM_S_VALID)
   continue;

  if (rule_locs) {
   if (cnt == cmd->rule_cnt)
    return -EMSGSIZE;

   rule_locs[cnt] = i;
  }

  cnt++;
 }

 return cnt;
}

static int fbnic_get_cls_rule(struct fbnic_net *fbn, struct ethtool_rxnfc *cmd)
{
 struct ethtool_rx_flow_spec *fsp;
 struct fbnic_dev *fbd = fbn->fbd;
 struct fbnic_act_tcam *act_tcam;
 int idx;

 fsp = (struct ethtool_rx_flow_spec *)&cmd->fs;

 if (fsp->location >= FBNIC_RPC_ACT_TBL_NFC_ENTRIES)
  return -EINVAL;

 idx = fsp->location + FBNIC_RPC_ACT_TBL_NFC_OFFSET;
 act_tcam = &fbd->act_tcam[idx];

 if (act_tcam->state != FBNIC_TCAM_S_VALID)
  return -EINVAL;

 /* Report maximum rule count */
 cmd->data = FBNIC_RPC_ACT_TBL_NFC_ENTRIES;

 /* Set flow type field */
 if (!(act_tcam->value.tcam[1] & FBNIC_RPC_TCAM_ACT1_IP_VALID)) {
  fsp->flow_type = ETHER_FLOW;
  if (!FIELD_GET(FBNIC_RPC_TCAM_ACT1_L2_MACDA_IDX,
          act_tcam->mask.tcam[1])) {
   struct fbnic_mac_addr *mac_addr;

   idx = FIELD_GET(FBNIC_RPC_TCAM_ACT1_L2_MACDA_IDX,
     act_tcam->value.tcam[1]);
   mac_addr = &fbd->mac_addr[idx];

   ether_addr_copy(fsp->h_u.ether_spec.h_dest,
     mac_addr->value.addr8);
   eth_broadcast_addr(fsp->m_u.ether_spec.h_dest);
  }
 } else if (act_tcam->value.tcam[1] &
     FBNIC_RPC_TCAM_ACT1_OUTER_IP_VALID) {
  fsp->flow_type = IPV6_USER_FLOW;
  fsp->h_u.usr_ip6_spec.l4_proto = IPPROTO_IPV6;
  fsp->m_u.usr_ip6_spec.l4_proto = 0xff;

  if (!FIELD_GET(FBNIC_RPC_TCAM_ACT0_OUTER_IPSRC_IDX,
          act_tcam->mask.tcam[0])) {
   struct fbnic_ip_addr *ip_addr;
   int i;

   idx = FIELD_GET(FBNIC_RPC_TCAM_ACT0_OUTER_IPSRC_IDX,
     act_tcam->value.tcam[0]);
   ip_addr = &fbd->ipo_src[idx];

   for (i = 0; i < 4; i++) {
    fsp->h_u.usr_ip6_spec.ip6src[i] =
     ip_addr->value.s6_addr32[i];
    fsp->m_u.usr_ip6_spec.ip6src[i] =
     ~ip_addr->mask.s6_addr32[i];
   }
  }

  if (!FIELD_GET(FBNIC_RPC_TCAM_ACT0_OUTER_IPDST_IDX,
          act_tcam->mask.tcam[0])) {
   struct fbnic_ip_addr *ip_addr;
   int i;

   idx = FIELD_GET(FBNIC_RPC_TCAM_ACT0_OUTER_IPDST_IDX,
     act_tcam->value.tcam[0]);
   ip_addr = &fbd->ipo_dst[idx];

   for (i = 0; i < 4; i++) {
    fsp->h_u.usr_ip6_spec.ip6dst[i] =
     ip_addr->value.s6_addr32[i];
    fsp->m_u.usr_ip6_spec.ip6dst[i] =
     ~ip_addr->mask.s6_addr32[i];
   }
  }
 } else if ((act_tcam->value.tcam[1] & FBNIC_RPC_TCAM_ACT1_IP_IS_V6)) {
  if (act_tcam->value.tcam[1] & FBNIC_RPC_TCAM_ACT1_L4_VALID) {
   if (act_tcam->value.tcam[1] &
       FBNIC_RPC_TCAM_ACT1_L4_IS_UDP)
    fsp->flow_type = UDP_V6_FLOW;
   else
    fsp->flow_type = TCP_V6_FLOW;
   fsp->h_u.tcp_ip6_spec.psrc =
    cpu_to_be16(act_tcam->value.tcam[3]);
   fsp->m_u.tcp_ip6_spec.psrc =
    cpu_to_be16(~act_tcam->mask.tcam[3]);
   fsp->h_u.tcp_ip6_spec.pdst =
    cpu_to_be16(act_tcam->value.tcam[4]);
   fsp->m_u.tcp_ip6_spec.pdst =
    cpu_to_be16(~act_tcam->mask.tcam[4]);
  } else {
   fsp->flow_type = IPV6_USER_FLOW;
  }

  if (!FIELD_GET(FBNIC_RPC_TCAM_ACT0_IPSRC_IDX,
          act_tcam->mask.tcam[0])) {
   struct fbnic_ip_addr *ip_addr;
   int i;

   idx = FIELD_GET(FBNIC_RPC_TCAM_ACT0_IPSRC_IDX,
     act_tcam->value.tcam[0]);
   ip_addr = &fbd->ip_src[idx];

   for (i = 0; i < 4; i++) {
    fsp->h_u.usr_ip6_spec.ip6src[i] =
     ip_addr->value.s6_addr32[i];
    fsp->m_u.usr_ip6_spec.ip6src[i] =
     ~ip_addr->mask.s6_addr32[i];
   }
  }

  if (!FIELD_GET(FBNIC_RPC_TCAM_ACT0_IPDST_IDX,
          act_tcam->mask.tcam[0])) {
   struct fbnic_ip_addr *ip_addr;
   int i;

   idx = FIELD_GET(FBNIC_RPC_TCAM_ACT0_IPDST_IDX,
     act_tcam->value.tcam[0]);
   ip_addr = &fbd->ip_dst[idx];

   for (i = 0; i < 4; i++) {
    fsp->h_u.usr_ip6_spec.ip6dst[i] =
     ip_addr->value.s6_addr32[i];
    fsp->m_u.usr_ip6_spec.ip6dst[i] =
     ~ip_addr->mask.s6_addr32[i];
   }
  }
 } else {
  if (act_tcam->value.tcam[1] & FBNIC_RPC_TCAM_ACT1_L4_VALID) {
   if (act_tcam->value.tcam[1] &
       FBNIC_RPC_TCAM_ACT1_L4_IS_UDP)
    fsp->flow_type = UDP_V4_FLOW;
   else
    fsp->flow_type = TCP_V4_FLOW;
   fsp->h_u.tcp_ip4_spec.psrc =
    cpu_to_be16(act_tcam->value.tcam[3]);
   fsp->m_u.tcp_ip4_spec.psrc =
    cpu_to_be16(~act_tcam->mask.tcam[3]);
   fsp->h_u.tcp_ip4_spec.pdst =
    cpu_to_be16(act_tcam->value.tcam[4]);
   fsp->m_u.tcp_ip4_spec.pdst =
    cpu_to_be16(~act_tcam->mask.tcam[4]);
  } else {
   fsp->flow_type = IPV4_USER_FLOW;
   fsp->h_u.usr_ip4_spec.ip_ver = ETH_RX_NFC_IP4;
  }

  if (!FIELD_GET(FBNIC_RPC_TCAM_ACT0_IPSRC_IDX,
          act_tcam->mask.tcam[0])) {
   struct fbnic_ip_addr *ip_addr;

   idx = FIELD_GET(FBNIC_RPC_TCAM_ACT0_IPSRC_IDX,
     act_tcam->value.tcam[0]);
   ip_addr = &fbd->ip_src[idx];

   fsp->h_u.usr_ip4_spec.ip4src =
    ip_addr->value.s6_addr32[3];
   fsp->m_u.usr_ip4_spec.ip4src =
    ~ip_addr->mask.s6_addr32[3];
  }

  if (!FIELD_GET(FBNIC_RPC_TCAM_ACT0_IPDST_IDX,
          act_tcam->mask.tcam[0])) {
   struct fbnic_ip_addr *ip_addr;

   idx = FIELD_GET(FBNIC_RPC_TCAM_ACT0_IPDST_IDX,
     act_tcam->value.tcam[0]);
   ip_addr = &fbd->ip_dst[idx];

   fsp->h_u.usr_ip4_spec.ip4dst =
    ip_addr->value.s6_addr32[3];
   fsp->m_u.usr_ip4_spec.ip4dst =
    ~ip_addr->mask.s6_addr32[3];
  }
 }

 /* Record action */
 if (act_tcam->dest & FBNIC_RPC_ACT_TBL0_DROP)
  fsp->ring_cookie = RX_CLS_FLOW_DISC;
 else if (act_tcam->dest & FBNIC_RPC_ACT_TBL0_Q_SEL)
  fsp->ring_cookie = FIELD_GET(FBNIC_RPC_ACT_TBL0_Q_ID,
          act_tcam->dest);
 else
  fsp->flow_type |= FLOW_RSS;

 cmd->rss_context = FIELD_GET(FBNIC_RPC_ACT_TBL0_RSS_CTXT_ID,
         act_tcam->dest);

 return 0;
}

static int fbnic_get_rxnfc(struct net_device *netdev,
      struct ethtool_rxnfc *cmd, u32 *rule_locs)
{
 struct fbnic_net *fbn = netdev_priv(netdev);
 int ret = -EOPNOTSUPP;
 u32 special = 0;

 switch (cmd->cmd) {
 case ETHTOOL_GRXRINGS:
  cmd->data = fbn->num_rx_queues;
  ret = 0;
  break;
 case ETHTOOL_GRXCLSRULE:
  ret = fbnic_get_cls_rule(fbn, cmd);
  break;
 case ETHTOOL_GRXCLSRLCNT:
  rule_locs = NULL;
  special = RX_CLS_LOC_SPECIAL;
  fallthrough;
 case ETHTOOL_GRXCLSRLALL:
  ret = fbnic_get_cls_rule_all(fbn, cmd, rule_locs);
  if (ret < 0)
   break;

  cmd->data |= special;
  cmd->rule_cnt = ret;
  ret = 0;
  break;
 }

 return ret;
}

static int fbnic_cls_rule_any_loc(struct fbnic_dev *fbd)
{
 int i;

 for (i = FBNIC_RPC_ACT_TBL_NFC_ENTRIES; i--;) {
  int idx = i + FBNIC_RPC_ACT_TBL_NFC_OFFSET;

  if (fbd->act_tcam[idx].state != FBNIC_TCAM_S_VALID)
   return i;
 }

 return -ENOSPC;
}

static int fbnic_set_cls_rule_ins(struct fbnic_net *fbn,
      const struct ethtool_rxnfc *cmd)
{
 u16 flow_value = 0, flow_mask = 0xffff, ip_value = 0, ip_mask = 0xffff;
 u16 sport = 0, sport_mask = ~0, dport = 0, dport_mask = ~0;
 u16 misc = 0, misc_mask = ~0;
 u32 dest = FIELD_PREP(FBNIC_RPC_ACT_TBL0_DEST_MASK,
         FBNIC_RPC_ACT_TBL0_DEST_HOST);
 struct fbnic_ip_addr *ip_src = NULL, *ip_dst = NULL;
 struct fbnic_mac_addr *mac_addr = NULL;
 struct ethtool_rx_flow_spec *fsp;
 struct fbnic_dev *fbd = fbn->fbd;
 struct fbnic_act_tcam *act_tcam;
 struct in6_addr *addr6, *mask6;
 struct in_addr *addr4, *mask4;
 int hash_idx, location;
 u32 flow_type;
 int idx, j;

 fsp = (struct ethtool_rx_flow_spec *)&cmd->fs;

 if (fsp->location != RX_CLS_LOC_ANY)
  return -EINVAL;
 location = fbnic_cls_rule_any_loc(fbd);
 if (location < 0)
  return location;

 if (fsp->ring_cookie == RX_CLS_FLOW_DISC) {
  dest = FBNIC_RPC_ACT_TBL0_DROP;
 } else if (fsp->flow_type & FLOW_RSS) {
  if (cmd->rss_context == 1)
   dest |= FBNIC_RPC_ACT_TBL0_RSS_CTXT_ID;
 } else {
  u32 ring_idx = ethtool_get_flow_spec_ring(fsp->ring_cookie);

  if (ring_idx >= fbn->num_rx_queues)
   return -EINVAL;

  dest |= FBNIC_RPC_ACT_TBL0_Q_SEL |
   FIELD_PREP(FBNIC_RPC_ACT_TBL0_Q_ID, ring_idx);
 }

 idx = location + FBNIC_RPC_ACT_TBL_NFC_OFFSET;
 act_tcam = &fbd->act_tcam[idx];

 /* Do not allow overwriting for now.
 * To support overwriting rules we will need to add logic to free
 * any IP or MACDA TCAMs that may be associated with the old rule.
 */

 if (act_tcam->state != FBNIC_TCAM_S_DISABLED)
  return -EBUSY;

 flow_type = fsp->flow_type & ~(FLOW_EXT | FLOW_RSS);
 hash_idx = fbnic_get_rss_hash_idx(flow_type);

 switch (flow_type) {
 case UDP_V4_FLOW:
udp4_flow:
  flow_value |= FBNIC_RPC_TCAM_ACT1_L4_IS_UDP;
  fallthrough;
 case TCP_V4_FLOW:
tcp4_flow:
  flow_value |= FBNIC_RPC_TCAM_ACT1_L4_VALID;
  flow_mask &= ~(FBNIC_RPC_TCAM_ACT1_L4_IS_UDP |
          FBNIC_RPC_TCAM_ACT1_L4_VALID);

  sport = be16_to_cpu(fsp->h_u.tcp_ip4_spec.psrc);
  sport_mask = ~be16_to_cpu(fsp->m_u.tcp_ip4_spec.psrc);
  dport = be16_to_cpu(fsp->h_u.tcp_ip4_spec.pdst);
  dport_mask = ~be16_to_cpu(fsp->m_u.tcp_ip4_spec.pdst);
  goto ip4_flow;
 case IP_USER_FLOW:
  if (!fsp->m_u.usr_ip4_spec.proto)
   goto ip4_flow;
  if (fsp->m_u.usr_ip4_spec.proto != 0xff)
   return -EINVAL;
  if (fsp->h_u.usr_ip4_spec.proto == IPPROTO_UDP)
   goto udp4_flow;
  if (fsp->h_u.usr_ip4_spec.proto == IPPROTO_TCP)
   goto tcp4_flow;
  return -EINVAL;
ip4_flow:
  addr4 = (struct in_addr *)&fsp->h_u.usr_ip4_spec.ip4src;
  mask4 = (struct in_addr *)&fsp->m_u.usr_ip4_spec.ip4src;
  if (mask4->s_addr) {
   ip_src = __fbnic_ip4_sync(fbd, fbd->ip_src,
        addr4, mask4);
   if (!ip_src)
    return -ENOSPC;

   set_bit(idx, ip_src->act_tcam);
   ip_value |= FBNIC_RPC_TCAM_ACT0_IPSRC_VALID |
        FIELD_PREP(FBNIC_RPC_TCAM_ACT0_IPSRC_IDX,
            ip_src - fbd->ip_src);
   ip_mask &= ~(FBNIC_RPC_TCAM_ACT0_IPSRC_VALID |
         FBNIC_RPC_TCAM_ACT0_IPSRC_IDX);
  }

  addr4 = (struct in_addr *)&fsp->h_u.usr_ip4_spec.ip4dst;
  mask4 = (struct in_addr *)&fsp->m_u.usr_ip4_spec.ip4dst;
  if (mask4->s_addr) {
   ip_dst = __fbnic_ip4_sync(fbd, fbd->ip_dst,
        addr4, mask4);
   if (!ip_dst) {
    if (ip_src && ip_src->state == FBNIC_TCAM_S_ADD)
     memset(ip_src, 0, sizeof(*ip_src));
    return -ENOSPC;
   }

   set_bit(idx, ip_dst->act_tcam);
   ip_value |= FBNIC_RPC_TCAM_ACT0_IPDST_VALID |
        FIELD_PREP(FBNIC_RPC_TCAM_ACT0_IPDST_IDX,
            ip_dst - fbd->ip_dst);
   ip_mask &= ~(FBNIC_RPC_TCAM_ACT0_IPDST_VALID |
         FBNIC_RPC_TCAM_ACT0_IPDST_IDX);
  }
  flow_value |= FBNIC_RPC_TCAM_ACT1_IP_VALID |
         FBNIC_RPC_TCAM_ACT1_L2_MACDA_VALID;
  flow_mask &= ~(FBNIC_RPC_TCAM_ACT1_IP_IS_V6 |
          FBNIC_RPC_TCAM_ACT1_IP_VALID |
          FBNIC_RPC_TCAM_ACT1_L2_MACDA_VALID);
  break;
 case UDP_V6_FLOW:
udp6_flow:
  flow_value |= FBNIC_RPC_TCAM_ACT1_L4_IS_UDP;
  fallthrough;
 case TCP_V6_FLOW:
tcp6_flow:
  flow_value |= FBNIC_RPC_TCAM_ACT1_L4_VALID;
  flow_mask &= ~(FBNIC_RPC_TCAM_ACT1_L4_IS_UDP |
     FBNIC_RPC_TCAM_ACT1_L4_VALID);

  sport = be16_to_cpu(fsp->h_u.tcp_ip6_spec.psrc);
  sport_mask = ~be16_to_cpu(fsp->m_u.tcp_ip6_spec.psrc);
  dport = be16_to_cpu(fsp->h_u.tcp_ip6_spec.pdst);
  dport_mask = ~be16_to_cpu(fsp->m_u.tcp_ip6_spec.pdst);
  goto ipv6_flow;
 case IPV6_USER_FLOW:
  if (!fsp->m_u.usr_ip6_spec.l4_proto)
   goto ipv6_flow;

  if (fsp->m_u.usr_ip6_spec.l4_proto != 0xff)
   return -EINVAL;
  if (fsp->h_u.usr_ip6_spec.l4_proto == IPPROTO_UDP)
   goto udp6_flow;
  if (fsp->h_u.usr_ip6_spec.l4_proto == IPPROTO_TCP)
   goto tcp6_flow;
  if (fsp->h_u.usr_ip6_spec.l4_proto != IPPROTO_IPV6)
   return -EINVAL;

  addr6 = (struct in6_addr *)fsp->h_u.usr_ip6_spec.ip6src;
  mask6 = (struct in6_addr *)fsp->m_u.usr_ip6_spec.ip6src;
  if (!ipv6_addr_any(mask6)) {
   ip_src = __fbnic_ip6_sync(fbd, fbd->ipo_src,
        addr6, mask6);
   if (!ip_src)
    return -ENOSPC;

   set_bit(idx, ip_src->act_tcam);
   ip_value |=
    FBNIC_RPC_TCAM_ACT0_OUTER_IPSRC_VALID |
    FIELD_PREP(FBNIC_RPC_TCAM_ACT0_OUTER_IPSRC_IDX,
        ip_src - fbd->ipo_src);
   ip_mask &=
    ~(FBNIC_RPC_TCAM_ACT0_OUTER_IPSRC_VALID |
      FBNIC_RPC_TCAM_ACT0_OUTER_IPSRC_IDX);
  }

  addr6 = (struct in6_addr *)fsp->h_u.usr_ip6_spec.ip6dst;
  mask6 = (struct in6_addr *)fsp->m_u.usr_ip6_spec.ip6dst;
  if (!ipv6_addr_any(mask6)) {
   ip_dst = __fbnic_ip6_sync(fbd, fbd->ipo_dst,
        addr6, mask6);
   if (!ip_dst) {
    if (ip_src && ip_src->state == FBNIC_TCAM_S_ADD)
     memset(ip_src, 0, sizeof(*ip_src));
    return -ENOSPC;
   }

   set_bit(idx, ip_dst->act_tcam);
   ip_value |=
    FBNIC_RPC_TCAM_ACT0_OUTER_IPDST_VALID |
    FIELD_PREP(FBNIC_RPC_TCAM_ACT0_OUTER_IPDST_IDX,
        ip_dst - fbd->ipo_dst);
   ip_mask &= ~(FBNIC_RPC_TCAM_ACT0_OUTER_IPDST_VALID |
         FBNIC_RPC_TCAM_ACT0_OUTER_IPDST_IDX);
  }

  flow_value |= FBNIC_RPC_TCAM_ACT1_OUTER_IP_VALID;
  flow_mask &= FBNIC_RPC_TCAM_ACT1_OUTER_IP_VALID;
ipv6_flow:
  addr6 = (struct in6_addr *)fsp->h_u.usr_ip6_spec.ip6src;
  mask6 = (struct in6_addr *)fsp->m_u.usr_ip6_spec.ip6src;
  if (!ip_src && !ipv6_addr_any(mask6)) {
   ip_src = __fbnic_ip6_sync(fbd, fbd->ip_src,
        addr6, mask6);
   if (!ip_src)
    return -ENOSPC;

   set_bit(idx, ip_src->act_tcam);
   ip_value |= FBNIC_RPC_TCAM_ACT0_IPSRC_VALID |
        FIELD_PREP(FBNIC_RPC_TCAM_ACT0_IPSRC_IDX,
            ip_src - fbd->ip_src);
   ip_mask &= ~(FBNIC_RPC_TCAM_ACT0_IPSRC_VALID |
           FBNIC_RPC_TCAM_ACT0_IPSRC_IDX);
  }

  addr6 = (struct in6_addr *)fsp->h_u.usr_ip6_spec.ip6dst;
  mask6 = (struct in6_addr *)fsp->m_u.usr_ip6_spec.ip6dst;
  if (!ip_dst && !ipv6_addr_any(mask6)) {
   ip_dst = __fbnic_ip6_sync(fbd, fbd->ip_dst,
        addr6, mask6);
   if (!ip_dst) {
    if (ip_src && ip_src->state == FBNIC_TCAM_S_ADD)
     memset(ip_src, 0, sizeof(*ip_src));
    return -ENOSPC;
   }

   set_bit(idx, ip_dst->act_tcam);
   ip_value |= FBNIC_RPC_TCAM_ACT0_IPDST_VALID |
        FIELD_PREP(FBNIC_RPC_TCAM_ACT0_IPDST_IDX,
            ip_dst - fbd->ip_dst);
   ip_mask &= ~(FBNIC_RPC_TCAM_ACT0_IPDST_VALID |
           FBNIC_RPC_TCAM_ACT0_IPDST_IDX);
  }

  flow_value |= FBNIC_RPC_TCAM_ACT1_IP_IS_V6 |
         FBNIC_RPC_TCAM_ACT1_IP_VALID |
         FBNIC_RPC_TCAM_ACT1_L2_MACDA_VALID;
  flow_mask &= ~(FBNIC_RPC_TCAM_ACT1_IP_IS_V6 |
          FBNIC_RPC_TCAM_ACT1_IP_VALID |
          FBNIC_RPC_TCAM_ACT1_L2_MACDA_VALID);
  break;
 case ETHER_FLOW:
  if (!is_zero_ether_addr(fsp->m_u.ether_spec.h_dest)) {
   u8 *addr = fsp->h_u.ether_spec.h_dest;
   u8 *mask = fsp->m_u.ether_spec.h_dest;

   /* Do not allow MAC addr of 0 */
   if (is_zero_ether_addr(addr))
    return -EINVAL;

   /* Only support full MAC address to avoid
 * conflicts with other MAC addresses.
 */

   if (!is_broadcast_ether_addr(mask))
    return -EINVAL;

   if (is_multicast_ether_addr(addr))
    mac_addr = __fbnic_mc_sync(fbd, addr);
   else
    mac_addr = __fbnic_uc_sync(fbd, addr);

   if (!mac_addr)
    return -ENOSPC;

   set_bit(idx, mac_addr->act_tcam);
   flow_value |=
    FIELD_PREP(FBNIC_RPC_TCAM_ACT1_L2_MACDA_IDX,
        mac_addr - fbd->mac_addr);
   flow_mask &= ~FBNIC_RPC_TCAM_ACT1_L2_MACDA_IDX;
  }

  flow_value |= FBNIC_RPC_TCAM_ACT1_L2_MACDA_VALID;
  flow_mask &= ~FBNIC_RPC_TCAM_ACT1_L2_MACDA_VALID;
  break;
 default:
  return -EINVAL;
 }

 /* Write action table values */
 act_tcam->dest = dest;
 act_tcam->rss_en_mask = fbnic_flow_hash_2_rss_en_mask(fbn, hash_idx);

 /* Write IP Match value/mask to action_tcam[0] */
 act_tcam->value.tcam[0] = ip_value;
 act_tcam->mask.tcam[0] = ip_mask;

 /* Write flow type value/mask to action_tcam[1] */
 act_tcam->value.tcam[1] = flow_value;
 act_tcam->mask.tcam[1] = flow_mask;

 /* Write error, DSCP, extra L4 matches to action_tcam[2] */
 act_tcam->value.tcam[2] = misc;
 act_tcam->mask.tcam[2] = misc_mask;

 /* Write source/destination port values */
 act_tcam->value.tcam[3] = sport;
 act_tcam->mask.tcam[3] = sport_mask;
 act_tcam->value.tcam[4] = dport;
 act_tcam->mask.tcam[4] = dport_mask;

 for (j = 5; j < FBNIC_RPC_TCAM_ACT_WORD_LEN; j++)
  act_tcam->mask.tcam[j] = 0xffff;

 act_tcam->state = FBNIC_TCAM_S_UPDATE;
 fsp->location = location;

 if (netif_running(fbn->netdev)) {
  fbnic_write_rules(fbd);
  if (ip_src || ip_dst)
   fbnic_write_ip_addr(fbd);
  if (mac_addr)
   fbnic_write_macda(fbd);
 }

 return 0;
}

static void fbnic_clear_nfc_macda(struct fbnic_net *fbn,
      unsigned int tcam_idx)
{
 struct fbnic_dev *fbd = fbn->fbd;
 int idx;

 for (idx = ARRAY_SIZE(fbd->mac_addr); idx--;)
  __fbnic_xc_unsync(&fbd->mac_addr[idx], tcam_idx);

 /* Write updates to hardware */
 if (netif_running(fbn->netdev))
  fbnic_write_macda(fbd);
}

static void fbnic_clear_nfc_ip_addr(struct fbnic_net *fbn,
        unsigned int tcam_idx)
{
 struct fbnic_dev *fbd = fbn->fbd;
 int idx;

 for (idx = ARRAY_SIZE(fbd->ip_src); idx--;)
  __fbnic_ip_unsync(&fbd->ip_src[idx], tcam_idx);
 for (idx = ARRAY_SIZE(fbd->ip_dst); idx--;)
  __fbnic_ip_unsync(&fbd->ip_dst[idx], tcam_idx);
 for (idx = ARRAY_SIZE(fbd->ipo_src); idx--;)
  __fbnic_ip_unsync(&fbd->ipo_src[idx], tcam_idx);
 for (idx = ARRAY_SIZE(fbd->ipo_dst); idx--;)
  __fbnic_ip_unsync(&fbd->ipo_dst[idx], tcam_idx);

 /* Write updates to hardware */
 if (netif_running(fbn->netdev))
  fbnic_write_ip_addr(fbd);
}

static int fbnic_set_cls_rule_del(struct fbnic_net *fbn,
      const struct ethtool_rxnfc *cmd)
{
 struct ethtool_rx_flow_spec *fsp;
 struct fbnic_dev *fbd = fbn->fbd;
 struct fbnic_act_tcam *act_tcam;
 int idx;

 fsp = (struct ethtool_rx_flow_spec *)&cmd->fs;

 if (fsp->location >= FBNIC_RPC_ACT_TBL_NFC_ENTRIES)
  return -EINVAL;

 idx = fsp->location + FBNIC_RPC_ACT_TBL_NFC_OFFSET;
 act_tcam = &fbd->act_tcam[idx];

 if (act_tcam->state != FBNIC_TCAM_S_VALID)
  return -EINVAL;

 act_tcam->state = FBNIC_TCAM_S_DELETE;

 if ((act_tcam->value.tcam[1] & FBNIC_RPC_TCAM_ACT1_L2_MACDA_VALID) &&
     (~act_tcam->mask.tcam[1] & FBNIC_RPC_TCAM_ACT1_L2_MACDA_IDX))
  fbnic_clear_nfc_macda(fbn, idx);

 if ((act_tcam->value.tcam[0] &
      (FBNIC_RPC_TCAM_ACT0_IPSRC_VALID |
       FBNIC_RPC_TCAM_ACT0_IPDST_VALID |
       FBNIC_RPC_TCAM_ACT0_OUTER_IPSRC_VALID |
       FBNIC_RPC_TCAM_ACT0_OUTER_IPDST_VALID)) &&
     (~act_tcam->mask.tcam[0] &
      (FBNIC_RPC_TCAM_ACT0_IPSRC_IDX |
       FBNIC_RPC_TCAM_ACT0_IPDST_IDX |
       FBNIC_RPC_TCAM_ACT0_OUTER_IPSRC_IDX |
       FBNIC_RPC_TCAM_ACT0_OUTER_IPDST_IDX)))
  fbnic_clear_nfc_ip_addr(fbn, idx);

 if (netif_running(fbn->netdev))
  fbnic_write_rules(fbd);

 return 0;
}

static int fbnic_set_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd)
{
 struct fbnic_net *fbn = netdev_priv(netdev);
 int ret = -EOPNOTSUPP;

 switch (cmd->cmd) {
 case ETHTOOL_SRXCLSRLINS:
  ret = fbnic_set_cls_rule_ins(fbn, cmd);
  break;
 case ETHTOOL_SRXCLSRLDEL:
  ret = fbnic_set_cls_rule_del(fbn, cmd);
  break;
 }

 return ret;
}

static u32 fbnic_get_rxfh_key_size(struct net_device *netdev)
{
 return FBNIC_RPC_RSS_KEY_BYTE_LEN;
}

static u32 fbnic_get_rxfh_indir_size(struct net_device *netdev)
{
 return FBNIC_RPC_RSS_TBL_SIZE;
}

static int
fbnic_get_rxfh(struct net_device *netdev, struct ethtool_rxfh_param *rxfh)
{
 struct fbnic_net *fbn = netdev_priv(netdev);
 unsigned int i;

 rxfh->hfunc = ETH_RSS_HASH_TOP;

 if (rxfh->key) {
  for (i = 0; i < FBNIC_RPC_RSS_KEY_BYTE_LEN; i++) {
   u32 rss_key = fbn->rss_key[i / 4] << ((i % 4) * 8);

   rxfh->key[i] = rss_key >> 24;
  }
 }

 if (rxfh->indir) {
  for (i = 0; i < FBNIC_RPC_RSS_TBL_SIZE; i++)
   rxfh->indir[i] = fbn->indir_tbl[0][i];
 }

 return 0;
}

static unsigned int
fbnic_set_indir(struct fbnic_net *fbn, unsigned int idx, const u32 *indir)
{
 unsigned int i, changes = 0;

 for (i = 0; i < FBNIC_RPC_RSS_TBL_SIZE; i++) {
  if (fbn->indir_tbl[idx][i] == indir[i])
   continue;

  fbn->indir_tbl[idx][i] = indir[i];
  changes++;
 }

 return changes;
}

static int
fbnic_set_rxfh(struct net_device *netdev, struct ethtool_rxfh_param *rxfh,
        struct netlink_ext_ack *extack)
{
 struct fbnic_net *fbn = netdev_priv(netdev);
 unsigned int i, changes = 0;

 if (rxfh->hfunc != ETH_RSS_HASH_NO_CHANGE &&
     rxfh->hfunc != ETH_RSS_HASH_TOP)
  return -EINVAL;

 if (rxfh->key) {
  u32 rss_key = 0;

  for (i = FBNIC_RPC_RSS_KEY_BYTE_LEN; i--;) {
   rss_key >>= 8;
   rss_key |= (u32)(rxfh->key[i]) << 24;

   if (i % 4)
    continue;

   if (fbn->rss_key[i / 4] == rss_key)
    continue;

   fbn->rss_key[i / 4] = rss_key;
   changes++;
  }
 }

 if (rxfh->indir)
  changes += fbnic_set_indir(fbn, 0, rxfh->indir);

 if (changes && netif_running(netdev))
  fbnic_rss_reinit_hw(fbn->fbd, fbn);

 return 0;
}

static int
fbnic_get_rss_hash_opts(struct net_device *netdev,
   struct ethtool_rxfh_fields *cmd)
{
 int hash_opt_idx = fbnic_get_rss_hash_idx(cmd->flow_type);
 struct fbnic_net *fbn = netdev_priv(netdev);

 if (hash_opt_idx < 0)
  return -EINVAL;

 /* Report options from rss_en table in fbn */
 cmd->data = fbn->rss_flow_hash[hash_opt_idx];

 return 0;
}

#define FBNIC_L2_HASH_OPTIONS \
 (RXH_L2DA | RXH_DISCARD)
#define FBNIC_L3_HASH_OPTIONS \
 (FBNIC_L2_HASH_OPTIONS | RXH_IP_SRC | RXH_IP_DST)
#define FBNIC_L4_HASH_OPTIONS \
 (FBNIC_L3_HASH_OPTIONS | RXH_L4_B_0_1 | RXH_L4_B_2_3)

static int
fbnic_set_rss_hash_opts(struct net_device *netdev,
   const struct ethtool_rxfh_fields *cmd,
   struct netlink_ext_ack *extack)
{
 struct fbnic_net *fbn = netdev_priv(netdev);
 int hash_opt_idx;

 /* Verify the type requested is correct */
 hash_opt_idx = fbnic_get_rss_hash_idx(cmd->flow_type);
 if (hash_opt_idx < 0)
  return -EINVAL;

 /* Verify the fields asked for can actually be assigned based on type */
 if (cmd->data & ~FBNIC_L4_HASH_OPTIONS ||
     (hash_opt_idx > FBNIC_L4_HASH_OPT &&
      cmd->data & ~FBNIC_L3_HASH_OPTIONS) ||
     (hash_opt_idx > FBNIC_IP_HASH_OPT &&
      cmd->data & ~FBNIC_L2_HASH_OPTIONS))
  return -EINVAL;

 fbn->rss_flow_hash[hash_opt_idx] = cmd->data;

 if (netif_running(fbn->netdev)) {
  fbnic_rss_reinit(fbn->fbd, fbn);
  fbnic_write_rules(fbn->fbd);
 }

 return 0;
}

static int
fbnic_modify_rxfh_context(struct net_device *netdev,
     struct ethtool_rxfh_context *ctx,
     const struct ethtool_rxfh_param *rxfh,
     struct netlink_ext_ack *extack)
{
 struct fbnic_net *fbn = netdev_priv(netdev);
 const u32 *indir = rxfh->indir;
 unsigned int changes;

 if (!indir)
  indir = ethtool_rxfh_context_indir(ctx);

 changes = fbnic_set_indir(fbn, rxfh->rss_context, indir);
 if (changes && netif_running(netdev))
  fbnic_rss_reinit_hw(fbn->fbd, fbn);

 return 0;
}

static int
fbnic_create_rxfh_context(struct net_device *netdev,
     struct ethtool_rxfh_context *ctx,
     const struct ethtool_rxfh_param *rxfh,
     struct netlink_ext_ack *extack)
{
 struct fbnic_net *fbn = netdev_priv(netdev);

 if (rxfh->hfunc && rxfh->hfunc != ETH_RSS_HASH_TOP) {
  NL_SET_ERR_MSG_MOD(extack, "RSS hash function not supported");
  return -EOPNOTSUPP;
 }
 ctx->hfunc = ETH_RSS_HASH_TOP;

 if (!rxfh->indir) {
  u32 *indir = ethtool_rxfh_context_indir(ctx);
  unsigned int num_rx = fbn->num_rx_queues;
  unsigned int i;

  for (i = 0; i < FBNIC_RPC_RSS_TBL_SIZE; i++)
   indir[i] = ethtool_rxfh_indir_default(i, num_rx);
 }

 return fbnic_modify_rxfh_context(netdev, ctx, rxfh, extack);
}

static int
fbnic_remove_rxfh_context(struct net_device *netdev,
     struct ethtool_rxfh_context *ctx, u32 rss_context,
     struct netlink_ext_ack *extack)
{
 /* Nothing to do, contexts are allocated statically */
 return 0;
}

static void fbnic_get_channels(struct net_device *netdev,
          struct ethtool_channels *ch)
{
 struct fbnic_net *fbn = netdev_priv(netdev);
 struct fbnic_dev *fbd = fbn->fbd;

 ch->max_rx = fbd->max_num_queues;
 ch->max_tx = fbd->max_num_queues;
 ch->max_combined = min(ch->max_rx, ch->max_tx);
 ch->max_other = FBNIC_NON_NAPI_VECTORS;

 if (fbn->num_rx_queues > fbn->num_napi ||
     fbn->num_tx_queues > fbn->num_napi)
  ch->combined_count = min(fbn->num_rx_queues,
      fbn->num_tx_queues);
 else
  ch->combined_count =
   fbn->num_rx_queues + fbn->num_tx_queues - fbn->num_napi;
 ch->rx_count = fbn->num_rx_queues - ch->combined_count;
 ch->tx_count = fbn->num_tx_queues - ch->combined_count;
 ch->other_count = FBNIC_NON_NAPI_VECTORS;
}

static void fbnic_set_queues(struct fbnic_net *fbn, struct ethtool_channels *ch,
        unsigned int max_napis)
{
 fbn->num_rx_queues = ch->rx_count + ch->combined_count;
 fbn->num_tx_queues = ch->tx_count + ch->combined_count;
 fbn->num_napi = min(ch->rx_count + ch->tx_count + ch->combined_count,
       max_napis);
}

static int fbnic_set_channels(struct net_device *netdev,
         struct ethtool_channels *ch)
{
 struct fbnic_net *fbn = netdev_priv(netdev);
 unsigned int max_napis, standalone;
 struct fbnic_dev *fbd = fbn->fbd;
 struct fbnic_net *clone;
 int err;

 max_napis = fbd->num_irqs - FBNIC_NON_NAPI_VECTORS;
 standalone = ch->rx_count + ch->tx_count;

 /* Limits for standalone queues:
 *  - each queue has its own NAPI (num_napi >= rx + tx + combined)
 *  - combining queues (combined not 0, rx or tx must be 0)
 */

 if ((ch->rx_count && ch->tx_count && ch->combined_count) ||
     (standalone && standalone + ch->combined_count > max_napis) ||
     ch->rx_count + ch->combined_count > fbd->max_num_queues ||
     ch->tx_count + ch->combined_count > fbd->max_num_queues ||
     ch->other_count != FBNIC_NON_NAPI_VECTORS)
  return -EINVAL;

 if (!netif_running(netdev)) {
  fbnic_set_queues(fbn, ch, max_napis);
  fbnic_reset_indir_tbl(fbn);
  return 0;
 }

 clone = fbnic_clone_create(fbn);
 if (!clone)
  return -ENOMEM;

 fbnic_set_queues(clone, ch, max_napis);

 err = fbnic_alloc_napi_vectors(clone);
 if (err)
  goto err_free_clone;

 err = fbnic_alloc_resources(clone);
 if (err)
  goto err_free_napis;

 fbnic_down_noidle(fbn);
 err = fbnic_wait_all_queues_idle(fbn->fbd, true);
 if (err)
  goto err_start_stack;

 err = fbnic_set_netif_queues(clone);
 if (err)
  goto err_start_stack;

 /* Nothing can fail past this point */
 fbnic_flush(fbn);

 fbnic_clone_swap(fbn, clone);

 /* Reset RSS indirection table */
 fbnic_reset_indir_tbl(fbn);

 fbnic_up(fbn);

 fbnic_free_resources(clone);
 fbnic_free_napi_vectors(clone);
 fbnic_clone_free(clone);

 return 0;

err_start_stack:
 fbnic_flush(fbn);
 fbnic_up(fbn);
 fbnic_free_resources(clone);
err_free_napis:
 fbnic_free_napi_vectors(clone);
err_free_clone:
 fbnic_clone_free(clone);
 return err;
}

static int
fbnic_get_ts_info(struct net_device *netdev,
    struct kernel_ethtool_ts_info *tsinfo)
{
 struct fbnic_net *fbn = netdev_priv(netdev);

 tsinfo->phc_index = ptp_clock_index(fbn->fbd->ptp);

 tsinfo->so_timestamping =
  SOF_TIMESTAMPING_TX_SOFTWARE |
  SOF_TIMESTAMPING_TX_HARDWARE |
  SOF_TIMESTAMPING_RX_HARDWARE |
  SOF_TIMESTAMPING_RAW_HARDWARE;

 tsinfo->tx_types =
  BIT(HWTSTAMP_TX_OFF) |
  BIT(HWTSTAMP_TX_ON);

 tsinfo->rx_filters =
  BIT(HWTSTAMP_FILTER_NONE) |
  BIT(HWTSTAMP_FILTER_PTP_V1_L4_EVENT) |
  BIT(HWTSTAMP_FILTER_PTP_V2_L4_EVENT) |
  BIT(HWTSTAMP_FILTER_PTP_V2_L2_EVENT) |
  BIT(HWTSTAMP_FILTER_PTP_V2_EVENT) |
  BIT(HWTSTAMP_FILTER_ALL);

 return 0;
}

static void fbnic_get_ts_stats(struct net_device *netdev,
          struct ethtool_ts_stats *ts_stats)
{
 struct fbnic_net *fbn = netdev_priv(netdev);
 u64 ts_packets, ts_lost;
 struct fbnic_ring *ring;
 unsigned int start;
 int i;

 ts_stats->pkts = fbn->tx_stats.twq.ts_packets;
 ts_stats->lost = fbn->tx_stats.twq.ts_lost;
 for (i = 0; i < fbn->num_tx_queues; i++) {
  ring = fbn->tx[i];
  do {
   start = u64_stats_fetch_begin(&ring->stats.syncp);
   ts_packets = ring->stats.twq.ts_packets;
   ts_lost = ring->stats.twq.ts_lost;
  } while (u64_stats_fetch_retry(&ring->stats.syncp, start));
  ts_stats->pkts += ts_packets;
  ts_stats->lost += ts_lost;
 }
}

static void fbnic_set_counter(u64 *stat, struct fbnic_stat_counter *counter)
{
 if (counter->reported)
  *stat = counter->value;
}

static void
fbnic_get_eth_mac_stats(struct net_device *netdev,
   struct ethtool_eth_mac_stats *eth_mac_stats)
{
 struct fbnic_net *fbn = netdev_priv(netdev);
 struct fbnic_mac_stats *mac_stats;
 struct fbnic_dev *fbd = fbn->fbd;
 const struct fbnic_mac *mac;

 mac_stats = &fbd->hw_stats.mac;
 mac = fbd->mac;

 mac->get_eth_mac_stats(fbd, false, &mac_stats->eth_mac);

 fbnic_set_counter(ð_mac_stats->FramesTransmittedOK,
     &mac_stats->eth_mac.FramesTransmittedOK);
 fbnic_set_counter(ð_mac_stats->FramesReceivedOK,
     &mac_stats->eth_mac.FramesReceivedOK);
 fbnic_set_counter(ð_mac_stats->FrameCheckSequenceErrors,
     &mac_stats->eth_mac.FrameCheckSequenceErrors);
 fbnic_set_counter(ð_mac_stats->AlignmentErrors,
     &mac_stats->eth_mac.AlignmentErrors);
 fbnic_set_counter(ð_mac_stats->OctetsTransmittedOK,
     &mac_stats->eth_mac.OctetsTransmittedOK);
 fbnic_set_counter(ð_mac_stats->FramesLostDueToIntMACXmitError,
     &mac_stats->eth_mac.FramesLostDueToIntMACXmitError);
 fbnic_set_counter(ð_mac_stats->OctetsReceivedOK,
     &mac_stats->eth_mac.OctetsReceivedOK);
 fbnic_set_counter(ð_mac_stats->FramesLostDueToIntMACRcvError,
     &mac_stats->eth_mac.FramesLostDueToIntMACRcvError);
 fbnic_set_counter(ð_mac_stats->MulticastFramesXmittedOK,
     &mac_stats->eth_mac.MulticastFramesXmittedOK);
 fbnic_set_counter(ð_mac_stats->BroadcastFramesXmittedOK,
     &mac_stats->eth_mac.BroadcastFramesXmittedOK);
 fbnic_set_counter(ð_mac_stats->MulticastFramesReceivedOK,
     &mac_stats->eth_mac.MulticastFramesReceivedOK);
 fbnic_set_counter(ð_mac_stats->BroadcastFramesReceivedOK,
     &mac_stats->eth_mac.BroadcastFramesReceivedOK);
 fbnic_set_counter(ð_mac_stats->FrameTooLongErrors,
     &mac_stats->eth_mac.FrameTooLongErrors);
}

static void
fbnic_get_eth_ctrl_stats(struct net_device *netdev,
    struct ethtool_eth_ctrl_stats *eth_ctrl_stats)
{
 struct fbnic_net *fbn = netdev_priv(netdev);
 struct fbnic_mac_stats *mac_stats;
 struct fbnic_dev *fbd = fbn->fbd;

 mac_stats = &fbd->hw_stats.mac;

 fbd->mac->get_eth_ctrl_stats(fbd, false, &mac_stats->eth_ctrl);

 eth_ctrl_stats->MACControlFramesReceived =
  mac_stats->eth_ctrl.MACControlFramesReceived.value;
 eth_ctrl_stats->MACControlFramesTransmitted =
  mac_stats->eth_ctrl.MACControlFramesTransmitted.value;
}

static const struct ethtool_rmon_hist_range fbnic_rmon_ranges[] = {
 {    0,   64 },
 {   65,  127 },
 {  128,  255 },
 {  256,  511 },
 {  512, 1023 },
 { 1024, 1518 },
 { 1519, 2047 },
 { 2048, 4095 },
 { 4096, 8191 },
 { 8192, 9216 },
 { 9217, FBNIC_MAX_JUMBO_FRAME_SIZE },
 {}
};

static void
fbnic_get_rmon_stats(struct net_device *netdev,
       struct ethtool_rmon_stats *rmon_stats,
       const struct ethtool_rmon_hist_range **ranges)
{
 struct fbnic_net *fbn = netdev_priv(netdev);
 struct fbnic_mac_stats *mac_stats;
 struct fbnic_dev *fbd = fbn->fbd;
 int i;

 mac_stats = &fbd->hw_stats.mac;

 fbd->mac->get_rmon_stats(fbd, false, &mac_stats->rmon);

 rmon_stats->undersize_pkts =
  mac_stats->rmon.undersize_pkts.value;
 rmon_stats->oversize_pkts =
  mac_stats->rmon.oversize_pkts.value;
 rmon_stats->fragments =
  mac_stats->rmon.fragments.value;
 rmon_stats->jabbers =
  mac_stats->rmon.jabbers.value;

 for (i = 0; fbnic_rmon_ranges[i].high; i++) {
  rmon_stats->hist[i] = mac_stats->rmon.hist[i].value;
  rmon_stats->hist_tx[i] = mac_stats->rmon.hist_tx[i].value;
 }

 *ranges = fbnic_rmon_ranges;
}

static const struct ethtool_ops fbnic_ethtool_ops = {
 .supported_coalesce_params = ETHTOOL_COALESCE_USECS |
       ETHTOOL_COALESCE_RX_MAX_FRAMES,
 .rxfh_max_num_contexts  = FBNIC_RPC_RSS_TBL_COUNT,
 .get_drvinfo   = fbnic_get_drvinfo,
 .get_regs_len   = fbnic_get_regs_len,
 .get_regs   = fbnic_get_regs,
 .get_link   = ethtool_op_get_link,
 .get_coalesce   = fbnic_get_coalesce,
 .set_coalesce   = fbnic_set_coalesce,
 .get_ringparam   = fbnic_get_ringparam,
 .set_ringparam   = fbnic_set_ringparam,
 .get_pauseparam   = fbnic_phylink_get_pauseparam,
 .set_pauseparam   = fbnic_phylink_set_pauseparam,
 .get_strings   = fbnic_get_strings,
 .get_ethtool_stats  = fbnic_get_ethtool_stats,
 .get_sset_count   = fbnic_get_sset_count,
 .get_rxnfc   = fbnic_get_rxnfc,
 .set_rxnfc   = fbnic_set_rxnfc,
 .get_rxfh_key_size  = fbnic_get_rxfh_key_size,
 .get_rxfh_indir_size  = fbnic_get_rxfh_indir_size,
 .get_rxfh   = fbnic_get_rxfh,
 .set_rxfh   = fbnic_set_rxfh,
 .get_rxfh_fields  = fbnic_get_rss_hash_opts,
 .set_rxfh_fields  = fbnic_set_rss_hash_opts,
 .create_rxfh_context  = fbnic_create_rxfh_context,
 .modify_rxfh_context  = fbnic_modify_rxfh_context,
 .remove_rxfh_context  = fbnic_remove_rxfh_context,
 .get_channels   = fbnic_get_channels,
 .set_channels   = fbnic_set_channels,
 .get_ts_info   = fbnic_get_ts_info,
 .get_ts_stats   = fbnic_get_ts_stats,
 .get_link_ksettings  = fbnic_phylink_ethtool_ksettings_get,
 .get_fecparam   = fbnic_phylink_get_fecparam,
 .get_eth_mac_stats  = fbnic_get_eth_mac_stats,
 .get_eth_ctrl_stats  = fbnic_get_eth_ctrl_stats,
 .get_rmon_stats   = fbnic_get_rmon_stats,
};

void fbnic_set_ethtool_ops(struct net_device *dev)
{
 dev->ethtool_ops = &fbnic_ethtool_ops;
}

Messung V0.5
C=99 H=84 G=91

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