Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/LibreOffice/icon-themes/breeze/cmd/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 248 B image not shown  

Quelle  mvpp2_main.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0
/*
 * Driver for Marvell PPv2 network controller for Armada 375 SoC.
 *
 * Copyright (C) 2014 Marvell
 *
 * Marcin Wojtas <mw@semihalf.com>
 */


#include <linux/acpi.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/platform_device.h>
#include <linux/skbuff.h>
#include <linux/inetdevice.h>
#include <linux/mbus.h>
#include <linux/module.h>
#include <linux/mfd/syscon.h>
#include <linux/interrupt.h>
#include <linux/cpumask.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_mdio.h>
#include <linux/of_net.h>
#include <linux/of_address.h>
#include <linux/phy.h>
#include <linux/phylink.h>
#include <linux/phy/phy.h>
#include <linux/ptp_classify.h>
#include <linux/clk.h>
#include <linux/hrtimer.h>
#include <linux/ktime.h>
#include <linux/regmap.h>
#include <uapi/linux/ppp_defs.h>
#include <net/ip.h>
#include <net/ipv6.h>
#include <net/page_pool/helpers.h>
#include <net/tso.h>
#include <linux/bpf_trace.h>

#include "mvpp2.h"
#include "mvpp2_prs.h"
#include "mvpp2_cls.h"

enum mvpp2_bm_pool_log_num {
 MVPP2_BM_SHORT,
 MVPP2_BM_LONG,
 MVPP2_BM_JUMBO,
 MVPP2_BM_POOLS_NUM
};

static struct {
 int pkt_size;
 int buf_num;
} mvpp2_pools[MVPP2_BM_POOLS_NUM];

/* The prototype is added here to be used in start_dev when using ACPI. This
 * will be removed once phylink is used for all modes (dt+ACPI).
 */

static void mvpp2_acpi_start(struct mvpp2_port *port);

/* Queue modes */
#define MVPP2_QDIST_SINGLE_MODE 0
#define MVPP2_QDIST_MULTI_MODE 1

static int queue_mode = MVPP2_QDIST_MULTI_MODE;

module_param(queue_mode, int, 0444);
MODULE_PARM_DESC(queue_mode, "Set queue_mode (single=0, multi=1)");

/* Utility/helper methods */

void mvpp2_write(struct mvpp2 *priv, u32 offset, u32 data)
{
 writel(data, priv->swth_base[0] + offset);
}

u32 mvpp2_read(struct mvpp2 *priv, u32 offset)
{
 return readl(priv->swth_base[0] + offset);
}

static u32 mvpp2_read_relaxed(struct mvpp2 *priv, u32 offset)
{
 return readl_relaxed(priv->swth_base[0] + offset);
}

static inline u32 mvpp2_cpu_to_thread(struct mvpp2 *priv, int cpu)
{
 return cpu % priv->nthreads;
}

static void mvpp2_cm3_write(struct mvpp2 *priv, u32 offset, u32 data)
{
 writel(data, priv->cm3_base + offset);
}

static u32 mvpp2_cm3_read(struct mvpp2 *priv, u32 offset)
{
 return readl(priv->cm3_base + offset);
}

static struct page_pool *
mvpp2_create_page_pool(struct device *dev, int num, int len,
         enum dma_data_direction dma_dir)
{
 struct page_pool_params pp_params = {
  /* internal DMA mapping in page_pool */
  .flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV,
  .pool_size = num,
  .nid = NUMA_NO_NODE,
  .dev = dev,
  .dma_dir = dma_dir,
  .offset = MVPP2_SKB_HEADROOM,
  .max_len = len,
 };

 return page_pool_create(&pp_params);
}

/* These accessors should be used to access:
 *
 * - per-thread registers, where each thread has its own copy of the
 *   register.
 *
 *   MVPP2_BM_VIRT_ALLOC_REG
 *   MVPP2_BM_ADDR_HIGH_ALLOC
 *   MVPP22_BM_ADDR_HIGH_RLS_REG
 *   MVPP2_BM_VIRT_RLS_REG
 *   MVPP2_ISR_RX_TX_CAUSE_REG
 *   MVPP2_ISR_RX_TX_MASK_REG
 *   MVPP2_TXQ_NUM_REG
 *   MVPP2_AGGR_TXQ_UPDATE_REG
 *   MVPP2_TXQ_RSVD_REQ_REG
 *   MVPP2_TXQ_RSVD_RSLT_REG
 *   MVPP2_TXQ_SENT_REG
 *   MVPP2_RXQ_NUM_REG
 *
 * - global registers that must be accessed through a specific thread
 *   window, because they are related to an access to a per-thread
 *   register
 *
 *   MVPP2_BM_PHY_ALLOC_REG    (related to MVPP2_BM_VIRT_ALLOC_REG)
 *   MVPP2_BM_PHY_RLS_REG      (related to MVPP2_BM_VIRT_RLS_REG)
 *   MVPP2_RXQ_THRESH_REG      (related to MVPP2_RXQ_NUM_REG)
 *   MVPP2_RXQ_DESC_ADDR_REG   (related to MVPP2_RXQ_NUM_REG)
 *   MVPP2_RXQ_DESC_SIZE_REG   (related to MVPP2_RXQ_NUM_REG)
 *   MVPP2_RXQ_INDEX_REG       (related to MVPP2_RXQ_NUM_REG)
 *   MVPP2_TXQ_PENDING_REG     (related to MVPP2_TXQ_NUM_REG)
 *   MVPP2_TXQ_DESC_ADDR_REG   (related to MVPP2_TXQ_NUM_REG)
 *   MVPP2_TXQ_DESC_SIZE_REG   (related to MVPP2_TXQ_NUM_REG)
 *   MVPP2_TXQ_INDEX_REG       (related to MVPP2_TXQ_NUM_REG)
 *   MVPP2_TXQ_PENDING_REG     (related to MVPP2_TXQ_NUM_REG)
 *   MVPP2_TXQ_PREF_BUF_REG    (related to MVPP2_TXQ_NUM_REG)
 *   MVPP2_TXQ_PREF_BUF_REG    (related to MVPP2_TXQ_NUM_REG)
 */

static void mvpp2_thread_write(struct mvpp2 *priv, unsigned int thread,
          u32 offset, u32 data)
{
 writel(data, priv->swth_base[thread] + offset);
}

static u32 mvpp2_thread_read(struct mvpp2 *priv, unsigned int thread,
        u32 offset)
{
 return readl(priv->swth_base[thread] + offset);
}

static void mvpp2_thread_write_relaxed(struct mvpp2 *priv, unsigned int thread,
           u32 offset, u32 data)
{
 writel_relaxed(data, priv->swth_base[thread] + offset);
}

static u32 mvpp2_thread_read_relaxed(struct mvpp2 *priv, unsigned int thread,
         u32 offset)
{
 return readl_relaxed(priv->swth_base[thread] + offset);
}

static dma_addr_t mvpp2_txdesc_dma_addr_get(struct mvpp2_port *port,
         struct mvpp2_tx_desc *tx_desc)
{
 if (port->priv->hw_version == MVPP21)
  return le32_to_cpu(tx_desc->pp21.buf_dma_addr);
 else
  return le64_to_cpu(tx_desc->pp22.buf_dma_addr_ptp) &
         MVPP2_DESC_DMA_MASK;
}

static void mvpp2_txdesc_dma_addr_set(struct mvpp2_port *port,
          struct mvpp2_tx_desc *tx_desc,
          dma_addr_t dma_addr)
{
 dma_addr_t addr, offset;

 addr = dma_addr & ~MVPP2_TX_DESC_ALIGN;
 offset = dma_addr & MVPP2_TX_DESC_ALIGN;

 if (port->priv->hw_version == MVPP21) {
  tx_desc->pp21.buf_dma_addr = cpu_to_le32(addr);
  tx_desc->pp21.packet_offset = offset;
 } else {
  __le64 val = cpu_to_le64(addr);

  tx_desc->pp22.buf_dma_addr_ptp &= ~cpu_to_le64(MVPP2_DESC_DMA_MASK);
  tx_desc->pp22.buf_dma_addr_ptp |= val;
  tx_desc->pp22.packet_offset = offset;
 }
}

static size_t mvpp2_txdesc_size_get(struct mvpp2_port *port,
        struct mvpp2_tx_desc *tx_desc)
{
 if (port->priv->hw_version == MVPP21)
  return le16_to_cpu(tx_desc->pp21.data_size);
 else
  return le16_to_cpu(tx_desc->pp22.data_size);
}

static void mvpp2_txdesc_size_set(struct mvpp2_port *port,
      struct mvpp2_tx_desc *tx_desc,
      size_t size)
{
 if (port->priv->hw_version == MVPP21)
  tx_desc->pp21.data_size = cpu_to_le16(size);
 else
  tx_desc->pp22.data_size = cpu_to_le16(size);
}

static void mvpp2_txdesc_txq_set(struct mvpp2_port *port,
     struct mvpp2_tx_desc *tx_desc,
     unsigned int txq)
{
 if (port->priv->hw_version == MVPP21)
  tx_desc->pp21.phys_txq = txq;
 else
  tx_desc->pp22.phys_txq = txq;
}

static void mvpp2_txdesc_cmd_set(struct mvpp2_port *port,
     struct mvpp2_tx_desc *tx_desc,
     unsigned int command)
{
 if (port->priv->hw_version == MVPP21)
  tx_desc->pp21.command = cpu_to_le32(command);
 else
  tx_desc->pp22.command = cpu_to_le32(command);
}

static unsigned int mvpp2_txdesc_offset_get(struct mvpp2_port *port,
         struct mvpp2_tx_desc *tx_desc)
{
 if (port->priv->hw_version == MVPP21)
  return tx_desc->pp21.packet_offset;
 else
  return tx_desc->pp22.packet_offset;
}

static dma_addr_t mvpp2_rxdesc_dma_addr_get(struct mvpp2_port *port,
         struct mvpp2_rx_desc *rx_desc)
{
 if (port->priv->hw_version == MVPP21)
  return le32_to_cpu(rx_desc->pp21.buf_dma_addr);
 else
  return le64_to_cpu(rx_desc->pp22.buf_dma_addr_key_hash) &
         MVPP2_DESC_DMA_MASK;
}

static unsigned long mvpp2_rxdesc_cookie_get(struct mvpp2_port *port,
          struct mvpp2_rx_desc *rx_desc)
{
 if (port->priv->hw_version == MVPP21)
  return le32_to_cpu(rx_desc->pp21.buf_cookie);
 else
  return le64_to_cpu(rx_desc->pp22.buf_cookie_misc) &
         MVPP2_DESC_DMA_MASK;
}

static size_t mvpp2_rxdesc_size_get(struct mvpp2_port *port,
        struct mvpp2_rx_desc *rx_desc)
{
 if (port->priv->hw_version == MVPP21)
  return le16_to_cpu(rx_desc->pp21.data_size);
 else
  return le16_to_cpu(rx_desc->pp22.data_size);
}

static u32 mvpp2_rxdesc_status_get(struct mvpp2_port *port,
       struct mvpp2_rx_desc *rx_desc)
{
 if (port->priv->hw_version == MVPP21)
  return le32_to_cpu(rx_desc->pp21.status);
 else
  return le32_to_cpu(rx_desc->pp22.status);
}

static void mvpp2_txq_inc_get(struct mvpp2_txq_pcpu *txq_pcpu)
{
 txq_pcpu->txq_get_index++;
 if (txq_pcpu->txq_get_index == txq_pcpu->size)
  txq_pcpu->txq_get_index = 0;
}

static void mvpp2_txq_inc_put(struct mvpp2_port *port,
         struct mvpp2_txq_pcpu *txq_pcpu,
         void *data,
         struct mvpp2_tx_desc *tx_desc,
         enum mvpp2_tx_buf_type buf_type)
{
 struct mvpp2_txq_pcpu_buf *tx_buf =
  txq_pcpu->buffs + txq_pcpu->txq_put_index;
 tx_buf->type = buf_type;
 if (buf_type == MVPP2_TYPE_SKB)
  tx_buf->skb = data;
 else
  tx_buf->xdpf = data;
 tx_buf->size = mvpp2_txdesc_size_get(port, tx_desc);
 tx_buf->dma = mvpp2_txdesc_dma_addr_get(port, tx_desc) +
  mvpp2_txdesc_offset_get(port, tx_desc);
 txq_pcpu->txq_put_index++;
 if (txq_pcpu->txq_put_index == txq_pcpu->size)
  txq_pcpu->txq_put_index = 0;
}

/* Get number of maximum RXQ */
static int mvpp2_get_nrxqs(struct mvpp2 *priv)
{
 unsigned int nrxqs;

 if (priv->hw_version >= MVPP22 && queue_mode == MVPP2_QDIST_SINGLE_MODE)
  return 1;

 /* According to the PPv2.2 datasheet and our experiments on
 * PPv2.1, RX queues have an allocation granularity of 4 (when
 * more than a single one on PPv2.2).
 * Round up to nearest multiple of 4.
 */

 nrxqs = (num_possible_cpus() + 3) & ~0x3;
 if (nrxqs > MVPP2_PORT_MAX_RXQ)
  nrxqs = MVPP2_PORT_MAX_RXQ;

 return nrxqs;
}

/* Get number of physical egress port */
static inline int mvpp2_egress_port(struct mvpp2_port *port)
{
 return MVPP2_MAX_TCONT + port->id;
}

/* Get number of physical TXQ */
static inline int mvpp2_txq_phys(int port, int txq)
{
 return (MVPP2_MAX_TCONT + port) * MVPP2_MAX_TXQ + txq;
}

/* Returns a struct page if page_pool is set, otherwise a buffer */
static void *mvpp2_frag_alloc(const struct mvpp2_bm_pool *pool,
         struct page_pool *page_pool)
{
 if (page_pool)
  return page_pool_dev_alloc_pages(page_pool);

 if (likely(pool->frag_size <= PAGE_SIZE))
  return netdev_alloc_frag(pool->frag_size);

 return kmalloc(pool->frag_size, GFP_ATOMIC);
}

static void mvpp2_frag_free(const struct mvpp2_bm_pool *pool,
       struct page_pool *page_pool, void *data)
{
 if (page_pool)
  page_pool_put_full_page(page_pool, virt_to_head_page(data), false);
 else if (likely(pool->frag_size <= PAGE_SIZE))
  skb_free_frag(data);
 else
  kfree(data);
}

/* Buffer Manager configuration routines */

/* Create pool */
static int mvpp2_bm_pool_create(struct device *dev, struct mvpp2 *priv,
    struct mvpp2_bm_pool *bm_pool, int size)
{
 u32 val;

 /* Number of buffer pointers must be a multiple of 16, as per
 * hardware constraints
 */

 if (!IS_ALIGNED(size, 16))
  return -EINVAL;

 /* PPv2.1 needs 8 bytes per buffer pointer, PPv2.2 and PPv2.3 needs 16
 * bytes per buffer pointer
 */

 if (priv->hw_version == MVPP21)
  bm_pool->size_bytes = 2 * sizeof(u32) * size;
 else
  bm_pool->size_bytes = 2 * sizeof(u64) * size;

 bm_pool->virt_addr = dma_alloc_coherent(dev, bm_pool->size_bytes,
      &bm_pool->dma_addr,
      GFP_KERNEL);
 if (!bm_pool->virt_addr)
  return -ENOMEM;

 if (!IS_ALIGNED((unsigned long)bm_pool->virt_addr,
   MVPP2_BM_POOL_PTR_ALIGN)) {
  dma_free_coherent(dev, bm_pool->size_bytes,
      bm_pool->virt_addr, bm_pool->dma_addr);
  dev_err(dev, "BM pool %d is not %d bytes aligned\n",
   bm_pool->id, MVPP2_BM_POOL_PTR_ALIGN);
  return -ENOMEM;
 }

 mvpp2_write(priv, MVPP2_BM_POOL_BASE_REG(bm_pool->id),
      lower_32_bits(bm_pool->dma_addr));
 mvpp2_write(priv, MVPP2_BM_POOL_SIZE_REG(bm_pool->id), size);

 val = mvpp2_read(priv, MVPP2_BM_POOL_CTRL_REG(bm_pool->id));
 val |= MVPP2_BM_START_MASK;

 val &= ~MVPP2_BM_LOW_THRESH_MASK;
 val &= ~MVPP2_BM_HIGH_THRESH_MASK;

 /* Set 8 Pools BPPI threshold for MVPP23 */
 if (priv->hw_version == MVPP23) {
  val |= MVPP2_BM_LOW_THRESH_VALUE(MVPP23_BM_BPPI_LOW_THRESH);
  val |= MVPP2_BM_HIGH_THRESH_VALUE(MVPP23_BM_BPPI_HIGH_THRESH);
 } else {
  val |= MVPP2_BM_LOW_THRESH_VALUE(MVPP2_BM_BPPI_LOW_THRESH);
  val |= MVPP2_BM_HIGH_THRESH_VALUE(MVPP2_BM_BPPI_HIGH_THRESH);
 }

 mvpp2_write(priv, MVPP2_BM_POOL_CTRL_REG(bm_pool->id), val);

 bm_pool->size = size;
 bm_pool->pkt_size = 0;
 bm_pool->buf_num = 0;

 return 0;
}

/* Set pool buffer size */
static void mvpp2_bm_pool_bufsize_set(struct mvpp2 *priv,
          struct mvpp2_bm_pool *bm_pool,
          int buf_size)
{
 u32 val;

 bm_pool->buf_size = buf_size;

 val = ALIGN(buf_size, 1 << MVPP2_POOL_BUF_SIZE_OFFSET);
 mvpp2_write(priv, MVPP2_POOL_BUF_SIZE_REG(bm_pool->id), val);
}

static void mvpp2_bm_bufs_get_addrs(struct device *dev, struct mvpp2 *priv,
        struct mvpp2_bm_pool *bm_pool,
        dma_addr_t *dma_addr,
        phys_addr_t *phys_addr)
{
 unsigned int thread = mvpp2_cpu_to_thread(priv, get_cpu());

 *dma_addr = mvpp2_thread_read(priv, thread,
          MVPP2_BM_PHY_ALLOC_REG(bm_pool->id));
 *phys_addr = mvpp2_thread_read(priv, thread, MVPP2_BM_VIRT_ALLOC_REG);

 if (priv->hw_version >= MVPP22) {
  u32 val;
  u32 dma_addr_highbits, phys_addr_highbits;

  val = mvpp2_thread_read(priv, thread, MVPP22_BM_ADDR_HIGH_ALLOC);
  dma_addr_highbits = (val & MVPP22_BM_ADDR_HIGH_PHYS_MASK);
  phys_addr_highbits = (val & MVPP22_BM_ADDR_HIGH_VIRT_MASK) >>
   MVPP22_BM_ADDR_HIGH_VIRT_SHIFT;

  if (sizeof(dma_addr_t) == 8)
   *dma_addr |= (u64)dma_addr_highbits << 32;

  if (sizeof(phys_addr_t) == 8)
   *phys_addr |= (u64)phys_addr_highbits << 32;
 }

 put_cpu();
}

/* Free all buffers from the pool */
static void mvpp2_bm_bufs_free(struct device *dev, struct mvpp2 *priv,
          struct mvpp2_bm_pool *bm_pool, int buf_num)
{
 struct page_pool *pp = NULL;
 int i;

 if (buf_num > bm_pool->buf_num) {
  WARN(1, "Pool does not have so many bufs pool(%d) bufs(%d)\n",
       bm_pool->id, buf_num);
  buf_num = bm_pool->buf_num;
 }

 if (priv->percpu_pools)
  pp = priv->page_pool[bm_pool->id];

 for (i = 0; i < buf_num; i++) {
  dma_addr_t buf_dma_addr;
  phys_addr_t buf_phys_addr;
  void *data;

  mvpp2_bm_bufs_get_addrs(dev, priv, bm_pool,
     &buf_dma_addr, &buf_phys_addr);

  if (!pp)
   dma_unmap_single(dev, buf_dma_addr,
      bm_pool->buf_size, DMA_FROM_DEVICE);

  data = (void *)phys_to_virt(buf_phys_addr);
  if (!data)
   break;

  mvpp2_frag_free(bm_pool, pp, data);
 }

 /* Update BM driver with number of buffers removed from pool */
 bm_pool->buf_num -= i;
}

/* Check number of buffers in BM pool */
static int mvpp2_check_hw_buf_num(struct mvpp2 *priv, struct mvpp2_bm_pool *bm_pool)
{
 int buf_num = 0;

 buf_num += mvpp2_read(priv, MVPP2_BM_POOL_PTRS_NUM_REG(bm_pool->id)) &
        MVPP22_BM_POOL_PTRS_NUM_MASK;
 buf_num += mvpp2_read(priv, MVPP2_BM_BPPI_PTRS_NUM_REG(bm_pool->id)) &
        MVPP2_BM_BPPI_PTR_NUM_MASK;

 /* HW has one buffer ready which is not reflected in the counters */
 if (buf_num)
  buf_num += 1;

 return buf_num;
}

/* Cleanup pool */
static int mvpp2_bm_pool_destroy(struct device *dev, struct mvpp2 *priv,
     struct mvpp2_bm_pool *bm_pool)
{
 int buf_num;
 u32 val;

 buf_num = mvpp2_check_hw_buf_num(priv, bm_pool);
 mvpp2_bm_bufs_free(dev, priv, bm_pool, buf_num);

 /* Check buffer counters after free */
 buf_num = mvpp2_check_hw_buf_num(priv, bm_pool);
 if (buf_num) {
  WARN(1, "cannot free all buffers in pool %d, buf_num left %d\n",
       bm_pool->id, bm_pool->buf_num);
  return 0;
 }

 val = mvpp2_read(priv, MVPP2_BM_POOL_CTRL_REG(bm_pool->id));
 val |= MVPP2_BM_STOP_MASK;
 mvpp2_write(priv, MVPP2_BM_POOL_CTRL_REG(bm_pool->id), val);

 if (priv->percpu_pools) {
  page_pool_destroy(priv->page_pool[bm_pool->id]);
  priv->page_pool[bm_pool->id] = NULL;
 }

 dma_free_coherent(dev, bm_pool->size_bytes,
     bm_pool->virt_addr,
     bm_pool->dma_addr);
 return 0;
}

static int mvpp2_bm_pools_init(struct device *dev, struct mvpp2 *priv)
{
 int i, err, size, poolnum = MVPP2_BM_POOLS_NUM;
 struct mvpp2_bm_pool *bm_pool;

 if (priv->percpu_pools)
  poolnum = mvpp2_get_nrxqs(priv) * 2;

 /* Create all pools with maximum size */
 size = MVPP2_BM_POOL_SIZE_MAX;
 for (i = 0; i < poolnum; i++) {
  bm_pool = &priv->bm_pools[i];
  bm_pool->id = i;
  err = mvpp2_bm_pool_create(dev, priv, bm_pool, size);
  if (err)
   goto err_unroll_pools;
  mvpp2_bm_pool_bufsize_set(priv, bm_pool, 0);
 }
 return 0;

err_unroll_pools:
 dev_err(dev, "failed to create BM pool %d, size %d\n", i, size);
 for (i = i - 1; i >= 0; i--)
  mvpp2_bm_pool_destroy(dev, priv, &priv->bm_pools[i]);
 return err;
}

/* Routine enable PPv23 8 pool mode */
static void mvpp23_bm_set_8pool_mode(struct mvpp2 *priv)
{
 int val;

 val = mvpp2_read(priv, MVPP22_BM_POOL_BASE_ADDR_HIGH_REG);
 val |= MVPP23_BM_8POOL_MODE;
 mvpp2_write(priv, MVPP22_BM_POOL_BASE_ADDR_HIGH_REG, val);
}

/* Cleanup pool before actual initialization in the OS */
static void mvpp2_bm_pool_cleanup(struct mvpp2 *priv, int pool_id)
{
 unsigned int thread = mvpp2_cpu_to_thread(priv, get_cpu());
 u32 val;
 int i;

 /* Drain the BM from all possible residues left by firmware */
 for (i = 0; i < MVPP2_BM_POOL_SIZE_MAX; i++)
  mvpp2_thread_read(priv, thread, MVPP2_BM_PHY_ALLOC_REG(pool_id));

 put_cpu();

 /* Stop the BM pool */
 val = mvpp2_read(priv, MVPP2_BM_POOL_CTRL_REG(pool_id));
 val |= MVPP2_BM_STOP_MASK;
 mvpp2_write(priv, MVPP2_BM_POOL_CTRL_REG(pool_id), val);
}

static int mvpp2_bm_init(struct device *dev, struct mvpp2 *priv)
{
 enum dma_data_direction dma_dir = DMA_FROM_DEVICE;
 int i, err, poolnum = MVPP2_BM_POOLS_NUM;
 struct mvpp2_port *port;

 if (priv->percpu_pools)
  poolnum = mvpp2_get_nrxqs(priv) * 2;

 /* Clean up the pool state in case it contains stale state */
 for (i = 0; i < poolnum; i++)
  mvpp2_bm_pool_cleanup(priv, i);

 if (priv->percpu_pools) {
  for (i = 0; i < priv->port_count; i++) {
   port = priv->port_list[i];
   if (port->xdp_prog) {
    dma_dir = DMA_BIDIRECTIONAL;
    break;
   }
  }

  for (i = 0; i < poolnum; i++) {
   /* the pool in use */
   int pn = i / (poolnum / 2);

   priv->page_pool[i] =
    mvpp2_create_page_pool(dev,
             mvpp2_pools[pn].buf_num,
             mvpp2_pools[pn].pkt_size,
             dma_dir);
   if (IS_ERR(priv->page_pool[i])) {
    int j;

    for (j = 0; j < i; j++) {
     page_pool_destroy(priv->page_pool[j]);
     priv->page_pool[j] = NULL;
    }
    return PTR_ERR(priv->page_pool[i]);
   }
  }
 }

 dev_info(dev, "using %d %s buffers\n", poolnum,
   priv->percpu_pools ? "per-cpu" : "shared");

 for (i = 0; i < poolnum; i++) {
  /* Mask BM all interrupts */
  mvpp2_write(priv, MVPP2_BM_INTR_MASK_REG(i), 0);
  /* Clear BM cause register */
  mvpp2_write(priv, MVPP2_BM_INTR_CAUSE_REG(i), 0);
 }

 /* Allocate and initialize BM pools */
 priv->bm_pools = devm_kcalloc(dev, poolnum,
          sizeof(*priv->bm_pools), GFP_KERNEL);
 if (!priv->bm_pools)
  return -ENOMEM;

 if (priv->hw_version == MVPP23)
  mvpp23_bm_set_8pool_mode(priv);

 err = mvpp2_bm_pools_init(dev, priv);
 if (err < 0)
  return err;
 return 0;
}

static void mvpp2_setup_bm_pool(void)
{
 /* Short pool */
 mvpp2_pools[MVPP2_BM_SHORT].buf_num  = MVPP2_BM_SHORT_BUF_NUM;
 mvpp2_pools[MVPP2_BM_SHORT].pkt_size = MVPP2_BM_SHORT_PKT_SIZE;

 /* Long pool */
 mvpp2_pools[MVPP2_BM_LONG].buf_num  = MVPP2_BM_LONG_BUF_NUM;
 mvpp2_pools[MVPP2_BM_LONG].pkt_size = MVPP2_BM_LONG_PKT_SIZE;

 /* Jumbo pool */
 mvpp2_pools[MVPP2_BM_JUMBO].buf_num  = MVPP2_BM_JUMBO_BUF_NUM;
 mvpp2_pools[MVPP2_BM_JUMBO].pkt_size = MVPP2_BM_JUMBO_PKT_SIZE;
}

/* Attach long pool to rxq */
static void mvpp2_rxq_long_pool_set(struct mvpp2_port *port,
        int lrxq, int long_pool)
{
 u32 val, mask;
 int prxq;

 /* Get queue physical ID */
 prxq = port->rxqs[lrxq]->id;

 if (port->priv->hw_version == MVPP21)
  mask = MVPP21_RXQ_POOL_LONG_MASK;
 else
  mask = MVPP22_RXQ_POOL_LONG_MASK;

 val = mvpp2_read(port->priv, MVPP2_RXQ_CONFIG_REG(prxq));
 val &= ~mask;
 val |= (long_pool << MVPP2_RXQ_POOL_LONG_OFFS) & mask;
 mvpp2_write(port->priv, MVPP2_RXQ_CONFIG_REG(prxq), val);
}

/* Attach short pool to rxq */
static void mvpp2_rxq_short_pool_set(struct mvpp2_port *port,
         int lrxq, int short_pool)
{
 u32 val, mask;
 int prxq;

 /* Get queue physical ID */
 prxq = port->rxqs[lrxq]->id;

 if (port->priv->hw_version == MVPP21)
  mask = MVPP21_RXQ_POOL_SHORT_MASK;
 else
  mask = MVPP22_RXQ_POOL_SHORT_MASK;

 val = mvpp2_read(port->priv, MVPP2_RXQ_CONFIG_REG(prxq));
 val &= ~mask;
 val |= (short_pool << MVPP2_RXQ_POOL_SHORT_OFFS) & mask;
 mvpp2_write(port->priv, MVPP2_RXQ_CONFIG_REG(prxq), val);
}

static void *mvpp2_buf_alloc(struct mvpp2_port *port,
        struct mvpp2_bm_pool *bm_pool,
        struct page_pool *page_pool,
        dma_addr_t *buf_dma_addr,
        phys_addr_t *buf_phys_addr,
        gfp_t gfp_mask)
{
 dma_addr_t dma_addr;
 struct page *page;
 void *data;

 data = mvpp2_frag_alloc(bm_pool, page_pool);
 if (!data)
  return NULL;

 if (page_pool) {
  page = (struct page *)data;
  dma_addr = page_pool_get_dma_addr(page);
  data = page_to_virt(page);
 } else {
  dma_addr = dma_map_single(port->dev->dev.parent, data,
       MVPP2_RX_BUF_SIZE(bm_pool->pkt_size),
       DMA_FROM_DEVICE);
  if (unlikely(dma_mapping_error(port->dev->dev.parent, dma_addr))) {
   mvpp2_frag_free(bm_pool, NULL, data);
   return NULL;
  }
 }
 *buf_dma_addr = dma_addr;
 *buf_phys_addr = virt_to_phys(data);

 return data;
}

/* Routine enable flow control for RXQs condition */
static void mvpp2_rxq_enable_fc(struct mvpp2_port *port)
{
 int val, cm3_state, host_id, q;
 int fq = port->first_rxq;
 unsigned long flags;

 spin_lock_irqsave(&port->priv->mss_spinlock, flags);

 /* Remove Flow control enable bit to prevent race between FW and Kernel
 * If Flow control was enabled, it would be re-enabled.
 */

 val = mvpp2_cm3_read(port->priv, MSS_FC_COM_REG);
 cm3_state = (val & FLOW_CONTROL_ENABLE_BIT);
 val &= ~FLOW_CONTROL_ENABLE_BIT;
 mvpp2_cm3_write(port->priv, MSS_FC_COM_REG, val);

 /* Set same Flow control for all RXQs */
 for (q = 0; q < port->nrxqs; q++) {
  /* Set stop and start Flow control RXQ thresholds */
  val = MSS_THRESHOLD_START;
  val |= (MSS_THRESHOLD_STOP << MSS_RXQ_TRESH_STOP_OFFS);
  mvpp2_cm3_write(port->priv, MSS_RXQ_TRESH_REG(q, fq), val);

  val = mvpp2_cm3_read(port->priv, MSS_RXQ_ASS_REG(q, fq));
  /* Set RXQ port ID */
  val &= ~(MSS_RXQ_ASS_PORTID_MASK << MSS_RXQ_ASS_Q_BASE(q, fq));
  val |= (port->id << MSS_RXQ_ASS_Q_BASE(q, fq));
  val &= ~(MSS_RXQ_ASS_HOSTID_MASK << (MSS_RXQ_ASS_Q_BASE(q, fq)
   + MSS_RXQ_ASS_HOSTID_OFFS));

  /* Calculate RXQ host ID:
 * In Single queue mode: Host ID equal to Host ID used for
 *  shared RX interrupt
 * In Multi queue mode: Host ID equal to number of
 * RXQ ID / number of CoS queues
 * In Single resource mode: Host ID always equal to 0
 */

  if (queue_mode == MVPP2_QDIST_SINGLE_MODE)
   host_id = port->nqvecs;
  else if (queue_mode == MVPP2_QDIST_MULTI_MODE)
   host_id = q;
  else
   host_id = 0;

  /* Set RXQ host ID */
  val |= (host_id << (MSS_RXQ_ASS_Q_BASE(q, fq)
   + MSS_RXQ_ASS_HOSTID_OFFS));

  mvpp2_cm3_write(port->priv, MSS_RXQ_ASS_REG(q, fq), val);
 }

 /* Notify Firmware that Flow control config space ready for update */
 val = mvpp2_cm3_read(port->priv, MSS_FC_COM_REG);
 val |= FLOW_CONTROL_UPDATE_COMMAND_BIT;
 val |= cm3_state;
 mvpp2_cm3_write(port->priv, MSS_FC_COM_REG, val);

 spin_unlock_irqrestore(&port->priv->mss_spinlock, flags);
}

/* Routine disable flow control for RXQs condition */
static void mvpp2_rxq_disable_fc(struct mvpp2_port *port)
{
 int val, cm3_state, q;
 unsigned long flags;
 int fq = port->first_rxq;

 spin_lock_irqsave(&port->priv->mss_spinlock, flags);

 /* Remove Flow control enable bit to prevent race between FW and Kernel
 * If Flow control was enabled, it would be re-enabled.
 */

 val = mvpp2_cm3_read(port->priv, MSS_FC_COM_REG);
 cm3_state = (val & FLOW_CONTROL_ENABLE_BIT);
 val &= ~FLOW_CONTROL_ENABLE_BIT;
 mvpp2_cm3_write(port->priv, MSS_FC_COM_REG, val);

 /* Disable Flow control for all RXQs */
 for (q = 0; q < port->nrxqs; q++) {
  /* Set threshold 0 to disable Flow control */
  val = 0;
  val |= (0 << MSS_RXQ_TRESH_STOP_OFFS);
  mvpp2_cm3_write(port->priv, MSS_RXQ_TRESH_REG(q, fq), val);

  val = mvpp2_cm3_read(port->priv, MSS_RXQ_ASS_REG(q, fq));

  val &= ~(MSS_RXQ_ASS_PORTID_MASK << MSS_RXQ_ASS_Q_BASE(q, fq));

  val &= ~(MSS_RXQ_ASS_HOSTID_MASK << (MSS_RXQ_ASS_Q_BASE(q, fq)
   + MSS_RXQ_ASS_HOSTID_OFFS));

  mvpp2_cm3_write(port->priv, MSS_RXQ_ASS_REG(q, fq), val);
 }

 /* Notify Firmware that Flow control config space ready for update */
 val = mvpp2_cm3_read(port->priv, MSS_FC_COM_REG);
 val |= FLOW_CONTROL_UPDATE_COMMAND_BIT;
 val |= cm3_state;
 mvpp2_cm3_write(port->priv, MSS_FC_COM_REG, val);

 spin_unlock_irqrestore(&port->priv->mss_spinlock, flags);
}

/* Routine disable/enable flow control for BM pool condition */
static void mvpp2_bm_pool_update_fc(struct mvpp2_port *port,
        struct mvpp2_bm_pool *pool,
        bool en)
{
 int val, cm3_state;
 unsigned long flags;

 spin_lock_irqsave(&port->priv->mss_spinlock, flags);

 /* Remove Flow control enable bit to prevent race between FW and Kernel
 * If Flow control were enabled, it would be re-enabled.
 */

 val = mvpp2_cm3_read(port->priv, MSS_FC_COM_REG);
 cm3_state = (val & FLOW_CONTROL_ENABLE_BIT);
 val &= ~FLOW_CONTROL_ENABLE_BIT;
 mvpp2_cm3_write(port->priv, MSS_FC_COM_REG, val);

 /* Check if BM pool should be enabled/disable */
 if (en) {
  /* Set BM pool start and stop thresholds per port */
  val = mvpp2_cm3_read(port->priv, MSS_BUF_POOL_REG(pool->id));
  val |= MSS_BUF_POOL_PORT_OFFS(port->id);
  val &= ~MSS_BUF_POOL_START_MASK;
  val |= (MSS_THRESHOLD_START << MSS_BUF_POOL_START_OFFS);
  val &= ~MSS_BUF_POOL_STOP_MASK;
  val |= MSS_THRESHOLD_STOP;
  mvpp2_cm3_write(port->priv, MSS_BUF_POOL_REG(pool->id), val);
 } else {
  /* Remove BM pool from the port */
  val = mvpp2_cm3_read(port->priv, MSS_BUF_POOL_REG(pool->id));
  val &= ~MSS_BUF_POOL_PORT_OFFS(port->id);

  /* Zero BM pool start and stop thresholds to disable pool
 * flow control if pool empty (not used by any port)
 */

  if (!pool->buf_num) {
   val &= ~MSS_BUF_POOL_START_MASK;
   val &= ~MSS_BUF_POOL_STOP_MASK;
  }

  mvpp2_cm3_write(port->priv, MSS_BUF_POOL_REG(pool->id), val);
 }

 /* Notify Firmware that Flow control config space ready for update */
 val = mvpp2_cm3_read(port->priv, MSS_FC_COM_REG);
 val |= FLOW_CONTROL_UPDATE_COMMAND_BIT;
 val |= cm3_state;
 mvpp2_cm3_write(port->priv, MSS_FC_COM_REG, val);

 spin_unlock_irqrestore(&port->priv->mss_spinlock, flags);
}

/* disable/enable flow control for BM pool on all ports */
static void mvpp2_bm_pool_update_priv_fc(struct mvpp2 *priv, bool en)
{
 struct mvpp2_port *port;
 int i, j;

 for (i = 0; i < priv->port_count; i++) {
  port = priv->port_list[i];
  if (port->priv->percpu_pools) {
   for (j = 0; j < port->nrxqs; j++)
    mvpp2_bm_pool_update_fc(port, &port->priv->bm_pools[j],
       port->tx_fc & en);
  } else {
   mvpp2_bm_pool_update_fc(port, port->pool_long, port->tx_fc & en);
   mvpp2_bm_pool_update_fc(port, port->pool_short, port->tx_fc & en);
  }
 }
}

static int mvpp2_enable_global_fc(struct mvpp2 *priv)
{
 int val, timeout = 0;

 /* Enable global flow control. In this stage global
 * flow control enabled, but still disabled per port.
 */

 val = mvpp2_cm3_read(priv, MSS_FC_COM_REG);
 val |= FLOW_CONTROL_ENABLE_BIT;
 mvpp2_cm3_write(priv, MSS_FC_COM_REG, val);

 /* Check if Firmware running and disable FC if not*/
 val |= FLOW_CONTROL_UPDATE_COMMAND_BIT;
 mvpp2_cm3_write(priv, MSS_FC_COM_REG, val);

 while (timeout < MSS_FC_MAX_TIMEOUT) {
  val = mvpp2_cm3_read(priv, MSS_FC_COM_REG);

  if (!(val & FLOW_CONTROL_UPDATE_COMMAND_BIT))
   return 0;
  usleep_range(10, 20);
  timeout++;
 }

 priv->global_tx_fc = false;
 return -EOPNOTSUPP;
}

/* Release buffer to BM */
static inline void mvpp2_bm_pool_put(struct mvpp2_port *port, int pool,
         dma_addr_t buf_dma_addr,
         phys_addr_t buf_phys_addr)
{
 unsigned int thread = mvpp2_cpu_to_thread(port->priv, get_cpu());
 unsigned long flags = 0;

 if (test_bit(thread, &port->priv->lock_map))
  spin_lock_irqsave(&port->bm_lock[thread], flags);

 if (port->priv->hw_version >= MVPP22) {
  u32 val = 0;

  if (sizeof(dma_addr_t) == 8)
   val |= upper_32_bits(buf_dma_addr) &
    MVPP22_BM_ADDR_HIGH_PHYS_RLS_MASK;

  if (sizeof(phys_addr_t) == 8)
   val |= (upper_32_bits(buf_phys_addr)
    << MVPP22_BM_ADDR_HIGH_VIRT_RLS_SHIFT) &
    MVPP22_BM_ADDR_HIGH_VIRT_RLS_MASK;

  mvpp2_thread_write_relaxed(port->priv, thread,
        MVPP22_BM_ADDR_HIGH_RLS_REG, val);
 }

 /* MVPP2_BM_VIRT_RLS_REG is not interpreted by HW, and simply
 * returned in the "cookie" field of the RX
 * descriptor. Instead of storing the virtual address, we
 * store the physical address
 */

 mvpp2_thread_write_relaxed(port->priv, thread,
       MVPP2_BM_VIRT_RLS_REG, buf_phys_addr);
 mvpp2_thread_write_relaxed(port->priv, thread,
       MVPP2_BM_PHY_RLS_REG(pool), buf_dma_addr);

 if (test_bit(thread, &port->priv->lock_map))
  spin_unlock_irqrestore(&port->bm_lock[thread], flags);

 put_cpu();
}

/* Allocate buffers for the pool */
static int mvpp2_bm_bufs_add(struct mvpp2_port *port,
        struct mvpp2_bm_pool *bm_pool, int buf_num)
{
 int i, buf_size, total_size;
 dma_addr_t dma_addr;
 phys_addr_t phys_addr;
 struct page_pool *pp = NULL;
 void *buf;

 if (port->priv->percpu_pools &&
     bm_pool->pkt_size > MVPP2_BM_LONG_PKT_SIZE) {
  netdev_err(port->dev,
      "attempted to use jumbo frames with per-cpu pools");
  return 0;
 }

 buf_size = MVPP2_RX_BUF_SIZE(bm_pool->pkt_size);
 total_size = MVPP2_RX_TOTAL_SIZE(buf_size);

 if (buf_num < 0 ||
     (buf_num + bm_pool->buf_num > bm_pool->size)) {
  netdev_err(port->dev,
      "cannot allocate %d buffers for pool %d\n",
      buf_num, bm_pool->id);
  return 0;
 }

 if (port->priv->percpu_pools)
  pp = port->priv->page_pool[bm_pool->id];
 for (i = 0; i < buf_num; i++) {
  buf = mvpp2_buf_alloc(port, bm_pool, pp, &dma_addr,
          &phys_addr, GFP_KERNEL);
  if (!buf)
   break;

  mvpp2_bm_pool_put(port, bm_pool->id, dma_addr,
      phys_addr);
 }

 /* Update BM driver with number of buffers added to pool */
 bm_pool->buf_num += i;

 netdev_dbg(port->dev,
     "pool %d: pkt_size=%4d, buf_size=%4d, total_size=%4d\n",
     bm_pool->id, bm_pool->pkt_size, buf_size, total_size);

 netdev_dbg(port->dev,
     "pool %d: %d of %d buffers added\n",
     bm_pool->id, i, buf_num);
 return i;
}

/* Notify the driver that BM pool is being used as specific type and return the
 * pool pointer on success
 */

static struct mvpp2_bm_pool *
mvpp2_bm_pool_use(struct mvpp2_port *port, unsigned pool, int pkt_size)
{
 struct mvpp2_bm_pool *new_pool = &port->priv->bm_pools[pool];
 int num;

 if ((port->priv->percpu_pools && pool > mvpp2_get_nrxqs(port->priv) * 2) ||
     (!port->priv->percpu_pools && pool >= MVPP2_BM_POOLS_NUM)) {
  netdev_err(port->dev, "Invalid pool %d\n", pool);
  return NULL;
 }

 /* Allocate buffers in case BM pool is used as long pool, but packet
 * size doesn't match MTU or BM pool hasn't being used yet
 */

 if (new_pool->pkt_size == 0) {
  int pkts_num;

  /* Set default buffer number or free all the buffers in case
 * the pool is not empty
 */

  pkts_num = new_pool->buf_num;
  if (pkts_num == 0) {
   if (port->priv->percpu_pools) {
    if (pool < port->nrxqs)
     pkts_num = mvpp2_pools[MVPP2_BM_SHORT].buf_num;
    else
     pkts_num = mvpp2_pools[MVPP2_BM_LONG].buf_num;
   } else {
    pkts_num = mvpp2_pools[pool].buf_num;
   }
  } else {
   mvpp2_bm_bufs_free(port->dev->dev.parent,
        port->priv, new_pool, pkts_num);
  }

  new_pool->pkt_size = pkt_size;
  new_pool->frag_size =
   SKB_DATA_ALIGN(MVPP2_RX_BUF_SIZE(pkt_size)) +
   MVPP2_SKB_SHINFO_SIZE;

  /* Allocate buffers for this pool */
  num = mvpp2_bm_bufs_add(port, new_pool, pkts_num);
  if (num != pkts_num) {
   WARN(1, "pool %d: %d of %d allocated\n",
        new_pool->id, num, pkts_num);
   return NULL;
  }
 }

 mvpp2_bm_pool_bufsize_set(port->priv, new_pool,
      MVPP2_RX_BUF_SIZE(new_pool->pkt_size));

 return new_pool;
}

static struct mvpp2_bm_pool *
mvpp2_bm_pool_use_percpu(struct mvpp2_port *port, int type,
    unsigned int pool, int pkt_size)
{
 struct mvpp2_bm_pool *new_pool = &port->priv->bm_pools[pool];
 int num;

 if (pool > port->nrxqs * 2) {
  netdev_err(port->dev, "Invalid pool %d\n", pool);
  return NULL;
 }

 /* Allocate buffers in case BM pool is used as long pool, but packet
 * size doesn't match MTU or BM pool hasn't being used yet
 */

 if (new_pool->pkt_size == 0) {
  int pkts_num;

  /* Set default buffer number or free all the buffers in case
 * the pool is not empty
 */

  pkts_num = new_pool->buf_num;
  if (pkts_num == 0)
   pkts_num = mvpp2_pools[type].buf_num;
  else
   mvpp2_bm_bufs_free(port->dev->dev.parent,
        port->priv, new_pool, pkts_num);

  new_pool->pkt_size = pkt_size;
  new_pool->frag_size =
   SKB_DATA_ALIGN(MVPP2_RX_BUF_SIZE(pkt_size)) +
   MVPP2_SKB_SHINFO_SIZE;

  /* Allocate buffers for this pool */
  num = mvpp2_bm_bufs_add(port, new_pool, pkts_num);
  if (num != pkts_num) {
   WARN(1, "pool %d: %d of %d allocated\n",
        new_pool->id, num, pkts_num);
   return NULL;
  }
 }

 mvpp2_bm_pool_bufsize_set(port->priv, new_pool,
      MVPP2_RX_BUF_SIZE(new_pool->pkt_size));

 return new_pool;
}

/* Initialize pools for swf, shared buffers variant */
static int mvpp2_swf_bm_pool_init_shared(struct mvpp2_port *port)
{
 enum mvpp2_bm_pool_log_num long_log_pool, short_log_pool;
 int rxq;

 /* If port pkt_size is higher than 1518B:
 * HW Long pool - SW Jumbo pool, HW Short pool - SW Long pool
 * else: HW Long pool - SW Long pool, HW Short pool - SW Short pool
 */

 if (port->pkt_size > MVPP2_BM_LONG_PKT_SIZE) {
  long_log_pool = MVPP2_BM_JUMBO;
  short_log_pool = MVPP2_BM_LONG;
 } else {
  long_log_pool = MVPP2_BM_LONG;
  short_log_pool = MVPP2_BM_SHORT;
 }

 if (!port->pool_long) {
  port->pool_long =
   mvpp2_bm_pool_use(port, long_log_pool,
       mvpp2_pools[long_log_pool].pkt_size);
  if (!port->pool_long)
   return -ENOMEM;

  port->pool_long->port_map |= BIT(port->id);

  for (rxq = 0; rxq < port->nrxqs; rxq++)
   mvpp2_rxq_long_pool_set(port, rxq, port->pool_long->id);
 }

 if (!port->pool_short) {
  port->pool_short =
   mvpp2_bm_pool_use(port, short_log_pool,
       mvpp2_pools[short_log_pool].pkt_size);
  if (!port->pool_short)
   return -ENOMEM;

  port->pool_short->port_map |= BIT(port->id);

  for (rxq = 0; rxq < port->nrxqs; rxq++)
   mvpp2_rxq_short_pool_set(port, rxq,
       port->pool_short->id);
 }

 return 0;
}

/* Initialize pools for swf, percpu buffers variant */
static int mvpp2_swf_bm_pool_init_percpu(struct mvpp2_port *port)
{
 struct mvpp2_bm_pool *bm_pool;
 int i;

 for (i = 0; i < port->nrxqs; i++) {
  bm_pool = mvpp2_bm_pool_use_percpu(port, MVPP2_BM_SHORT, i,
         mvpp2_pools[MVPP2_BM_SHORT].pkt_size);
  if (!bm_pool)
   return -ENOMEM;

  bm_pool->port_map |= BIT(port->id);
  mvpp2_rxq_short_pool_set(port, i, bm_pool->id);
 }

 for (i = 0; i < port->nrxqs; i++) {
  bm_pool = mvpp2_bm_pool_use_percpu(port, MVPP2_BM_LONG, i + port->nrxqs,
         mvpp2_pools[MVPP2_BM_LONG].pkt_size);
  if (!bm_pool)
   return -ENOMEM;

  bm_pool->port_map |= BIT(port->id);
  mvpp2_rxq_long_pool_set(port, i, bm_pool->id);
 }

 port->pool_long = NULL;
 port->pool_short = NULL;

 return 0;
}

static int mvpp2_swf_bm_pool_init(struct mvpp2_port *port)
{
 if (port->priv->percpu_pools)
  return mvpp2_swf_bm_pool_init_percpu(port);
 else
  return mvpp2_swf_bm_pool_init_shared(port);
}

static void mvpp2_set_hw_csum(struct mvpp2_port *port,
         enum mvpp2_bm_pool_log_num new_long_pool)
{
 const netdev_features_t csums = NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM;

 /* Update L4 checksum when jumbo enable/disable on port.
 * Only port 0 supports hardware checksum offload due to
 * the Tx FIFO size limitation.
 * Also, don't set NETIF_F_HW_CSUM because L3_offset in TX descriptor
 * has 7 bits, so the maximum L3 offset is 128.
 */

 if (new_long_pool == MVPP2_BM_JUMBO && port->id != 0) {
  port->dev->features &= ~csums;
  port->dev->hw_features &= ~csums;
 } else {
  port->dev->features |= csums;
  port->dev->hw_features |= csums;
 }
}

static int mvpp2_bm_update_mtu(struct net_device *dev, int mtu)
{
 struct mvpp2_port *port = netdev_priv(dev);
 enum mvpp2_bm_pool_log_num new_long_pool;
 int pkt_size = MVPP2_RX_PKT_SIZE(mtu);

 if (port->priv->percpu_pools)
  goto out_set;

 /* If port MTU is higher than 1518B:
 * HW Long pool - SW Jumbo pool, HW Short pool - SW Long pool
 * else: HW Long pool - SW Long pool, HW Short pool - SW Short pool
 */

 if (pkt_size > MVPP2_BM_LONG_PKT_SIZE)
  new_long_pool = MVPP2_BM_JUMBO;
 else
  new_long_pool = MVPP2_BM_LONG;

 if (new_long_pool != port->pool_long->id) {
  if (port->tx_fc) {
   if (pkt_size > MVPP2_BM_LONG_PKT_SIZE)
    mvpp2_bm_pool_update_fc(port,
       port->pool_short,
       false);
   else
    mvpp2_bm_pool_update_fc(port, port->pool_long,
       false);
  }

  /* Remove port from old short & long pool */
  port->pool_long = mvpp2_bm_pool_use(port, port->pool_long->id,
          port->pool_long->pkt_size);
  port->pool_long->port_map &= ~BIT(port->id);
  port->pool_long = NULL;

  port->pool_short = mvpp2_bm_pool_use(port, port->pool_short->id,
           port->pool_short->pkt_size);
  port->pool_short->port_map &= ~BIT(port->id);
  port->pool_short = NULL;

  port->pkt_size =  pkt_size;

  /* Add port to new short & long pool */
  mvpp2_swf_bm_pool_init(port);

  mvpp2_set_hw_csum(port, new_long_pool);

  if (port->tx_fc) {
   if (pkt_size > MVPP2_BM_LONG_PKT_SIZE)
    mvpp2_bm_pool_update_fc(port, port->pool_long,
       true);
   else
    mvpp2_bm_pool_update_fc(port, port->pool_short,
       true);
  }

  /* Update L4 checksum when jumbo enable/disable on port */
  if (new_long_pool == MVPP2_BM_JUMBO && port->id != 0) {
   dev->features &= ~(NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM);
   dev->hw_features &= ~(NETIF_F_IP_CSUM |
           NETIF_F_IPV6_CSUM);
  } else {
   dev->features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM;
   dev->hw_features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM;
  }
 }

out_set:
 WRITE_ONCE(dev->mtu, mtu);
 dev->wanted_features = dev->features;

 netdev_update_features(dev);
 return 0;
}

static inline void mvpp2_interrupts_enable(struct mvpp2_port *port)
{
 int i, sw_thread_mask = 0;

 for (i = 0; i < port->nqvecs; i++)
  sw_thread_mask |= port->qvecs[i].sw_thread_mask;

 mvpp2_write(port->priv, MVPP2_ISR_ENABLE_REG(port->id),
      MVPP2_ISR_ENABLE_INTERRUPT(sw_thread_mask));
}

static inline void mvpp2_interrupts_disable(struct mvpp2_port *port)
{
 int i, sw_thread_mask = 0;

 for (i = 0; i < port->nqvecs; i++)
  sw_thread_mask |= port->qvecs[i].sw_thread_mask;

 mvpp2_write(port->priv, MVPP2_ISR_ENABLE_REG(port->id),
      MVPP2_ISR_DISABLE_INTERRUPT(sw_thread_mask));
}

static inline void mvpp2_qvec_interrupt_enable(struct mvpp2_queue_vector *qvec)
{
 struct mvpp2_port *port = qvec->port;

 mvpp2_write(port->priv, MVPP2_ISR_ENABLE_REG(port->id),
      MVPP2_ISR_ENABLE_INTERRUPT(qvec->sw_thread_mask));
}

static inline void mvpp2_qvec_interrupt_disable(struct mvpp2_queue_vector *qvec)
{
 struct mvpp2_port *port = qvec->port;

 mvpp2_write(port->priv, MVPP2_ISR_ENABLE_REG(port->id),
      MVPP2_ISR_DISABLE_INTERRUPT(qvec->sw_thread_mask));
}

/* Mask the current thread's Rx/Tx interrupts
 * Called by on_each_cpu(), guaranteed to run with migration disabled,
 * using smp_processor_id() is OK.
 */

static void mvpp2_interrupts_mask(void *arg)
{
 struct mvpp2_port *port = arg;
 int cpu = smp_processor_id();
 u32 thread;

 /* If the thread isn't used, don't do anything */
 if (cpu > port->priv->nthreads)
  return;

 thread = mvpp2_cpu_to_thread(port->priv, cpu);

 mvpp2_thread_write(port->priv, thread,
      MVPP2_ISR_RX_TX_MASK_REG(port->id), 0);
 mvpp2_thread_write(port->priv, thread,
      MVPP2_ISR_RX_ERR_CAUSE_REG(port->id), 0);
}

/* Unmask the current thread's Rx/Tx interrupts.
 * Called by on_each_cpu(), guaranteed to run with migration disabled,
 * using smp_processor_id() is OK.
 */

static void mvpp2_interrupts_unmask(void *arg)
{
 struct mvpp2_port *port = arg;
 int cpu = smp_processor_id();
 u32 val, thread;

 /* If the thread isn't used, don't do anything */
 if (cpu >= port->priv->nthreads)
  return;

 thread = mvpp2_cpu_to_thread(port->priv, cpu);

 val = MVPP2_CAUSE_MISC_SUM_MASK |
  MVPP2_CAUSE_RXQ_OCCUP_DESC_ALL_MASK(port->priv->hw_version);
 if (port->has_tx_irqs)
  val |= MVPP2_CAUSE_TXQ_OCCUP_DESC_ALL_MASK;

 mvpp2_thread_write(port->priv, thread,
      MVPP2_ISR_RX_TX_MASK_REG(port->id), val);
 mvpp2_thread_write(port->priv, thread,
      MVPP2_ISR_RX_ERR_CAUSE_REG(port->id),
      MVPP2_ISR_RX_ERR_CAUSE_NONOCC_MASK);
}

static void
mvpp2_shared_interrupt_mask_unmask(struct mvpp2_port *port, bool mask)
{
 u32 val;
 int i;

 if (port->priv->hw_version == MVPP21)
  return;

 if (mask)
  val = 0;
 else
  val = MVPP2_CAUSE_RXQ_OCCUP_DESC_ALL_MASK(MVPP22);

 for (i = 0; i < port->nqvecs; i++) {
  struct mvpp2_queue_vector *v = port->qvecs + i;

  if (v->type != MVPP2_QUEUE_VECTOR_SHARED)
   continue;

  mvpp2_thread_write(port->priv, v->sw_thread_id,
       MVPP2_ISR_RX_TX_MASK_REG(port->id), val);
  mvpp2_thread_write(port->priv, v->sw_thread_id,
       MVPP2_ISR_RX_ERR_CAUSE_REG(port->id),
       MVPP2_ISR_RX_ERR_CAUSE_NONOCC_MASK);
 }
}

/* Only GOP port 0 has an XLG MAC */
static bool mvpp2_port_supports_xlg(struct mvpp2_port *port)
{
 return port->gop_id == 0;
}

static bool mvpp2_port_supports_rgmii(struct mvpp2_port *port)
{
 return !(port->priv->hw_version >= MVPP22 && port->gop_id == 0);
}

/* Port configuration routines */
static bool mvpp2_is_xlg(phy_interface_t interface)
{
 return interface == PHY_INTERFACE_MODE_10GBASER ||
        interface == PHY_INTERFACE_MODE_5GBASER ||
        interface == PHY_INTERFACE_MODE_XAUI;
}

static void mvpp2_modify(void __iomem *ptr, u32 mask, u32 set)
{
 u32 old, val;

 old = val = readl(ptr);
 val &= ~mask;
 val |= set;
 if (old != val)
  writel(val, ptr);
}

static void mvpp22_gop_init_rgmii(struct mvpp2_port *port)
{
 struct mvpp2 *priv = port->priv;
 u32 val;

 regmap_read(priv->sysctrl_base, GENCONF_PORT_CTRL0, &val);
 val |= GENCONF_PORT_CTRL0_BUS_WIDTH_SELECT;
 regmap_write(priv->sysctrl_base, GENCONF_PORT_CTRL0, val);

 regmap_read(priv->sysctrl_base, GENCONF_CTRL0, &val);
 if (port->gop_id == 2) {
  val |= GENCONF_CTRL0_PORT2_RGMII;
 } else if (port->gop_id == 3) {
  val |= GENCONF_CTRL0_PORT3_RGMII_MII;

  /* According to the specification, GENCONF_CTRL0_PORT3_RGMII
 * should be set to 1 for RGMII and 0 for MII. However, tests
 * show that it is the other way around. This is also what
 * U-Boot does for mvpp2, so it is assumed to be correct.
 */

  if (port->phy_interface == PHY_INTERFACE_MODE_MII)
   val |= GENCONF_CTRL0_PORT3_RGMII;
  else
   val &= ~GENCONF_CTRL0_PORT3_RGMII;
 }
 regmap_write(priv->sysctrl_base, GENCONF_CTRL0, val);
}

static void mvpp22_gop_init_sgmii(struct mvpp2_port *port)
{
 struct mvpp2 *priv = port->priv;
 u32 val;

 regmap_read(priv->sysctrl_base, GENCONF_PORT_CTRL0, &val);
 val |= GENCONF_PORT_CTRL0_BUS_WIDTH_SELECT |
        GENCONF_PORT_CTRL0_RX_DATA_SAMPLE;
 regmap_write(priv->sysctrl_base, GENCONF_PORT_CTRL0, val);

 if (port->gop_id > 1) {
  regmap_read(priv->sysctrl_base, GENCONF_CTRL0, &val);
  if (port->gop_id == 2)
   val &= ~GENCONF_CTRL0_PORT2_RGMII;
  else if (port->gop_id == 3)
   val &= ~GENCONF_CTRL0_PORT3_RGMII_MII;
  regmap_write(priv->sysctrl_base, GENCONF_CTRL0, val);
 }
}

static void mvpp22_gop_init_10gkr(struct mvpp2_port *port)
{
 struct mvpp2 *priv = port->priv;
 void __iomem *mpcs = priv->iface_base + MVPP22_MPCS_BASE(port->gop_id);
 void __iomem *xpcs = priv->iface_base + MVPP22_XPCS_BASE(port->gop_id);
 u32 val;

 val = readl(xpcs + MVPP22_XPCS_CFG0);
 val &= ~(MVPP22_XPCS_CFG0_PCS_MODE(0x3) |
   MVPP22_XPCS_CFG0_ACTIVE_LANE(0x3));
 val |= MVPP22_XPCS_CFG0_ACTIVE_LANE(2);
 writel(val, xpcs + MVPP22_XPCS_CFG0);

 val = readl(mpcs + MVPP22_MPCS_CTRL);
 val &= ~MVPP22_MPCS_CTRL_FWD_ERR_CONN;
 writel(val, mpcs + MVPP22_MPCS_CTRL);

 val = readl(mpcs + MVPP22_MPCS_CLK_RESET);
 val &= ~MVPP22_MPCS_CLK_RESET_DIV_RATIO(0x7);
 val |= MVPP22_MPCS_CLK_RESET_DIV_RATIO(1);
 writel(val, mpcs + MVPP22_MPCS_CLK_RESET);
}

static void mvpp22_gop_fca_enable_periodic(struct mvpp2_port *port, bool en)
{
 struct mvpp2 *priv = port->priv;
 void __iomem *fca = priv->iface_base + MVPP22_FCA_BASE(port->gop_id);
 u32 val;

 val = readl(fca + MVPP22_FCA_CONTROL_REG);
 val &= ~MVPP22_FCA_ENABLE_PERIODIC;
 if (en)
  val |= MVPP22_FCA_ENABLE_PERIODIC;
 writel(val, fca + MVPP22_FCA_CONTROL_REG);
}

static void mvpp22_gop_fca_set_timer(struct mvpp2_port *port, u32 timer)
{
 struct mvpp2 *priv = port->priv;
 void __iomem *fca = priv->iface_base + MVPP22_FCA_BASE(port->gop_id);
 u32 lsb, msb;

 lsb = timer & MVPP22_FCA_REG_MASK;
 msb = timer >> MVPP22_FCA_REG_SIZE;

 writel(lsb, fca + MVPP22_PERIODIC_COUNTER_LSB_REG);
 writel(msb, fca + MVPP22_PERIODIC_COUNTER_MSB_REG);
}

/* Set Flow Control timer x100 faster than pause quanta to ensure that link
 * partner won't send traffic if port is in XOFF mode.
 */

static void mvpp22_gop_fca_set_periodic_timer(struct mvpp2_port *port)
{
 u32 timer;

 timer = (port->priv->tclk / (USEC_PER_SEC * FC_CLK_DIVIDER))
  * FC_QUANTA;

 mvpp22_gop_fca_enable_periodic(port, false);

 mvpp22_gop_fca_set_timer(port, timer);

 mvpp22_gop_fca_enable_periodic(port, true);
}

static int mvpp22_gop_init(struct mvpp2_port *port, phy_interface_t interface)
{
 struct mvpp2 *priv = port->priv;
 u32 val;

 if (!priv->sysctrl_base)
  return 0;

 switch (interface) {
 case PHY_INTERFACE_MODE_MII:
 case PHY_INTERFACE_MODE_RGMII:
 case PHY_INTERFACE_MODE_RGMII_ID:
 case PHY_INTERFACE_MODE_RGMII_RXID:
 case PHY_INTERFACE_MODE_RGMII_TXID:
  if (!mvpp2_port_supports_rgmii(port))
   goto invalid_conf;
  mvpp22_gop_init_rgmii(port);
  break;
 case PHY_INTERFACE_MODE_SGMII:
 case PHY_INTERFACE_MODE_1000BASEX:
 case PHY_INTERFACE_MODE_2500BASEX:
  mvpp22_gop_init_sgmii(port);
  break;
 case PHY_INTERFACE_MODE_5GBASER:
 case PHY_INTERFACE_MODE_10GBASER:
  if (!mvpp2_port_supports_xlg(port))
   goto invalid_conf;
  mvpp22_gop_init_10gkr(port);
  break;
 default:
  goto unsupported_conf;
 }

 regmap_read(priv->sysctrl_base, GENCONF_PORT_CTRL1, &val);
 val |= GENCONF_PORT_CTRL1_RESET(port->gop_id) |
        GENCONF_PORT_CTRL1_EN(port->gop_id);
 regmap_write(priv->sysctrl_base, GENCONF_PORT_CTRL1, val);

 regmap_read(priv->sysctrl_base, GENCONF_PORT_CTRL0, &val);
 val |= GENCONF_PORT_CTRL0_CLK_DIV_PHASE_CLR;
 regmap_write(priv->sysctrl_base, GENCONF_PORT_CTRL0, val);

 regmap_read(priv->sysctrl_base, GENCONF_SOFT_RESET1, &val);
 val |= GENCONF_SOFT_RESET1_GOP;
 regmap_write(priv->sysctrl_base, GENCONF_SOFT_RESET1, val);

 mvpp22_gop_fca_set_periodic_timer(port);

unsupported_conf:
 return 0;

invalid_conf:
 netdev_err(port->dev, "Invalid port configuration\n");
 return -EINVAL;
}

static void mvpp22_gop_unmask_irq(struct mvpp2_port *port)
{
 u32 val;

 if (phy_interface_mode_is_rgmii(port->phy_interface) ||
     phy_interface_mode_is_8023z(port->phy_interface) ||
     port->phy_interface == PHY_INTERFACE_MODE_SGMII) {
  /* Enable the GMAC link status irq for this port */
  val = readl(port->base + MVPP22_GMAC_INT_SUM_MASK);
  val |= MVPP22_GMAC_INT_SUM_MASK_LINK_STAT;
  writel(val, port->base + MVPP22_GMAC_INT_SUM_MASK);
 }

 if (mvpp2_port_supports_xlg(port)) {
  /* Enable the XLG/GIG irqs for this port */
  val = readl(port->base + MVPP22_XLG_EXT_INT_MASK);
  if (mvpp2_is_xlg(port->phy_interface))
   val |= MVPP22_XLG_EXT_INT_MASK_XLG;
  else
   val |= MVPP22_XLG_EXT_INT_MASK_GIG;
  writel(val, port->base + MVPP22_XLG_EXT_INT_MASK);
 }
}

static void mvpp22_gop_mask_irq(struct mvpp2_port *port)
{
 u32 val;

 if (mvpp2_port_supports_xlg(port)) {
  val = readl(port->base + MVPP22_XLG_EXT_INT_MASK);
  val &= ~(MVPP22_XLG_EXT_INT_MASK_XLG |
    MVPP22_XLG_EXT_INT_MASK_GIG);
  writel(val, port->base + MVPP22_XLG_EXT_INT_MASK);
 }

 if (phy_interface_mode_is_rgmii(port->phy_interface) ||
     phy_interface_mode_is_8023z(port->phy_interface) ||
     port->phy_interface == PHY_INTERFACE_MODE_SGMII) {
  val = readl(port->base + MVPP22_GMAC_INT_SUM_MASK);
  val &= ~MVPP22_GMAC_INT_SUM_MASK_LINK_STAT;
  writel(val, port->base + MVPP22_GMAC_INT_SUM_MASK);
 }
}

static void mvpp22_gop_setup_irq(struct mvpp2_port *port)
{
 u32 val;

 mvpp2_modify(port->base + MVPP22_GMAC_INT_SUM_MASK,
       MVPP22_GMAC_INT_SUM_MASK_PTP,
       MVPP22_GMAC_INT_SUM_MASK_PTP);

 if (port->phylink ||
     phy_interface_mode_is_rgmii(port->phy_interface) ||
     phy_interface_mode_is_8023z(port->phy_interface) ||
     port->phy_interface == PHY_INTERFACE_MODE_SGMII) {
  val = readl(port->base + MVPP22_GMAC_INT_MASK);
  val |= MVPP22_GMAC_INT_MASK_LINK_STAT;
  writel(val, port->base + MVPP22_GMAC_INT_MASK);
 }

 if (mvpp2_port_supports_xlg(port)) {
  val = readl(port->base + MVPP22_XLG_INT_MASK);
  val |= MVPP22_XLG_INT_MASK_LINK;
  writel(val, port->base + MVPP22_XLG_INT_MASK);

  mvpp2_modify(port->base + MVPP22_XLG_EXT_INT_MASK,
        MVPP22_XLG_EXT_INT_MASK_PTP,
        MVPP22_XLG_EXT_INT_MASK_PTP);
 }

 mvpp22_gop_unmask_irq(port);
}

/* Sets the PHY mode of the COMPHY (which configures the serdes lanes).
 *
 * The PHY mode used by the PPv2 driver comes from the network subsystem, while
 * the one given to the COMPHY comes from the generic PHY subsystem. Hence they
 * differ.
 *
 * The COMPHY configures the serdes lanes regardless of the actual use of the
 * lanes by the physical layer. This is why configurations like
 * "PPv2 (2500BaseX) - COMPHY (2500SGMII)" are valid.
 */

static int mvpp22_comphy_init(struct mvpp2_port *port,
         phy_interface_t interface)
{
 int ret;

 if (!port->comphy)
  return 0;

 ret = phy_set_mode_ext(port->comphy, PHY_MODE_ETHERNET, interface);
 if (ret)
  return ret;

 return phy_power_on(port->comphy);
}

static void mvpp2_port_enable(struct mvpp2_port *port)
{
 u32 val;

 if (mvpp2_port_supports_xlg(port) &&
     mvpp2_is_xlg(port->phy_interface)) {
  val = readl(port->base + MVPP22_XLG_CTRL0_REG);
  val |= MVPP22_XLG_CTRL0_PORT_EN;
  val &= ~MVPP22_XLG_CTRL0_MIB_CNT_DIS;
  writel(val, port->base + MVPP22_XLG_CTRL0_REG);
 } else {
  val = readl(port->base + MVPP2_GMAC_CTRL_0_REG);
  val |= MVPP2_GMAC_PORT_EN_MASK;
  val |= MVPP2_GMAC_MIB_CNTR_EN_MASK;
  writel(val, port->base + MVPP2_GMAC_CTRL_0_REG);
 }
}

static void mvpp2_port_disable(struct mvpp2_port *port)
{
 u32 val;

 if (mvpp2_port_supports_xlg(port) &&
     mvpp2_is_xlg(port->phy_interface)) {
  val = readl(port->base + MVPP22_XLG_CTRL0_REG);
  val &= ~MVPP22_XLG_CTRL0_PORT_EN;
  writel(val, port->base + MVPP22_XLG_CTRL0_REG);
 }

 val = readl(port->base + MVPP2_GMAC_CTRL_0_REG);
 val &= ~(MVPP2_GMAC_PORT_EN_MASK);
 writel(val, port->base + MVPP2_GMAC_CTRL_0_REG);
}

/* Set IEEE 802.3x Flow Control Xon Packet Transmission Mode */
static void mvpp2_port_periodic_xon_disable(struct mvpp2_port *port)
{
 u32 val;

 val = readl(port->base + MVPP2_GMAC_CTRL_1_REG) &
      ~MVPP2_GMAC_PERIODIC_XON_EN_MASK;
 writel(val, port->base + MVPP2_GMAC_CTRL_1_REG);
}

/* Configure loopback port */
static void mvpp2_port_loopback_set(struct mvpp2_port *port,
        const struct phylink_link_state *state)
{
 u32 val;

 val = readl(port->base + MVPP2_GMAC_CTRL_1_REG);

 if (state->speed == 1000)
  val |= MVPP2_GMAC_GMII_LB_EN_MASK;
 else
  val &= ~MVPP2_GMAC_GMII_LB_EN_MASK;

 if (phy_interface_mode_is_8023z(state->interface) ||
     state->interface == PHY_INTERFACE_MODE_SGMII)
  val |= MVPP2_GMAC_PCS_LB_EN_MASK;
 else
  val &= ~MVPP2_GMAC_PCS_LB_EN_MASK;

 writel(val, port->base + MVPP2_GMAC_CTRL_1_REG);
}

enum {
 ETHTOOL_XDP_REDIRECT,
 ETHTOOL_XDP_PASS,
 ETHTOOL_XDP_DROP,
 ETHTOOL_XDP_TX,
 ETHTOOL_XDP_TX_ERR,
 ETHTOOL_XDP_XMIT,
 ETHTOOL_XDP_XMIT_ERR,
};

struct mvpp2_ethtool_counter {
 unsigned int offset;
 const char string[ETH_GSTRING_LEN];
 bool reg_is_64b;
};

static u64 mvpp2_read_count(struct mvpp2_port *port,
       const struct mvpp2_ethtool_counter *counter)
{
 u64 val;

 val = readl(port->stats_base + counter->offset);
 if (counter->reg_is_64b)
  val += (u64)readl(port->stats_base + counter->offset + 4) << 32;

 return val;
}

/* Some counters are accessed indirectly by first writing an index to
 * MVPP2_CTRS_IDX. The index can represent various resources depending on the
 * register we access, it can be a hit counter for some classification tables,
 * a counter specific to a rxq, a txq or a buffer pool.
 */

static u32 mvpp2_read_index(struct mvpp2 *priv, u32 index, u32 reg)
{
 mvpp2_write(priv, MVPP2_CTRS_IDX, index);
 return mvpp2_read(priv, reg);
}

/* Due to the fact that software statistics and hardware statistics are, by
 * design, incremented at different moments in the chain of packet processing,
 * it is very likely that incoming packets could have been dropped after being
 * counted by hardware but before reaching software statistics (most probably
 * multicast packets), and in the opposite way, during transmission, FCS bytes
 * are added in between as well as TSO skb will be split and header bytes added.
 * Hence, statistics gathered from userspace with ifconfig (software) and
 * ethtool (hardware) cannot be compared.
 */

static const struct mvpp2_ethtool_counter mvpp2_ethtool_mib_regs[] = {
 { MVPP2_MIB_GOOD_OCTETS_RCVD, "good_octets_received"true },
 { MVPP2_MIB_BAD_OCTETS_RCVD, "bad_octets_received" },
 { MVPP2_MIB_CRC_ERRORS_SENT, "crc_errors_sent" },
 { MVPP2_MIB_UNICAST_FRAMES_RCVD, "unicast_frames_received" },
 { MVPP2_MIB_BROADCAST_FRAMES_RCVD, "broadcast_frames_received" },
 { MVPP2_MIB_MULTICAST_FRAMES_RCVD, "multicast_frames_received" },
 { MVPP2_MIB_FRAMES_64_OCTETS, "frames_64_octets" },
 { MVPP2_MIB_FRAMES_65_TO_127_OCTETS, "frames_65_to_127_octet" },
 { MVPP2_MIB_FRAMES_128_TO_255_OCTETS, "frames_128_to_255_octet" },
 { MVPP2_MIB_FRAMES_256_TO_511_OCTETS, "frames_256_to_511_octet" },
 { MVPP2_MIB_FRAMES_512_TO_1023_OCTETS, "frames_512_to_1023_octet" },
 { MVPP2_MIB_FRAMES_1024_TO_MAX_OCTETS, "frames_1024_to_max_octet" },
 { MVPP2_MIB_GOOD_OCTETS_SENT, "good_octets_sent"true },
 { MVPP2_MIB_UNICAST_FRAMES_SENT, "unicast_frames_sent" },
 { MVPP2_MIB_MULTICAST_FRAMES_SENT, "multicast_frames_sent" },
 { MVPP2_MIB_BROADCAST_FRAMES_SENT, "broadcast_frames_sent" },
 { MVPP2_MIB_FC_SENT, "fc_sent" },
 { MVPP2_MIB_FC_RCVD, "fc_received" },
 { MVPP2_MIB_RX_FIFO_OVERRUN, "rx_fifo_overrun" },
 { MVPP2_MIB_UNDERSIZE_RCVD, "undersize_received" },
 { MVPP2_MIB_FRAGMENTS_RCVD, "fragments_received" },
 { MVPP2_MIB_OVERSIZE_RCVD, "oversize_received" },
 { MVPP2_MIB_JABBER_RCVD, "jabber_received" },
 { MVPP2_MIB_MAC_RCV_ERROR, "mac_receive_error" },
 { MVPP2_MIB_BAD_CRC_EVENT, "bad_crc_event" },
 { MVPP2_MIB_COLLISION, "collision" },
 { MVPP2_MIB_LATE_COLLISION, "late_collision" },
};

static const struct mvpp2_ethtool_counter mvpp2_ethtool_port_regs[] = {
 { MVPP2_OVERRUN_ETH_DROP, "rx_fifo_or_parser_overrun_drops" },
 { MVPP2_CLS_ETH_DROP, "rx_classifier_drops" },
};

static const struct mvpp2_ethtool_counter mvpp2_ethtool_txq_regs[] = {
 { MVPP2_TX_DESC_ENQ_CTR, "txq_%d_desc_enqueue" },
 { MVPP2_TX_DESC_ENQ_TO_DDR_CTR, "txq_%d_desc_enqueue_to_ddr" },
 { MVPP2_TX_BUFF_ENQ_TO_DDR_CTR, "txq_%d_buff_euqueue_to_ddr" },
 { MVPP2_TX_DESC_ENQ_HW_FWD_CTR, "txq_%d_desc_hardware_forwarded" },
 { MVPP2_TX_PKTS_DEQ_CTR, "txq_%d_packets_dequeued" },
 { MVPP2_TX_PKTS_FULL_QUEUE_DROP_CTR, "txq_%d_queue_full_drops" },
 { MVPP2_TX_PKTS_EARLY_DROP_CTR, "txq_%d_packets_early_drops" },
 { MVPP2_TX_PKTS_BM_DROP_CTR, "txq_%d_packets_bm_drops" },
 { MVPP2_TX_PKTS_BM_MC_DROP_CTR, "txq_%d_packets_rep_bm_drops" },
};

static const struct mvpp2_ethtool_counter mvpp2_ethtool_rxq_regs[] = {
 { MVPP2_RX_DESC_ENQ_CTR, "rxq_%d_desc_enqueue" },
 { MVPP2_RX_PKTS_FULL_QUEUE_DROP_CTR, "rxq_%d_queue_full_drops" },
 { MVPP2_RX_PKTS_EARLY_DROP_CTR, "rxq_%d_packets_early_drops" },
 { MVPP2_RX_PKTS_BM_DROP_CTR, "rxq_%d_packets_bm_drops" },
};

static const struct mvpp2_ethtool_counter mvpp2_ethtool_xdp[] = {
 { ETHTOOL_XDP_REDIRECT, "rx_xdp_redirect", },
 { ETHTOOL_XDP_PASS, "rx_xdp_pass", },
 { ETHTOOL_XDP_DROP, "rx_xdp_drop", },
 { ETHTOOL_XDP_TX, "rx_xdp_tx", },
 { ETHTOOL_XDP_TX_ERR, "rx_xdp_tx_errors", },
 { ETHTOOL_XDP_XMIT, "tx_xdp_xmit", },
 { ETHTOOL_XDP_XMIT_ERR, "tx_xdp_xmit_errors", },
};

#define MVPP2_N_ETHTOOL_STATS(ntxqs, nrxqs) (ARRAY_SIZE(mvpp2_ethtool_mib_regs) + \
       ARRAY_SIZE(mvpp2_ethtool_port_regs) + \
       (ARRAY_SIZE(mvpp2_ethtool_txq_regs) * (ntxqs)) + \
       (ARRAY_SIZE(mvpp2_ethtool_rxq_regs) * (nrxqs)) + \
       ARRAY_SIZE(mvpp2_ethtool_xdp))

static void mvpp2_ethtool_get_strings(struct net_device *netdev, u32 sset,
          u8 *data)
{
 struct mvpp2_port *port = netdev_priv(netdev);
 const char *str;
 int i, q;

 if (sset != ETH_SS_STATS)
  return;

 for (i = 0; i < ARRAY_SIZE(mvpp2_ethtool_mib_regs); i++)
  ethtool_puts(&data, mvpp2_ethtool_mib_regs[i].string);

 for (i = 0; i < ARRAY_SIZE(mvpp2_ethtool_port_regs); i++)
  ethtool_puts(&data, mvpp2_ethtool_port_regs[i].string);

 for (q = 0; q < port->ntxqs; q++)
  for (i = 0; i < ARRAY_SIZE(mvpp2_ethtool_txq_regs); i++) {
   str = mvpp2_ethtool_txq_regs[i].string;
   ethtool_sprintf(&data, str, q);
  }

 for (q = 0; q < port->nrxqs; q++)
  for (i = 0; i < ARRAY_SIZE(mvpp2_ethtool_rxq_regs); i++) {
   str = mvpp2_ethtool_rxq_regs[i].string;
   ethtool_sprintf(&data, str, q);
  }

 for (i = 0; i < ARRAY_SIZE(mvpp2_ethtool_xdp); i++)
  ethtool_puts(&data, mvpp2_ethtool_xdp[i].string);
}

static void
mvpp2_get_xdp_stats(struct mvpp2_port *port, struct mvpp2_pcpu_stats *xdp_stats)
{
 unsigned int start;
 unsigned int cpu;

 /* Gather XDP Statistics */
 for_each_possible_cpu(cpu) {
  struct mvpp2_pcpu_stats *cpu_stats;
  u64 xdp_redirect;
  u64 xdp_pass;
  u64 xdp_drop;
  u64 xdp_xmit;
  u64 xdp_xmit_err;
  u64 xdp_tx;
  u64 xdp_tx_err;

  cpu_stats = per_cpu_ptr(port->stats, cpu);
  do {
   start = u64_stats_fetch_begin(&cpu_stats->syncp);
   xdp_redirect = cpu_stats->xdp_redirect;
   xdp_pass   = cpu_stats->xdp_pass;
   xdp_drop = cpu_stats->xdp_drop;
   xdp_xmit   = cpu_stats->xdp_xmit;
   xdp_xmit_err   = cpu_stats->xdp_xmit_err;
   xdp_tx   = cpu_stats->xdp_tx;
   xdp_tx_err   = cpu_stats->xdp_tx_err;
  } while (u64_stats_fetch_retry(&cpu_stats->syncp, start));

  xdp_stats->xdp_redirect += xdp_redirect;
  xdp_stats->xdp_pass   += xdp_pass;
  xdp_stats->xdp_drop += xdp_drop;
  xdp_stats->xdp_xmit   += xdp_xmit;
  xdp_stats->xdp_xmit_err   += xdp_xmit_err;
  xdp_stats->xdp_tx   += xdp_tx;
  xdp_stats->xdp_tx_err   += xdp_tx_err;
 }
}

static void mvpp2_read_stats(struct mvpp2_port *port)
{
 struct mvpp2_pcpu_stats xdp_stats = {};
 const struct mvpp2_ethtool_counter *s;
 u64 *pstats;
 int i, q;

 pstats = port->ethtool_stats;

 for (i = 0; i < ARRAY_SIZE(mvpp2_ethtool_mib_regs); i++)
  *pstats++ += mvpp2_read_count(port, &mvpp2_ethtool_mib_regs[i]);

 for (i = 0; i < ARRAY_SIZE(mvpp2_ethtool_port_regs); i++)
  *pstats++ += mvpp2_read(port->priv,
     mvpp2_ethtool_port_regs[i].offset +
     4 * port->id);

 for (q = 0; q < port->ntxqs; q++)
  for (i = 0; i < ARRAY_SIZE(mvpp2_ethtool_txq_regs); i++)
   *pstats++ += mvpp2_read_index(port->priv,
            MVPP22_CTRS_TX_CTR(port->id, q),
            mvpp2_ethtool_txq_regs[i].offset);

 /* Rxqs are numbered from 0 from the user standpoint, but not from the
 * driver's. We need to add the  port->first_rxq offset.
 */

 for (q = 0; q < port->nrxqs; q++)
  for (i = 0; i < ARRAY_SIZE(mvpp2_ethtool_rxq_regs); i++)
   *pstats++ += mvpp2_read_index(port->priv,
            port->first_rxq + q,
            mvpp2_ethtool_rxq_regs[i].offset);

 /* Gather XDP Statistics */
 mvpp2_get_xdp_stats(port, &xdp_stats);

 for (i = 0, s = mvpp2_ethtool_xdp;
   s < mvpp2_ethtool_xdp + ARRAY_SIZE(mvpp2_ethtool_xdp);
      s++, i++) {
  switch (s->offset) {
  case ETHTOOL_XDP_REDIRECT:
   *pstats++ = xdp_stats.xdp_redirect;
   break;
  case ETHTOOL_XDP_PASS:
   *pstats++ = xdp_stats.xdp_pass;
   break;
  case ETHTOOL_XDP_DROP:
   *pstats++ = xdp_stats.xdp_drop;
   break;
  case ETHTOOL_XDP_TX:
   *pstats++ = xdp_stats.xdp_tx;
   break;
  case ETHTOOL_XDP_TX_ERR:
   *pstats++ = xdp_stats.xdp_tx_err;
   break;
  case ETHTOOL_XDP_XMIT:
   *pstats++ = xdp_stats.xdp_xmit;
   break;
  case ETHTOOL_XDP_XMIT_ERR:
   *pstats++ = xdp_stats.xdp_xmit_err;
   break;
  }
 }
}

static void mvpp2_gather_hw_statistics(struct work_struct *work)
{
 struct delayed_work *del_work = to_delayed_work(work);
 struct mvpp2_port *port = container_of(del_work, struct mvpp2_port,
            stats_work);

 mutex_lock(&port->gather_stats_lock);

 mvpp2_read_stats(port);

 /* No need to read again the counters right after this function if it
 * was called asynchronously by the user (ie. use of ethtool).
 */

 cancel_delayed_work(&port->stats_work);
 queue_delayed_work(port->priv->stats_queue, &port->stats_work,
      MVPP2_MIB_COUNTERS_STATS_DELAY);

 mutex_unlock(&port->gather_stats_lock);
}

static void mvpp2_ethtool_get_stats(struct net_device *dev,
        struct ethtool_stats *stats, u64 *data)
{
 struct mvpp2_port *port = netdev_priv(dev);

 /* Update statistics for the given port, then take the lock to avoid
 * concurrent accesses on the ethtool_stats structure during its copy.
 */

 mvpp2_gather_hw_statistics(&port->stats_work.work);

 mutex_lock(&port->gather_stats_lock);
 memcpy(data, port->ethtool_stats,
        sizeof(u64) * MVPP2_N_ETHTOOL_STATS(port->ntxqs, port->nrxqs));
 mutex_unlock(&port->gather_stats_lock);
}

static int mvpp2_ethtool_get_sset_count(struct net_device *dev, int sset)
{
 struct mvpp2_port *port = netdev_priv(dev);

 if (sset == ETH_SS_STATS)
  return MVPP2_N_ETHTOOL_STATS(port->ntxqs, port->nrxqs);

 return -EOPNOTSUPP;
}

static void mvpp2_mac_reset_assert(struct mvpp2_port *port)
{
 u32 val;

 val = readl(port->base + MVPP2_GMAC_CTRL_2_REG) |
       MVPP2_GMAC_PORT_RESET_MASK;
 writel(val, port->base + MVPP2_GMAC_CTRL_2_REG);

 if (port->priv->hw_version >= MVPP22 && port->gop_id == 0) {
  val = readl(port->base + MVPP22_XLG_CTRL0_REG) &
        ~MVPP22_XLG_CTRL0_MAC_RESET_DIS;
  writel(val, port->base + MVPP22_XLG_CTRL0_REG);
 }
}

static void mvpp22_pcs_reset_assert(struct mvpp2_port *port)
{
 struct mvpp2 *priv = port->priv;
 void __iomem *mpcs, *xpcs;
 u32 val;

 if (port->priv->hw_version == MVPP21 || port->gop_id != 0)
  return;

 mpcs = priv->iface_base + MVPP22_MPCS_BASE(port->gop_id);
 xpcs = priv->iface_base + MVPP22_XPCS_BASE(port->gop_id);

 val = readl(mpcs + MVPP22_MPCS_CLK_RESET);
 val &= ~(MAC_CLK_RESET_MAC | MAC_CLK_RESET_SD_RX | MAC_CLK_RESET_SD_TX);
 val |= MVPP22_MPCS_CLK_RESET_DIV_SET;
 writel(val, mpcs + MVPP22_MPCS_CLK_RESET);

 val = readl(xpcs + MVPP22_XPCS_CFG0);
 writel(val & ~MVPP22_XPCS_CFG0_RESET_DIS, xpcs + MVPP22_XPCS_CFG0);
}

static void mvpp22_pcs_reset_deassert(struct mvpp2_port *port,
          phy_interface_t interface)
{
 struct mvpp2 *priv = port->priv;
 void __iomem *mpcs, *xpcs;
 u32 val;

 if (port->priv->hw_version == MVPP21 || port->gop_id != 0)
  return;

 mpcs = priv->iface_base + MVPP22_MPCS_BASE(port->gop_id);
 xpcs = priv->iface_base + MVPP22_XPCS_BASE(port->gop_id);

 switch (interface) {
 case PHY_INTERFACE_MODE_5GBASER:
 case PHY_INTERFACE_MODE_10GBASER:
  val = readl(mpcs + MVPP22_MPCS_CLK_RESET);
  val |= MAC_CLK_RESET_MAC | MAC_CLK_RESET_SD_RX |
         MAC_CLK_RESET_SD_TX;
  val &= ~MVPP22_MPCS_CLK_RESET_DIV_SET;
  writel(val, mpcs + MVPP22_MPCS_CLK_RESET);
  break;
 case PHY_INTERFACE_MODE_XAUI:
 case PHY_INTERFACE_MODE_RXAUI:
  val = readl(xpcs + MVPP22_XPCS_CFG0);
  writel(val | MVPP22_XPCS_CFG0_RESET_DIS, xpcs + MVPP22_XPCS_CFG0);
  break;
 default:
  break;
 }
}

/* Change maximum receive size of the port */
static inline void mvpp2_gmac_max_rx_size_set(struct mvpp2_port *port)
{
 u32 val;

 val = readl(port->base + MVPP2_GMAC_CTRL_0_REG);
 val &= ~MVPP2_GMAC_MAX_RX_SIZE_MASK;
 val |= (((port->pkt_size - MVPP2_MH_SIZE) / 2) <<
      MVPP2_GMAC_MAX_RX_SIZE_OFFS);
 writel(val, port->base + MVPP2_GMAC_CTRL_0_REG);
}

/* Change maximum receive size of the port */
static inline void mvpp2_xlg_max_rx_size_set(struct mvpp2_port *port)
{
 u32 val;

 val =  readl(port->base + MVPP22_XLG_CTRL1_REG);
 val &= ~MVPP22_XLG_CTRL1_FRAMESIZELIMIT_MASK;
 val |= ((port->pkt_size - MVPP2_MH_SIZE) / 2) <<
        MVPP22_XLG_CTRL1_FRAMESIZELIMIT_OFFS;
 writel(val, port->base + MVPP22_XLG_CTRL1_REG);
}

/* Set defaults to the MVPP2 port */
static void mvpp2_defaults_set(struct mvpp2_port *port)
{
 int tx_port_num, val, queue, lrxq;

 if (port->priv->hw_version == MVPP21) {
  /* Update TX FIFO MIN Threshold */
  val = readl(port->base + MVPP2_GMAC_PORT_FIFO_CFG_1_REG);
  val &= ~MVPP2_GMAC_TX_FIFO_MIN_TH_ALL_MASK;
  /* Min. TX threshold must be less than minimal packet length */
  val |= MVPP2_GMAC_TX_FIFO_MIN_TH_MASK(64 - 4 - 2);
  writel(val, port->base + MVPP2_GMAC_PORT_FIFO_CFG_1_REG);
 }

 /* Disable Legacy WRR, Disable EJP, Release from reset */
 tx_port_num = mvpp2_egress_port(port);
 mvpp2_write(port->priv, MVPP2_TXP_SCHED_PORT_INDEX_REG,
      tx_port_num);
 mvpp2_write(port->priv, MVPP2_TXP_SCHED_CMD_1_REG, 0);

 /* Set TXQ scheduling to Round-Robin */
 mvpp2_write(port->priv, MVPP2_TXP_SCHED_FIXED_PRIO_REG, 0);

 /* Close bandwidth for all queues */
--> --------------------

--> maximum size reached

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

Messung V0.5
C=96 H=93 G=94

¤ 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.0.33Bemerkung:  (vorverarbeitet)  ¤

*Bot Zugriff






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.