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

Quelle  vmxnet3_drv.c   Sprache: C

 
/*
 * Linux driver for VMware's vmxnet3 ethernet NIC.
 *
 * Copyright (C) 2008-2024, VMware, Inc. All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; version 2 of the License and no later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
 * NON INFRINGEMENT. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * The full GNU General Public License is included in this distribution in
 * the file called "COPYING".
 *
 * Maintained by: pv-drivers@vmware.com
 *
 */


#include <linux/module.h>
#include <net/ip6_checksum.h>

#ifdef CONFIG_X86
#include <asm/msr.h>
#endif

#include "vmxnet3_int.h"
#include "vmxnet3_xdp.h"

char vmxnet3_driver_name[] = "vmxnet3";
#define VMXNET3_DRIVER_DESC "VMware vmxnet3 virtual NIC driver"

/*
 * PCI Device ID Table
 * Last entry must be all 0s
 */

static const struct pci_device_id vmxnet3_pciid_table[] = {
 {PCI_VDEVICE(VMWARE, PCI_DEVICE_ID_VMWARE_VMXNET3)},
 {0}
};

MODULE_DEVICE_TABLE(pci, vmxnet3_pciid_table);

static int enable_mq = 1;

static void
vmxnet3_write_mac_addr(struct vmxnet3_adapter *adapter, const u8 *mac);

/*
 *    Enable/Disable the given intr
 */

static void
vmxnet3_enable_intr(struct vmxnet3_adapter *adapter, unsigned intr_idx)
{
 VMXNET3_WRITE_BAR0_REG(adapter, VMXNET3_REG_IMR + intr_idx * 8, 0);
}


static void
vmxnet3_disable_intr(struct vmxnet3_adapter *adapter, unsigned intr_idx)
{
 VMXNET3_WRITE_BAR0_REG(adapter, VMXNET3_REG_IMR + intr_idx * 8, 1);
}


/*
 *    Enable/Disable all intrs used by the device
 */

static void
vmxnet3_enable_all_intrs(struct vmxnet3_adapter *adapter)
{
 int i;

 for (i = 0; i < adapter->intr.num_intrs; i++)
  vmxnet3_enable_intr(adapter, i);
 if (!VMXNET3_VERSION_GE_6(adapter) ||
     !adapter->queuesExtEnabled) {
  adapter->shared->devRead.intrConf.intrCtrl &=
     cpu_to_le32(~VMXNET3_IC_DISABLE_ALL);
 } else {
  adapter->shared->devReadExt.intrConfExt.intrCtrl &=
     cpu_to_le32(~VMXNET3_IC_DISABLE_ALL);
 }
}


static void
vmxnet3_disable_all_intrs(struct vmxnet3_adapter *adapter)
{
 int i;

 if (!VMXNET3_VERSION_GE_6(adapter) ||
     !adapter->queuesExtEnabled) {
  adapter->shared->devRead.intrConf.intrCtrl |=
     cpu_to_le32(VMXNET3_IC_DISABLE_ALL);
 } else {
  adapter->shared->devReadExt.intrConfExt.intrCtrl |=
     cpu_to_le32(VMXNET3_IC_DISABLE_ALL);
 }
 for (i = 0; i < adapter->intr.num_intrs; i++)
  vmxnet3_disable_intr(adapter, i);
}


static void
vmxnet3_ack_events(struct vmxnet3_adapter *adapter, u32 events)
{
 VMXNET3_WRITE_BAR1_REG(adapter, VMXNET3_REG_ECR, events);
}


static bool
vmxnet3_tq_stopped(struct vmxnet3_tx_queue *tq, struct vmxnet3_adapter *adapter)
{
 return tq->stopped;
}


static void
vmxnet3_tq_start(struct vmxnet3_tx_queue *tq, struct vmxnet3_adapter *adapter)
{
 tq->stopped = false;
 netif_start_subqueue(adapter->netdev, tq - adapter->tx_queue);
}


static void
vmxnet3_tq_wake(struct vmxnet3_tx_queue *tq, struct vmxnet3_adapter *adapter)
{
 tq->stopped = false;
 netif_wake_subqueue(adapter->netdev, (tq - adapter->tx_queue));
}


static void
vmxnet3_tq_stop(struct vmxnet3_tx_queue *tq, struct vmxnet3_adapter *adapter)
{
 tq->stopped = true;
 tq->num_stop++;
 netif_stop_subqueue(adapter->netdev, (tq - adapter->tx_queue));
}

static u64
vmxnet3_get_cycles(int pmc)
{
#ifdef CONFIG_X86
 return native_read_pmc(pmc);
#else
 return 0;
#endif
}

static bool
vmxnet3_apply_timestamp(struct vmxnet3_tx_queue *tq, u16 rate)
{
#ifdef CONFIG_X86
 if (rate > 0) {
  if (tq->tsPktCount == 1) {
   if (rate != 1)
    tq->tsPktCount = rate;
   return true;
  }
  tq->tsPktCount--;
 }
#endif
 return false;
}

/* Check if capability is supported by UPT device or
 * UPT is even requested
 */

bool
vmxnet3_check_ptcapability(u32 cap_supported, u32 cap)
{
 if (cap_supported & (1UL << VMXNET3_DCR_ERROR) ||
     cap_supported & (1UL << cap)) {
  return true;
 }

 return false;
}


/*
 * Check the link state. This may start or stop the tx queue.
 */

static void
vmxnet3_check_link(struct vmxnet3_adapter *adapter, bool affectTxQueue)
{
 u32 ret;
 int i;
 unsigned long flags;

 spin_lock_irqsave(&adapter->cmd_lock, flags);
 VMXNET3_WRITE_BAR1_REG(adapter, VMXNET3_REG_CMD, VMXNET3_CMD_GET_LINK);
 ret = VMXNET3_READ_BAR1_REG(adapter, VMXNET3_REG_CMD);
 spin_unlock_irqrestore(&adapter->cmd_lock, flags);

 adapter->link_speed = ret >> 16;
 if (ret & 1) { /* Link is up. */
  /*
 * From vmxnet3 v9, the hypervisor reports the speed in Gbps.
 * Convert the speed to Mbps before rporting it to the kernel.
 * Max link speed supported is 10000G.
 */

  if (VMXNET3_VERSION_GE_9(adapter) &&
      adapter->link_speed < 10000)
   adapter->link_speed = adapter->link_speed * 1000;
  netdev_info(adapter->netdev, "NIC Link is Up %d Mbps\n",
       adapter->link_speed);
  netif_carrier_on(adapter->netdev);

  if (affectTxQueue) {
   for (i = 0; i < adapter->num_tx_queues; i++)
    vmxnet3_tq_start(&adapter->tx_queue[i],
       adapter);
  }
 } else {
  netdev_info(adapter->netdev, "NIC Link is Down\n");
  netif_carrier_off(adapter->netdev);

  if (affectTxQueue) {
   for (i = 0; i < adapter->num_tx_queues; i++)
    vmxnet3_tq_stop(&adapter->tx_queue[i], adapter);
  }
 }
}

static void
vmxnet3_process_events(struct vmxnet3_adapter *adapter)
{
 int i;
 unsigned long flags;
 u32 events = le32_to_cpu(adapter->shared->ecr);
 if (!events)
  return;

 vmxnet3_ack_events(adapter, events);

 /* Check if link state has changed */
 if (events & VMXNET3_ECR_LINK)
  vmxnet3_check_link(adapter, true);

 /* Check if there is an error on xmit/recv queues */
 if (events & (VMXNET3_ECR_TQERR | VMXNET3_ECR_RQERR)) {
  spin_lock_irqsave(&adapter->cmd_lock, flags);
  VMXNET3_WRITE_BAR1_REG(adapter, VMXNET3_REG_CMD,
           VMXNET3_CMD_GET_QUEUE_STATUS);
  spin_unlock_irqrestore(&adapter->cmd_lock, flags);

  for (i = 0; i < adapter->num_tx_queues; i++)
   if (adapter->tqd_start[i].status.stopped)
    dev_err(&adapter->netdev->dev,
     "%s: tq[%d] error 0x%x\n",
     adapter->netdev->name, i, le32_to_cpu(
     adapter->tqd_start[i].status.error));
  for (i = 0; i < adapter->num_rx_queues; i++)
   if (adapter->rqd_start[i].status.stopped)
    dev_err(&adapter->netdev->dev,
     "%s: rq[%d] error 0x%x\n",
     adapter->netdev->name, i,
     adapter->rqd_start[i].status.error);

  schedule_work(&adapter->work);
 }
}

#ifdef __BIG_ENDIAN_BITFIELD
/*
 * The device expects the bitfields in shared structures to be written in
 * little endian. When CPU is big endian, the following routines are used to
 * correctly read and write into ABI.
 * The general technique used here is : double word bitfields are defined in
 * opposite order for big endian architecture. Then before reading them in
 * driver the complete double word is translated using le32_to_cpu. Similarly
 * After the driver writes into bitfields, cpu_to_le32 is used to translate the
 * double words into required format.
 * In order to avoid touching bits in shared structure more than once, temporary
 * descriptors are used. These are passed as srcDesc to following functions.
 */

static void vmxnet3_RxDescToCPU(const struct Vmxnet3_RxDesc *srcDesc,
    struct Vmxnet3_RxDesc *dstDesc)
{
 u32 *src = (u32 *)srcDesc + 2;
 u32 *dst = (u32 *)dstDesc + 2;
 dstDesc->addr = le64_to_cpu(srcDesc->addr);
 *dst = le32_to_cpu(*src);
 dstDesc->ext1 = le32_to_cpu(srcDesc->ext1);
}

static void vmxnet3_TxDescToLe(const struct Vmxnet3_TxDesc *srcDesc,
          struct Vmxnet3_TxDesc *dstDesc)
{
 int i;
 u32 *src = (u32 *)(srcDesc + 1);
 u32 *dst = (u32 *)(dstDesc + 1);

 /* Working backwards so that the gen bit is set at the end. */
 for (i = 2; i > 0; i--) {
  src--;
  dst--;
  *dst = cpu_to_le32(*src);
 }
}


static void vmxnet3_RxCompToCPU(const struct Vmxnet3_RxCompDesc *srcDesc,
    struct Vmxnet3_RxCompDesc *dstDesc)
{
 int i = 0;
 u32 *src = (u32 *)srcDesc;
 u32 *dst = (u32 *)dstDesc;
 for (i = 0; i < sizeof(struct Vmxnet3_RxCompDesc) / sizeof(u32); i++) {
  *dst = le32_to_cpu(*src);
  src++;
  dst++;
 }
}


/* Used to read bitfield values from double words. */
static u32 get_bitfield32(const __le32 *bitfield, u32 pos, u32 size)
{
 u32 temp = le32_to_cpu(*bitfield);
 u32 mask = ((1 << size) - 1) << pos;
 temp &= mask;
 temp >>= pos;
 return temp;
}



#endif  /* __BIG_ENDIAN_BITFIELD */

#ifdef __BIG_ENDIAN_BITFIELD

#   define VMXNET3_TXDESC_GET_GEN(txdesc) get_bitfield32(((const __le32 *) \
   txdesc) + VMXNET3_TXD_GEN_DWORD_SHIFT, \
   VMXNET3_TXD_GEN_SHIFT, VMXNET3_TXD_GEN_SIZE)
#   define VMXNET3_TXDESC_GET_EOP(txdesc) get_bitfield32(((const __le32 *) \
   txdesc) + VMXNET3_TXD_EOP_DWORD_SHIFT, \
   VMXNET3_TXD_EOP_SHIFT, VMXNET3_TXD_EOP_SIZE)
#   define VMXNET3_TCD_GET_GEN(tcd) get_bitfield32(((const __le32 *)tcd) + \
   VMXNET3_TCD_GEN_DWORD_SHIFT, VMXNET3_TCD_GEN_SHIFT, \
   VMXNET3_TCD_GEN_SIZE)
#   define VMXNET3_TCD_GET_TXIDX(tcd) get_bitfield32((const __le32 *)tcd, \
   VMXNET3_TCD_TXIDX_SHIFT, VMXNET3_TCD_TXIDX_SIZE)
#   define vmxnet3_getRxComp(dstrcd, rcd, tmp) do { \
   (dstrcd) = (tmp); \
   vmxnet3_RxCompToCPU((rcd), (tmp)); \
  } while (0)
#   define vmxnet3_getRxDesc(dstrxd, rxd, tmp) do { \
   (dstrxd) = (tmp); \
   vmxnet3_RxDescToCPU((rxd), (tmp)); \
  } while (0)

#else

#   define VMXNET3_TXDESC_GET_GEN(txdesc) ((txdesc)->gen)
#   define VMXNET3_TXDESC_GET_EOP(txdesc) ((txdesc)->eop)
#   define VMXNET3_TCD_GET_GEN(tcd) ((tcd)->gen)
#   define VMXNET3_TCD_GET_TXIDX(tcd) ((tcd)->txdIdx)
#   define vmxnet3_getRxComp(dstrcd, rcd, tmp) (dstrcd) = (rcd)
#   define vmxnet3_getRxDesc(dstrxd, rxd, tmp) (dstrxd) = (rxd)

#endif /* __BIG_ENDIAN_BITFIELD  */


static void
vmxnet3_unmap_tx_buf(struct vmxnet3_tx_buf_info *tbi,
       struct pci_dev *pdev)
{
 u32 map_type = tbi->map_type;

 if (map_type & VMXNET3_MAP_SINGLE)
  dma_unmap_single(&pdev->dev, tbi->dma_addr, tbi->len,
     DMA_TO_DEVICE);
 else if (map_type & VMXNET3_MAP_PAGE)
  dma_unmap_page(&pdev->dev, tbi->dma_addr, tbi->len,
          DMA_TO_DEVICE);
 else
  BUG_ON(map_type & ~VMXNET3_MAP_XDP);

 tbi->map_type = VMXNET3_MAP_NONE; /* to help debugging */
}


static int
vmxnet3_unmap_pkt(u32 eop_idx, struct vmxnet3_tx_queue *tq,
    struct pci_dev *pdev, struct vmxnet3_adapter *adapter,
    struct xdp_frame_bulk *bq)
{
 struct vmxnet3_tx_buf_info *tbi;
 int entries = 0;
 u32 map_type;

 /* no out of order completion */
 BUG_ON(tq->buf_info[eop_idx].sop_idx != tq->tx_ring.next2comp);
 BUG_ON(VMXNET3_TXDESC_GET_EOP(&(tq->tx_ring.base[eop_idx].txd)) != 1);

 tbi = &tq->buf_info[eop_idx];
 BUG_ON(!tbi->skb);
 map_type = tbi->map_type;
 VMXNET3_INC_RING_IDX_ONLY(eop_idx, tq->tx_ring.size);

 while (tq->tx_ring.next2comp != eop_idx) {
  vmxnet3_unmap_tx_buf(tq->buf_info + tq->tx_ring.next2comp,
         pdev);

  /* update next2comp w/o tx_lock. Since we are marking more,
 * instead of less, tx ring entries avail, the worst case is
 * that the tx routine incorrectly re-queues a pkt due to
 * insufficient tx ring entries.
 */

  vmxnet3_cmd_ring_adv_next2comp(&tq->tx_ring);
  entries++;
 }

 if (map_type & VMXNET3_MAP_XDP)
  xdp_return_frame_bulk(tbi->xdpf, bq);
 else
  dev_kfree_skb_any(tbi->skb);

 /* xdpf and skb are in an anonymous union. */
 tbi->skb = NULL;

 return entries;
}


static int
vmxnet3_tq_tx_complete(struct vmxnet3_tx_queue *tq,
   struct vmxnet3_adapter *adapter)
{
 union Vmxnet3_GenericDesc *gdesc;
 struct xdp_frame_bulk bq;
 int completed = 0;

 xdp_frame_bulk_init(&bq);
 rcu_read_lock();

 gdesc = tq->comp_ring.base + tq->comp_ring.next2proc;
 while (VMXNET3_TCD_GET_GEN(&gdesc->tcd) == tq->comp_ring.gen) {
  /* Prevent any &gdesc->tcd field from being (speculatively)
 * read before (&gdesc->tcd)->gen is read.
 */

  dma_rmb();

  completed += vmxnet3_unmap_pkt(VMXNET3_TCD_GET_TXIDX(
            &gdesc->tcd), tq, adapter->pdev,
            adapter, &bq);

  vmxnet3_comp_ring_adv_next2proc(&tq->comp_ring);
  gdesc = tq->comp_ring.base + tq->comp_ring.next2proc;
 }
 xdp_flush_frame_bulk(&bq);
 rcu_read_unlock();

 if (completed) {
  spin_lock(&tq->tx_lock);
  if (unlikely(vmxnet3_tq_stopped(tq, adapter) &&
        vmxnet3_cmd_ring_desc_avail(&tq->tx_ring) >
        VMXNET3_WAKE_QUEUE_THRESHOLD(tq) &&
        netif_carrier_ok(adapter->netdev))) {
   vmxnet3_tq_wake(tq, adapter);
  }
  spin_unlock(&tq->tx_lock);
 }
 return completed;
}


static void
vmxnet3_tq_cleanup(struct vmxnet3_tx_queue *tq,
     struct vmxnet3_adapter *adapter)
{
 struct xdp_frame_bulk bq;
 u32 map_type;
 int i;

 xdp_frame_bulk_init(&bq);
 rcu_read_lock();

 while (tq->tx_ring.next2comp != tq->tx_ring.next2fill) {
  struct vmxnet3_tx_buf_info *tbi;

  tbi = tq->buf_info + tq->tx_ring.next2comp;
  map_type = tbi->map_type;

  vmxnet3_unmap_tx_buf(tbi, adapter->pdev);
  if (tbi->skb) {
   if (map_type & VMXNET3_MAP_XDP)
    xdp_return_frame_bulk(tbi->xdpf, &bq);
   else
    dev_kfree_skb_any(tbi->skb);
   tbi->skb = NULL;
  }
  vmxnet3_cmd_ring_adv_next2comp(&tq->tx_ring);
 }

 xdp_flush_frame_bulk(&bq);
 rcu_read_unlock();

 /* sanity check, verify all buffers are indeed unmapped */
 for (i = 0; i < tq->tx_ring.size; i++)
  BUG_ON(tq->buf_info[i].map_type != VMXNET3_MAP_NONE);

 tq->tx_ring.gen = VMXNET3_INIT_GEN;
 tq->tx_ring.next2fill = tq->tx_ring.next2comp = 0;

 tq->comp_ring.gen = VMXNET3_INIT_GEN;
 tq->comp_ring.next2proc = 0;
}


static void
vmxnet3_tq_destroy(struct vmxnet3_tx_queue *tq,
     struct vmxnet3_adapter *adapter)
{
 if (tq->tx_ring.base) {
  dma_free_coherent(&adapter->pdev->dev, tq->tx_ring.size *
      sizeof(struct Vmxnet3_TxDesc),
      tq->tx_ring.base, tq->tx_ring.basePA);
  tq->tx_ring.base = NULL;
 }
 if (tq->data_ring.base) {
  dma_free_coherent(&adapter->pdev->dev,
      tq->data_ring.size * tq->txdata_desc_size,
      tq->data_ring.base, tq->data_ring.basePA);
  tq->data_ring.base = NULL;
 }
 if (tq->ts_ring.base) {
  dma_free_coherent(&adapter->pdev->dev,
      tq->tx_ring.size * tq->tx_ts_desc_size,
      tq->ts_ring.base, tq->ts_ring.basePA);
  tq->ts_ring.base = NULL;
 }
 if (tq->comp_ring.base) {
  dma_free_coherent(&adapter->pdev->dev, tq->comp_ring.size *
      sizeof(struct Vmxnet3_TxCompDesc),
      tq->comp_ring.base, tq->comp_ring.basePA);
  tq->comp_ring.base = NULL;
 }
 kfree(tq->buf_info);
 tq->buf_info = NULL;
}


/* Destroy all tx queues */
void
vmxnet3_tq_destroy_all(struct vmxnet3_adapter *adapter)
{
 int i;

 for (i = 0; i < adapter->num_tx_queues; i++)
  vmxnet3_tq_destroy(&adapter->tx_queue[i], adapter);
}


static void
vmxnet3_tq_init(struct vmxnet3_tx_queue *tq,
  struct vmxnet3_adapter *adapter)
{
 int i;

 /* reset the tx ring contents to 0 and reset the tx ring states */
 memset(tq->tx_ring.base, 0, tq->tx_ring.size *
        sizeof(struct Vmxnet3_TxDesc));
 tq->tx_ring.next2fill = tq->tx_ring.next2comp = 0;
 tq->tx_ring.gen = VMXNET3_INIT_GEN;

 memset(tq->data_ring.base, 0,
        tq->data_ring.size * tq->txdata_desc_size);

 if (tq->ts_ring.base)
  memset(tq->ts_ring.base, 0,
         tq->tx_ring.size * tq->tx_ts_desc_size);

 /* reset the tx comp ring contents to 0 and reset comp ring states */
 memset(tq->comp_ring.base, 0, tq->comp_ring.size *
        sizeof(struct Vmxnet3_TxCompDesc));
 tq->comp_ring.next2proc = 0;
 tq->comp_ring.gen = VMXNET3_INIT_GEN;

 /* reset the bookkeeping data */
 memset(tq->buf_info, 0, sizeof(tq->buf_info[0]) * tq->tx_ring.size);
 for (i = 0; i < tq->tx_ring.size; i++)
  tq->buf_info[i].map_type = VMXNET3_MAP_NONE;

 /* stats are not reset */
}


static int
vmxnet3_tq_create(struct vmxnet3_tx_queue *tq,
    struct vmxnet3_adapter *adapter)
{
 BUG_ON(tq->tx_ring.base || tq->data_ring.base ||
        tq->comp_ring.base || tq->buf_info);

 tq->tx_ring.base = dma_alloc_coherent(&adapter->pdev->dev,
   tq->tx_ring.size * sizeof(struct Vmxnet3_TxDesc),
   &tq->tx_ring.basePA, GFP_KERNEL);
 if (!tq->tx_ring.base) {
  netdev_err(adapter->netdev, "failed to allocate tx ring\n");
  goto err;
 }

 tq->data_ring.base = dma_alloc_coherent(&adapter->pdev->dev,
   tq->data_ring.size * tq->txdata_desc_size,
   &tq->data_ring.basePA, GFP_KERNEL);
 if (!tq->data_ring.base) {
  netdev_err(adapter->netdev, "failed to allocate tx data ring\n");
  goto err;
 }

 if (tq->tx_ts_desc_size != 0) {
  tq->ts_ring.base = dma_alloc_coherent(&adapter->pdev->dev,
            tq->tx_ring.size * tq->tx_ts_desc_size,
            &tq->ts_ring.basePA, GFP_KERNEL);
  if (!tq->ts_ring.base) {
   netdev_err(adapter->netdev, "failed to allocate tx ts ring\n");
   tq->tx_ts_desc_size = 0;
  }
 } else {
  tq->ts_ring.base = NULL;
 }

 tq->comp_ring.base = dma_alloc_coherent(&adapter->pdev->dev,
   tq->comp_ring.size * sizeof(struct Vmxnet3_TxCompDesc),
   &tq->comp_ring.basePA, GFP_KERNEL);
 if (!tq->comp_ring.base) {
  netdev_err(adapter->netdev, "failed to allocate tx comp ring\n");
  goto err;
 }

 tq->buf_info = kcalloc_node(tq->tx_ring.size, sizeof(tq->buf_info[0]),
        GFP_KERNEL,
        dev_to_node(&adapter->pdev->dev));
 if (!tq->buf_info)
  goto err;

 return 0;

err:
 vmxnet3_tq_destroy(tq, adapter);
 return -ENOMEM;
}

static void
vmxnet3_tq_cleanup_all(struct vmxnet3_adapter *adapter)
{
 int i;

 for (i = 0; i < adapter->num_tx_queues; i++)
  vmxnet3_tq_cleanup(&adapter->tx_queue[i], adapter);
}

/*
 *    starting from ring->next2fill, allocate rx buffers for the given ring
 *    of the rx queue and update the rx desc. stop after @num_to_alloc buffers
 *    are allocated or allocation fails
 */


static int
vmxnet3_rq_alloc_rx_buf(struct vmxnet3_rx_queue *rq, u32 ring_idx,
   int num_to_alloc, struct vmxnet3_adapter *adapter)
{
 int num_allocated = 0;
 struct vmxnet3_rx_buf_info *rbi_base = rq->buf_info[ring_idx];
 struct vmxnet3_cmd_ring *ring = &rq->rx_ring[ring_idx];
 u32 val;

 while (num_allocated <= num_to_alloc) {
  struct vmxnet3_rx_buf_info *rbi;
  union Vmxnet3_GenericDesc *gd;

  rbi = rbi_base + ring->next2fill;
  gd = ring->base + ring->next2fill;
  rbi->comp_state = VMXNET3_RXD_COMP_PENDING;

  if (rbi->buf_type == VMXNET3_RX_BUF_XDP) {
   void *data = vmxnet3_pp_get_buff(rq->page_pool,
        &rbi->dma_addr,
        GFP_KERNEL);
   if (!data) {
    rq->stats.rx_buf_alloc_failure++;
    break;
   }
   rbi->page = virt_to_page(data);
   val = VMXNET3_RXD_BTYPE_HEAD << VMXNET3_RXD_BTYPE_SHIFT;
  } else if (rbi->buf_type == VMXNET3_RX_BUF_SKB) {
   if (rbi->skb == NULL) {
    rbi->skb = __netdev_alloc_skb_ip_align(adapter->netdev,
               rbi->len,
               GFP_KERNEL);
    if (unlikely(rbi->skb == NULL)) {
     rq->stats.rx_buf_alloc_failure++;
     break;
    }

    rbi->dma_addr = dma_map_single(
      &adapter->pdev->dev,
      rbi->skb->data, rbi->len,
      DMA_FROM_DEVICE);
    if (dma_mapping_error(&adapter->pdev->dev,
            rbi->dma_addr)) {
     dev_kfree_skb_any(rbi->skb);
     rbi->skb = NULL;
     rq->stats.rx_buf_alloc_failure++;
     break;
    }
   } else {
    /* rx buffer skipped by the device */
   }
   val = VMXNET3_RXD_BTYPE_HEAD << VMXNET3_RXD_BTYPE_SHIFT;
  } else {
   BUG_ON(rbi->buf_type != VMXNET3_RX_BUF_PAGE ||
          rbi->len  != PAGE_SIZE);

   if (rbi->page == NULL) {
    rbi->page = alloc_page(GFP_ATOMIC);
    if (unlikely(rbi->page == NULL)) {
     rq->stats.rx_buf_alloc_failure++;
     break;
    }
    rbi->dma_addr = dma_map_page(
      &adapter->pdev->dev,
      rbi->page, 0, PAGE_SIZE,
      DMA_FROM_DEVICE);
    if (dma_mapping_error(&adapter->pdev->dev,
            rbi->dma_addr)) {
     put_page(rbi->page);
     rbi->page = NULL;
     rq->stats.rx_buf_alloc_failure++;
     break;
    }
   } else {
    /* rx buffers skipped by the device */
   }
   val = VMXNET3_RXD_BTYPE_BODY << VMXNET3_RXD_BTYPE_SHIFT;
  }

  gd->rxd.addr = cpu_to_le64(rbi->dma_addr);
  gd->dword[2] = cpu_to_le32((!ring->gen << VMXNET3_RXD_GEN_SHIFT)
        | val | rbi->len);

  /* Fill the last buffer but dont mark it ready, or else the
 * device will think that the queue is full */

  if (num_allocated == num_to_alloc) {
   rbi->comp_state = VMXNET3_RXD_COMP_DONE;
   break;
  }

  gd->dword[2] |= cpu_to_le32(ring->gen << VMXNET3_RXD_GEN_SHIFT);
  num_allocated++;
  vmxnet3_cmd_ring_adv_next2fill(ring);
 }

 netdev_dbg(adapter->netdev,
  "alloc_rx_buf: %d allocated, next2fill %u, next2comp %u\n",
  num_allocated, ring->next2fill, ring->next2comp);

 /* so that the device can distinguish a full ring and an empty ring */
 BUG_ON(num_allocated != 0 && ring->next2fill == ring->next2comp);

 return num_allocated;
}


static void
vmxnet3_append_frag(struct sk_buff *skb, struct Vmxnet3_RxCompDesc *rcd,
      struct vmxnet3_rx_buf_info *rbi)
{
 skb_frag_t *frag = skb_shinfo(skb)->frags + skb_shinfo(skb)->nr_frags;

 BUG_ON(skb_shinfo(skb)->nr_frags >= MAX_SKB_FRAGS);

 skb_frag_fill_page_desc(frag, rbi->page, 0, rcd->len);
 skb->data_len += rcd->len;
 skb->truesize += PAGE_SIZE;
 skb_shinfo(skb)->nr_frags++;
}


static int
vmxnet3_map_pkt(struct sk_buff *skb, struct vmxnet3_tx_ctx *ctx,
  struct vmxnet3_tx_queue *tq, struct pci_dev *pdev,
  struct vmxnet3_adapter *adapter)
{
 u32 dw2, len;
 unsigned long buf_offset;
 int i;
 union Vmxnet3_GenericDesc *gdesc;
 struct vmxnet3_tx_buf_info *tbi = NULL;

 BUG_ON(ctx->copy_size > skb_headlen(skb));

 /* use the previous gen bit for the SOP desc */
 dw2 = (tq->tx_ring.gen ^ 0x1) << VMXNET3_TXD_GEN_SHIFT;

 ctx->sop_txd = tq->tx_ring.base + tq->tx_ring.next2fill;
 gdesc = ctx->sop_txd; /* both loops below can be skipped */

 /* no need to map the buffer if headers are copied */
 if (ctx->copy_size) {
  ctx->sop_txd->txd.addr = cpu_to_le64(tq->data_ring.basePA +
     tq->tx_ring.next2fill *
     tq->txdata_desc_size);
  ctx->sop_txd->dword[2] = cpu_to_le32(dw2 | ctx->copy_size);
  ctx->sop_txd->dword[3] = 0;

  tbi = tq->buf_info + tq->tx_ring.next2fill;
  tbi->map_type = VMXNET3_MAP_NONE;

  netdev_dbg(adapter->netdev,
   "txd[%u]: 0x%Lx 0x%x 0x%x\n",
   tq->tx_ring.next2fill,
   le64_to_cpu(ctx->sop_txd->txd.addr),
   ctx->sop_txd->dword[2], ctx->sop_txd->dword[3]);
  vmxnet3_cmd_ring_adv_next2fill(&tq->tx_ring);

  /* use the right gen for non-SOP desc */
  dw2 = tq->tx_ring.gen << VMXNET3_TXD_GEN_SHIFT;
 }

 /* linear part can use multiple tx desc if it's big */
 len = skb_headlen(skb) - ctx->copy_size;
 buf_offset = ctx->copy_size;
 while (len) {
  u32 buf_size;

  if (len < VMXNET3_MAX_TX_BUF_SIZE) {
   buf_size = len;
   dw2 |= len;
  } else {
   buf_size = VMXNET3_MAX_TX_BUF_SIZE;
   /* spec says that for TxDesc.len, 0 == 2^14 */
  }

  tbi = tq->buf_info + tq->tx_ring.next2fill;
  tbi->map_type = VMXNET3_MAP_SINGLE;
  tbi->dma_addr = dma_map_single(&adapter->pdev->dev,
    skb->data + buf_offset, buf_size,
    DMA_TO_DEVICE);
  if (dma_mapping_error(&adapter->pdev->dev, tbi->dma_addr))
   return -EFAULT;

  tbi->len = buf_size;

  gdesc = tq->tx_ring.base + tq->tx_ring.next2fill;
  BUG_ON(gdesc->txd.gen == tq->tx_ring.gen);

  gdesc->txd.addr = cpu_to_le64(tbi->dma_addr);
  gdesc->dword[2] = cpu_to_le32(dw2);
  gdesc->dword[3] = 0;

  netdev_dbg(adapter->netdev,
   "txd[%u]: 0x%Lx 0x%x 0x%x\n",
   tq->tx_ring.next2fill, le64_to_cpu(gdesc->txd.addr),
   le32_to_cpu(gdesc->dword[2]), gdesc->dword[3]);
  vmxnet3_cmd_ring_adv_next2fill(&tq->tx_ring);
  dw2 = tq->tx_ring.gen << VMXNET3_TXD_GEN_SHIFT;

  len -= buf_size;
  buf_offset += buf_size;
 }

 for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
  const skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
  u32 buf_size;

  buf_offset = 0;
  len = skb_frag_size(frag);
  while (len) {
   tbi = tq->buf_info + tq->tx_ring.next2fill;
   if (len < VMXNET3_MAX_TX_BUF_SIZE) {
    buf_size = len;
    dw2 |= len;
   } else {
    buf_size = VMXNET3_MAX_TX_BUF_SIZE;
    /* spec says that for TxDesc.len, 0 == 2^14 */
   }
   tbi->map_type = VMXNET3_MAP_PAGE;
   tbi->dma_addr = skb_frag_dma_map(&adapter->pdev->dev, frag,
        buf_offset, buf_size,
        DMA_TO_DEVICE);
   if (dma_mapping_error(&adapter->pdev->dev, tbi->dma_addr))
    return -EFAULT;

   tbi->len = buf_size;

   gdesc = tq->tx_ring.base + tq->tx_ring.next2fill;
   BUG_ON(gdesc->txd.gen == tq->tx_ring.gen);

   gdesc->txd.addr = cpu_to_le64(tbi->dma_addr);
   gdesc->dword[2] = cpu_to_le32(dw2);
   gdesc->dword[3] = 0;

   netdev_dbg(adapter->netdev,
    "txd[%u]: 0x%llx %u %u\n",
    tq->tx_ring.next2fill, le64_to_cpu(gdesc->txd.addr),
    le32_to_cpu(gdesc->dword[2]), gdesc->dword[3]);
   vmxnet3_cmd_ring_adv_next2fill(&tq->tx_ring);
   dw2 = tq->tx_ring.gen << VMXNET3_TXD_GEN_SHIFT;

   len -= buf_size;
   buf_offset += buf_size;
  }
 }

 ctx->eop_txd = gdesc;

 /* set the last buf_info for the pkt */
 tbi->skb = skb;
 tbi->sop_idx = ctx->sop_txd - tq->tx_ring.base;
 if (tq->tx_ts_desc_size != 0) {
  ctx->ts_txd = (struct Vmxnet3_TxTSDesc *)((u8 *)tq->ts_ring.base +
         tbi->sop_idx * tq->tx_ts_desc_size);
  ctx->ts_txd->ts.tsi = 0;
 }

 return 0;
}


/* Init all tx queues */
static void
vmxnet3_tq_init_all(struct vmxnet3_adapter *adapter)
{
 int i;

 for (i = 0; i < adapter->num_tx_queues; i++)
  vmxnet3_tq_init(&adapter->tx_queue[i], adapter);
}


/*
 *    parse relevant protocol headers:
 *      For a tso pkt, relevant headers are L2/3/4 including options
 *      For a pkt requesting csum offloading, they are L2/3 and may include L4
 *      if it's a TCP/UDP pkt
 *
 * Returns:
 *    -1:  error happens during parsing
 *     0:  protocol headers parsed, but too big to be copied
 *     1:  protocol headers parsed and copied
 *
 * Other effects:
 *    1. related *ctx fields are updated.
 *    2. ctx->copy_size is # of bytes copied
 *    3. the portion to be copied is guaranteed to be in the linear part
 *
 */

static int
vmxnet3_parse_hdr(struct sk_buff *skb, struct vmxnet3_tx_queue *tq,
    struct vmxnet3_tx_ctx *ctx,
    struct vmxnet3_adapter *adapter)
{
 u8 protocol = 0;

 if (ctx->mss) { /* TSO */
  if (VMXNET3_VERSION_GE_4(adapter) && skb->encapsulation) {
   ctx->l4_offset = skb_inner_transport_offset(skb);
   ctx->l4_hdr_size = inner_tcp_hdrlen(skb);
   ctx->copy_size = ctx->l4_offset + ctx->l4_hdr_size;
  } else {
   ctx->l4_offset = skb_transport_offset(skb);
   ctx->l4_hdr_size = tcp_hdrlen(skb);
   ctx->copy_size = ctx->l4_offset + ctx->l4_hdr_size;
  }
 } else {
  if (skb->ip_summed == CHECKSUM_PARTIAL) {
   /* For encap packets, skb_checksum_start_offset refers
 * to inner L4 offset. Thus, below works for encap as
 * well as non-encap case
 */

   ctx->l4_offset = skb_checksum_start_offset(skb);

   if (VMXNET3_VERSION_GE_4(adapter) &&
       skb->encapsulation) {
    struct iphdr *iph = inner_ip_hdr(skb);

    if (iph->version == 4) {
     protocol = iph->protocol;
    } else {
     const struct ipv6hdr *ipv6h;

     ipv6h = inner_ipv6_hdr(skb);
     protocol = ipv6h->nexthdr;
    }
   } else {
    if (ctx->ipv4) {
     const struct iphdr *iph = ip_hdr(skb);

     protocol = iph->protocol;
    } else if (ctx->ipv6) {
     const struct ipv6hdr *ipv6h;

     ipv6h = ipv6_hdr(skb);
     protocol = ipv6h->nexthdr;
    }
   }

   switch (protocol) {
   case IPPROTO_TCP:
    ctx->l4_hdr_size = skb->encapsulation ? inner_tcp_hdrlen(skb) :
         tcp_hdrlen(skb);
    break;
   case IPPROTO_UDP:
    ctx->l4_hdr_size = sizeof(struct udphdr);
    break;
   default:
    ctx->l4_hdr_size = 0;
    break;
   }

   ctx->copy_size = min(ctx->l4_offset +
      ctx->l4_hdr_size, skb->len);
  } else {
   ctx->l4_offset = 0;
   ctx->l4_hdr_size = 0;
   /* copy as much as allowed */
   ctx->copy_size = min_t(unsigned int,
            tq->txdata_desc_size,
            skb_headlen(skb));
  }

  if (skb->len <= tq->txdata_desc_size)
   ctx->copy_size = skb->len;

  /* make sure headers are accessible directly */
  if (unlikely(!pskb_may_pull(skb, ctx->copy_size)))
   goto err;
 }

 if (unlikely(ctx->copy_size > tq->txdata_desc_size)) {
  tq->stats.oversized_hdr++;
  ctx->copy_size = 0;
  return 0;
 }

 return 1;
err:
 return -1;
}

/*
 *    copy relevant protocol headers to the transmit ring:
 *      For a tso pkt, relevant headers are L2/3/4 including options
 *      For a pkt requesting csum offloading, they are L2/3 and may include L4
 *      if it's a TCP/UDP pkt
 *
 *
 *    Note that this requires that vmxnet3_parse_hdr be called first to set the
 *      appropriate bits in ctx first
 */

static void
vmxnet3_copy_hdr(struct sk_buff *skb, struct vmxnet3_tx_queue *tq,
   struct vmxnet3_tx_ctx *ctx,
   struct vmxnet3_adapter *adapter)
{
 struct Vmxnet3_TxDataDesc *tdd;

 tdd = (struct Vmxnet3_TxDataDesc *)((u8 *)tq->data_ring.base +
         tq->tx_ring.next2fill *
         tq->txdata_desc_size);

 memcpy(tdd->data, skb->data, ctx->copy_size);
 netdev_dbg(adapter->netdev,
  "copy %u bytes to dataRing[%u]\n",
  ctx->copy_size, tq->tx_ring.next2fill);
}


static void
vmxnet3_prepare_inner_tso(struct sk_buff *skb,
     struct vmxnet3_tx_ctx *ctx)
{
 struct tcphdr *tcph = inner_tcp_hdr(skb);
 struct iphdr *iph = inner_ip_hdr(skb);

 if (iph->version == 4) {
  iph->check = 0;
  tcph->check = ~csum_tcpudp_magic(iph->saddr, iph->daddr, 0,
       IPPROTO_TCP, 0);
 } else {
  struct ipv6hdr *iph = inner_ipv6_hdr(skb);

  tcph->check = ~csum_ipv6_magic(&iph->saddr, &iph->daddr, 0,
            IPPROTO_TCP, 0);
 }
}

static void
vmxnet3_prepare_tso(struct sk_buff *skb,
      struct vmxnet3_tx_ctx *ctx)
{
 struct tcphdr *tcph = tcp_hdr(skb);

 if (ctx->ipv4) {
  struct iphdr *iph = ip_hdr(skb);

  iph->check = 0;
  tcph->check = ~csum_tcpudp_magic(iph->saddr, iph->daddr, 0,
       IPPROTO_TCP, 0);
 } else if (ctx->ipv6) {
  tcp_v6_gso_csum_prep(skb);
 }
}

static int txd_estimate(const struct sk_buff *skb)
{
 int count = VMXNET3_TXD_NEEDED(skb_headlen(skb)) + 1;
 int i;

 for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
  const skb_frag_t *frag = &skb_shinfo(skb)->frags[i];

  count += VMXNET3_TXD_NEEDED(skb_frag_size(frag));
 }
 return count;
}

/*
 * Transmits a pkt thru a given tq
 * Returns:
 *    NETDEV_TX_OK:      descriptors are setup successfully
 *    NETDEV_TX_OK:      error occurred, the pkt is dropped
 *    NETDEV_TX_BUSY:    tx ring is full, queue is stopped
 *
 * Side-effects:
 *    1. tx ring may be changed
 *    2. tq stats may be updated accordingly
 *    3. shared->txNumDeferred may be updated
 */


static int
vmxnet3_tq_xmit(struct sk_buff *skb, struct vmxnet3_tx_queue *tq,
  struct vmxnet3_adapter *adapter, struct net_device *netdev)
{
 int ret;
 u32 count;
 int num_pkts;
 int tx_num_deferred;
 unsigned long flags;
 struct vmxnet3_tx_ctx ctx;
 union Vmxnet3_GenericDesc *gdesc;
#ifdef __BIG_ENDIAN_BITFIELD
 /* Use temporary descriptor to avoid touching bits multiple times */
 union Vmxnet3_GenericDesc tempTxDesc;
#endif

 count = txd_estimate(skb);

 ctx.ipv4 = (vlan_get_protocol(skb) == cpu_to_be16(ETH_P_IP));
 ctx.ipv6 = (vlan_get_protocol(skb) == cpu_to_be16(ETH_P_IPV6));

 ctx.mss = skb_shinfo(skb)->gso_size;
 if (ctx.mss) {
  if (skb_header_cloned(skb)) {
   if (unlikely(pskb_expand_head(skb, 0, 0,
            GFP_ATOMIC) != 0)) {
    tq->stats.drop_tso++;
    goto drop_pkt;
   }
   tq->stats.copy_skb_header++;
  }
  if (unlikely(count > VMXNET3_MAX_TSO_TXD_PER_PKT)) {
   /* tso pkts must not use more than
 * VMXNET3_MAX_TSO_TXD_PER_PKT entries
 */

   if (skb_linearize(skb) != 0) {
    tq->stats.drop_too_many_frags++;
    goto drop_pkt;
   }
   tq->stats.linearized++;

   /* recalculate the # of descriptors to use */
   count = VMXNET3_TXD_NEEDED(skb_headlen(skb)) + 1;
   if (unlikely(count > VMXNET3_MAX_TSO_TXD_PER_PKT)) {
    tq->stats.drop_too_many_frags++;
    goto drop_pkt;
   }
  }
  if (skb->encapsulation) {
   vmxnet3_prepare_inner_tso(skb, &ctx);
  } else {
   vmxnet3_prepare_tso(skb, &ctx);
  }
 } else {
  if (unlikely(count > VMXNET3_MAX_TXD_PER_PKT)) {

   /* non-tso pkts must not use more than
 * VMXNET3_MAX_TXD_PER_PKT entries
 */

   if (skb_linearize(skb) != 0) {
    tq->stats.drop_too_many_frags++;
    goto drop_pkt;
   }
   tq->stats.linearized++;

   /* recalculate the # of descriptors to use */
   count = VMXNET3_TXD_NEEDED(skb_headlen(skb)) + 1;
  }
 }

 ret = vmxnet3_parse_hdr(skb, tq, &ctx, adapter);
 if (ret >= 0) {
  BUG_ON(ret <= 0 && ctx.copy_size != 0);
  /* hdrs parsed, check against other limits */
  if (ctx.mss) {
   if (unlikely(ctx.l4_offset + ctx.l4_hdr_size >
         VMXNET3_MAX_TX_BUF_SIZE)) {
    tq->stats.drop_oversized_hdr++;
    goto drop_pkt;
   }
  } else {
   if (skb->ip_summed == CHECKSUM_PARTIAL) {
    if (unlikely(ctx.l4_offset +
          skb->csum_offset >
          VMXNET3_MAX_CSUM_OFFSET)) {
     tq->stats.drop_oversized_hdr++;
     goto drop_pkt;
    }
   }
  }
 } else {
  tq->stats.drop_hdr_inspect_err++;
  goto drop_pkt;
 }

 spin_lock_irqsave(&tq->tx_lock, flags);

 if (count > vmxnet3_cmd_ring_desc_avail(&tq->tx_ring)) {
  tq->stats.tx_ring_full++;
  netdev_dbg(adapter->netdev,
   "tx queue stopped on %s, next2comp %u"
   " next2fill %u\n", adapter->netdev->name,
   tq->tx_ring.next2comp, tq->tx_ring.next2fill);

  vmxnet3_tq_stop(tq, adapter);
  spin_unlock_irqrestore(&tq->tx_lock, flags);
  return NETDEV_TX_BUSY;
 }


 vmxnet3_copy_hdr(skb, tq, &ctx, adapter);

 /* fill tx descs related to addr & len */
 if (vmxnet3_map_pkt(skb, &ctx, tq, adapter->pdev, adapter))
  goto unlock_drop_pkt;

 /* setup the EOP desc */
 ctx.eop_txd->dword[3] = cpu_to_le32(VMXNET3_TXD_CQ | VMXNET3_TXD_EOP);

 /* setup the SOP desc */
#ifdef __BIG_ENDIAN_BITFIELD
 gdesc = &tempTxDesc;
 gdesc->dword[2] = ctx.sop_txd->dword[2];
 gdesc->dword[3] = ctx.sop_txd->dword[3];
#else
 gdesc = ctx.sop_txd;
#endif
 tx_num_deferred = le32_to_cpu(tq->shared->txNumDeferred);
 if (ctx.mss) {
  if (VMXNET3_VERSION_GE_4(adapter) && skb->encapsulation) {
   gdesc->txd.hlen = ctx.l4_offset + ctx.l4_hdr_size;
   if (VMXNET3_VERSION_GE_7(adapter)) {
    gdesc->txd.om = VMXNET3_OM_TSO;
    gdesc->txd.ext1 = 1;
   } else {
    gdesc->txd.om = VMXNET3_OM_ENCAP;
   }
   gdesc->txd.msscof = ctx.mss;

   if (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_TUNNEL_CSUM)
    gdesc->txd.oco = 1;
  } else {
   gdesc->txd.hlen = ctx.l4_offset + ctx.l4_hdr_size;
   gdesc->txd.om = VMXNET3_OM_TSO;
   gdesc->txd.msscof = ctx.mss;
  }
  num_pkts = (skb->len - gdesc->txd.hlen + ctx.mss - 1) / ctx.mss;
 } else {
  if (skb->ip_summed == CHECKSUM_PARTIAL) {
   if (VMXNET3_VERSION_GE_4(adapter) &&
       skb->encapsulation) {
    gdesc->txd.hlen = ctx.l4_offset +
        ctx.l4_hdr_size;
    if (VMXNET3_VERSION_GE_7(adapter)) {
     gdesc->txd.om = VMXNET3_OM_CSUM;
     gdesc->txd.msscof = ctx.l4_offset +
           skb->csum_offset;
     gdesc->txd.ext1 = 1;
    } else {
     gdesc->txd.om = VMXNET3_OM_ENCAP;
     gdesc->txd.msscof = 0;  /* Reserved */
    }
   } else {
    gdesc->txd.hlen = ctx.l4_offset;
    gdesc->txd.om = VMXNET3_OM_CSUM;
    gdesc->txd.msscof = ctx.l4_offset +
          skb->csum_offset;
   }
  } else {
   gdesc->txd.om = 0;
   gdesc->txd.msscof = 0;
  }
  num_pkts = 1;
 }
 le32_add_cpu(&tq->shared->txNumDeferred, num_pkts);
 tx_num_deferred += num_pkts;

 if (skb_vlan_tag_present(skb)) {
  gdesc->txd.ti = 1;
  gdesc->txd.tci = skb_vlan_tag_get(skb);
 }

 if (tq->tx_ts_desc_size != 0 &&
     adapter->latencyConf->sampleRate != 0) {
  if (vmxnet3_apply_timestamp(tq, adapter->latencyConf->sampleRate)) {
   ctx.ts_txd->ts.tsData = vmxnet3_get_cycles(VMXNET3_PMC_PSEUDO_TSC);
   ctx.ts_txd->ts.tsi = 1;
  }
 }

 /* Ensure that the write to (&gdesc->txd)->gen will be observed after
 * all other writes to &gdesc->txd.
 */

 dma_wmb();

 /* finally flips the GEN bit of the SOP desc. */
 gdesc->dword[2] = cpu_to_le32(le32_to_cpu(gdesc->dword[2]) ^
        VMXNET3_TXD_GEN);
#ifdef __BIG_ENDIAN_BITFIELD
 /* Finished updating in bitfields of Tx Desc, so write them in original
 * place.
 */

 vmxnet3_TxDescToLe((struct Vmxnet3_TxDesc *)gdesc,
      (struct Vmxnet3_TxDesc *)ctx.sop_txd);
 gdesc = ctx.sop_txd;
#endif
 netdev_dbg(adapter->netdev,
  "txd[%u]: SOP 0x%Lx 0x%x 0x%x\n",
  (u32)(ctx.sop_txd -
  tq->tx_ring.base), le64_to_cpu(gdesc->txd.addr),
  le32_to_cpu(gdesc->dword[2]), le32_to_cpu(gdesc->dword[3]));

 spin_unlock_irqrestore(&tq->tx_lock, flags);

 if (tx_num_deferred >= le32_to_cpu(tq->shared->txThreshold)) {
  tq->shared->txNumDeferred = 0;
  VMXNET3_WRITE_BAR0_REG(adapter,
           adapter->tx_prod_offset + tq->qid * 8,
           tq->tx_ring.next2fill);
 }

 return NETDEV_TX_OK;

unlock_drop_pkt:
 spin_unlock_irqrestore(&tq->tx_lock, flags);
drop_pkt:
 tq->stats.drop_total++;
 dev_kfree_skb_any(skb);
 return NETDEV_TX_OK;
}

static int
vmxnet3_create_pp(struct vmxnet3_adapter *adapter,
    struct vmxnet3_rx_queue *rq, int size)
{
 bool xdp_prog = vmxnet3_xdp_enabled(adapter);
 const struct page_pool_params pp_params = {
  .order = 0,
  .flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV,
  .pool_size = size,
  .nid = NUMA_NO_NODE,
  .dev = &adapter->pdev->dev,
  .offset = VMXNET3_XDP_RX_OFFSET,
  .max_len = VMXNET3_XDP_MAX_FRSIZE,
  .dma_dir = xdp_prog ? DMA_BIDIRECTIONAL : DMA_FROM_DEVICE,
 };
 struct page_pool *pp;
 int err;

 pp = page_pool_create(&pp_params);
 if (IS_ERR(pp))
  return PTR_ERR(pp);

 err = xdp_rxq_info_reg(&rq->xdp_rxq, adapter->netdev, rq->qid,
          rq->napi.napi_id);
 if (err < 0)
  goto err_free_pp;

 err = xdp_rxq_info_reg_mem_model(&rq->xdp_rxq, MEM_TYPE_PAGE_POOL, pp);
 if (err)
  goto err_unregister_rxq;

 rq->page_pool = pp;

 return 0;

err_unregister_rxq:
 xdp_rxq_info_unreg(&rq->xdp_rxq);
err_free_pp:
 page_pool_destroy(pp);

 return err;
}

void *
vmxnet3_pp_get_buff(struct page_pool *pp, dma_addr_t *dma_addr,
      gfp_t gfp_mask)
{
 struct page *page;

 page = page_pool_alloc_pages(pp, gfp_mask | __GFP_NOWARN);
 if (unlikely(!page))
  return NULL;

 *dma_addr = page_pool_get_dma_addr(page) + pp->p.offset;

 return page_address(page);
}

static netdev_tx_t
vmxnet3_xmit_frame(struct sk_buff *skb, struct net_device *netdev)
{
 struct vmxnet3_adapter *adapter = netdev_priv(netdev);

 BUG_ON(skb->queue_mapping > adapter->num_tx_queues);
 return vmxnet3_tq_xmit(skb,
          &adapter->tx_queue[skb->queue_mapping],
          adapter, netdev);
}


static void
vmxnet3_rx_csum(struct vmxnet3_adapter *adapter,
  struct sk_buff *skb,
  union Vmxnet3_GenericDesc *gdesc)
{
 if (!gdesc->rcd.cnc && adapter->netdev->features & NETIF_F_RXCSUM) {
  if (gdesc->rcd.v4 &&
      (le32_to_cpu(gdesc->dword[3]) &
       VMXNET3_RCD_CSUM_OK) == VMXNET3_RCD_CSUM_OK) {
   skb->ip_summed = CHECKSUM_UNNECESSARY;
   if ((le32_to_cpu(gdesc->dword[0]) &
         (1UL << VMXNET3_RCD_HDR_INNER_SHIFT))) {
    skb->csum_level = 1;
   }
   WARN_ON_ONCE(!(gdesc->rcd.tcp || gdesc->rcd.udp) &&
         !(le32_to_cpu(gdesc->dword[0]) &
         (1UL << VMXNET3_RCD_HDR_INNER_SHIFT)));
   WARN_ON_ONCE(gdesc->rcd.frg &&
         !(le32_to_cpu(gdesc->dword[0]) &
         (1UL << VMXNET3_RCD_HDR_INNER_SHIFT)));
  } else if (gdesc->rcd.v6 && (le32_to_cpu(gdesc->dword[3]) &
          (1 << VMXNET3_RCD_TUC_SHIFT))) {
   skb->ip_summed = CHECKSUM_UNNECESSARY;
   if ((le32_to_cpu(gdesc->dword[0]) &
         (1UL << VMXNET3_RCD_HDR_INNER_SHIFT))) {
    skb->csum_level = 1;
   }
   WARN_ON_ONCE(!(gdesc->rcd.tcp || gdesc->rcd.udp) &&
         !(le32_to_cpu(gdesc->dword[0]) &
         (1UL << VMXNET3_RCD_HDR_INNER_SHIFT)));
   WARN_ON_ONCE(gdesc->rcd.frg &&
         !(le32_to_cpu(gdesc->dword[0]) &
         (1UL << VMXNET3_RCD_HDR_INNER_SHIFT)));
  } else {
   if (gdesc->rcd.csum) {
    skb->csum = htons(gdesc->rcd.csum);
    skb->ip_summed = CHECKSUM_PARTIAL;
   } else {
    skb_checksum_none_assert(skb);
   }
  }
 } else {
  skb_checksum_none_assert(skb);
 }
}


static void
vmxnet3_rx_error(struct vmxnet3_rx_queue *rq, struct Vmxnet3_RxCompDesc *rcd,
   struct vmxnet3_rx_ctx *ctx,  struct vmxnet3_adapter *adapter)
{
 rq->stats.drop_err++;
 if (!rcd->fcs)
  rq->stats.drop_fcs++;

 rq->stats.drop_total++;

 /*
 * We do not unmap and chain the rx buffer to the skb.
 * We basically pretend this buffer is not used and will be recycled
 * by vmxnet3_rq_alloc_rx_buf()
 */


 /*
 * ctx->skb may be NULL if this is the first and the only one
 * desc for the pkt
 */

 if (ctx->skb)
  dev_kfree_skb_irq(ctx->skb);

 ctx->skb = NULL;
}


static u32
vmxnet3_get_hdr_len(struct vmxnet3_adapter *adapter, struct sk_buff *skb,
      union Vmxnet3_GenericDesc *gdesc)
{
 u32 hlen, maplen;
 union {
  void *ptr;
  struct ethhdr *eth;
  struct vlan_ethhdr *veth;
  struct iphdr *ipv4;
  struct ipv6hdr *ipv6;
  struct tcphdr *tcp;
 } hdr;
 BUG_ON(gdesc->rcd.tcp == 0);

 maplen = skb_headlen(skb);
 if (unlikely(sizeof(struct iphdr) + sizeof(struct tcphdr) > maplen))
  return 0;

 if (skb->protocol == cpu_to_be16(ETH_P_8021Q) ||
     skb->protocol == cpu_to_be16(ETH_P_8021AD))
  hlen = sizeof(struct vlan_ethhdr);
 else
  hlen = sizeof(struct ethhdr);

 hdr.eth = eth_hdr(skb);
 if (gdesc->rcd.v4) {
  BUG_ON(hdr.eth->h_proto != htons(ETH_P_IP) &&
         hdr.veth->h_vlan_encapsulated_proto != htons(ETH_P_IP));
  hdr.ptr += hlen;
  BUG_ON(hdr.ipv4->protocol != IPPROTO_TCP);
  hlen = hdr.ipv4->ihl << 2;
  hdr.ptr += hdr.ipv4->ihl << 2;
 } else if (gdesc->rcd.v6) {
  BUG_ON(hdr.eth->h_proto != htons(ETH_P_IPV6) &&
         hdr.veth->h_vlan_encapsulated_proto != htons(ETH_P_IPV6));
  hdr.ptr += hlen;
  /* Use an estimated value, since we also need to handle
 * TSO case.
 */

  if (hdr.ipv6->nexthdr != IPPROTO_TCP)
   return sizeof(struct ipv6hdr) + sizeof(struct tcphdr);
  hlen = sizeof(struct ipv6hdr);
  hdr.ptr += sizeof(struct ipv6hdr);
 } else {
  /* Non-IP pkt, dont estimate header length */
  return 0;
 }

 if (hlen + sizeof(struct tcphdr) > maplen)
  return 0;

 return (hlen + (hdr.tcp->doff << 2));
}

static void
vmxnet3_lro_tunnel(struct sk_buff *skb, __be16 ip_proto)
{
 struct udphdr *uh = NULL;

 if (ip_proto == htons(ETH_P_IP)) {
  struct iphdr *iph = (struct iphdr *)skb->data;

  if (iph->protocol == IPPROTO_UDP)
   uh = (struct udphdr *)(iph + 1);
 } else {
  struct ipv6hdr *iph = (struct ipv6hdr *)skb->data;

  if (iph->nexthdr == IPPROTO_UDP)
   uh = (struct udphdr *)(iph + 1);
 }
 if (uh) {
  if (uh->check)
   skb_shinfo(skb)->gso_type |= SKB_GSO_UDP_TUNNEL_CSUM;
  else
   skb_shinfo(skb)->gso_type |= SKB_GSO_UDP_TUNNEL;
 }
}

static int
vmxnet3_rq_rx_complete(struct vmxnet3_rx_queue *rq,
         struct vmxnet3_adapter *adapter, int quota)
{
 u32 rxprod_reg[2] = {
  adapter->rx_prod_offset, adapter->rx_prod2_offset
 };
 u32 num_pkts = 0;
 bool skip_page_frags = false;
 bool encap_lro = false;
 struct Vmxnet3_RxCompDesc *rcd;
 struct vmxnet3_rx_ctx *ctx = &rq->rx_ctx;
 u16 segCnt = 0, mss = 0;
 int comp_offset, fill_offset;
#ifdef __BIG_ENDIAN_BITFIELD
 struct Vmxnet3_RxDesc rxCmdDesc;
 struct Vmxnet3_RxCompDesc rxComp;
#endif
 bool need_flush = false;

 vmxnet3_getRxComp(rcd, &rq->comp_ring.base[rq->comp_ring.next2proc].rcd,
     &rxComp);
 while (rcd->gen == rq->comp_ring.gen) {
  struct vmxnet3_rx_buf_info *rbi;
  struct sk_buff *skb, *new_skb = NULL;
  struct page *new_page = NULL;
  dma_addr_t new_dma_addr;
  int num_to_alloc;
  struct Vmxnet3_RxDesc *rxd;
  u32 idx, ring_idx;
  struct vmxnet3_cmd_ring *ring = NULL;
  if (num_pkts >= quota) {
   /* we may stop even before we see the EOP desc of
 * the current pkt
 */

   break;
  }

  /* Prevent any rcd field from being (speculatively) read before
 * rcd->gen is read.
 */

  dma_rmb();

  BUG_ON(rcd->rqID != rq->qid && rcd->rqID != rq->qid2 &&
         rcd->rqID != rq->dataRingQid);
  idx = rcd->rxdIdx;
  ring_idx = VMXNET3_GET_RING_IDX(adapter, rcd->rqID);
  ring = rq->rx_ring + ring_idx;
  vmxnet3_getRxDesc(rxd, &rq->rx_ring[ring_idx].base[idx].rxd,
      &rxCmdDesc);
  rbi = rq->buf_info[ring_idx] + idx;

  BUG_ON(rxd->addr != rbi->dma_addr ||
         rxd->len != rbi->len);

  if (unlikely(rcd->eop && rcd->err)) {
   vmxnet3_rx_error(rq, rcd, ctx, adapter);
   goto rcd_done;
  }

  if (rcd->sop && rcd->eop && vmxnet3_xdp_enabled(adapter)) {
   struct sk_buff *skb_xdp_pass;
   int act;

   if (VMXNET3_RX_DATA_RING(adapter, rcd->rqID)) {
    ctx->skb = NULL;
    goto skip_xdp; /* Handle it later. */
   }

   if (rbi->buf_type != VMXNET3_RX_BUF_XDP)
    goto rcd_done;

   act = vmxnet3_process_xdp(adapter, rq, rcd, rbi, rxd,
        &skb_xdp_pass);
   if (act == XDP_PASS) {
    ctx->skb = skb_xdp_pass;
    goto sop_done;
   }
   ctx->skb = NULL;
   need_flush |= act == XDP_REDIRECT;

   goto rcd_done;
  }
skip_xdp:

  if (rcd->sop) { /* first buf of the pkt */
   bool rxDataRingUsed;
   u16 len;

   BUG_ON(rxd->btype != VMXNET3_RXD_BTYPE_HEAD ||
          (rcd->rqID != rq->qid &&
    rcd->rqID != rq->dataRingQid));

   BUG_ON(rbi->buf_type != VMXNET3_RX_BUF_SKB &&
          rbi->buf_type != VMXNET3_RX_BUF_XDP);
   BUG_ON(ctx->skb != NULL || rbi->skb == NULL);

   if (unlikely(rcd->len == 0)) {
    /* Pretend the rx buffer is skipped. */
    BUG_ON(!(rcd->sop && rcd->eop));
    netdev_dbg(adapter->netdev,
     "rxRing[%u][%u] 0 length\n",
     ring_idx, idx);
    goto rcd_done;
   }

   skip_page_frags = false;
   ctx->skb = rbi->skb;

   if (rq->rx_ts_desc_size != 0 && rcd->ext2) {
    struct Vmxnet3_RxTSDesc *ts_rxd;

    ts_rxd = (struct Vmxnet3_RxTSDesc *)((u8 *)rq->ts_ring.base +
             idx * rq->rx_ts_desc_size);
    ts_rxd->ts.tsData = vmxnet3_get_cycles(VMXNET3_PMC_PSEUDO_TSC);
    ts_rxd->ts.tsi = 1;
   }

   rxDataRingUsed =
    VMXNET3_RX_DATA_RING(adapter, rcd->rqID);
   len = rxDataRingUsed ? rcd->len : rbi->len;

   if (rxDataRingUsed && vmxnet3_xdp_enabled(adapter)) {
    struct sk_buff *skb_xdp_pass;
    size_t sz;
    int act;

    sz = rcd->rxdIdx * rq->data_ring.desc_size;
    act = vmxnet3_process_xdp_small(adapter, rq,
        &rq->data_ring.base[sz],
        rcd->len,
        &skb_xdp_pass);
    if (act == XDP_PASS) {
     ctx->skb = skb_xdp_pass;
     goto sop_done;
    }
    need_flush |= act == XDP_REDIRECT;

    goto rcd_done;
   }
   new_skb = netdev_alloc_skb_ip_align(adapter->netdev,
           len);
   if (new_skb == NULL) {
    /* Skb allocation failed, do not handover this
 * skb to stack. Reuse it. Drop the existing pkt
 */

    rq->stats.rx_buf_alloc_failure++;
    ctx->skb = NULL;
    rq->stats.drop_total++;
    skip_page_frags = true;
    goto rcd_done;
   }

   if (rxDataRingUsed && adapter->rxdataring_enabled) {
    size_t sz;

    BUG_ON(rcd->len > rq->data_ring.desc_size);

    ctx->skb = new_skb;
    sz = rcd->rxdIdx * rq->data_ring.desc_size;
    memcpy(new_skb->data,
           &rq->data_ring.base[sz], rcd->len);
   } else {
    ctx->skb = rbi->skb;

    new_dma_addr =
     dma_map_single(&adapter->pdev->dev,
             new_skb->data, rbi->len,
             DMA_FROM_DEVICE);
    if (dma_mapping_error(&adapter->pdev->dev,
            new_dma_addr)) {
     dev_kfree_skb(new_skb);
     /* Skb allocation failed, do not
 * handover this skb to stack. Reuse
 * it. Drop the existing pkt.
 */

     rq->stats.rx_buf_alloc_failure++;
     ctx->skb = NULL;
     rq->stats.drop_total++;
     skip_page_frags = true;
     goto rcd_done;
    }

    dma_unmap_single(&adapter->pdev->dev,
       rbi->dma_addr,
       rbi->len,
       DMA_FROM_DEVICE);

    /* Immediate refill */
    rbi->skb = new_skb;
    rbi->dma_addr = new_dma_addr;
    rxd->addr = cpu_to_le64(rbi->dma_addr);
    rxd->len = rbi->len;
   }

   skb_record_rx_queue(ctx->skb, rq->qid);
   skb_put(ctx->skb, rcd->len);

   if (VMXNET3_VERSION_GE_2(adapter) &&
       rcd->type == VMXNET3_CDTYPE_RXCOMP_LRO) {
    struct Vmxnet3_RxCompDescExt *rcdlro;
    union Vmxnet3_GenericDesc *gdesc;

    rcdlro = (struct Vmxnet3_RxCompDescExt *)rcd;
    gdesc = (union Vmxnet3_GenericDesc *)rcd;

    segCnt = rcdlro->segCnt;
    WARN_ON_ONCE(segCnt == 0);
    mss = rcdlro->mss;
    if (unlikely(segCnt <= 1))
     segCnt = 0;
    encap_lro = (le32_to_cpu(gdesc->dword[0]) &
     (1UL << VMXNET3_RCD_HDR_INNER_SHIFT));
   } else {
    segCnt = 0;
   }
  } else {
   BUG_ON(ctx->skb == NULL && !skip_page_frags);

   /* non SOP buffer must be type 1 in most cases */
   BUG_ON(rbi->buf_type != VMXNET3_RX_BUF_PAGE);
   BUG_ON(rxd->btype != VMXNET3_RXD_BTYPE_BODY);

   /* If an sop buffer was dropped, skip all
 * following non-sop fragments. They will be reused.
 */

   if (skip_page_frags)
    goto rcd_done;

   if (rcd->len) {
    new_page = alloc_page(GFP_ATOMIC);
    /* Replacement page frag could not be allocated.
 * Reuse this page. Drop the pkt and free the
 * skb which contained this page as a frag. Skip
 * processing all the following non-sop frags.
 */

    if (unlikely(!new_page)) {
     rq->stats.rx_buf_alloc_failure++;
     dev_kfree_skb(ctx->skb);
     ctx->skb = NULL;
     skip_page_frags = true;
     goto rcd_done;
    }
    new_dma_addr = dma_map_page(&adapter->pdev->dev,
           new_page,
           0, PAGE_SIZE,
           DMA_FROM_DEVICE);
    if (dma_mapping_error(&adapter->pdev->dev,
            new_dma_addr)) {
     put_page(new_page);
     rq->stats.rx_buf_alloc_failure++;
     dev_kfree_skb(ctx->skb);
     ctx->skb = NULL;
     skip_page_frags = true;
     goto rcd_done;
    }

    dma_unmap_page(&adapter->pdev->dev,
            rbi->dma_addr, rbi->len,
            DMA_FROM_DEVICE);

    vmxnet3_append_frag(ctx->skb, rcd, rbi);

    /* Immediate refill */
    rbi->page = new_page;
    rbi->dma_addr = new_dma_addr;
    rxd->addr = cpu_to_le64(rbi->dma_addr);
    rxd->len = rbi->len;
   }
  }


sop_done:
  skb = ctx->skb;
  if (rcd->eop) {
   u32 mtu = adapter->netdev->mtu;
   skb->len += skb->data_len;

#ifdef VMXNET3_RSS
   if (rcd->rssType != VMXNET3_RCD_RSS_TYPE_NONE &&
       (adapter->netdev->features & NETIF_F_RXHASH)) {
    enum pkt_hash_types hash_type;

    switch (rcd->rssType) {
    case VMXNET3_RCD_RSS_TYPE_IPV4:
    case VMXNET3_RCD_RSS_TYPE_IPV6:
     hash_type = PKT_HASH_TYPE_L3;
     break;
    case VMXNET3_RCD_RSS_TYPE_TCPIPV4:
    case VMXNET3_RCD_RSS_TYPE_TCPIPV6:
    case VMXNET3_RCD_RSS_TYPE_UDPIPV4:
    case VMXNET3_RCD_RSS_TYPE_UDPIPV6:
     hash_type = PKT_HASH_TYPE_L4;
     break;
    default:
     hash_type = PKT_HASH_TYPE_L3;
     break;
    }
    skb_set_hash(skb,
          le32_to_cpu(rcd->rssHash),
          hash_type);
   }
#endif
   vmxnet3_rx_csum(adapter, skb,
     (union Vmxnet3_GenericDesc *)rcd);
   skb->protocol = eth_type_trans(skb, adapter->netdev);
   if ((!rcd->tcp && !encap_lro) ||
       !(adapter->netdev->features & NETIF_F_LRO))
    goto not_lro;

   if (segCnt != 0 && mss != 0) {
    skb_shinfo(skb)->gso_type = rcd->v4 ?
     SKB_GSO_TCPV4 : SKB_GSO_TCPV6;
    if (encap_lro)
     vmxnet3_lro_tunnel(skb, skb->protocol);
    skb_shinfo(skb)->gso_size = mss;
    skb_shinfo(skb)->gso_segs = segCnt;
   } else if ((segCnt != 0 || skb->len > mtu) && !encap_lro) {
    u32 hlen;

    hlen = vmxnet3_get_hdr_len(adapter, skb,
     (union Vmxnet3_GenericDesc *)rcd);
    if (hlen == 0)
     goto not_lro;

    skb_shinfo(skb)->gso_type =
     rcd->v4 ? SKB_GSO_TCPV4 : SKB_GSO_TCPV6;
    if (segCnt != 0) {
     skb_shinfo(skb)->gso_segs = segCnt;
     skb_shinfo(skb)->gso_size =
      DIV_ROUND_UP(skb->len -
       hlen, segCnt);
    } else {
     skb_shinfo(skb)->gso_size = mtu - hlen;
    }
   }
not_lro:
   if (unlikely(rcd->ts))
    __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), rcd->tci);

   /* Use GRO callback if UPT is enabled */
   if ((adapter->netdev->features & NETIF_F_LRO) &&
       !rq->shared->updateRxProd)
    netif_receive_skb(skb);
   else
    napi_gro_receive(&rq->napi, skb);

   ctx->skb = NULL;
   encap_lro = false;
   num_pkts++;
  }

rcd_done:
  /* device may have skipped some rx descs */
  ring = rq->rx_ring + ring_idx;
  rbi->comp_state = VMXNET3_RXD_COMP_DONE;

  comp_offset = vmxnet3_cmd_ring_desc_avail(ring);
  fill_offset = (idx > ring->next2fill ? 0 : ring->size) +
         idx - ring->next2fill - 1;
  if (!ring->isOutOfOrder || fill_offset >= comp_offset)
   ring->next2comp = idx;
  num_to_alloc = vmxnet3_cmd_ring_desc_avail(ring);

  /* Ensure that the writes to rxd->gen bits will be observed
 * after all other writes to rxd objects.
 */

  dma_wmb();

  while (num_to_alloc) {
   rbi = rq->buf_info[ring_idx] + ring->next2fill;
   if (!(adapter->dev_caps[0] & (1UL << VMXNET3_CAP_OOORX_COMP)))
    goto refill_buf;
   if (ring_idx == 0) {
    /* ring0 Type1 buffers can get skipped; re-fill them */
    if (rbi->buf_type != VMXNET3_RX_BUF_SKB)
     goto refill_buf;
   }
   if (rbi->comp_state == VMXNET3_RXD_COMP_DONE) {
refill_buf:
    vmxnet3_getRxDesc(rxd, &ring->base[ring->next2fill].rxd,
        &rxCmdDesc);
    WARN_ON(!rxd->addr);

    /* Recv desc is ready to be used by the device */
    rxd->gen = ring->gen;
    vmxnet3_cmd_ring_adv_next2fill(ring);
    rbi->comp_state = VMXNET3_RXD_COMP_PENDING;
    num_to_alloc--;
   } else {
    /* rx completion hasn't occurred */
    ring->isOutOfOrder = 1;
    break;
   }
  }

  if (num_to_alloc == 0) {
   ring->isOutOfOrder = 0;
  }

  /* if needed, update the register */
  if (unlikely(rq->shared->updateRxProd) && (ring->next2fill & 0xf) == 0) {
   VMXNET3_WRITE_BAR0_REG(adapter,
            rxprod_reg[ring_idx] + rq->qid * 8,
            ring->next2fill);
  }

  vmxnet3_comp_ring_adv_next2proc(&rq->comp_ring);
  vmxnet3_getRxComp(rcd,
      &rq->comp_ring.base[rq->comp_ring.next2proc].rcd, &rxComp);
 }
 if (need_flush)
  xdp_do_flush();

 return num_pkts;
}


static void
vmxnet3_rq_cleanup(struct vmxnet3_rx_queue *rq,
     struct vmxnet3_adapter *adapter)
{
 u32 i, ring_idx;
 struct Vmxnet3_RxDesc *rxd;

 /* ring has already been cleaned up */
 if (!rq->rx_ring[0].base)
  return;

 for (ring_idx = 0; ring_idx < 2; ring_idx++) {
  for (i = 0; i < rq->rx_ring[ring_idx].size; i++) {
   struct vmxnet3_rx_buf_info *rbi;
#ifdef __BIG_ENDIAN_BITFIELD
   struct Vmxnet3_RxDesc rxDesc;
#endif

   rbi = &rq->buf_info[ring_idx][i];
   vmxnet3_getRxDesc(rxd,
    &rq->rx_ring[ring_idx].base[i].rxd, &rxDesc);

   if (rxd->btype == VMXNET3_RXD_BTYPE_HEAD &&
       rbi->page && rbi->buf_type == VMXNET3_RX_BUF_XDP) {
    page_pool_recycle_direct(rq->page_pool,
        rbi->page);
    rbi->page = NULL;
   } else if (rxd->btype == VMXNET3_RXD_BTYPE_HEAD &&
       rbi->skb) {
    dma_unmap_single(&adapter->pdev->dev, rxd->addr,
       rxd->len, DMA_FROM_DEVICE);
    dev_kfree_skb(rbi->skb);
    rbi->skb = NULL;
   } else if (rxd->btype == VMXNET3_RXD_BTYPE_BODY &&
       rbi->page) {
    dma_unmap_page(&adapter->pdev->dev, rxd->addr,
            rxd->len, DMA_FROM_DEVICE);
    put_page(rbi->page);
    rbi->page = NULL;
   }
  }

  rq->rx_ring[ring_idx].gen = VMXNET3_INIT_GEN;
  rq->rx_ring[ring_idx].next2fill =
     rq->rx_ring[ring_idx].next2comp = 0;
 }

 rq->comp_ring.gen = VMXNET3_INIT_GEN;
 rq->comp_ring.next2proc = 0;

 if (xdp_rxq_info_is_reg(&rq->xdp_rxq))
  xdp_rxq_info_unreg(&rq->xdp_rxq);
 page_pool_destroy(rq->page_pool);
 rq->page_pool = NULL;
}


static void
vmxnet3_rq_cleanup_all(struct vmxnet3_adapter *adapter)
{
 int i;

 for (i = 0; i < adapter->num_rx_queues; i++)
  vmxnet3_rq_cleanup(&adapter->rx_queue[i], adapter);
 rcu_assign_pointer(adapter->xdp_bpf_prog, NULL);
}


static void vmxnet3_rq_destroy(struct vmxnet3_rx_queue *rq,
          struct vmxnet3_adapter *adapter)
{
 int i;
 int j;

 /* all rx buffers must have already been freed */
 for (i = 0; i < 2; i++) {
  if (rq->buf_info[i]) {
   for (j = 0; j < rq->rx_ring[i].size; j++)
    BUG_ON(rq->buf_info[i][j].page != NULL);
  }
 }


 for (i = 0; i < 2; i++) {
  if (rq->rx_ring[i].base) {
   dma_free_coherent(&adapter->pdev->dev,
       rq->rx_ring[i].size
       * sizeof(struct Vmxnet3_RxDesc),
       rq->rx_ring[i].base,
       rq->rx_ring[i].basePA);
   rq->rx_ring[i].base = NULL;
  }
 }

 if (rq->data_ring.base) {
  dma_free_coherent(&adapter->pdev->dev,
      rq->rx_ring[0].size * rq->data_ring.desc_size,
      rq->data_ring.base, rq->data_ring.basePA);
  rq->data_ring.base = NULL;
 }

 if (rq->ts_ring.base) {
  dma_free_coherent(&adapter->pdev->dev,
      rq->rx_ring[0].size * rq->rx_ts_desc_size,
      rq->ts_ring.base, rq->ts_ring.basePA);
  rq->ts_ring.base = NULL;
 }

 if (rq->comp_ring.base) {
  dma_free_coherent(&adapter->pdev->dev, rq->comp_ring.size
      * sizeof(struct Vmxnet3_RxCompDesc),
      rq->comp_ring.base, rq->comp_ring.basePA);
  rq->comp_ring.base = NULL;
 }

 kfree(rq->buf_info[0]);
 rq->buf_info[0] = NULL;
 rq->buf_info[1] = NULL;
}

static void
vmxnet3_rq_destroy_all_rxdataring(struct vmxnet3_adapter *adapter)
{
 int i;

 for (i = 0; i < adapter->num_rx_queues; i++) {
  struct vmxnet3_rx_queue *rq = &adapter->rx_queue[i];

  if (rq->data_ring.base) {
   dma_free_coherent(&adapter->pdev->dev,
       (rq->rx_ring[0].size *
       rq->data_ring.desc_size),
       rq->data_ring.base,
       rq->data_ring.basePA);
   rq->data_ring.base = NULL;
  }
  rq->data_ring.desc_size = 0;
 }
}

static int
vmxnet3_rq_init(struct vmxnet3_rx_queue *rq,
  struct vmxnet3_adapter  *adapter)
{
 int i, err;

 /* initialize buf_info */
 for (i = 0; i < rq->rx_ring[0].size; i++) {

  /* 1st buf for a pkt is skbuff or xdp page */
  if (i % adapter->rx_buf_per_pkt == 0) {
   rq->buf_info[0][i].buf_type = vmxnet3_xdp_enabled(adapter) ?
            VMXNET3_RX_BUF_XDP :
            VMXNET3_RX_BUF_SKB;
   rq->buf_info[0][i].len = adapter->skb_buf_size;
  } else { /* subsequent bufs for a pkt is frag */
   rq->buf_info[0][i].buf_type = VMXNET3_RX_BUF_PAGE;
   rq->buf_info[0][i].len = PAGE_SIZE;
  }
 }
 for (i = 0; i < rq->rx_ring[1].size; i++) {
  rq->buf_info[1][i].buf_type = VMXNET3_RX_BUF_PAGE;
  rq->buf_info[1][i].len = PAGE_SIZE;
 }

 /* reset internal state and allocate buffers for both rings */
 for (i = 0; i < 2; i++) {
  rq->rx_ring[i].next2fill = rq->rx_ring[i].next2comp = 0;

  memset(rq->rx_ring[i].base, 0, rq->rx_ring[i].size *
         sizeof(struct Vmxnet3_RxDesc));
  rq->rx_ring[i].gen = VMXNET3_INIT_GEN;
  rq->rx_ring[i].isOutOfOrder = 0;
 }

 err = vmxnet3_create_pp(adapter, rq,
    rq->rx_ring[0].size + rq->rx_ring[1].size);
 if (err)
  return err;

 if (vmxnet3_rq_alloc_rx_buf(rq, 0, rq->rx_ring[0].size - 1,
        adapter) == 0) {
  xdp_rxq_info_unreg(&rq->xdp_rxq);
  page_pool_destroy(rq->page_pool);
  rq->page_pool = NULL;

  /* at least has 1 rx buffer for the 1st ring */
  return -ENOMEM;
 }
 vmxnet3_rq_alloc_rx_buf(rq, 1, rq->rx_ring[1].size - 1, adapter);

 if (rq->ts_ring.base)
  memset(rq->ts_ring.base, 0,
         rq->rx_ring[0].size * rq->rx_ts_desc_size);

 /* reset the comp ring */
 rq->comp_ring.next2proc = 0;
 memset(rq->comp_ring.base, 0, rq->comp_ring.size *
        sizeof(struct Vmxnet3_RxCompDesc));
 rq->comp_ring.gen = VMXNET3_INIT_GEN;

 /* reset rxctx */
 rq->rx_ctx.skb = NULL;

 /* stats are not reset */
 return 0;
}


static int
vmxnet3_rq_init_all(struct vmxnet3_adapter *adapter)
{
 int i, err = 0;

 for (i = 0; i < adapter->num_rx_queues; i++) {
  err = vmxnet3_rq_init(&adapter->rx_queue[i], adapter);
  if (unlikely(err)) {
   dev_err(&adapter->netdev->dev, "%s: failed to "
    "initialize rx queue%i\n",
    adapter->netdev->name, i);
   break;
  }
 }
 return err;

}


static int
vmxnet3_rq_create(struct vmxnet3_rx_queue *rq, struct vmxnet3_adapter *adapter)
{
 int i;
 size_t sz;
 struct vmxnet3_rx_buf_info *bi;

 for (i = 0; i < 2; i++) {

  sz = rq->rx_ring[i].size * sizeof(struct Vmxnet3_RxDesc);
  rq->rx_ring[i].base = dma_alloc_coherent(
      &adapter->pdev->dev, sz,
      &rq->rx_ring[i].basePA,
      GFP_KERNEL);
  if (!rq->rx_ring[i].base) {
   netdev_err(adapter->netdev,
       "failed to allocate rx ring %d\n", i);
   goto err;
  }
 }

 if ((adapter->rxdataring_enabled) && (rq->data_ring.desc_size != 0)) {
  sz = rq->rx_ring[0].size * rq->data_ring.desc_size;
  rq->data_ring.base =
   dma_alloc_coherent(&adapter->pdev->dev, sz,
        &rq->data_ring.basePA,
        GFP_KERNEL);
  if (!rq->data_ring.base) {
   netdev_err(adapter->netdev,
       "rx data ring will be disabled\n");
   adapter->rxdataring_enabled = false;
  }
 } else {
  rq->data_ring.base = NULL;
  rq->data_ring.desc_size = 0;
 }

 if (rq->rx_ts_desc_size != 0) {
  sz = rq->rx_ring[0].size * rq->rx_ts_desc_size;
  rq->ts_ring.base =
   dma_alloc_coherent(&adapter->pdev->dev, sz,
        &rq->ts_ring.basePA,
        GFP_KERNEL);
  if (!rq->ts_ring.base) {
   netdev_err(adapter->netdev,
       "rx ts ring will be disabled\n");
   rq->rx_ts_desc_size = 0;
  }
 } else {
  rq->ts_ring.base = NULL;
 }

 sz = rq->comp_ring.size * sizeof(struct Vmxnet3_RxCompDesc);
 rq->comp_ring.base = dma_alloc_coherent(&adapter->pdev->dev, sz,
      &rq->comp_ring.basePA,
      GFP_KERNEL);
 if (!rq->comp_ring.base) {
  netdev_err(adapter->netdev, "failed to allocate rx comp ring\n");
  goto err;
 }

 bi = kcalloc_node(rq->rx_ring[0].size + rq->rx_ring[1].size,
     sizeof(rq->buf_info[0][0]), GFP_KERNEL,
     dev_to_node(&adapter->pdev->dev));
 if (!bi)
  goto err;

 rq->buf_info[0] = bi;
 rq->buf_info[1] = bi + rq->rx_ring[0].size;

 return 0;

err:
 vmxnet3_rq_destroy(rq, adapter);
 return -ENOMEM;
}


int
vmxnet3_rq_create_all(struct vmxnet3_adapter *adapter)
{
 int i, err = 0;

 adapter->rxdataring_enabled = VMXNET3_VERSION_GE_3(adapter);

 for (i = 0; i < adapter->num_rx_queues; i++) {
  err = vmxnet3_rq_create(&adapter->rx_queue[i], adapter);
  if (unlikely(err)) {
   dev_err(&adapter->netdev->dev,
    "%s: failed to create rx queue%i\n",
    adapter->netdev->name, i);
   goto err_out;
  }
 }

 if (!adapter->rxdataring_enabled)
  vmxnet3_rq_destroy_all_rxdataring(adapter);

 return err;
err_out:
 vmxnet3_rq_destroy_all(adapter);
 return err;

}

/* Multiple queue aware polling function for tx and rx */

static int
vmxnet3_do_poll(struct vmxnet3_adapter *adapter, int budget)
{
 int rcd_done = 0, i;
 if (unlikely(adapter->shared->ecr))
  vmxnet3_process_events(adapter);
 for (i = 0; i < adapter->num_tx_queues; i++)
  vmxnet3_tq_tx_complete(&adapter->tx_queue[i], adapter);

 for (i = 0; i < adapter->num_rx_queues; i++)
  rcd_done += vmxnet3_rq_rx_complete(&adapter->rx_queue[i],
         adapter, budget);
 return rcd_done;
}


static int
vmxnet3_poll(struct napi_struct *napi, int budget)
{
 struct vmxnet3_rx_queue *rx_queue = container_of(napi,
       struct vmxnet3_rx_queue, napi);
 int rxd_done;

 rxd_done = vmxnet3_do_poll(rx_queue->adapter, budget);

 if (rxd_done < budget) {
  napi_complete_done(napi, rxd_done);
  vmxnet3_enable_all_intrs(rx_queue->adapter);
 }
 return rxd_done;
}

/*
 * NAPI polling function for MSI-X mode with multiple Rx queues
 * Returns the # of the NAPI credit consumed (# of rx descriptors processed)
 */


static int
vmxnet3_poll_rx_only(struct napi_struct *napi, int budget)
{
 struct vmxnet3_rx_queue *rq = container_of(napi,
      struct vmxnet3_rx_queue, napi);
 struct vmxnet3_adapter *adapter = rq->adapter;
 int rxd_done;

 /* When sharing interrupt with corresponding tx queue, process
 * tx completions in that queue as well
 */

 if (adapter->share_intr == VMXNET3_INTR_BUDDYSHARE) {
  struct vmxnet3_tx_queue *tq =
    &adapter->tx_queue[rq - adapter->rx_queue];
  vmxnet3_tq_tx_complete(tq, adapter);
 }

 rxd_done = vmxnet3_rq_rx_complete(rq, adapter, budget);

 if (rxd_done < budget) {
  napi_complete_done(napi, rxd_done);
  vmxnet3_enable_intr(adapter, rq->comp_ring.intr_idx);
 }
 return rxd_done;
}


#ifdef CONFIG_PCI_MSI

/*
 * Handle completion interrupts on tx queues
 * Returns whether or not the intr is handled
 */


static irqreturn_t
vmxnet3_msix_tx(int irq, void *data)
{
 struct vmxnet3_tx_queue *tq = data;
 struct vmxnet3_adapter *adapter = tq->adapter;

 if (adapter->intr.mask_mode == VMXNET3_IMM_ACTIVE)
  vmxnet3_disable_intr(adapter, tq->comp_ring.intr_idx);

 /* Handle the case where only one irq is allocate for all tx queues */
 if (adapter->share_intr == VMXNET3_INTR_TXSHARE) {
  int i;
  for (i = 0; i < adapter->num_tx_queues; i++) {
   struct vmxnet3_tx_queue *txq = &adapter->tx_queue[i];
   vmxnet3_tq_tx_complete(txq, adapter);
  }
 } else {
  vmxnet3_tq_tx_complete(tq, adapter);
 }
 vmxnet3_enable_intr(adapter, tq->comp_ring.intr_idx);

 return IRQ_HANDLED;
}


/*
 * Handle completion interrupts on rx queues. Returns whether or not the
 * intr is handled
 */


static irqreturn_t
vmxnet3_msix_rx(int irq, void *data)
{
 struct vmxnet3_rx_queue *rq = data;
 struct vmxnet3_adapter *adapter = rq->adapter;

 /* disable intr if needed */
 if (adapter->intr.mask_mode == VMXNET3_IMM_ACTIVE)
  vmxnet3_disable_intr(adapter, rq->comp_ring.intr_idx);
 napi_schedule(&rq->napi);

 return IRQ_HANDLED;
}

/*
 *----------------------------------------------------------------------------
 *
 * vmxnet3_msix_event --
 *
 *    vmxnet3 msix event intr handler
 *
 * Result:
 *    whether or not the intr is handled
 *
 *----------------------------------------------------------------------------
 */


static irqreturn_t
vmxnet3_msix_event(int irq, void *data)
{
 struct net_device *dev = data;
 struct vmxnet3_adapter *adapter = netdev_priv(dev);

 /* disable intr if needed */
 if (adapter->intr.mask_mode == VMXNET3_IMM_ACTIVE)
  vmxnet3_disable_intr(adapter, adapter->intr.event_intr_idx);

 if (adapter->shared->ecr)
--> --------------------

--> maximum size reached

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

Messung V0.5
C=96 H=82 G=89

¤ Dauer der Verarbeitung: 0.25 Sekunden  ¤

*© 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.