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

Quelle  bcmgenet.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Broadcom GENET (Gigabit Ethernet) controller driver
 *
 * Copyright (c) 2014-2025 Broadcom
 */


#define pr_fmt(fmt)    "bcmgenet: " fmt

#include <linux/acpi.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/interrupt.h>
#include <linux/string.h>
#include <linux/if_ether.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/pm.h>
#include <linux/clk.h>
#include <net/arp.h>

#include <linux/mii.h>
#include <linux/ethtool.h>
#include <linux/netdevice.h>
#include <linux/inetdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/phy.h>
#include <linux/platform_data/bcmgenet.h>

#include <linux/unaligned.h>

#include "bcmgenet.h"

/* Default highest priority queue for multi queue support */
#define GENET_Q1_PRIORITY 0
#define GENET_Q0_PRIORITY 1

#define GENET_Q0_RX_BD_CNT \
 (TOTAL_DESC - priv->hw_params->rx_queues * priv->hw_params->rx_bds_per_q)
#define GENET_Q0_TX_BD_CNT \
 (TOTAL_DESC - priv->hw_params->tx_queues * priv->hw_params->tx_bds_per_q)

#define RX_BUF_LENGTH  2048
#define SKB_ALIGNMENT  32

/* Tx/Rx DMA register offset, skip 256 descriptors */
#define WORDS_PER_BD(p)  (p->hw_params->words_per_bd)
#define DMA_DESC_SIZE  (WORDS_PER_BD(priv) * sizeof(u32))

#define GENET_TDMA_REG_OFF (priv->hw_params->tdma_offset + \
    TOTAL_DESC * DMA_DESC_SIZE)

#define GENET_RDMA_REG_OFF (priv->hw_params->rdma_offset + \
    TOTAL_DESC * DMA_DESC_SIZE)

/* Forward declarations */
static void bcmgenet_set_rx_mode(struct net_device *dev);

static inline void bcmgenet_writel(u32 value, void __iomem *offset)
{
 /* MIPS chips strapped for BE will automagically configure the
 * peripheral registers for CPU-native byte order.
 */

 if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN))
  __raw_writel(value, offset);
 else
  writel_relaxed(value, offset);
}

static inline u32 bcmgenet_readl(void __iomem *offset)
{
 if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN))
  return __raw_readl(offset);
 else
  return readl_relaxed(offset);
}

static inline void dmadesc_set_length_status(struct bcmgenet_priv *priv,
          void __iomem *d, u32 value)
{
 bcmgenet_writel(value, d + DMA_DESC_LENGTH_STATUS);
}

static inline void dmadesc_set_addr(struct bcmgenet_priv *priv,
        void __iomem *d,
        dma_addr_t addr)
{
 bcmgenet_writel(lower_32_bits(addr), d + DMA_DESC_ADDRESS_LO);

 /* Register writes to GISB bus can take couple hundred nanoseconds
 * and are done for each packet, save these expensive writes unless
 * the platform is explicitly configured for 64-bits/LPAE.
 */

#ifdef CONFIG_PHYS_ADDR_T_64BIT
 if (bcmgenet_has_40bits(priv))
  bcmgenet_writel(upper_32_bits(addr), d + DMA_DESC_ADDRESS_HI);
#endif
}

/* Combined address + length/status setter */
static inline void dmadesc_set(struct bcmgenet_priv *priv,
          void __iomem *d, dma_addr_t addr, u32 val)
{
 dmadesc_set_addr(priv, d, addr);
 dmadesc_set_length_status(priv, d, val);
}

#define GENET_VER_FMT "%1d.%1d EPHY: 0x%04x"

#define GENET_MSG_DEFAULT (NETIF_MSG_DRV | NETIF_MSG_PROBE | \
    NETIF_MSG_LINK)

static inline u32 bcmgenet_rbuf_ctrl_get(struct bcmgenet_priv *priv)
{
 if (GENET_IS_V1(priv))
  return bcmgenet_rbuf_readl(priv, RBUF_FLUSH_CTRL_V1);
 else
  return bcmgenet_sys_readl(priv, SYS_RBUF_FLUSH_CTRL);
}

static inline void bcmgenet_rbuf_ctrl_set(struct bcmgenet_priv *priv, u32 val)
{
 if (GENET_IS_V1(priv))
  bcmgenet_rbuf_writel(priv, val, RBUF_FLUSH_CTRL_V1);
 else
  bcmgenet_sys_writel(priv, val, SYS_RBUF_FLUSH_CTRL);
}

/* These macros are defined to deal with register map change
 * between GENET1.1 and GENET2. Only those currently being used
 * by driver are defined.
 */

static inline u32 bcmgenet_tbuf_ctrl_get(struct bcmgenet_priv *priv)
{
 if (GENET_IS_V1(priv))
  return bcmgenet_rbuf_readl(priv, TBUF_CTRL_V1);
 else
  return bcmgenet_readl(priv->base +
          priv->hw_params->tbuf_offset + TBUF_CTRL);
}

static inline void bcmgenet_tbuf_ctrl_set(struct bcmgenet_priv *priv, u32 val)
{
 if (GENET_IS_V1(priv))
  bcmgenet_rbuf_writel(priv, val, TBUF_CTRL_V1);
 else
  bcmgenet_writel(val, priv->base +
    priv->hw_params->tbuf_offset + TBUF_CTRL);
}

static inline u32 bcmgenet_bp_mc_get(struct bcmgenet_priv *priv)
{
 if (GENET_IS_V1(priv))
  return bcmgenet_rbuf_readl(priv, TBUF_BP_MC_V1);
 else
  return bcmgenet_readl(priv->base +
          priv->hw_params->tbuf_offset + TBUF_BP_MC);
}

static inline void bcmgenet_bp_mc_set(struct bcmgenet_priv *priv, u32 val)
{
 if (GENET_IS_V1(priv))
  bcmgenet_rbuf_writel(priv, val, TBUF_BP_MC_V1);
 else
  bcmgenet_writel(val, priv->base +
    priv->hw_params->tbuf_offset + TBUF_BP_MC);
}

/* RX/TX DMA register accessors */
enum dma_reg {
 DMA_RING_CFG = 0,
 DMA_CTRL,
 DMA_STATUS,
 DMA_SCB_BURST_SIZE,
 DMA_ARB_CTRL,
 DMA_PRIORITY_0,
 DMA_PRIORITY_1,
 DMA_PRIORITY_2,
 DMA_INDEX2RING_0,
 DMA_INDEX2RING_1,
 DMA_INDEX2RING_2,
 DMA_INDEX2RING_3,
 DMA_INDEX2RING_4,
 DMA_INDEX2RING_5,
 DMA_INDEX2RING_6,
 DMA_INDEX2RING_7,
 DMA_RING0_TIMEOUT,
 DMA_RING1_TIMEOUT,
 DMA_RING2_TIMEOUT,
 DMA_RING3_TIMEOUT,
 DMA_RING4_TIMEOUT,
 DMA_RING5_TIMEOUT,
 DMA_RING6_TIMEOUT,
 DMA_RING7_TIMEOUT,
 DMA_RING8_TIMEOUT,
 DMA_RING9_TIMEOUT,
 DMA_RING10_TIMEOUT,
 DMA_RING11_TIMEOUT,
 DMA_RING12_TIMEOUT,
 DMA_RING13_TIMEOUT,
 DMA_RING14_TIMEOUT,
 DMA_RING15_TIMEOUT,
 DMA_RING16_TIMEOUT,
};

static const u8 bcmgenet_dma_regs_v3plus[] = {
 [DMA_RING_CFG]  = 0x00,
 [DMA_CTRL]  = 0x04,
 [DMA_STATUS]  = 0x08,
 [DMA_SCB_BURST_SIZE] = 0x0C,
 [DMA_ARB_CTRL]  = 0x2C,
 [DMA_PRIORITY_0] = 0x30,
 [DMA_PRIORITY_1] = 0x34,
 [DMA_PRIORITY_2] = 0x38,
 [DMA_RING0_TIMEOUT] = 0x2C,
 [DMA_RING1_TIMEOUT] = 0x30,
 [DMA_RING2_TIMEOUT] = 0x34,
 [DMA_RING3_TIMEOUT] = 0x38,
 [DMA_RING4_TIMEOUT] = 0x3c,
 [DMA_RING5_TIMEOUT] = 0x40,
 [DMA_RING6_TIMEOUT] = 0x44,
 [DMA_RING7_TIMEOUT] = 0x48,
 [DMA_RING8_TIMEOUT] = 0x4c,
 [DMA_RING9_TIMEOUT] = 0x50,
 [DMA_RING10_TIMEOUT] = 0x54,
 [DMA_RING11_TIMEOUT] = 0x58,
 [DMA_RING12_TIMEOUT] = 0x5c,
 [DMA_RING13_TIMEOUT] = 0x60,
 [DMA_RING14_TIMEOUT] = 0x64,
 [DMA_RING15_TIMEOUT] = 0x68,
 [DMA_RING16_TIMEOUT] = 0x6C,
 [DMA_INDEX2RING_0] = 0x70,
 [DMA_INDEX2RING_1] = 0x74,
 [DMA_INDEX2RING_2] = 0x78,
 [DMA_INDEX2RING_3] = 0x7C,
 [DMA_INDEX2RING_4] = 0x80,
 [DMA_INDEX2RING_5] = 0x84,
 [DMA_INDEX2RING_6] = 0x88,
 [DMA_INDEX2RING_7] = 0x8C,
};

static const u8 bcmgenet_dma_regs_v2[] = {
 [DMA_RING_CFG]  = 0x00,
 [DMA_CTRL]  = 0x04,
 [DMA_STATUS]  = 0x08,
 [DMA_SCB_BURST_SIZE] = 0x0C,
 [DMA_ARB_CTRL]  = 0x30,
 [DMA_PRIORITY_0] = 0x34,
 [DMA_PRIORITY_1] = 0x38,
 [DMA_PRIORITY_2] = 0x3C,
 [DMA_RING0_TIMEOUT] = 0x2C,
 [DMA_RING1_TIMEOUT] = 0x30,
 [DMA_RING2_TIMEOUT] = 0x34,
 [DMA_RING3_TIMEOUT] = 0x38,
 [DMA_RING4_TIMEOUT] = 0x3c,
 [DMA_RING5_TIMEOUT] = 0x40,
 [DMA_RING6_TIMEOUT] = 0x44,
 [DMA_RING7_TIMEOUT] = 0x48,
 [DMA_RING8_TIMEOUT] = 0x4c,
 [DMA_RING9_TIMEOUT] = 0x50,
 [DMA_RING10_TIMEOUT] = 0x54,
 [DMA_RING11_TIMEOUT] = 0x58,
 [DMA_RING12_TIMEOUT] = 0x5c,
 [DMA_RING13_TIMEOUT] = 0x60,
 [DMA_RING14_TIMEOUT] = 0x64,
 [DMA_RING15_TIMEOUT] = 0x68,
 [DMA_RING16_TIMEOUT] = 0x6C,
};

static const u8 bcmgenet_dma_regs_v1[] = {
 [DMA_CTRL]  = 0x00,
 [DMA_STATUS]  = 0x04,
 [DMA_SCB_BURST_SIZE] = 0x0C,
 [DMA_ARB_CTRL]  = 0x30,
 [DMA_PRIORITY_0] = 0x34,
 [DMA_PRIORITY_1] = 0x38,
 [DMA_PRIORITY_2] = 0x3C,
 [DMA_RING0_TIMEOUT] = 0x2C,
 [DMA_RING1_TIMEOUT] = 0x30,
 [DMA_RING2_TIMEOUT] = 0x34,
 [DMA_RING3_TIMEOUT] = 0x38,
 [DMA_RING4_TIMEOUT] = 0x3c,
 [DMA_RING5_TIMEOUT] = 0x40,
 [DMA_RING6_TIMEOUT] = 0x44,
 [DMA_RING7_TIMEOUT] = 0x48,
 [DMA_RING8_TIMEOUT] = 0x4c,
 [DMA_RING9_TIMEOUT] = 0x50,
 [DMA_RING10_TIMEOUT] = 0x54,
 [DMA_RING11_TIMEOUT] = 0x58,
 [DMA_RING12_TIMEOUT] = 0x5c,
 [DMA_RING13_TIMEOUT] = 0x60,
 [DMA_RING14_TIMEOUT] = 0x64,
 [DMA_RING15_TIMEOUT] = 0x68,
 [DMA_RING16_TIMEOUT] = 0x6C,
};

/* Set at runtime once bcmgenet version is known */
static const u8 *bcmgenet_dma_regs;

static inline struct bcmgenet_priv *dev_to_priv(struct device *dev)
{
 return netdev_priv(dev_get_drvdata(dev));
}

static inline u32 bcmgenet_tdma_readl(struct bcmgenet_priv *priv,
          enum dma_reg r)
{
 return bcmgenet_readl(priv->base + GENET_TDMA_REG_OFF +
         DMA_RINGS_SIZE + bcmgenet_dma_regs[r]);
}

static inline void bcmgenet_tdma_writel(struct bcmgenet_priv *priv,
     u32 val, enum dma_reg r)
{
 bcmgenet_writel(val, priv->base + GENET_TDMA_REG_OFF +
   DMA_RINGS_SIZE + bcmgenet_dma_regs[r]);
}

static inline u32 bcmgenet_rdma_readl(struct bcmgenet_priv *priv,
          enum dma_reg r)
{
 return bcmgenet_readl(priv->base + GENET_RDMA_REG_OFF +
         DMA_RINGS_SIZE + bcmgenet_dma_regs[r]);
}

static inline void bcmgenet_rdma_writel(struct bcmgenet_priv *priv,
     u32 val, enum dma_reg r)
{
 bcmgenet_writel(val, priv->base + GENET_RDMA_REG_OFF +
   DMA_RINGS_SIZE + bcmgenet_dma_regs[r]);
}

/* RDMA/TDMA ring registers and accessors
 * we merge the common fields and just prefix with T/D the registers
 * having different meaning depending on the direction
 */

enum dma_ring_reg {
 TDMA_READ_PTR = 0,
 RDMA_WRITE_PTR = TDMA_READ_PTR,
 TDMA_READ_PTR_HI,
 RDMA_WRITE_PTR_HI = TDMA_READ_PTR_HI,
 TDMA_CONS_INDEX,
 RDMA_PROD_INDEX = TDMA_CONS_INDEX,
 TDMA_PROD_INDEX,
 RDMA_CONS_INDEX = TDMA_PROD_INDEX,
 DMA_RING_BUF_SIZE,
 DMA_START_ADDR,
 DMA_START_ADDR_HI,
 DMA_END_ADDR,
 DMA_END_ADDR_HI,
 DMA_MBUF_DONE_THRESH,
 TDMA_FLOW_PERIOD,
 RDMA_XON_XOFF_THRESH = TDMA_FLOW_PERIOD,
 TDMA_WRITE_PTR,
 RDMA_READ_PTR = TDMA_WRITE_PTR,
 TDMA_WRITE_PTR_HI,
 RDMA_READ_PTR_HI = TDMA_WRITE_PTR_HI
};

/* GENET v4 supports 40-bits pointer addressing
 * for obvious reasons the LO and HI word parts
 * are contiguous, but this offsets the other
 * registers.
 */

static const u8 genet_dma_ring_regs_v4[] = {
 [TDMA_READ_PTR]   = 0x00,
 [TDMA_READ_PTR_HI]  = 0x04,
 [TDMA_CONS_INDEX]  = 0x08,
 [TDMA_PROD_INDEX]  = 0x0C,
 [DMA_RING_BUF_SIZE]  = 0x10,
 [DMA_START_ADDR]  = 0x14,
 [DMA_START_ADDR_HI]  = 0x18,
 [DMA_END_ADDR]   = 0x1C,
 [DMA_END_ADDR_HI]  = 0x20,
 [DMA_MBUF_DONE_THRESH]  = 0x24,
 [TDMA_FLOW_PERIOD]  = 0x28,
 [TDMA_WRITE_PTR]  = 0x2C,
 [TDMA_WRITE_PTR_HI]  = 0x30,
};

static const u8 genet_dma_ring_regs_v123[] = {
 [TDMA_READ_PTR]   = 0x00,
 [TDMA_CONS_INDEX]  = 0x04,
 [TDMA_PROD_INDEX]  = 0x08,
 [DMA_RING_BUF_SIZE]  = 0x0C,
 [DMA_START_ADDR]  = 0x10,
 [DMA_END_ADDR]   = 0x14,
 [DMA_MBUF_DONE_THRESH]  = 0x18,
 [TDMA_FLOW_PERIOD]  = 0x1C,
 [TDMA_WRITE_PTR]  = 0x20,
};

/* Set at runtime once GENET version is known */
static const u8 *genet_dma_ring_regs;

static inline u32 bcmgenet_tdma_ring_readl(struct bcmgenet_priv *priv,
        unsigned int ring,
        enum dma_ring_reg r)
{
 return bcmgenet_readl(priv->base + GENET_TDMA_REG_OFF +
         (DMA_RING_SIZE * ring) +
         genet_dma_ring_regs[r]);
}

static inline void bcmgenet_tdma_ring_writel(struct bcmgenet_priv *priv,
          unsigned int ring, u32 val,
          enum dma_ring_reg r)
{
 bcmgenet_writel(val, priv->base + GENET_TDMA_REG_OFF +
   (DMA_RING_SIZE * ring) +
   genet_dma_ring_regs[r]);
}

static inline u32 bcmgenet_rdma_ring_readl(struct bcmgenet_priv *priv,
        unsigned int ring,
        enum dma_ring_reg r)
{
 return bcmgenet_readl(priv->base + GENET_RDMA_REG_OFF +
         (DMA_RING_SIZE * ring) +
         genet_dma_ring_regs[r]);
}

static inline void bcmgenet_rdma_ring_writel(struct bcmgenet_priv *priv,
          unsigned int ring, u32 val,
          enum dma_ring_reg r)
{
 bcmgenet_writel(val, priv->base + GENET_RDMA_REG_OFF +
   (DMA_RING_SIZE * ring) +
   genet_dma_ring_regs[r]);
}

static void bcmgenet_hfb_enable_filter(struct bcmgenet_priv *priv, u32 f_index)
{
 u32 offset;
 u32 reg;

 if (GENET_IS_V1(priv) || GENET_IS_V2(priv)) {
  reg = bcmgenet_hfb_reg_readl(priv, HFB_CTRL);
  reg |= (1 << ((f_index % 32) + RBUF_HFB_FILTER_EN_SHIFT)) |
   RBUF_HFB_EN;
  bcmgenet_hfb_reg_writel(priv, reg, HFB_CTRL);
 } else {
  offset = HFB_FLT_ENABLE_V3PLUS + (f_index < 32) * sizeof(u32);
  reg = bcmgenet_hfb_reg_readl(priv, offset);
  reg |= (1 << (f_index % 32));
  bcmgenet_hfb_reg_writel(priv, reg, offset);
  reg = bcmgenet_hfb_reg_readl(priv, HFB_CTRL);
  reg |= RBUF_HFB_EN;
  bcmgenet_hfb_reg_writel(priv, reg, HFB_CTRL);
 }
}

static void bcmgenet_hfb_disable_filter(struct bcmgenet_priv *priv, u32 f_index)
{
 u32 offset, reg, reg1;

 if (GENET_IS_V1(priv) || GENET_IS_V2(priv)) {
  reg = bcmgenet_hfb_reg_readl(priv, HFB_CTRL);
  reg &= ~(1 << ((f_index % 32) + RBUF_HFB_FILTER_EN_SHIFT));
  if (!(reg & RBUF_HFB_FILTER_EN_MASK))
   reg &= ~RBUF_HFB_EN;
  bcmgenet_hfb_reg_writel(priv, reg, HFB_CTRL);
 } else {
  offset = HFB_FLT_ENABLE_V3PLUS;
  reg = bcmgenet_hfb_reg_readl(priv, offset);
  reg1 = bcmgenet_hfb_reg_readl(priv, offset + sizeof(u32));
  if  (f_index < 32) {
   reg1 &= ~(1 << (f_index % 32));
   bcmgenet_hfb_reg_writel(priv, reg1, offset + sizeof(u32));
  } else {
   reg &= ~(1 << (f_index % 32));
   bcmgenet_hfb_reg_writel(priv, reg, offset);
  }
  if (!reg && !reg1) {
   reg = bcmgenet_hfb_reg_readl(priv, HFB_CTRL);
   reg &= ~RBUF_HFB_EN;
   bcmgenet_hfb_reg_writel(priv, reg, HFB_CTRL);
  }
 }
}

static void bcmgenet_hfb_set_filter_rx_queue_mapping(struct bcmgenet_priv *priv,
           u32 f_index, u32 rx_queue)
{
 u32 offset;
 u32 reg;

 if (GENET_IS_V1(priv) || GENET_IS_V2(priv))
  return;

 offset = f_index / 8;
 reg = bcmgenet_rdma_readl(priv, DMA_INDEX2RING_0 + offset);
 reg &= ~(0xF << (4 * (f_index % 8)));
 reg |= ((rx_queue & 0xF) << (4 * (f_index % 8)));
 bcmgenet_rdma_writel(priv, reg, DMA_INDEX2RING_0 + offset);
}

static void bcmgenet_hfb_set_filter_length(struct bcmgenet_priv *priv,
        u32 f_index, u32 f_length)
{
 u32 offset;
 u32 reg;

 if (GENET_IS_V1(priv) || GENET_IS_V2(priv))
  offset = HFB_FLT_LEN_V2;
 else
  offset = HFB_FLT_LEN_V3PLUS;

 offset += sizeof(u32) *
    ((priv->hw_params->hfb_filter_cnt - 1 - f_index) / 4);
 reg = bcmgenet_hfb_reg_readl(priv, offset);
 reg &= ~(0xFF << (8 * (f_index % 4)));
 reg |= ((f_length & 0xFF) << (8 * (f_index % 4)));
 bcmgenet_hfb_reg_writel(priv, reg, offset);
}

static int bcmgenet_hfb_validate_mask(void *mask, size_t size)
{
 while (size) {
  switch (*(unsigned char *)mask++) {
  case 0x00:
  case 0x0f:
  case 0xf0:
  case 0xff:
   size--;
   continue;
  default:
   return -EINVAL;
  }
 }

 return 0;
}

#define VALIDATE_MASK(x) \
 bcmgenet_hfb_validate_mask(&(x), sizeof(x))

static int bcmgenet_hfb_insert_data(struct bcmgenet_priv *priv, u32 f_index,
        u32 offset, void *val, void *mask,
        size_t size)
{
 u32 index, tmp;

 index = f_index * priv->hw_params->hfb_filter_size + offset / 2;
 tmp = bcmgenet_hfb_readl(priv, index * sizeof(u32));

 while (size--) {
  if (offset++ & 1) {
   tmp &= ~0x300FF;
   tmp |= (*(unsigned char *)val++);
   switch ((*(unsigned char *)mask++)) {
   case 0xFF:
    tmp |= 0x30000;
    break;
   case 0xF0:
    tmp |= 0x20000;
    break;
   case 0x0F:
    tmp |= 0x10000;
    break;
   }
   bcmgenet_hfb_writel(priv, tmp, index++ * sizeof(u32));
   if (size)
    tmp = bcmgenet_hfb_readl(priv,
        index * sizeof(u32));
  } else {
   tmp &= ~0xCFF00;
   tmp |= (*(unsigned char *)val++) << 8;
   switch ((*(unsigned char *)mask++)) {
   case 0xFF:
    tmp |= 0xC0000;
    break;
   case 0xF0:
    tmp |= 0x80000;
    break;
   case 0x0F:
    tmp |= 0x40000;
    break;
   }
   if (!size)
    bcmgenet_hfb_writel(priv, tmp, index * sizeof(u32));
  }
 }

 return 0;
}

static void bcmgenet_hfb_create_rxnfc_filter(struct bcmgenet_priv *priv,
          struct bcmgenet_rxnfc_rule *rule)
{
 struct ethtool_rx_flow_spec *fs = &rule->fs;
 u32 offset = 0, f_length = 0, f, q;
 u8 val_8, mask_8;
 __be16 val_16;
 u16 mask_16;
 size_t size;

 f = fs->location + 1;
 if (fs->flow_type & FLOW_MAC_EXT) {
  bcmgenet_hfb_insert_data(priv, f, 0,
      &fs->h_ext.h_dest, &fs->m_ext.h_dest,
      sizeof(fs->h_ext.h_dest));
 }

 if (fs->flow_type & FLOW_EXT) {
  if (fs->m_ext.vlan_etype ||
      fs->m_ext.vlan_tci) {
   bcmgenet_hfb_insert_data(priv, f, 12,
       &fs->h_ext.vlan_etype,
       &fs->m_ext.vlan_etype,
       sizeof(fs->h_ext.vlan_etype));
   bcmgenet_hfb_insert_data(priv, f, 14,
       &fs->h_ext.vlan_tci,
       &fs->m_ext.vlan_tci,
       sizeof(fs->h_ext.vlan_tci));
   offset += VLAN_HLEN;
   f_length += DIV_ROUND_UP(VLAN_HLEN, 2);
  }
 }

 switch (fs->flow_type & ~(FLOW_EXT | FLOW_MAC_EXT)) {
 case ETHER_FLOW:
  f_length += DIV_ROUND_UP(ETH_HLEN, 2);
  bcmgenet_hfb_insert_data(priv, f, 0,
      &fs->h_u.ether_spec.h_dest,
      &fs->m_u.ether_spec.h_dest,
      sizeof(fs->h_u.ether_spec.h_dest));
  bcmgenet_hfb_insert_data(priv, f, ETH_ALEN,
      &fs->h_u.ether_spec.h_source,
      &fs->m_u.ether_spec.h_source,
      sizeof(fs->h_u.ether_spec.h_source));
  bcmgenet_hfb_insert_data(priv, f, (2 * ETH_ALEN) + offset,
      &fs->h_u.ether_spec.h_proto,
      &fs->m_u.ether_spec.h_proto,
      sizeof(fs->h_u.ether_spec.h_proto));
  break;
 case IP_USER_FLOW:
  f_length += DIV_ROUND_UP(ETH_HLEN + 20, 2);
  /* Specify IP Ether Type */
  val_16 = htons(ETH_P_IP);
  mask_16 = 0xFFFF;
  bcmgenet_hfb_insert_data(priv, f, (2 * ETH_ALEN) + offset,
      &val_16, &mask_16, sizeof(val_16));
  bcmgenet_hfb_insert_data(priv, f, 15 + offset,
      &fs->h_u.usr_ip4_spec.tos,
      &fs->m_u.usr_ip4_spec.tos,
      sizeof(fs->h_u.usr_ip4_spec.tos));
  bcmgenet_hfb_insert_data(priv, f, 23 + offset,
      &fs->h_u.usr_ip4_spec.proto,
      &fs->m_u.usr_ip4_spec.proto,
      sizeof(fs->h_u.usr_ip4_spec.proto));
  bcmgenet_hfb_insert_data(priv, f, 26 + offset,
      &fs->h_u.usr_ip4_spec.ip4src,
      &fs->m_u.usr_ip4_spec.ip4src,
      sizeof(fs->h_u.usr_ip4_spec.ip4src));
  bcmgenet_hfb_insert_data(priv, f, 30 + offset,
      &fs->h_u.usr_ip4_spec.ip4dst,
      &fs->m_u.usr_ip4_spec.ip4dst,
      sizeof(fs->h_u.usr_ip4_spec.ip4dst));
  if (!fs->m_u.usr_ip4_spec.l4_4_bytes)
   break;

  /* Only supports 20 byte IPv4 header */
  val_8 = 0x45;
  mask_8 = 0xFF;
  bcmgenet_hfb_insert_data(priv, f, ETH_HLEN + offset,
      &val_8, &mask_8,
      sizeof(val_8));
  size = sizeof(fs->h_u.usr_ip4_spec.l4_4_bytes);
  bcmgenet_hfb_insert_data(priv, f,
      ETH_HLEN + 20 + offset,
      &fs->h_u.usr_ip4_spec.l4_4_bytes,
      &fs->m_u.usr_ip4_spec.l4_4_bytes,
      size);
  f_length += DIV_ROUND_UP(size, 2);
  break;
 }

 bcmgenet_hfb_set_filter_length(priv, f, 2 * f_length);
 if (fs->ring_cookie == RX_CLS_FLOW_WAKE)
  q = 0;
 else if (fs->ring_cookie == RX_CLS_FLOW_DISC)
  q = priv->hw_params->rx_queues + 1;
 else
  /* Other Rx rings are direct mapped here */
  q = fs->ring_cookie;
 bcmgenet_hfb_set_filter_rx_queue_mapping(priv, f, q);
 bcmgenet_hfb_enable_filter(priv, f);
 rule->state = BCMGENET_RXNFC_STATE_ENABLED;
}

/* bcmgenet_hfb_clear
 *
 * Clear Hardware Filter Block and disable all filtering.
 */

static void bcmgenet_hfb_clear_filter(struct bcmgenet_priv *priv, u32 f_index)
{
 u32 base, i;

 bcmgenet_hfb_set_filter_length(priv, f_index, 0);
 base = f_index * priv->hw_params->hfb_filter_size;
 for (i = 0; i < priv->hw_params->hfb_filter_size; i++)
  bcmgenet_hfb_writel(priv, 0x0, (base + i) * sizeof(u32));
}

static void bcmgenet_hfb_clear(struct bcmgenet_priv *priv)
{
 u32 i;

 bcmgenet_hfb_reg_writel(priv, 0, HFB_CTRL);

 if (!GENET_IS_V1(priv) && !GENET_IS_V2(priv)) {
  bcmgenet_hfb_reg_writel(priv, 0,
     HFB_FLT_ENABLE_V3PLUS);
  bcmgenet_hfb_reg_writel(priv, 0,
     HFB_FLT_ENABLE_V3PLUS + 4);
  for (i = DMA_INDEX2RING_0; i <= DMA_INDEX2RING_7; i++)
   bcmgenet_rdma_writel(priv, 0, i);
 }

 for (i = 0; i < priv->hw_params->hfb_filter_cnt; i++)
  bcmgenet_hfb_clear_filter(priv, i);

 /* Enable filter 0 to send default flow to ring 0 */
 bcmgenet_hfb_set_filter_length(priv, 0, 4);
 bcmgenet_hfb_enable_filter(priv, 0);
}

static void bcmgenet_hfb_init(struct bcmgenet_priv *priv)
{
 int i;

 INIT_LIST_HEAD(&priv->rxnfc_list);
 for (i = 0; i < MAX_NUM_OF_FS_RULES; i++) {
  INIT_LIST_HEAD(&priv->rxnfc_rules[i].list);
  priv->rxnfc_rules[i].state = BCMGENET_RXNFC_STATE_UNUSED;
 }

 bcmgenet_hfb_clear(priv);
}

static int bcmgenet_begin(struct net_device *dev)
{
 struct bcmgenet_priv *priv = netdev_priv(dev);

 /* Turn on the clock */
 return clk_prepare_enable(priv->clk);
}

static void bcmgenet_complete(struct net_device *dev)
{
 struct bcmgenet_priv *priv = netdev_priv(dev);

 /* Turn off the clock */
 clk_disable_unprepare(priv->clk);
}

static int bcmgenet_get_link_ksettings(struct net_device *dev,
           struct ethtool_link_ksettings *cmd)
{
 if (!netif_running(dev))
  return -EINVAL;

 if (!dev->phydev)
  return -ENODEV;

 phy_ethtool_ksettings_get(dev->phydev, cmd);

 return 0;
}

static int bcmgenet_set_link_ksettings(struct net_device *dev,
           const struct ethtool_link_ksettings *cmd)
{
 if (!netif_running(dev))
  return -EINVAL;

 if (!dev->phydev)
  return -ENODEV;

 return phy_ethtool_ksettings_set(dev->phydev, cmd);
}

static int bcmgenet_set_features(struct net_device *dev,
     netdev_features_t features)
{
 struct bcmgenet_priv *priv = netdev_priv(dev);
 u32 reg;
 int ret;

 ret = clk_prepare_enable(priv->clk);
 if (ret)
  return ret;

 /* Make sure we reflect the value of CRC_CMD_FWD */
 reg = bcmgenet_umac_readl(priv, UMAC_CMD);
 priv->crc_fwd_en = !!(reg & CMD_CRC_FWD);

 clk_disable_unprepare(priv->clk);

 return ret;
}

static u32 bcmgenet_get_msglevel(struct net_device *dev)
{
 struct bcmgenet_priv *priv = netdev_priv(dev);

 return priv->msg_enable;
}

static void bcmgenet_set_msglevel(struct net_device *dev, u32 level)
{
 struct bcmgenet_priv *priv = netdev_priv(dev);

 priv->msg_enable = level;
}

static int bcmgenet_get_coalesce(struct net_device *dev,
     struct ethtool_coalesce *ec,
     struct kernel_ethtool_coalesce *kernel_coal,
     struct netlink_ext_ack *extack)
{
 struct bcmgenet_priv *priv = netdev_priv(dev);
 struct bcmgenet_rx_ring *ring;
 unsigned int i;

 ec->tx_max_coalesced_frames =
  bcmgenet_tdma_ring_readl(priv, 0, DMA_MBUF_DONE_THRESH);
 ec->rx_max_coalesced_frames =
  bcmgenet_rdma_ring_readl(priv, 0, DMA_MBUF_DONE_THRESH);
 ec->rx_coalesce_usecs =
  bcmgenet_rdma_readl(priv, DMA_RING0_TIMEOUT) * 8192 / 1000;

 for (i = 0; i <= priv->hw_params->rx_queues; i++) {
  ring = &priv->rx_rings[i];
  ec->use_adaptive_rx_coalesce |= ring->dim.use_dim;
 }

 return 0;
}

static void bcmgenet_set_rx_coalesce(struct bcmgenet_rx_ring *ring,
         u32 usecs, u32 pkts)
{
 struct bcmgenet_priv *priv = ring->priv;
 unsigned int i = ring->index;
 u32 reg;

 bcmgenet_rdma_ring_writel(priv, i, pkts, DMA_MBUF_DONE_THRESH);

 reg = bcmgenet_rdma_readl(priv, DMA_RING0_TIMEOUT + i);
 reg &= ~DMA_TIMEOUT_MASK;
 reg |= DIV_ROUND_UP(usecs * 1000, 8192);
 bcmgenet_rdma_writel(priv, reg, DMA_RING0_TIMEOUT + i);
}

static void bcmgenet_set_ring_rx_coalesce(struct bcmgenet_rx_ring *ring,
       struct ethtool_coalesce *ec)
{
 struct dim_cq_moder moder;
 u32 usecs, pkts;

 ring->rx_coalesce_usecs = ec->rx_coalesce_usecs;
 ring->rx_max_coalesced_frames = ec->rx_max_coalesced_frames;
 usecs = ring->rx_coalesce_usecs;
 pkts = ring->rx_max_coalesced_frames;

 if (ec->use_adaptive_rx_coalesce && !ring->dim.use_dim) {
  moder = net_dim_get_def_rx_moderation(ring->dim.dim.mode);
  usecs = moder.usec;
  pkts = moder.pkts;
 }

 ring->dim.use_dim = ec->use_adaptive_rx_coalesce;
 bcmgenet_set_rx_coalesce(ring, usecs, pkts);
}

static int bcmgenet_set_coalesce(struct net_device *dev,
     struct ethtool_coalesce *ec,
     struct kernel_ethtool_coalesce *kernel_coal,
     struct netlink_ext_ack *extack)
{
 struct bcmgenet_priv *priv = netdev_priv(dev);
 unsigned int i;

 /* Base system clock is 125Mhz, DMA timeout is this reference clock
 * divided by 1024, which yields roughly 8.192us, our maximum value
 * has to fit in the DMA_TIMEOUT_MASK (16 bits)
 */

 if (ec->tx_max_coalesced_frames > DMA_INTR_THRESHOLD_MASK ||
     ec->tx_max_coalesced_frames == 0 ||
     ec->rx_max_coalesced_frames > DMA_INTR_THRESHOLD_MASK ||
     ec->rx_coalesce_usecs > (DMA_TIMEOUT_MASK * 8) + 1)
  return -EINVAL;

 if (ec->rx_coalesce_usecs == 0 && ec->rx_max_coalesced_frames == 0)
  return -EINVAL;

 /* GENET TDMA hardware does not support a configurable timeout, but will
 * always generate an interrupt either after MBDONE packets have been
 * transmitted, or when the ring is empty.
 */


 /* Program all TX queues with the same values, as there is no
 * ethtool knob to do coalescing on a per-queue basis
 */

 for (i = 0; i <= priv->hw_params->tx_queues; i++)
  bcmgenet_tdma_ring_writel(priv, i,
       ec->tx_max_coalesced_frames,
       DMA_MBUF_DONE_THRESH);

 for (i = 0; i <= priv->hw_params->rx_queues; i++)
  bcmgenet_set_ring_rx_coalesce(&priv->rx_rings[i], ec);

 return 0;
}

static void bcmgenet_get_pauseparam(struct net_device *dev,
        struct ethtool_pauseparam *epause)
{
 struct bcmgenet_priv *priv;
 u32 umac_cmd;

 priv = netdev_priv(dev);

 epause->autoneg = priv->autoneg_pause;

 if (netif_carrier_ok(dev)) {
  /* report active state when link is up */
  umac_cmd = bcmgenet_umac_readl(priv, UMAC_CMD);
  epause->tx_pause = !(umac_cmd & CMD_TX_PAUSE_IGNORE);
  epause->rx_pause = !(umac_cmd & CMD_RX_PAUSE_IGNORE);
 } else {
  /* otherwise report stored settings */
  epause->tx_pause = priv->tx_pause;
  epause->rx_pause = priv->rx_pause;
 }
}

static int bcmgenet_set_pauseparam(struct net_device *dev,
       struct ethtool_pauseparam *epause)
{
 struct bcmgenet_priv *priv = netdev_priv(dev);

 if (!dev->phydev)
  return -ENODEV;

 if (!phy_validate_pause(dev->phydev, epause))
  return -EINVAL;

 priv->autoneg_pause = !!epause->autoneg;
 priv->tx_pause = !!epause->tx_pause;
 priv->rx_pause = !!epause->rx_pause;

 bcmgenet_phy_pause_set(dev, priv->rx_pause, priv->tx_pause);

 return 0;
}

/* standard ethtool support functions. */
enum bcmgenet_stat_type {
 BCMGENET_STAT_RTNL = -1,
 BCMGENET_STAT_MIB_RX,
 BCMGENET_STAT_MIB_TX,
 BCMGENET_STAT_RUNT,
 BCMGENET_STAT_MISC,
 BCMGENET_STAT_SOFT,
 BCMGENET_STAT_SOFT64,
};

struct bcmgenet_stats {
 char stat_string[ETH_GSTRING_LEN];
 int stat_sizeof;
 int stat_offset;
 enum bcmgenet_stat_type type;
 /* reg offset from UMAC base for misc counters */
 u16 reg_offset;
 /* sync for u64 stats counters */
 int syncp_offset;
};

#define STAT_RTNL(m) { \
 .stat_string = __stringify(m), \
 .stat_sizeof = sizeof(((struct rtnl_link_stats64 *)0)->m), \
 .stat_offset = offsetof(struct rtnl_link_stats64, m), \
 .type = BCMGENET_STAT_RTNL, \
}

#define STAT_GENET_MIB(str, m, _type) { \
 .stat_string = str, \
 .stat_sizeof = sizeof(((struct bcmgenet_priv *)0)->m), \
 .stat_offset = offsetof(struct bcmgenet_priv, m), \
 .type = _type, \
}

#define STAT_GENET_SOFT_MIB64(str, s, m) { \
 .stat_string = str, \
 .stat_sizeof = sizeof(((struct bcmgenet_priv *)0)->s.m), \
 .stat_offset = offsetof(struct bcmgenet_priv, s.m), \
 .type = BCMGENET_STAT_SOFT64, \
 .syncp_offset = offsetof(struct bcmgenet_priv, s.syncp), \
}

#define STAT_GENET_MIB_RX(str, m) STAT_GENET_MIB(str, m, BCMGENET_STAT_MIB_RX)
#define STAT_GENET_MIB_TX(str, m) STAT_GENET_MIB(str, m, BCMGENET_STAT_MIB_TX)
#define STAT_GENET_RUNT(str, m) STAT_GENET_MIB(str, m, BCMGENET_STAT_RUNT)
#define STAT_GENET_SOFT_MIB(str, m) STAT_GENET_MIB(str, m, BCMGENET_STAT_SOFT)

#define STAT_GENET_MISC(str, m, offset) { \
 .stat_string = str, \
 .stat_sizeof = sizeof(((struct bcmgenet_priv *)0)->m), \
 .stat_offset = offsetof(struct bcmgenet_priv, m), \
 .type = BCMGENET_STAT_MISC, \
 .reg_offset = offset, \
}

#define STAT_GENET_Q(num) \
 STAT_GENET_SOFT_MIB64("txq" __stringify(num) "_packets", \
   tx_rings[num].stats64, packets), \
 STAT_GENET_SOFT_MIB64("txq" __stringify(num) "_bytes", \
   tx_rings[num].stats64, bytes), \
 STAT_GENET_SOFT_MIB64("txq" __stringify(num) "_errors", \
   tx_rings[num].stats64, errors), \
 STAT_GENET_SOFT_MIB64("txq" __stringify(num) "_dropped", \
   tx_rings[num].stats64, dropped), \
 STAT_GENET_SOFT_MIB64("rxq" __stringify(num) "_bytes", \
   rx_rings[num].stats64, bytes),  \
 STAT_GENET_SOFT_MIB64("rxq" __stringify(num) "_packets", \
   rx_rings[num].stats64, packets), \
 STAT_GENET_SOFT_MIB64("rxq" __stringify(num) "_errors", \
   rx_rings[num].stats64, errors), \
 STAT_GENET_SOFT_MIB64("rxq" __stringify(num) "_dropped", \
   rx_rings[num].stats64, dropped), \
 STAT_GENET_SOFT_MIB64("rxq" __stringify(num) "_multicast", \
   rx_rings[num].stats64, multicast), \
 STAT_GENET_SOFT_MIB64("rxq" __stringify(num) "_missed", \
   rx_rings[num].stats64, missed), \
 STAT_GENET_SOFT_MIB64("rxq" __stringify(num) "_length_errors", \
   rx_rings[num].stats64, length_errors), \
 STAT_GENET_SOFT_MIB64("rxq" __stringify(num) "_over_errors", \
   rx_rings[num].stats64, over_errors), \
 STAT_GENET_SOFT_MIB64("rxq" __stringify(num) "_crc_errors", \
   rx_rings[num].stats64, crc_errors), \
 STAT_GENET_SOFT_MIB64("rxq" __stringify(num) "_frame_errors", \
   rx_rings[num].stats64, frame_errors), \
 STAT_GENET_SOFT_MIB64("rxq" __stringify(num) "_fragmented_errors", \
   rx_rings[num].stats64, fragmented_errors), \
 STAT_GENET_SOFT_MIB64("rxq" __stringify(num) "_broadcast", \
   rx_rings[num].stats64, broadcast)

/* There is a 0xC gap between the end of RX and beginning of TX stats and then
 * between the end of TX stats and the beginning of the RX RUNT
 */

#define BCMGENET_STAT_OFFSET 0xc

/* Hardware counters must be kept in sync because the order/offset
 * is important here (order in structure declaration = order in hardware)
 */

static const struct bcmgenet_stats bcmgenet_gstrings_stats[] = {
 /* general stats */
 STAT_RTNL(rx_packets),
 STAT_RTNL(tx_packets),
 STAT_RTNL(rx_bytes),
 STAT_RTNL(tx_bytes),
 STAT_RTNL(rx_errors),
 STAT_RTNL(tx_errors),
 STAT_RTNL(rx_dropped),
 STAT_RTNL(tx_dropped),
 STAT_RTNL(multicast),
 STAT_RTNL(rx_missed_errors),
 STAT_RTNL(rx_length_errors),
 STAT_RTNL(rx_over_errors),
 STAT_RTNL(rx_crc_errors),
 STAT_RTNL(rx_frame_errors),
 /* UniMAC RSV counters */
 STAT_GENET_MIB_RX("rx_64_octets", mib.rx.pkt_cnt.cnt_64),
 STAT_GENET_MIB_RX("rx_65_127_oct", mib.rx.pkt_cnt.cnt_127),
 STAT_GENET_MIB_RX("rx_128_255_oct", mib.rx.pkt_cnt.cnt_255),
 STAT_GENET_MIB_RX("rx_256_511_oct", mib.rx.pkt_cnt.cnt_511),
 STAT_GENET_MIB_RX("rx_512_1023_oct", mib.rx.pkt_cnt.cnt_1023),
 STAT_GENET_MIB_RX("rx_1024_1518_oct", mib.rx.pkt_cnt.cnt_1518),
 STAT_GENET_MIB_RX("rx_vlan_1519_1522_oct", mib.rx.pkt_cnt.cnt_mgv),
 STAT_GENET_MIB_RX("rx_1522_2047_oct", mib.rx.pkt_cnt.cnt_2047),
 STAT_GENET_MIB_RX("rx_2048_4095_oct", mib.rx.pkt_cnt.cnt_4095),
 STAT_GENET_MIB_RX("rx_4096_9216_oct", mib.rx.pkt_cnt.cnt_9216),
 STAT_GENET_MIB_RX("rx_pkts", mib.rx.pkt),
 STAT_GENET_MIB_RX("rx_bytes", mib.rx.bytes),
 STAT_GENET_MIB_RX("rx_multicast", mib.rx.mca),
 STAT_GENET_MIB_RX("rx_broadcast", mib.rx.bca),
 STAT_GENET_MIB_RX("rx_fcs", mib.rx.fcs),
 STAT_GENET_MIB_RX("rx_control", mib.rx.cf),
 STAT_GENET_MIB_RX("rx_pause", mib.rx.pf),
 STAT_GENET_MIB_RX("rx_unknown", mib.rx.uo),
 STAT_GENET_MIB_RX("rx_align", mib.rx.aln),
 STAT_GENET_MIB_RX("rx_outrange", mib.rx.flr),
 STAT_GENET_MIB_RX("rx_code", mib.rx.cde),
 STAT_GENET_MIB_RX("rx_carrier", mib.rx.fcr),
 STAT_GENET_MIB_RX("rx_oversize", mib.rx.ovr),
 STAT_GENET_MIB_RX("rx_jabber", mib.rx.jbr),
 STAT_GENET_MIB_RX("rx_mtu_err", mib.rx.mtue),
 STAT_GENET_MIB_RX("rx_good_pkts", mib.rx.pok),
 STAT_GENET_MIB_RX("rx_unicast", mib.rx.uc),
 STAT_GENET_MIB_RX("rx_ppp", mib.rx.ppp),
 STAT_GENET_MIB_RX("rx_crc", mib.rx.rcrc),
 /* UniMAC TSV counters */
 STAT_GENET_MIB_TX("tx_64_octets", mib.tx.pkt_cnt.cnt_64),
 STAT_GENET_MIB_TX("tx_65_127_oct", mib.tx.pkt_cnt.cnt_127),
 STAT_GENET_MIB_TX("tx_128_255_oct", mib.tx.pkt_cnt.cnt_255),
 STAT_GENET_MIB_TX("tx_256_511_oct", mib.tx.pkt_cnt.cnt_511),
 STAT_GENET_MIB_TX("tx_512_1023_oct", mib.tx.pkt_cnt.cnt_1023),
 STAT_GENET_MIB_TX("tx_1024_1518_oct", mib.tx.pkt_cnt.cnt_1518),
 STAT_GENET_MIB_TX("tx_vlan_1519_1522_oct", mib.tx.pkt_cnt.cnt_mgv),
 STAT_GENET_MIB_TX("tx_1522_2047_oct", mib.tx.pkt_cnt.cnt_2047),
 STAT_GENET_MIB_TX("tx_2048_4095_oct", mib.tx.pkt_cnt.cnt_4095),
 STAT_GENET_MIB_TX("tx_4096_9216_oct", mib.tx.pkt_cnt.cnt_9216),
 STAT_GENET_MIB_TX("tx_pkts", mib.tx.pkts),
 STAT_GENET_MIB_TX("tx_multicast", mib.tx.mca),
 STAT_GENET_MIB_TX("tx_broadcast", mib.tx.bca),
 STAT_GENET_MIB_TX("tx_pause", mib.tx.pf),
 STAT_GENET_MIB_TX("tx_control", mib.tx.cf),
 STAT_GENET_MIB_TX("tx_fcs_err", mib.tx.fcs),
 STAT_GENET_MIB_TX("tx_oversize", mib.tx.ovr),
 STAT_GENET_MIB_TX("tx_defer", mib.tx.drf),
 STAT_GENET_MIB_TX("tx_excess_defer", mib.tx.edf),
 STAT_GENET_MIB_TX("tx_single_col", mib.tx.scl),
 STAT_GENET_MIB_TX("tx_multi_col", mib.tx.mcl),
 STAT_GENET_MIB_TX("tx_late_col", mib.tx.lcl),
 STAT_GENET_MIB_TX("tx_excess_col", mib.tx.ecl),
 STAT_GENET_MIB_TX("tx_frags", mib.tx.frg),
 STAT_GENET_MIB_TX("tx_total_col", mib.tx.ncl),
 STAT_GENET_MIB_TX("tx_jabber", mib.tx.jbr),
 STAT_GENET_MIB_TX("tx_bytes", mib.tx.bytes),
 STAT_GENET_MIB_TX("tx_good_pkts", mib.tx.pok),
 STAT_GENET_MIB_TX("tx_unicast", mib.tx.uc),
 /* UniMAC RUNT counters */
 STAT_GENET_RUNT("rx_runt_pkts", mib.rx_runt_cnt),
 STAT_GENET_RUNT("rx_runt_valid_fcs", mib.rx_runt_fcs),
 STAT_GENET_RUNT("rx_runt_inval_fcs_align", mib.rx_runt_fcs_align),
 STAT_GENET_RUNT("rx_runt_bytes", mib.rx_runt_bytes),
 /* Misc UniMAC counters */
 STAT_GENET_MISC("rbuf_ovflow_cnt", mib.rbuf_ovflow_cnt,
   UMAC_RBUF_OVFL_CNT_V1),
 STAT_GENET_MISC("rbuf_err_cnt", mib.rbuf_err_cnt,
   UMAC_RBUF_ERR_CNT_V1),
 STAT_GENET_MISC("mdf_err_cnt", mib.mdf_err_cnt, UMAC_MDF_ERR_CNT),
 STAT_GENET_SOFT_MIB("alloc_rx_buff_failed", mib.alloc_rx_buff_failed),
 STAT_GENET_SOFT_MIB("rx_dma_failed", mib.rx_dma_failed),
 STAT_GENET_SOFT_MIB("tx_dma_failed", mib.tx_dma_failed),
 STAT_GENET_SOFT_MIB("tx_realloc_tsb", mib.tx_realloc_tsb),
 STAT_GENET_SOFT_MIB("tx_realloc_tsb_failed",
       mib.tx_realloc_tsb_failed),
 /* Per TX queues */
 STAT_GENET_Q(0),
 STAT_GENET_Q(1),
 STAT_GENET_Q(2),
 STAT_GENET_Q(3),
 STAT_GENET_Q(4),
};

#define BCMGENET_STATS_LEN ARRAY_SIZE(bcmgenet_gstrings_stats)

#define BCMGENET_STATS64_ADD(stats, m, v) \
 do { \
  u64_stats_update_begin(&stats->syncp); \
  u64_stats_add(&stats->m, v); \
  u64_stats_update_end(&stats->syncp); \
 } while (0)

#define BCMGENET_STATS64_INC(stats, m) \
 do { \
  u64_stats_update_begin(&stats->syncp); \
  u64_stats_inc(&stats->m); \
  u64_stats_update_end(&stats->syncp); \
 } while (0)

static void bcmgenet_get_drvinfo(struct net_device *dev,
     struct ethtool_drvinfo *info)
{
 strscpy(info->driver, "bcmgenet"sizeof(info->driver));
}

static int bcmgenet_get_sset_count(struct net_device *dev, int string_set)
{
 switch (string_set) {
 case ETH_SS_STATS:
  return BCMGENET_STATS_LEN;
 default:
  return -EOPNOTSUPP;
 }
}

static void bcmgenet_get_strings(struct net_device *dev, u32 stringset,
     u8 *data)
{
 const char *str;
 int i;

 switch (stringset) {
 case ETH_SS_STATS:
  for (i = 0; i < BCMGENET_STATS_LEN; i++) {
   str = bcmgenet_gstrings_stats[i].stat_string;
   ethtool_puts(&data, str);
  }
  break;
 }
}

static u32 bcmgenet_update_stat_misc(struct bcmgenet_priv *priv, u16 offset)
{
 u16 new_offset;
 u32 val;

 switch (offset) {
 case UMAC_RBUF_OVFL_CNT_V1:
  if (GENET_IS_V2(priv))
   new_offset = RBUF_OVFL_CNT_V2;
  else
   new_offset = RBUF_OVFL_CNT_V3PLUS;

  val = bcmgenet_rbuf_readl(priv, new_offset);
  /* clear if overflowed */
  if (val == ~0)
   bcmgenet_rbuf_writel(priv, 0, new_offset);
  break;
 case UMAC_RBUF_ERR_CNT_V1:
  if (GENET_IS_V2(priv))
   new_offset = RBUF_ERR_CNT_V2;
  else
   new_offset = RBUF_ERR_CNT_V3PLUS;

  val = bcmgenet_rbuf_readl(priv, new_offset);
  /* clear if overflowed */
  if (val == ~0)
   bcmgenet_rbuf_writel(priv, 0, new_offset);
  break;
 default:
  val = bcmgenet_umac_readl(priv, offset);
  /* clear if overflowed */
  if (val == ~0)
   bcmgenet_umac_writel(priv, 0, offset);
  break;
 }

 return val;
}

static void bcmgenet_update_mib_counters(struct bcmgenet_priv *priv)
{
 int i, j = 0;

 for (i = 0; i < BCMGENET_STATS_LEN; i++) {
  const struct bcmgenet_stats *s;
  u8 offset = 0;
  u32 val = 0;
  char *p;

  s = &bcmgenet_gstrings_stats[i];
  switch (s->type) {
  case BCMGENET_STAT_RTNL:
  case BCMGENET_STAT_SOFT:
  case BCMGENET_STAT_SOFT64:
   continue;
  case BCMGENET_STAT_RUNT:
   offset += BCMGENET_STAT_OFFSET;
   fallthrough;
  case BCMGENET_STAT_MIB_TX:
   offset += BCMGENET_STAT_OFFSET;
   fallthrough;
  case BCMGENET_STAT_MIB_RX:
   val = bcmgenet_umac_readl(priv,
        UMAC_MIB_START + j + offset);
   offset = 0; /* Reset Offset */
   break;
  case BCMGENET_STAT_MISC:
   if (GENET_IS_V1(priv)) {
    val = bcmgenet_umac_readl(priv, s->reg_offset);
    /* clear if overflowed */
    if (val == ~0)
     bcmgenet_umac_writel(priv, 0,
            s->reg_offset);
   } else {
    val = bcmgenet_update_stat_misc(priv,
        s->reg_offset);
   }
   break;
  }

  j += s->stat_sizeof;
  p = (char *)priv + s->stat_offset;
  *(u32 *)p = val;
 }
}

static void bcmgenet_get_ethtool_stats(struct net_device *dev,
           struct ethtool_stats *stats,
           u64 *data)
{
 struct bcmgenet_priv *priv = netdev_priv(dev);
 struct rtnl_link_stats64 stats64;
 struct u64_stats_sync *syncp;
 unsigned int start;
 int i;

 if (netif_running(dev))
  bcmgenet_update_mib_counters(priv);

 dev_get_stats(dev, &stats64);

 for (i = 0; i < BCMGENET_STATS_LEN; i++) {
  const struct bcmgenet_stats *s;
  char *p;

  s = &bcmgenet_gstrings_stats[i];
  p = (char *)priv;

  if (s->type == BCMGENET_STAT_SOFT64) {
   syncp = (struct u64_stats_sync *)(p + s->syncp_offset);
   do {
    start = u64_stats_fetch_begin(syncp);
    data[i] = u64_stats_read((u64_stats_t *)(p + s->stat_offset));
   } while (u64_stats_fetch_retry(syncp, start));
  } else {
   if (s->type == BCMGENET_STAT_RTNL)
    p = (char *)&stats64;

   p += s->stat_offset;
   if (sizeof(unsigned long) != sizeof(u32) &&
    s->stat_sizeof == sizeof(unsigned long))
    data[i] = *(unsigned long *)p;
   else
    data[i] = *(u32 *)p;
  }
 }
}

void bcmgenet_eee_enable_set(struct net_device *dev, bool enable,
        bool tx_lpi_enabled)
{
 struct bcmgenet_priv *priv = netdev_priv(dev);
 u32 off = priv->hw_params->tbuf_offset + TBUF_ENERGY_CTRL;
 u32 reg;

 if (enable && !priv->clk_eee_enabled) {
  clk_prepare_enable(priv->clk_eee);
  priv->clk_eee_enabled = true;
 }

 reg = bcmgenet_umac_readl(priv, UMAC_EEE_CTRL);
 if (enable)
  reg |= EEE_EN;
 else
  reg &= ~EEE_EN;
 bcmgenet_umac_writel(priv, reg, UMAC_EEE_CTRL);

 /* Enable EEE and switch to a 27Mhz clock automatically */
 reg = bcmgenet_readl(priv->base + off);
 if (tx_lpi_enabled)
  reg |= TBUF_EEE_EN | TBUF_PM_EN;
 else
  reg &= ~(TBUF_EEE_EN | TBUF_PM_EN);
 bcmgenet_writel(reg, priv->base + off);

 /* Do the same for thing for RBUF */
 reg = bcmgenet_rbuf_readl(priv, RBUF_ENERGY_CTRL);
 if (enable)
  reg |= RBUF_EEE_EN | RBUF_PM_EN;
 else
  reg &= ~(RBUF_EEE_EN | RBUF_PM_EN);
 bcmgenet_rbuf_writel(priv, reg, RBUF_ENERGY_CTRL);

 if (!enable && priv->clk_eee_enabled) {
  clk_disable_unprepare(priv->clk_eee);
  priv->clk_eee_enabled = false;
 }

 priv->eee.eee_enabled = enable;
 priv->eee.tx_lpi_enabled = tx_lpi_enabled;
}

static int bcmgenet_get_eee(struct net_device *dev, struct ethtool_keee *e)
{
 struct bcmgenet_priv *priv = netdev_priv(dev);
 struct ethtool_keee *p = &priv->eee;

 if (GENET_IS_V1(priv))
  return -EOPNOTSUPP;

 if (!dev->phydev)
  return -ENODEV;

 e->tx_lpi_enabled = p->tx_lpi_enabled;
 e->tx_lpi_timer = bcmgenet_umac_readl(priv, UMAC_EEE_LPI_TIMER);

 return phy_ethtool_get_eee(dev->phydev, e);
}

static int bcmgenet_set_eee(struct net_device *dev, struct ethtool_keee *e)
{
 struct bcmgenet_priv *priv = netdev_priv(dev);
 struct ethtool_keee *p = &priv->eee;
 bool active;

 if (GENET_IS_V1(priv))
  return -EOPNOTSUPP;

 if (!dev->phydev)
  return -ENODEV;

 p->eee_enabled = e->eee_enabled;

 if (!p->eee_enabled) {
  bcmgenet_eee_enable_set(dev, falsefalse);
 } else {
  active = phy_init_eee(dev->phydev, false) >= 0;
  bcmgenet_umac_writel(priv, e->tx_lpi_timer, UMAC_EEE_LPI_TIMER);
  bcmgenet_eee_enable_set(dev, active, e->tx_lpi_enabled);
 }

 return phy_ethtool_set_eee(dev->phydev, e);
}

static int bcmgenet_validate_flow(struct net_device *dev,
      struct ethtool_rxnfc *cmd)
{
 struct ethtool_usrip4_spec *l4_mask;
 struct ethhdr *eth_mask;

 if (cmd->fs.location >= MAX_NUM_OF_FS_RULES &&
     cmd->fs.location != RX_CLS_LOC_ANY) {
  netdev_err(dev, "rxnfc: Invalid location (%d)\n",
      cmd->fs.location);
  return -EINVAL;
 }

 switch (cmd->fs.flow_type & ~(FLOW_EXT | FLOW_MAC_EXT)) {
 case IP_USER_FLOW:
  l4_mask = &cmd->fs.m_u.usr_ip4_spec;
  /* don't allow mask which isn't valid */
  if (VALIDATE_MASK(l4_mask->ip4src) ||
      VALIDATE_MASK(l4_mask->ip4dst) ||
      VALIDATE_MASK(l4_mask->l4_4_bytes) ||
      VALIDATE_MASK(l4_mask->proto) ||
      VALIDATE_MASK(l4_mask->ip_ver) ||
      VALIDATE_MASK(l4_mask->tos)) {
   netdev_err(dev, "rxnfc: Unsupported mask\n");
   return -EINVAL;
  }
  break;
 case ETHER_FLOW:
  eth_mask = &cmd->fs.m_u.ether_spec;
  /* don't allow mask which isn't valid */
  if (VALIDATE_MASK(eth_mask->h_dest) ||
      VALIDATE_MASK(eth_mask->h_source) ||
      VALIDATE_MASK(eth_mask->h_proto)) {
   netdev_err(dev, "rxnfc: Unsupported mask\n");
   return -EINVAL;
  }
  break;
 default:
  netdev_err(dev, "rxnfc: Unsupported flow type (0x%x)\n",
      cmd->fs.flow_type);
  return -EINVAL;
 }

 if ((cmd->fs.flow_type & FLOW_EXT)) {
  /* don't allow mask which isn't valid */
  if (VALIDATE_MASK(cmd->fs.m_ext.vlan_etype) ||
      VALIDATE_MASK(cmd->fs.m_ext.vlan_tci)) {
   netdev_err(dev, "rxnfc: Unsupported mask\n");
   return -EINVAL;
  }
  if (cmd->fs.m_ext.data[0] || cmd->fs.m_ext.data[1]) {
   netdev_err(dev, "rxnfc: user-def not supported\n");
   return -EINVAL;
  }
 }

 if ((cmd->fs.flow_type & FLOW_MAC_EXT)) {
  /* don't allow mask which isn't valid */
  if (VALIDATE_MASK(cmd->fs.m_ext.h_dest)) {
   netdev_err(dev, "rxnfc: Unsupported mask\n");
   return -EINVAL;
  }
 }

 return 0;
}

static int bcmgenet_insert_flow(struct net_device *dev,
    struct ethtool_rxnfc *cmd)
{
 struct bcmgenet_priv *priv = netdev_priv(dev);
 struct bcmgenet_rxnfc_rule *loc_rule;
 int err, i;

 if (priv->hw_params->hfb_filter_size < 128) {
  netdev_err(dev, "rxnfc: Not supported by this device\n");
  return -EINVAL;
 }

 if (cmd->fs.ring_cookie > priv->hw_params->rx_queues &&
     cmd->fs.ring_cookie != RX_CLS_FLOW_WAKE &&
     cmd->fs.ring_cookie != RX_CLS_FLOW_DISC) {
  netdev_err(dev, "rxnfc: Unsupported action (%llu)\n",
      cmd->fs.ring_cookie);
  return -EINVAL;
 }

 err = bcmgenet_validate_flow(dev, cmd);
 if (err)
  return err;

 if (cmd->fs.location == RX_CLS_LOC_ANY) {
  list_for_each_entry(loc_rule, &priv->rxnfc_list, list) {
   cmd->fs.location = loc_rule->fs.location;
   err = memcmp(&loc_rule->fs, &cmd->fs,
         sizeof(struct ethtool_rx_flow_spec));
   if (!err)
    /* rule exists so return current location */
    return 0;
  }
  for (i = 0; i < MAX_NUM_OF_FS_RULES; i++) {
   loc_rule = &priv->rxnfc_rules[i];
   if (loc_rule->state == BCMGENET_RXNFC_STATE_UNUSED) {
    cmd->fs.location = i;
    break;
   }
  }
  if (i == MAX_NUM_OF_FS_RULES) {
   cmd->fs.location = RX_CLS_LOC_ANY;
   return -ENOSPC;
  }
 } else {
  loc_rule = &priv->rxnfc_rules[cmd->fs.location];
 }
 if (loc_rule->state == BCMGENET_RXNFC_STATE_ENABLED)
  bcmgenet_hfb_disable_filter(priv, cmd->fs.location + 1);
 if (loc_rule->state != BCMGENET_RXNFC_STATE_UNUSED) {
  list_del(&loc_rule->list);
  bcmgenet_hfb_clear_filter(priv, cmd->fs.location + 1);
 }
 loc_rule->state = BCMGENET_RXNFC_STATE_UNUSED;
 memcpy(&loc_rule->fs, &cmd->fs,
        sizeof(struct ethtool_rx_flow_spec));

 bcmgenet_hfb_create_rxnfc_filter(priv, loc_rule);

 list_add_tail(&loc_rule->list, &priv->rxnfc_list);

 return 0;
}

static int bcmgenet_delete_flow(struct net_device *dev,
    struct ethtool_rxnfc *cmd)
{
 struct bcmgenet_priv *priv = netdev_priv(dev);
 struct bcmgenet_rxnfc_rule *rule;
 int err = 0;

 if (cmd->fs.location >= MAX_NUM_OF_FS_RULES)
  return -EINVAL;

 rule = &priv->rxnfc_rules[cmd->fs.location];
 if (rule->state == BCMGENET_RXNFC_STATE_UNUSED) {
  err =  -ENOENT;
  goto out;
 }

 if (rule->state == BCMGENET_RXNFC_STATE_ENABLED)
  bcmgenet_hfb_disable_filter(priv, cmd->fs.location + 1);
 if (rule->state != BCMGENET_RXNFC_STATE_UNUSED) {
  list_del(&rule->list);
  bcmgenet_hfb_clear_filter(priv, cmd->fs.location + 1);
 }
 rule->state = BCMGENET_RXNFC_STATE_UNUSED;
 memset(&rule->fs, 0, sizeof(struct ethtool_rx_flow_spec));

out:
 return err;
}

static int bcmgenet_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd)
{
 struct bcmgenet_priv *priv = netdev_priv(dev);
 int err = 0;

 switch (cmd->cmd) {
 case ETHTOOL_SRXCLSRLINS:
  err = bcmgenet_insert_flow(dev, cmd);
  break;
 case ETHTOOL_SRXCLSRLDEL:
  err = bcmgenet_delete_flow(dev, cmd);
  break;
 default:
  netdev_warn(priv->dev, "Unsupported ethtool command. (%d)\n",
       cmd->cmd);
  return -EINVAL;
 }

 return err;
}

static int bcmgenet_get_flow(struct net_device *dev, struct ethtool_rxnfc *cmd,
        int loc)
{
 struct bcmgenet_priv *priv = netdev_priv(dev);
 struct bcmgenet_rxnfc_rule *rule;
 int err = 0;

 if (loc < 0 || loc >= MAX_NUM_OF_FS_RULES)
  return -EINVAL;

 rule = &priv->rxnfc_rules[loc];
 if (rule->state == BCMGENET_RXNFC_STATE_UNUSED)
  err = -ENOENT;
 else
  memcpy(&cmd->fs, &rule->fs,
         sizeof(struct ethtool_rx_flow_spec));

 return err;
}

static int bcmgenet_get_num_flows(struct bcmgenet_priv *priv)
{
 struct list_head *pos;
 int res = 0;

 list_for_each(pos, &priv->rxnfc_list)
  res++;

 return res;
}

static int bcmgenet_get_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd,
         u32 *rule_locs)
{
 struct bcmgenet_priv *priv = netdev_priv(dev);
 struct bcmgenet_rxnfc_rule *rule;
 int err = 0;
 int i = 0;

 switch (cmd->cmd) {
 case ETHTOOL_GRXRINGS:
  cmd->data = priv->hw_params->rx_queues ?: 1;
  break;
 case ETHTOOL_GRXCLSRLCNT:
  cmd->rule_cnt = bcmgenet_get_num_flows(priv);
  cmd->data = MAX_NUM_OF_FS_RULES | RX_CLS_LOC_SPECIAL;
  break;
 case ETHTOOL_GRXCLSRULE:
  err = bcmgenet_get_flow(dev, cmd, cmd->fs.location);
  break;
 case ETHTOOL_GRXCLSRLALL:
  list_for_each_entry(rule, &priv->rxnfc_list, list)
   if (i < cmd->rule_cnt)
    rule_locs[i++] = rule->fs.location;
  cmd->rule_cnt = i;
  cmd->data = MAX_NUM_OF_FS_RULES;
  break;
 default:
  err = -EOPNOTSUPP;
  break;
 }

 return err;
}

/* standard ethtool support functions. */
static const struct ethtool_ops bcmgenet_ethtool_ops = {
 .supported_coalesce_params = ETHTOOL_COALESCE_RX_USECS |
         ETHTOOL_COALESCE_MAX_FRAMES |
         ETHTOOL_COALESCE_USE_ADAPTIVE_RX,
 .begin   = bcmgenet_begin,
 .complete  = bcmgenet_complete,
 .get_strings  = bcmgenet_get_strings,
 .get_sset_count  = bcmgenet_get_sset_count,
 .get_ethtool_stats = bcmgenet_get_ethtool_stats,
 .get_drvinfo  = bcmgenet_get_drvinfo,
 .get_link  = ethtool_op_get_link,
 .get_msglevel  = bcmgenet_get_msglevel,
 .set_msglevel  = bcmgenet_set_msglevel,
 .get_wol  = bcmgenet_get_wol,
 .set_wol  = bcmgenet_set_wol,
 .get_eee  = bcmgenet_get_eee,
 .set_eee  = bcmgenet_set_eee,
 .nway_reset  = phy_ethtool_nway_reset,
 .get_coalesce  = bcmgenet_get_coalesce,
 .set_coalesce  = bcmgenet_set_coalesce,
 .get_link_ksettings = bcmgenet_get_link_ksettings,
 .set_link_ksettings = bcmgenet_set_link_ksettings,
 .get_ts_info  = ethtool_op_get_ts_info,
 .get_rxnfc  = bcmgenet_get_rxnfc,
 .set_rxnfc  = bcmgenet_set_rxnfc,
 .get_pauseparam  = bcmgenet_get_pauseparam,
 .set_pauseparam  = bcmgenet_set_pauseparam,
};

/* Power down the unimac, based on mode. */
static int bcmgenet_power_down(struct bcmgenet_priv *priv,
    enum bcmgenet_power_mode mode)
{
 int ret = 0;
 u32 reg;

 switch (mode) {
 case GENET_POWER_CABLE_SENSE:
  phy_detach(priv->dev->phydev);
  break;

 case GENET_POWER_WOL_MAGIC:
  ret = bcmgenet_wol_power_down_cfg(priv, mode);
  break;

 case GENET_POWER_PASSIVE:
  /* Power down LED */
  if (bcmgenet_has_ext(priv)) {
   reg = bcmgenet_ext_readl(priv, EXT_EXT_PWR_MGMT);
   if (GENET_IS_V5(priv) && !bcmgenet_has_ephy_16nm(priv))
    reg |= EXT_PWR_DOWN_PHY_EN |
           EXT_PWR_DOWN_PHY_RD |
           EXT_PWR_DOWN_PHY_SD |
           EXT_PWR_DOWN_PHY_RX |
           EXT_PWR_DOWN_PHY_TX |
           EXT_IDDQ_GLBL_PWR;
   else
    reg |= EXT_PWR_DOWN_PHY;

   reg |= (EXT_PWR_DOWN_DLL | EXT_PWR_DOWN_BIAS);
   bcmgenet_ext_writel(priv, reg, EXT_EXT_PWR_MGMT);

   bcmgenet_phy_power_set(priv->dev, false);
  }
  break;
 default:
  break;
 }

 return ret;
}

static int bcmgenet_power_up(struct bcmgenet_priv *priv,
        enum bcmgenet_power_mode mode)
{
 int ret = 0;
 u32 reg;

 if (!bcmgenet_has_ext(priv))
  return ret;

 reg = bcmgenet_ext_readl(priv, EXT_EXT_PWR_MGMT);

 switch (mode) {
 case GENET_POWER_PASSIVE:
  reg &= ~(EXT_PWR_DOWN_DLL | EXT_PWR_DOWN_BIAS |
    EXT_ENERGY_DET_MASK);
  if (GENET_IS_V5(priv) && !bcmgenet_has_ephy_16nm(priv)) {
   reg &= ~(EXT_PWR_DOWN_PHY_EN |
     EXT_PWR_DOWN_PHY_RD |
     EXT_PWR_DOWN_PHY_SD |
     EXT_PWR_DOWN_PHY_RX |
     EXT_PWR_DOWN_PHY_TX |
     EXT_IDDQ_GLBL_PWR);
   reg |=   EXT_PHY_RESET;
   bcmgenet_ext_writel(priv, reg, EXT_EXT_PWR_MGMT);
   mdelay(1);

   reg &=  ~EXT_PHY_RESET;
  } else {
   reg &= ~EXT_PWR_DOWN_PHY;
   reg |= EXT_PWR_DN_EN_LD;
  }
  bcmgenet_ext_writel(priv, reg, EXT_EXT_PWR_MGMT);
  bcmgenet_phy_power_set(priv->dev, true);
  break;

 case GENET_POWER_CABLE_SENSE:
  /* enable APD */
  if (!GENET_IS_V5(priv)) {
   reg |= EXT_PWR_DN_EN_LD;
   bcmgenet_ext_writel(priv, reg, EXT_EXT_PWR_MGMT);
  }
  break;
 case GENET_POWER_WOL_MAGIC:
  ret = bcmgenet_wol_power_up_cfg(priv, mode);
  break;
 default:
  break;
 }

 return ret;
}

static struct enet_cb *bcmgenet_get_txcb(struct bcmgenet_priv *priv,
      struct bcmgenet_tx_ring *ring)
{
 struct enet_cb *tx_cb_ptr;

 tx_cb_ptr = ring->cbs;
 tx_cb_ptr += ring->write_ptr - ring->cb_ptr;

 /* Advancing local write pointer */
 if (ring->write_ptr == ring->end_ptr)
  ring->write_ptr = ring->cb_ptr;
 else
  ring->write_ptr++;

 return tx_cb_ptr;
}

static struct enet_cb *bcmgenet_put_txcb(struct bcmgenet_priv *priv,
      struct bcmgenet_tx_ring *ring)
{
 struct enet_cb *tx_cb_ptr;

 tx_cb_ptr = ring->cbs;
 tx_cb_ptr += ring->write_ptr - ring->cb_ptr;

 /* Rewinding local write pointer */
 if (ring->write_ptr == ring->cb_ptr)
  ring->write_ptr = ring->end_ptr;
 else
  ring->write_ptr--;

 return tx_cb_ptr;
}

static inline void bcmgenet_rx_ring_int_disable(struct bcmgenet_rx_ring *ring)
{
 bcmgenet_intrl2_1_writel(ring->priv,
     1 << (UMAC_IRQ1_RX_INTR_SHIFT + ring->index),
     INTRL2_CPU_MASK_SET);
}

static inline void bcmgenet_rx_ring_int_enable(struct bcmgenet_rx_ring *ring)
{
 bcmgenet_intrl2_1_writel(ring->priv,
     1 << (UMAC_IRQ1_RX_INTR_SHIFT + ring->index),
     INTRL2_CPU_MASK_CLEAR);
}

static inline void bcmgenet_tx_ring_int_enable(struct bcmgenet_tx_ring *ring)
{
 bcmgenet_intrl2_1_writel(ring->priv, 1 << ring->index,
     INTRL2_CPU_MASK_CLEAR);
}

static inline void bcmgenet_tx_ring_int_disable(struct bcmgenet_tx_ring *ring)
{
 bcmgenet_intrl2_1_writel(ring->priv, 1 << ring->index,
     INTRL2_CPU_MASK_SET);
}

/* Simple helper to free a transmit control block's resources
 * Returns an skb when the last transmit control block associated with the
 * skb is freed.  The skb should be freed by the caller if necessary.
 */

static struct sk_buff *bcmgenet_free_tx_cb(struct device *dev,
        struct enet_cb *cb)
{
 struct sk_buff *skb;

 skb = cb->skb;

 if (skb) {
  cb->skb = NULL;
  if (cb == GENET_CB(skb)->first_cb)
   dma_unmap_single(dev, dma_unmap_addr(cb, dma_addr),
      dma_unmap_len(cb, dma_len),
      DMA_TO_DEVICE);
  else
   dma_unmap_page(dev, dma_unmap_addr(cb, dma_addr),
           dma_unmap_len(cb, dma_len),
           DMA_TO_DEVICE);
  dma_unmap_addr_set(cb, dma_addr, 0);

  if (cb == GENET_CB(skb)->last_cb)
   return skb;

 } else if (dma_unmap_addr(cb, dma_addr)) {
  dma_unmap_page(dev,
          dma_unmap_addr(cb, dma_addr),
          dma_unmap_len(cb, dma_len),
          DMA_TO_DEVICE);
  dma_unmap_addr_set(cb, dma_addr, 0);
 }

 return NULL;
}

/* Simple helper to free a receive control block's resources */
static struct sk_buff *bcmgenet_free_rx_cb(struct device *dev,
        struct enet_cb *cb)
{
 struct sk_buff *skb;

 skb = cb->skb;
 cb->skb = NULL;

 if (dma_unmap_addr(cb, dma_addr)) {
  dma_unmap_single(dev, dma_unmap_addr(cb, dma_addr),
     dma_unmap_len(cb, dma_len), DMA_FROM_DEVICE);
  dma_unmap_addr_set(cb, dma_addr, 0);
 }

 return skb;
}

/* Unlocked version of the reclaim routine */
static unsigned int __bcmgenet_tx_reclaim(struct net_device *dev,
       struct bcmgenet_tx_ring *ring)
{
 struct bcmgenet_tx_stats64 *stats = &ring->stats64;
 struct bcmgenet_priv *priv = netdev_priv(dev);
 unsigned int txbds_processed = 0;
 unsigned int bytes_compl = 0;
 unsigned int pkts_compl = 0;
 unsigned int txbds_ready;
 unsigned int c_index;
 struct sk_buff *skb;

 /* Clear status before servicing to reduce spurious interrupts */
 bcmgenet_intrl2_1_writel(priv, (1 << ring->index), INTRL2_CPU_CLEAR);

 /* Compute how many buffers are transmitted since last xmit call */
 c_index = bcmgenet_tdma_ring_readl(priv, ring->index, TDMA_CONS_INDEX)
  & DMA_C_INDEX_MASK;
 txbds_ready = (c_index - ring->c_index) & DMA_C_INDEX_MASK;

 netif_dbg(priv, tx_done, dev,
    "%s ring=%d old_c_index=%u c_index=%u txbds_ready=%u\n",
    __func__, ring->index, ring->c_index, c_index, txbds_ready);

 /* Reclaim transmitted buffers */
 while (txbds_processed < txbds_ready) {
  skb = bcmgenet_free_tx_cb(&priv->pdev->dev,
       &priv->tx_cbs[ring->clean_ptr]);
  if (skb) {
   pkts_compl++;
   bytes_compl += GENET_CB(skb)->bytes_sent;
   dev_consume_skb_any(skb);
  }

  txbds_processed++;
  if (likely(ring->clean_ptr < ring->end_ptr))
   ring->clean_ptr++;
  else
   ring->clean_ptr = ring->cb_ptr;
 }

 ring->free_bds += txbds_processed;
 ring->c_index = c_index;

 u64_stats_update_begin(&stats->syncp);
 u64_stats_add(&stats->packets, pkts_compl);
 u64_stats_add(&stats->bytes, bytes_compl);
 u64_stats_update_end(&stats->syncp);

 netdev_tx_completed_queue(netdev_get_tx_queue(dev, ring->index),
      pkts_compl, bytes_compl);

 return txbds_processed;
}

static unsigned int bcmgenet_tx_reclaim(struct net_device *dev,
    struct bcmgenet_tx_ring *ring,
    bool all)
{
 struct bcmgenet_priv *priv = netdev_priv(dev);
 struct device *kdev = &priv->pdev->dev;
 unsigned int released, drop, wr_ptr;
 struct enet_cb *cb_ptr;
 struct sk_buff *skb;

 spin_lock_bh(&ring->lock);
 released = __bcmgenet_tx_reclaim(dev, ring);
 if (all) {
  skb = NULL;
  drop = (ring->prod_index - ring->c_index) & DMA_C_INDEX_MASK;
  released += drop;
  ring->prod_index = ring->c_index & DMA_C_INDEX_MASK;
  while (drop--) {
   cb_ptr = bcmgenet_put_txcb(priv, ring);
   skb = cb_ptr->skb;
   bcmgenet_free_tx_cb(kdev, cb_ptr);
   if (skb && cb_ptr == GENET_CB(skb)->first_cb) {
    dev_consume_skb_any(skb);
    skb = NULL;
   }
  }
  if (skb)
   dev_consume_skb_any(skb);
  bcmgenet_tdma_ring_writel(priv, ring->index,
       ring->prod_index, TDMA_PROD_INDEX);
  wr_ptr = ring->write_ptr * WORDS_PER_BD(priv);
  bcmgenet_tdma_ring_writel(priv, ring->index, wr_ptr,
       TDMA_WRITE_PTR);
 }
 spin_unlock_bh(&ring->lock);

 return released;
}

static int bcmgenet_tx_poll(struct napi_struct *napi, int budget)
{
 struct bcmgenet_tx_ring *ring =
  container_of(napi, struct bcmgenet_tx_ring, napi);
 unsigned int work_done = 0;
 struct netdev_queue *txq;

 spin_lock(&ring->lock);
 work_done = __bcmgenet_tx_reclaim(ring->priv->dev, ring);
 if (ring->free_bds > (MAX_SKB_FRAGS + 1)) {
  txq = netdev_get_tx_queue(ring->priv->dev, ring->index);
  netif_tx_wake_queue(txq);
 }
 spin_unlock(&ring->lock);

 if (work_done == 0) {
  napi_complete(napi);
  bcmgenet_tx_ring_int_enable(ring);

  return 0;
 }

 return budget;
}

static void bcmgenet_tx_reclaim_all(struct net_device *dev)
{
 struct bcmgenet_priv *priv = netdev_priv(dev);
 int i = 0;

 do {
  bcmgenet_tx_reclaim(dev, &priv->tx_rings[i++], true);
 } while (i <= priv->hw_params->tx_queues && netif_is_multiqueue(dev));
}

/* Reallocate the SKB to put enough headroom in front of it and insert
 * the transmit checksum offsets in the descriptors
 */

static struct sk_buff *bcmgenet_add_tsb(struct net_device *dev,
     struct sk_buff *skb,
     struct bcmgenet_tx_ring *ring)
{
 struct bcmgenet_tx_stats64 *stats = &ring->stats64;
 struct bcmgenet_priv *priv = netdev_priv(dev);
 struct status_64 *status = NULL;
 struct sk_buff *new_skb;
 u16 offset;
 u8 ip_proto;
 __be16 ip_ver;
 u32 tx_csum_info;

 if (unlikely(skb_headroom(skb) < sizeof(*status))) {
  /* If 64 byte status block enabled, must make sure skb has
 * enough headroom for us to insert 64B status block.
 */

  new_skb = skb_realloc_headroom(skb, sizeof(*status));
  if (!new_skb) {
   dev_kfree_skb_any(skb);
   priv->mib.tx_realloc_tsb_failed++;
   BCMGENET_STATS64_INC(stats, dropped);
   return NULL;
  }
  dev_consume_skb_any(skb);
  skb = new_skb;
  priv->mib.tx_realloc_tsb++;
 }

 skb_push(skb, sizeof(*status));
 status = (struct status_64 *)skb->data;

 if (skb->ip_summed  == CHECKSUM_PARTIAL) {
  ip_ver = skb->protocol;
  switch (ip_ver) {
  case htons(ETH_P_IP):
   ip_proto = ip_hdr(skb)->protocol;
   break;
  case htons(ETH_P_IPV6):
   ip_proto = ipv6_hdr(skb)->nexthdr;
   break;
  default:
   /* don't use UDP flag */
   ip_proto = 0;
   break;
  }

  offset = skb_checksum_start_offset(skb) - sizeof(*status);
  tx_csum_info = (offset << STATUS_TX_CSUM_START_SHIFT) |
    (offset + skb->csum_offset) |
    STATUS_TX_CSUM_LV;

  /* Set the special UDP flag for UDP */
  if (ip_proto == IPPROTO_UDP)
   tx_csum_info |= STATUS_TX_CSUM_PROTO_UDP;

  status->tx_csum_info = tx_csum_info;
 }

 return skb;
}

static void bcmgenet_hide_tsb(struct sk_buff *skb)
{
 __skb_pull(skb, sizeof(struct status_64));
}

static netdev_tx_t bcmgenet_xmit(struct sk_buff *skb, struct net_device *dev)
{
 struct bcmgenet_priv *priv = netdev_priv(dev);
 struct device *kdev = &priv->pdev->dev;
 struct bcmgenet_tx_ring *ring = NULL;
 struct enet_cb *tx_cb_ptr;
 struct netdev_queue *txq;
 int nr_frags, index;
 dma_addr_t mapping;
 unsigned int size;
 skb_frag_t *frag;
 u32 len_stat;
 int ret;
 int i;

 index = skb_get_queue_mapping(skb);
 /* Mapping strategy:
 * queue_mapping = 0, unclassified, packet xmited through ring 0
 * queue_mapping = 1, goes to ring 1. (highest priority queue)
 * queue_mapping = 2, goes to ring 2.
 * queue_mapping = 3, goes to ring 3.
 * queue_mapping = 4, goes to ring 4.
 */

 ring = &priv->tx_rings[index];
 txq = netdev_get_tx_queue(dev, index);

 nr_frags = skb_shinfo(skb)->nr_frags;

 spin_lock(&ring->lock);
 if (ring->free_bds <= (nr_frags + 1)) {
  if (!netif_tx_queue_stopped(txq))
   netif_tx_stop_queue(txq);
  ret = NETDEV_TX_BUSY;
  goto out;
 }

 /* Retain how many bytes will be sent on the wire, without TSB inserted
 * by transmit checksum offload
 */

 GENET_CB(skb)->bytes_sent = skb->len;

 /* add the Transmit Status Block */
 skb = bcmgenet_add_tsb(dev, skb, ring);
 if (!skb) {
  ret = NETDEV_TX_OK;
  goto out;
 }

 for (i = 0; i <= nr_frags; i++) {
  tx_cb_ptr = bcmgenet_get_txcb(priv, ring);

  BUG_ON(!tx_cb_ptr);

  if (!i) {
   /* Transmit single SKB or head of fragment list */
   GENET_CB(skb)->first_cb = tx_cb_ptr;
   size = skb_headlen(skb);
   mapping = dma_map_single(kdev, skb->data, size,
       DMA_TO_DEVICE);
  } else {
   /* xmit fragment */
   frag = &skb_shinfo(skb)->frags[i - 1];
   size = skb_frag_size(frag);
   mapping = skb_frag_dma_map(kdev, frag, 0, size,
         DMA_TO_DEVICE);
  }

  ret = dma_mapping_error(kdev, mapping);
  if (ret) {
   priv->mib.tx_dma_failed++;
   netif_err(priv, tx_err, dev, "Tx DMA map failed\n");
   ret = NETDEV_TX_OK;
   goto out_unmap_frags;
  }
  dma_unmap_addr_set(tx_cb_ptr, dma_addr, mapping);
  dma_unmap_len_set(tx_cb_ptr, dma_len, size);

  tx_cb_ptr->skb = skb;

  len_stat = (size << DMA_BUFLENGTH_SHIFT) |
      (priv->hw_params->qtag_mask << DMA_TX_QTAG_SHIFT);

  /* Note: if we ever change from DMA_TX_APPEND_CRC below we
 * will need to restore software padding of "runt" packets
 */

  len_stat |= DMA_TX_APPEND_CRC;

  if (!i) {
   len_stat |= DMA_SOP;
   if (skb->ip_summed == CHECKSUM_PARTIAL)
    len_stat |= DMA_TX_DO_CSUM;
  }
  if (i == nr_frags)
   len_stat |= DMA_EOP;

  dmadesc_set(priv, tx_cb_ptr->bd_addr, mapping, len_stat);
 }

 GENET_CB(skb)->last_cb = tx_cb_ptr;

 bcmgenet_hide_tsb(skb);
 skb_tx_timestamp(skb);

 /* Decrement total BD count and advance our write pointer */
 ring->free_bds -= nr_frags + 1;
 ring->prod_index += nr_frags + 1;
 ring->prod_index &= DMA_P_INDEX_MASK;

 netdev_tx_sent_queue(txq, GENET_CB(skb)->bytes_sent);

 if (ring->free_bds <= (MAX_SKB_FRAGS + 1))
  netif_tx_stop_queue(txq);

 if (!netdev_xmit_more() || netif_xmit_stopped(txq))
  /* Packets are ready, update producer index */
  bcmgenet_tdma_ring_writel(priv, ring->index,
       ring->prod_index, TDMA_PROD_INDEX);
out:
 spin_unlock(&ring->lock);

 return ret;

out_unmap_frags:
 /* Back up for failed control block mapping */
 bcmgenet_put_txcb(priv, ring);

 /* Unmap successfully mapped control blocks */
 while (i-- > 0) {
  tx_cb_ptr = bcmgenet_put_txcb(priv, ring);
  bcmgenet_free_tx_cb(kdev, tx_cb_ptr);
 }

 dev_kfree_skb(skb);
 goto out;
}

static struct sk_buff *bcmgenet_rx_refill(struct bcmgenet_priv *priv,
       struct enet_cb *cb)
{
 struct device *kdev = &priv->pdev->dev;
 struct sk_buff *skb;
 struct sk_buff *rx_skb;
 dma_addr_t mapping;

 /* Allocate a new Rx skb */
 skb = __netdev_alloc_skb(priv->dev, priv->rx_buf_len + SKB_ALIGNMENT,
     GFP_ATOMIC | __GFP_NOWARN);
 if (!skb) {
  priv->mib.alloc_rx_buff_failed++;
  netif_err(priv, rx_err, priv->dev,
     "%s: Rx skb allocation failed\n", __func__);
  return NULL;
 }

 /* DMA-map the new Rx skb */
 mapping = dma_map_single(kdev, skb->data, priv->rx_buf_len,
     DMA_FROM_DEVICE);
 if (dma_mapping_error(kdev, mapping)) {
  priv->mib.rx_dma_failed++;
  dev_kfree_skb_any(skb);
  netif_err(priv, rx_err, priv->dev,
     "%s: Rx skb DMA mapping failed\n", __func__);
  return NULL;
 }

 /* Grab the current Rx skb from the ring and DMA-unmap it */
 rx_skb = bcmgenet_free_rx_cb(kdev, cb);

 /* Put the new Rx skb on the ring */
 cb->skb = skb;
 dma_unmap_addr_set(cb, dma_addr, mapping);
 dma_unmap_len_set(cb, dma_len, priv->rx_buf_len);
 dmadesc_set_addr(priv, cb->bd_addr, mapping);

 /* Return the current Rx skb to caller */
 return rx_skb;
}

/* bcmgenet_desc_rx - descriptor based rx process.
 * this could be called from bottom half, or from NAPI polling method.
 */

static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
         unsigned int budget)
{
 struct bcmgenet_rx_stats64 *stats = &ring->stats64;
 struct bcmgenet_priv *priv = ring->priv;
 struct net_device *dev = priv->dev;
 struct enet_cb *cb;
 struct sk_buff *skb;
 u32 dma_length_status;
 unsigned long dma_flag;
 int len;
 unsigned int rxpktprocessed = 0, rxpkttoprocess;
 unsigned int bytes_processed = 0;
 unsigned int p_index, mask;
 unsigned int discards;

 /* Clear status before servicing to reduce spurious interrupts */
 mask = 1 << (UMAC_IRQ1_RX_INTR_SHIFT + ring->index);
 bcmgenet_intrl2_1_writel(priv, mask, INTRL2_CPU_CLEAR);

 p_index = bcmgenet_rdma_ring_readl(priv, ring->index, RDMA_PROD_INDEX);

 discards = (p_index >> DMA_P_INDEX_DISCARD_CNT_SHIFT) &
     DMA_P_INDEX_DISCARD_CNT_MASK;
 if (discards > ring->old_discards) {
  discards = discards - ring->old_discards;
  BCMGENET_STATS64_ADD(stats, missed, discards);
  ring->old_discards += discards;

  /* Clear HW register when we reach 75% of maximum 0xFFFF */
  if (ring->old_discards >= 0xC000) {
   ring->old_discards = 0;
   bcmgenet_rdma_ring_writel(priv, ring->index, 0,
        RDMA_PROD_INDEX);
  }
 }

 p_index &= DMA_P_INDEX_MASK;
 rxpkttoprocess = (p_index - ring->c_index) & DMA_C_INDEX_MASK;

 netif_dbg(priv, rx_status, dev,
    "RDMA: rxpkttoprocess=%d\n", rxpkttoprocess);

 while ((rxpktprocessed < rxpkttoprocess) &&
        (rxpktprocessed < budget)) {
  struct status_64 *status;
  __be16 rx_csum;

  cb = &priv->rx_cbs[ring->read_ptr];
  skb = bcmgenet_rx_refill(priv, cb);

  if (unlikely(!skb)) {
   BCMGENET_STATS64_INC(stats, dropped);
   goto next;
  }

  status = (struct status_64 *)skb->data;
  dma_length_status = status->length_status;
  if (dev->features & NETIF_F_RXCSUM) {
   rx_csum = (__force __be16)(status->rx_csum & 0xffff);
   if (rx_csum) {
    skb->csum = (__force __wsum)ntohs(rx_csum);
    skb->ip_summed = CHECKSUM_COMPLETE;
   }
  }

  /* DMA flags and length are still valid no matter how
 * we got the Receive Status Vector (64B RSB or register)
 */

  dma_flag = dma_length_status & 0xffff;
  len = dma_length_status >> DMA_BUFLENGTH_SHIFT;

  netif_dbg(priv, rx_status, dev,
     "%s:p_ind=%d c_ind=%d read_ptr=%d len_stat=0x%08x\n",
     __func__, p_index, ring->c_index,
     ring->read_ptr, dma_length_status);

  if (unlikely(len > RX_BUF_LENGTH)) {
   netif_err(priv, rx_status, dev, "oversized packet\n");
   BCMGENET_STATS64_INC(stats, length_errors);
   dev_kfree_skb_any(skb);
   goto next;
  }

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

--> maximum size reached

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

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

¤ 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.