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

Quelle  ionic_txrx.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2017 - 2019 Pensando Systems, Inc */

#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/if_vlan.h>
#include <net/ip6_checksum.h>
#include <net/netdev_queues.h>
#include <net/page_pool/helpers.h>

#include "ionic.h"
#include "ionic_lif.h"
#include "ionic_txrx.h"

static dma_addr_t ionic_tx_map_single(struct ionic_queue *q,
          void *data, size_t len);

static dma_addr_t ionic_tx_map_frag(struct ionic_queue *q,
        const skb_frag_t *frag,
        size_t offset, size_t len);

static void ionic_tx_desc_unmap_bufs(struct ionic_queue *q,
         struct ionic_tx_desc_info *desc_info);

static void ionic_tx_clean(struct ionic_queue *q,
      struct ionic_tx_desc_info *desc_info,
      struct ionic_txq_comp *comp,
      bool in_napi);

static inline void ionic_txq_post(struct ionic_queue *q, bool ring_dbell)
{
 /* Ensure TX descriptor writes reach memory before NIC reads them.
 * Prevents device from fetching stale descriptors.
 */

 dma_wmb();
 ionic_q_post(q, ring_dbell);
}

static inline void ionic_rxq_post(struct ionic_queue *q, bool ring_dbell)
{
 ionic_q_post(q, ring_dbell);
}

bool ionic_txq_poke_doorbell(struct ionic_queue *q)
{
 struct netdev_queue *netdev_txq;
 unsigned long now, then, dif;
 struct net_device *netdev;

 netdev = q->lif->netdev;
 netdev_txq = netdev_get_tx_queue(netdev, q->index);

 HARD_TX_LOCK(netdev, netdev_txq, smp_processor_id());

 if (q->tail_idx == q->head_idx) {
  HARD_TX_UNLOCK(netdev, netdev_txq);
  return false;
 }

 now = READ_ONCE(jiffies);
 then = q->dbell_jiffies;
 dif = now - then;

 if (dif > q->dbell_deadline) {
  ionic_dbell_ring(q->lif->kern_dbpage, q->hw_type,
     q->dbval | q->head_idx);

  q->dbell_jiffies = now;
 }

 HARD_TX_UNLOCK(netdev, netdev_txq);

 return true;
}

bool ionic_rxq_poke_doorbell(struct ionic_queue *q)
{
 unsigned long now, then, dif;

 /* no lock, called from rx napi or txrx napi, nothing else can fill */

 if (q->tail_idx == q->head_idx)
  return false;

 now = READ_ONCE(jiffies);
 then = q->dbell_jiffies;
 dif = now - then;

 if (dif > q->dbell_deadline) {
  ionic_dbell_ring(q->lif->kern_dbpage, q->hw_type,
     q->dbval | q->head_idx);

  q->dbell_jiffies = now;

  dif = 2 * q->dbell_deadline;
  if (dif > IONIC_RX_MAX_DOORBELL_DEADLINE)
   dif = IONIC_RX_MAX_DOORBELL_DEADLINE;

  q->dbell_deadline = dif;
 }

 return true;
}

static inline struct ionic_txq_sg_elem *ionic_tx_sg_elems(struct ionic_queue *q)
{
 if (likely(q->sg_desc_size == sizeof(struct ionic_txq_sg_desc_v1)))
  return q->txq_sgl_v1[q->head_idx].elems;
 else
  return q->txq_sgl[q->head_idx].elems;
}

static inline struct netdev_queue *q_to_ndq(struct net_device *netdev,
         struct ionic_queue *q)
{
 return netdev_get_tx_queue(netdev, q->index);
}

static void *ionic_rx_buf_va(struct ionic_buf_info *buf_info)
{
 return page_address(buf_info->page) + buf_info->page_offset;
}

static dma_addr_t ionic_rx_buf_pa(struct ionic_buf_info *buf_info)
{
 return page_pool_get_dma_addr(buf_info->page) + buf_info->page_offset;
}

static void __ionic_rx_put_buf(struct ionic_queue *q,
          struct ionic_buf_info *buf_info,
          bool recycle_direct)
{
 if (!buf_info->page)
  return;

 page_pool_put_full_page(q->page_pool, buf_info->page, recycle_direct);
 buf_info->page = NULL;
 buf_info->len = 0;
 buf_info->page_offset = 0;
}


static void ionic_rx_put_buf(struct ionic_queue *q,
        struct ionic_buf_info *buf_info)
{
 __ionic_rx_put_buf(q, buf_info, false);
}

static void ionic_rx_put_buf_direct(struct ionic_queue *q,
        struct ionic_buf_info *buf_info)
{
 __ionic_rx_put_buf(q, buf_info, true);
}

static void ionic_rx_add_skb_frag(struct ionic_queue *q,
      struct sk_buff *skb,
      struct ionic_buf_info *buf_info,
      u32 headroom, u32 len,
      bool synced)
{
 if (!synced)
  page_pool_dma_sync_for_cpu(q->page_pool,
        buf_info->page,
        buf_info->page_offset + headroom,
        len);

 skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
   buf_info->page, buf_info->page_offset + headroom,
   len, buf_info->len);

 /* napi_gro_frags() will release/recycle the
 * page_pool buffers from the frags list
 */

 buf_info->page = NULL;
 buf_info->len = 0;
 buf_info->page_offset = 0;
}

static struct sk_buff *ionic_rx_build_skb(struct ionic_queue *q,
       struct ionic_rx_desc_info *desc_info,
       unsigned int headroom,
       unsigned int len,
       unsigned int num_sg_elems,
       bool synced)
{
 struct ionic_buf_info *buf_info;
 struct sk_buff *skb;
 unsigned int i;
 u16 frag_len;

 buf_info = &desc_info->bufs[0];
 prefetchw(buf_info->page);

 skb = napi_get_frags(&q_to_qcq(q)->napi);
 if (unlikely(!skb)) {
  net_warn_ratelimited("%s: SKB alloc failed on %s!\n",
         dev_name(q->dev), q->name);
  q_to_rx_stats(q)->alloc_err++;
  return NULL;
 }
 skb_mark_for_recycle(skb);

 if (headroom)
  frag_len = min_t(u16, len,
     IONIC_XDP_MAX_LINEAR_MTU + VLAN_ETH_HLEN);
 else
  frag_len = min_t(u16, len, IONIC_PAGE_SIZE);

 if (unlikely(!buf_info->page))
  goto err_bad_buf_page;
 ionic_rx_add_skb_frag(q, skb, buf_info, headroom, frag_len, synced);
 len -= frag_len;
 buf_info++;

 for (i = 0; i < num_sg_elems; i++, buf_info++) {
  if (unlikely(!buf_info->page))
   goto err_bad_buf_page;
  frag_len = min_t(u16, len, buf_info->len);
  ionic_rx_add_skb_frag(q, skb, buf_info, 0, frag_len, synced);
  len -= frag_len;
 }

 return skb;

err_bad_buf_page:
 dev_kfree_skb(skb);
 return NULL;
}

static struct sk_buff *ionic_rx_copybreak(struct net_device *netdev,
       struct ionic_queue *q,
       struct ionic_rx_desc_info *desc_info,
       unsigned int headroom,
       unsigned int len,
       unsigned int num_sg_elems,
       bool synced)
{
 struct ionic_buf_info *buf_info;
 struct device *dev = q->dev;
 struct sk_buff *skb;
 int i;

 buf_info = &desc_info->bufs[0];

 skb = napi_alloc_skb(&q_to_qcq(q)->napi, len);
 if (unlikely(!skb)) {
  net_warn_ratelimited("%s: SKB alloc failed on %s!\n",
         dev_name(dev), q->name);
  q_to_rx_stats(q)->alloc_err++;
  return NULL;
 }
 skb_mark_for_recycle(skb);

 if (!synced)
  page_pool_dma_sync_for_cpu(q->page_pool,
        buf_info->page,
        buf_info->page_offset + headroom,
        len);

 skb_copy_to_linear_data(skb, ionic_rx_buf_va(buf_info) + headroom, len);

 skb_put(skb, len);
 skb->protocol = eth_type_trans(skb, netdev);

 /* recycle the Rx buffer now that we're done with it */
 ionic_rx_put_buf_direct(q, buf_info);
 buf_info++;
 for (i = 0; i < num_sg_elems; i++, buf_info++)
  ionic_rx_put_buf_direct(q, buf_info);

 return skb;
}

static void ionic_xdp_tx_desc_clean(struct ionic_queue *q,
        struct ionic_tx_desc_info *desc_info,
        bool in_napi)
{
 struct xdp_frame_bulk bq;

 if (!desc_info->nbufs)
  return;

 xdp_frame_bulk_init(&bq);
 rcu_read_lock(); /* need for xdp_return_frame_bulk */

 if (desc_info->act == XDP_TX) {
  if (likely(in_napi))
   xdp_return_frame_rx_napi(desc_info->xdpf);
  else
   xdp_return_frame(desc_info->xdpf);
 } else if (desc_info->act == XDP_REDIRECT) {
  ionic_tx_desc_unmap_bufs(q, desc_info);
  xdp_return_frame_bulk(desc_info->xdpf, &bq);
 }

 xdp_flush_frame_bulk(&bq);
 rcu_read_unlock();

 desc_info->nbufs = 0;
 desc_info->xdpf = NULL;
 desc_info->act = 0;
}

static int ionic_xdp_post_frame(struct ionic_queue *q, struct xdp_frame *frame,
    enum xdp_action act, struct page *page, int off,
    bool ring_doorbell)
{
 struct ionic_tx_desc_info *desc_info;
 struct ionic_buf_info *buf_info;
 struct ionic_tx_stats *stats;
 struct ionic_txq_desc *desc;
 size_t len = frame->len;
 dma_addr_t dma_addr;
 u64 cmd;

 desc_info = &q->tx_info[q->head_idx];
 desc = &q->txq[q->head_idx];
 buf_info = desc_info->bufs;
 stats = q_to_tx_stats(q);

 if (act == XDP_TX) {
  dma_addr = page_pool_get_dma_addr(page) +
      off + XDP_PACKET_HEADROOM;
  dma_sync_single_for_device(q->dev, dma_addr,
        len, DMA_TO_DEVICE);
 } else /* XDP_REDIRECT */ {
  dma_addr = ionic_tx_map_single(q, frame->data, len);
  if (dma_addr == DMA_MAPPING_ERROR)
   return -EIO;
 }

 buf_info->dma_addr = dma_addr;
 buf_info->len = len;
 buf_info->page = page;
 buf_info->page_offset = off;

 desc_info->nbufs = 1;
 desc_info->xdpf = frame;
 desc_info->act = act;

 if (xdp_frame_has_frags(frame)) {
  struct ionic_txq_sg_elem *elem;
  struct skb_shared_info *sinfo;
  struct ionic_buf_info *bi;
  skb_frag_t *frag;
  int i;

  bi = &buf_info[1];
  sinfo = xdp_get_shared_info_from_frame(frame);
  frag = sinfo->frags;
  elem = ionic_tx_sg_elems(q);
  for (i = 0; i < sinfo->nr_frags; i++, frag++, bi++) {
   if (act == XDP_TX) {
    struct page *pg = skb_frag_page(frag);

    dma_addr = page_pool_get_dma_addr(pg) +
        skb_frag_off(frag);
    dma_sync_single_for_device(q->dev, dma_addr,
          skb_frag_size(frag),
          DMA_TO_DEVICE);
   } else {
    dma_addr = ionic_tx_map_frag(q, frag, 0,
            skb_frag_size(frag));
    if (dma_addr == DMA_MAPPING_ERROR) {
     ionic_tx_desc_unmap_bufs(q, desc_info);
     return -EIO;
    }
   }
   bi->dma_addr = dma_addr;
   bi->len = skb_frag_size(frag);
   bi->page = skb_frag_page(frag);

   elem->addr = cpu_to_le64(bi->dma_addr);
   elem->len = cpu_to_le16(bi->len);
   elem++;

   desc_info->nbufs++;
  }
 }

 cmd = encode_txq_desc_cmd(IONIC_TXQ_DESC_OPCODE_CSUM_NONE,
      0, (desc_info->nbufs - 1), buf_info->dma_addr);
 desc->cmd = cpu_to_le64(cmd);
 desc->len = cpu_to_le16(len);
 desc->csum_start = 0;
 desc->csum_offset = 0;

 stats->xdp_frames++;
 stats->pkts++;
 stats->bytes += len;

 ionic_txq_post(q, ring_doorbell);

 return 0;
}

int ionic_xdp_xmit(struct net_device *netdev, int n,
     struct xdp_frame **xdp_frames, u32 flags)
{
 struct ionic_lif *lif = netdev_priv(netdev);
 struct ionic_queue *txq;
 struct netdev_queue *nq;
 int nxmit;
 int space;
 int cpu;
 int qi;

 if (unlikely(!test_bit(IONIC_LIF_F_UP, lif->state)))
  return -ENETDOWN;

 if (unlikely(flags & ~XDP_XMIT_FLAGS_MASK))
  return -EINVAL;

 /* AdminQ is assumed on cpu 0, while we attempt to affinitize the
 * TxRx queue pairs 0..n-1 on cpus 1..n.  We try to keep with that
 * affinitization here, but of course irqbalance and friends might
 * have juggled things anyway, so we have to check for the 0 case.
 */

 cpu = smp_processor_id();
 qi = cpu ? (cpu - 1) % lif->nxqs : cpu;

 txq = &lif->txqcqs[qi]->q;
 nq = netdev_get_tx_queue(netdev, txq->index);
 __netif_tx_lock(nq, cpu);
 txq_trans_cond_update(nq);

 if (netif_tx_queue_stopped(nq) ||
     !netif_txq_maybe_stop(q_to_ndq(netdev, txq),
      ionic_q_space_avail(txq),
      1, 1)) {
  __netif_tx_unlock(nq);
  return -EIO;
 }

 space = min_t(int, n, ionic_q_space_avail(txq));
 for (nxmit = 0; nxmit < space ; nxmit++) {
  if (ionic_xdp_post_frame(txq, xdp_frames[nxmit],
      XDP_REDIRECT,
      virt_to_page(xdp_frames[nxmit]->data),
      0, false)) {
   nxmit--;
   break;
  }
 }

 if (flags & XDP_XMIT_FLUSH)
  ionic_dbell_ring(lif->kern_dbpage, txq->hw_type,
     txq->dbval | txq->head_idx);

 netif_txq_maybe_stop(q_to_ndq(netdev, txq),
        ionic_q_space_avail(txq),
        4, 4);
 __netif_tx_unlock(nq);

 return nxmit;
}

static void ionic_xdp_rx_unlink_bufs(struct ionic_queue *q,
         struct ionic_buf_info *buf_info,
         int nbufs)
{
 int i;

 for (i = 0; i < nbufs; i++) {
  buf_info->page = NULL;
  buf_info++;
 }
}

static bool ionic_run_xdp(struct ionic_rx_stats *stats,
     struct net_device *netdev,
     struct bpf_prog *xdp_prog,
     struct ionic_queue *rxq,
     struct ionic_buf_info *buf_info,
     int len)
{
 u32 xdp_action = XDP_ABORTED;
 struct xdp_buff xdp_buf;
 struct ionic_queue *txq;
 struct netdev_queue *nq;
 struct xdp_frame *xdpf;
 int remain_len;
 int nbufs = 1;
 int frag_len;
 int err = 0;

 xdp_init_buff(&xdp_buf, IONIC_PAGE_SIZE, rxq->xdp_rxq_info);
 frag_len = min_t(u16, len, IONIC_XDP_MAX_LINEAR_MTU + VLAN_ETH_HLEN);
 xdp_prepare_buff(&xdp_buf, ionic_rx_buf_va(buf_info),
    XDP_PACKET_HEADROOM, frag_len, false);
 page_pool_dma_sync_for_cpu(rxq->page_pool, buf_info->page,
       buf_info->page_offset + XDP_PACKET_HEADROOM,
       frag_len);
 prefetchw(&xdp_buf.data_hard_start);

 /*  We limit MTU size to one buffer if !xdp_has_frags, so
 *  if the recv len is bigger than one buffer
 *     then we know we have frag info to gather
 */

 remain_len = len - frag_len;
 if (remain_len) {
  struct skb_shared_info *sinfo;
  struct ionic_buf_info *bi;
  skb_frag_t *frag;

  bi = buf_info;
  sinfo = xdp_get_shared_info_from_buff(&xdp_buf);
  sinfo->nr_frags = 0;
  sinfo->xdp_frags_size = 0;
  xdp_buff_set_frags_flag(&xdp_buf);

  do {
   if (unlikely(sinfo->nr_frags >= MAX_SKB_FRAGS)) {
    err = -ENOSPC;
    break;
   }

   frag = &sinfo->frags[sinfo->nr_frags];
   sinfo->nr_frags++;
   bi++;
   frag_len = min_t(u16, remain_len, bi->len);
   page_pool_dma_sync_for_cpu(rxq->page_pool, bi->page,
         buf_info->page_offset,
         frag_len);
   skb_frag_fill_page_desc(frag, bi->page, 0, frag_len);
   sinfo->xdp_frags_size += frag_len;
   remain_len -= frag_len;

   if (page_is_pfmemalloc(bi->page))
    xdp_buff_set_frag_pfmemalloc(&xdp_buf);
  } while (remain_len > 0);
  nbufs += sinfo->nr_frags;
 }

 xdp_action = bpf_prog_run_xdp(xdp_prog, &xdp_buf);

 switch (xdp_action) {
 case XDP_PASS:
  stats->xdp_pass++;
  return false;  /* false = we didn't consume the packet */

 case XDP_DROP:
  ionic_rx_put_buf_direct(rxq, buf_info);
  stats->xdp_drop++;
  break;

 case XDP_TX:
  xdpf = xdp_convert_buff_to_frame(&xdp_buf);
  if (!xdpf) {
   err = -ENOSPC;
   break;
  }

  txq = rxq->partner;
  nq = netdev_get_tx_queue(netdev, txq->index);
  __netif_tx_lock(nq, smp_processor_id());
  txq_trans_cond_update(nq);

  if (netif_tx_queue_stopped(nq) ||
      !netif_txq_maybe_stop(q_to_ndq(netdev, txq),
       ionic_q_space_avail(txq),
       1, 1)) {
   __netif_tx_unlock(nq);
   err = -EIO;
   break;
  }

  err = ionic_xdp_post_frame(txq, xdpf, XDP_TX,
        buf_info->page,
        buf_info->page_offset,
        true);
  __netif_tx_unlock(nq);
  if (unlikely(err)) {
   netdev_dbg(netdev, "tx ionic_xdp_post_frame err %d\n", err);
   break;
  }
  ionic_xdp_rx_unlink_bufs(rxq, buf_info, nbufs);
  stats->xdp_tx++;
  break;

 case XDP_REDIRECT:
  err = xdp_do_redirect(netdev, &xdp_buf, xdp_prog);
  if (unlikely(err)) {
   netdev_dbg(netdev, "xdp_do_redirect err %d\n", err);
   break;
  }
  ionic_xdp_rx_unlink_bufs(rxq, buf_info, nbufs);
  rxq->xdp_flush = true;
  stats->xdp_redirect++;
  break;

 case XDP_ABORTED:
 default:
  err = -EIO;
  break;
 }

 if (err) {
  ionic_rx_put_buf_direct(rxq, buf_info);
  trace_xdp_exception(netdev, xdp_prog, xdp_action);
  stats->xdp_aborted++;
 }

 return true;
}

static void ionic_rx_clean(struct ionic_queue *q,
      struct ionic_rx_desc_info *desc_info,
      struct ionic_rxq_comp *comp,
      struct bpf_prog *xdp_prog)
{
 struct net_device *netdev = q->lif->netdev;
 struct ionic_qcq *qcq = q_to_qcq(q);
 struct ionic_rx_stats *stats;
 unsigned int headroom = 0;
 struct sk_buff *skb;
 bool synced = false;
 bool use_copybreak;
 u16 len;

 stats = q_to_rx_stats(q);

 if (unlikely(comp->status)) {
  /* Most likely status==2 and the pkt received was bigger
 * than the buffer available: comp->len will show the
 * pkt size received that didn't fit the advertised desc.len
 */

  dev_dbg(q->dev, "q%d drop comp->status %d comp->len %d desc->len %d\n",
   q->index, comp->status, comp->len, q->rxq[q->head_idx].len);

  stats->dropped++;
  return;
 }

 len = le16_to_cpu(comp->len);
 stats->pkts++;
 stats->bytes += len;

 if (xdp_prog) {
  if (ionic_run_xdp(stats, netdev, xdp_prog, q, desc_info->bufs, len))
   return;
  synced = true;
  headroom = XDP_PACKET_HEADROOM;
 }

 use_copybreak = len <= q->lif->rx_copybreak;
 if (use_copybreak)
  skb = ionic_rx_copybreak(netdev, q, desc_info,
      headroom, len,
      comp->num_sg_elems, synced);
 else
  skb = ionic_rx_build_skb(q, desc_info, headroom, len,
      comp->num_sg_elems, synced);

 if (unlikely(!skb)) {
  stats->dropped++;
  return;
 }

 skb_record_rx_queue(skb, q->index);

 if (likely(netdev->features & NETIF_F_RXHASH)) {
  switch (comp->pkt_type_color & IONIC_RXQ_COMP_PKT_TYPE_MASK) {
  case IONIC_PKT_TYPE_IPV4:
  case IONIC_PKT_TYPE_IPV6:
   skb_set_hash(skb, le32_to_cpu(comp->rss_hash),
         PKT_HASH_TYPE_L3);
   break;
  case IONIC_PKT_TYPE_IPV4_TCP:
  case IONIC_PKT_TYPE_IPV6_TCP:
  case IONIC_PKT_TYPE_IPV4_UDP:
  case IONIC_PKT_TYPE_IPV6_UDP:
   skb_set_hash(skb, le32_to_cpu(comp->rss_hash),
         PKT_HASH_TYPE_L4);
   break;
  }
 }

 if (likely(netdev->features & NETIF_F_RXCSUM) &&
     (comp->csum_flags & IONIC_RXQ_COMP_CSUM_F_CALC)) {
  skb->ip_summed = CHECKSUM_COMPLETE;
  skb->csum = (__force __wsum)le16_to_cpu(comp->csum);
  stats->csum_complete++;
 } else {
  stats->csum_none++;
 }

 if (unlikely((comp->csum_flags & IONIC_RXQ_COMP_CSUM_F_TCP_BAD) ||
       (comp->csum_flags & IONIC_RXQ_COMP_CSUM_F_UDP_BAD) ||
       (comp->csum_flags & IONIC_RXQ_COMP_CSUM_F_IP_BAD)))
  stats->csum_error++;

 if (likely(netdev->features & NETIF_F_HW_VLAN_CTAG_RX) &&
     (comp->csum_flags & IONIC_RXQ_COMP_CSUM_F_VLAN)) {
  __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q),
           le16_to_cpu(comp->vlan_tci));
  stats->vlan_stripped++;
 }

 if (unlikely(q->features & IONIC_RXQ_F_HWSTAMP)) {
  __le64 *cq_desc_hwstamp;
  u64 hwstamp;

  cq_desc_hwstamp =
   (void *)comp +
   qcq->cq.desc_size -
   sizeof(struct ionic_rxq_comp) -
   IONIC_HWSTAMP_CQ_NEGOFFSET;

  hwstamp = le64_to_cpu(*cq_desc_hwstamp);

  if (hwstamp != IONIC_HWSTAMP_INVALID) {
   skb_hwtstamps(skb)->hwtstamp = ionic_lif_phc_ktime(q->lif, hwstamp);
   stats->hwstamp_valid++;
  } else {
   stats->hwstamp_invalid++;
  }
 }

 if (use_copybreak)
  napi_gro_receive(&qcq->napi, skb);
 else
  napi_gro_frags(&qcq->napi);
}

static bool __ionic_rx_service(struct ionic_cq *cq, struct bpf_prog *xdp_prog)
{
 struct ionic_rx_desc_info *desc_info;
 struct ionic_queue *q = cq->bound_q;
 struct ionic_rxq_comp *comp;

 comp = &((struct ionic_rxq_comp *)cq->base)[cq->tail_idx];

 if (!color_match(comp->pkt_type_color, cq->done_color))
  return false;

 /* check for empty queue */
 if (q->tail_idx == q->head_idx)
  return false;

 if (q->tail_idx != le16_to_cpu(comp->comp_index))
  return false;

 desc_info = &q->rx_info[q->tail_idx];
 q->tail_idx = (q->tail_idx + 1) & (q->num_descs - 1);

 /* clean the related q entry, only one per qc completion */
 ionic_rx_clean(q, desc_info, comp, xdp_prog);

 return true;
}

bool ionic_rx_service(struct ionic_cq *cq)
{
 return __ionic_rx_service(cq, NULL);
}

static inline void ionic_write_cmb_desc(struct ionic_queue *q,
     void *desc)
{
 /* Since Rx and Tx descriptors are the same size, we can
 * save an instruction or two and skip the qtype check.
 */

 if (unlikely(q_to_qcq(q)->flags & IONIC_QCQ_F_CMB_RINGS))
  memcpy_toio(&q->cmb_txq[q->head_idx], desc, sizeof(q->cmb_txq[0]));
}

void ionic_rx_fill(struct ionic_queue *q, struct bpf_prog *xdp_prog)
{
 struct net_device *netdev = q->lif->netdev;
 struct ionic_rx_desc_info *desc_info;
 struct ionic_rxq_sg_elem *sg_elem;
 struct ionic_buf_info *buf_info;
 unsigned int fill_threshold;
 struct ionic_rxq_desc *desc;
 unsigned int first_frag_len;
 unsigned int first_buf_len;
 unsigned int headroom = 0;
 unsigned int remain_len;
 unsigned int frag_len;
 unsigned int nfrags;
 unsigned int n_fill;
 unsigned int len;
 unsigned int i;
 unsigned int j;

 n_fill = ionic_q_space_avail(q);

 fill_threshold = min_t(unsigned int, IONIC_RX_FILL_THRESHOLD,
          q->num_descs / IONIC_RX_FILL_DIV);
 if (n_fill < fill_threshold)
  return;

 len = netdev->mtu + VLAN_ETH_HLEN;

 if (xdp_prog) {
  /* Always alloc the full size buffer, but only need
 * the actual frag_len in the descriptor
 * XDP uses space in the first buffer, so account for
 * head room, tail room, and ip header in the first frag size.
 */

  headroom = XDP_PACKET_HEADROOM;
  first_buf_len = IONIC_XDP_MAX_LINEAR_MTU + VLAN_ETH_HLEN + headroom;
  first_frag_len = min_t(u16, len + headroom, first_buf_len);
 } else {
  /* Use MTU size if smaller than max buffer size */
  first_frag_len = min_t(u16, len, IONIC_PAGE_SIZE);
  first_buf_len = first_frag_len;
 }

 for (i = n_fill; i; i--) {
  /* fill main descriptor - buf[0] */
  nfrags = 0;
  remain_len = len;
  desc = &q->rxq[q->head_idx];
  desc_info = &q->rx_info[q->head_idx];
  buf_info = &desc_info->bufs[0];

  buf_info->len = first_buf_len;
  frag_len = first_frag_len - headroom;

  /* get a new buffer if we can't reuse one */
  if (!buf_info->page)
   buf_info->page = page_pool_alloc(q->page_pool,
        &buf_info->page_offset,
        &buf_info->len,
        GFP_ATOMIC);
  if (unlikely(!buf_info->page)) {
   buf_info->len = 0;
   return;
  }

  desc->addr = cpu_to_le64(ionic_rx_buf_pa(buf_info) + headroom);
  desc->len = cpu_to_le16(frag_len);
  remain_len -= frag_len;
  buf_info++;
  nfrags++;

  /* fill sg descriptors - buf[1..n] */
  sg_elem = q->rxq_sgl[q->head_idx].elems;
  for (j = 0; remain_len > 0 && j < q->max_sg_elems; j++, sg_elem++) {
   frag_len = min_t(u16, remain_len, IONIC_PAGE_SIZE);

   /* Recycle any leftover buffers that are too small to reuse */
   if (unlikely(buf_info->page && buf_info->len < frag_len))
    ionic_rx_put_buf_direct(q, buf_info);

   /* Get new buffer if needed */
   if (!buf_info->page) {
    buf_info->len = frag_len;
    buf_info->page = page_pool_alloc(q->page_pool,
         &buf_info->page_offset,
         &buf_info->len,
         GFP_ATOMIC);
    if (unlikely(!buf_info->page)) {
     buf_info->len = 0;
     return;
    }
   }

   sg_elem->addr = cpu_to_le64(ionic_rx_buf_pa(buf_info));
   sg_elem->len = cpu_to_le16(frag_len);
   remain_len -= frag_len;
   buf_info++;
   nfrags++;
  }

  /* clear end sg element as a sentinel */
  if (j < q->max_sg_elems)
   memset(sg_elem, 0, sizeof(*sg_elem));

  desc->opcode = (nfrags > 1) ? IONIC_RXQ_DESC_OPCODE_SG :
           IONIC_RXQ_DESC_OPCODE_SIMPLE;
  desc_info->nbufs = nfrags;

  ionic_write_cmb_desc(q, desc);

  ionic_rxq_post(q, false);
 }

 ionic_dbell_ring(q->lif->kern_dbpage, q->hw_type,
    q->dbval | q->head_idx);

 q->dbell_deadline = IONIC_RX_MIN_DOORBELL_DEADLINE;
 q->dbell_jiffies = jiffies;
}

void ionic_rx_empty(struct ionic_queue *q)
{
 struct ionic_rx_desc_info *desc_info;
 unsigned int i, j;

 for (i = 0; i < q->num_descs; i++) {
  desc_info = &q->rx_info[i];
  for (j = 0; j < ARRAY_SIZE(desc_info->bufs); j++)
   ionic_rx_put_buf(q, &desc_info->bufs[j]);
  desc_info->nbufs = 0;
 }

 q->head_idx = 0;
 q->tail_idx = 0;
}

static void ionic_dim_update(struct ionic_qcq *qcq, int napi_mode)
{
 struct dim_sample dim_sample;
 struct ionic_lif *lif;
 unsigned int qi;
 u64 pkts, bytes;

 if (!qcq->intr.dim_coal_hw)
  return;

 lif = qcq->q.lif;
 qi = qcq->cq.bound_q->index;

 switch (napi_mode) {
 case IONIC_LIF_F_TX_DIM_INTR:
  pkts = lif->txqstats[qi].pkts;
  bytes = lif->txqstats[qi].bytes;
  break;
 case IONIC_LIF_F_RX_DIM_INTR:
  pkts = lif->rxqstats[qi].pkts;
  bytes = lif->rxqstats[qi].bytes;
  break;
 default:
  pkts = lif->txqstats[qi].pkts + lif->rxqstats[qi].pkts;
  bytes = lif->txqstats[qi].bytes + lif->rxqstats[qi].bytes;
  break;
 }

 dim_update_sample(qcq->cq.bound_intr->rearm_count,
     pkts, bytes, &dim_sample);

 net_dim(&qcq->dim, &dim_sample);
}

int ionic_tx_napi(struct napi_struct *napi, int budget)
{
 struct ionic_qcq *qcq = napi_to_qcq(napi);
 struct ionic_cq *cq = napi_to_cq(napi);
 u32 work_done = 0;
 u32 flags = 0;

 work_done = ionic_tx_cq_service(cq, budget, !!budget);

 if (unlikely(!budget))
  return budget;

 if (work_done < budget && napi_complete_done(napi, work_done)) {
  ionic_dim_update(qcq, IONIC_LIF_F_TX_DIM_INTR);
  flags |= IONIC_INTR_CRED_UNMASK;
  cq->bound_intr->rearm_count++;
 }

 if (work_done || flags) {
  flags |= IONIC_INTR_CRED_RESET_COALESCE;
  ionic_intr_credits(cq->idev->intr_ctrl,
       cq->bound_intr->index,
       work_done, flags);
 }

 if (!work_done && cq->bound_q->lif->doorbell_wa)
  ionic_txq_poke_doorbell(&qcq->q);

 return work_done;
}

static void ionic_xdp_do_flush(struct ionic_cq *cq)
{
 if (cq->bound_q->xdp_flush) {
  xdp_do_flush();
  cq->bound_q->xdp_flush = false;
 }
}

static unsigned int ionic_rx_cq_service(struct ionic_cq *cq,
     unsigned int work_to_do)
{
 struct ionic_queue *q = cq->bound_q;
 unsigned int work_done = 0;
 struct bpf_prog *xdp_prog;

 if (work_to_do == 0)
  return 0;

 xdp_prog = READ_ONCE(q->xdp_prog);
 while (__ionic_rx_service(cq, xdp_prog)) {
  if (cq->tail_idx == cq->num_descs - 1)
   cq->done_color = !cq->done_color;

  cq->tail_idx = (cq->tail_idx + 1) & (cq->num_descs - 1);

  if (++work_done >= work_to_do)
   break;
 }
 ionic_rx_fill(q, xdp_prog);
 ionic_xdp_do_flush(cq);

 return work_done;
}

int ionic_rx_napi(struct napi_struct *napi, int budget)
{
 struct ionic_qcq *qcq = napi_to_qcq(napi);
 struct ionic_cq *cq = napi_to_cq(napi);
 u32 work_done = 0;
 u32 flags = 0;

 if (unlikely(!budget))
  return budget;

 work_done = ionic_rx_cq_service(cq, budget);

 if (work_done < budget && napi_complete_done(napi, work_done)) {
  ionic_dim_update(qcq, IONIC_LIF_F_RX_DIM_INTR);
  flags |= IONIC_INTR_CRED_UNMASK;
  cq->bound_intr->rearm_count++;
 }

 if (work_done || flags) {
  flags |= IONIC_INTR_CRED_RESET_COALESCE;
  ionic_intr_credits(cq->idev->intr_ctrl,
       cq->bound_intr->index,
       work_done, flags);
 }

 if (!work_done && cq->bound_q->lif->doorbell_wa)
  ionic_rxq_poke_doorbell(&qcq->q);

 return work_done;
}

int ionic_txrx_napi(struct napi_struct *napi, int budget)
{
 struct ionic_qcq *rxqcq = napi_to_qcq(napi);
 struct ionic_cq *rxcq = napi_to_cq(napi);
 unsigned int qi = rxcq->bound_q->index;
 struct ionic_qcq *txqcq;
 struct ionic_lif *lif;
 struct ionic_cq *txcq;
 u32 rx_work_done = 0;
 u32 tx_work_done = 0;
 u32 flags = 0;

 lif = rxcq->bound_q->lif;
 txqcq = lif->txqcqs[qi];
 txcq = &lif->txqcqs[qi]->cq;

 tx_work_done = ionic_tx_cq_service(txcq, IONIC_TX_BUDGET_DEFAULT, !!budget);

 if (unlikely(!budget))
  return budget;

 rx_work_done = ionic_rx_cq_service(rxcq, budget);

 if (rx_work_done < budget && napi_complete_done(napi, rx_work_done)) {
  ionic_dim_update(rxqcq, 0);
  flags |= IONIC_INTR_CRED_UNMASK;
  rxcq->bound_intr->rearm_count++;
 }

 if (rx_work_done || flags) {
  flags |= IONIC_INTR_CRED_RESET_COALESCE;
  ionic_intr_credits(rxcq->idev->intr_ctrl, rxcq->bound_intr->index,
       tx_work_done + rx_work_done, flags);
 }

 if (lif->doorbell_wa) {
  if (!rx_work_done)
   ionic_rxq_poke_doorbell(&rxqcq->q);
  if (!tx_work_done)
   ionic_txq_poke_doorbell(&txqcq->q);
 }

 return rx_work_done;
}

static dma_addr_t ionic_tx_map_single(struct ionic_queue *q,
          void *data, size_t len)
{
 struct device *dev = q->dev;
 dma_addr_t dma_addr;

 dma_addr = dma_map_single(dev, data, len, DMA_TO_DEVICE);
 if (unlikely(dma_mapping_error(dev, dma_addr))) {
  net_warn_ratelimited("%s: DMA single map failed on %s!\n",
         dev_name(dev), q->name);
  q_to_tx_stats(q)->dma_map_err++;
  return DMA_MAPPING_ERROR;
 }
 return dma_addr;
}

static dma_addr_t ionic_tx_map_frag(struct ionic_queue *q,
        const skb_frag_t *frag,
        size_t offset, size_t len)
{
 struct device *dev = q->dev;
 dma_addr_t dma_addr;

 dma_addr = skb_frag_dma_map(dev, frag, offset, len, DMA_TO_DEVICE);
 if (unlikely(dma_mapping_error(dev, dma_addr))) {
  net_warn_ratelimited("%s: DMA frag map failed on %s!\n",
         dev_name(dev), q->name);
  q_to_tx_stats(q)->dma_map_err++;
  return DMA_MAPPING_ERROR;
 }
 return dma_addr;
}

static int ionic_tx_map_skb(struct ionic_queue *q, struct sk_buff *skb,
       struct ionic_tx_desc_info *desc_info)
{
 struct ionic_buf_info *buf_info = desc_info->bufs;
 struct device *dev = q->dev;
 dma_addr_t dma_addr;
 unsigned int nfrags;
 skb_frag_t *frag;
 int frag_idx;

 dma_addr = ionic_tx_map_single(q, skb->data, skb_headlen(skb));
 if (dma_addr == DMA_MAPPING_ERROR)
  return -EIO;
 buf_info->dma_addr = dma_addr;
 buf_info->len = skb_headlen(skb);
 buf_info++;

 frag = skb_shinfo(skb)->frags;
 nfrags = skb_shinfo(skb)->nr_frags;
 for (frag_idx = 0; frag_idx < nfrags; frag_idx++, frag++) {
  dma_addr = ionic_tx_map_frag(q, frag, 0, skb_frag_size(frag));
  if (dma_addr == DMA_MAPPING_ERROR)
   goto dma_fail;
  buf_info->dma_addr = dma_addr;
  buf_info->len = skb_frag_size(frag);
  buf_info++;
 }

 desc_info->nbufs = 1 + nfrags;

 return 0;

dma_fail:
 /* unwind the frag mappings and the head mapping */
 while (frag_idx > 0) {
  frag_idx--;
  buf_info--;
  dma_unmap_page(dev, buf_info->dma_addr,
          buf_info->len, DMA_TO_DEVICE);
 }
 dma_unmap_single(dev, desc_info->bufs[0].dma_addr,
    desc_info->bufs[0].len, DMA_TO_DEVICE);
 return -EIO;
}

static void ionic_tx_desc_unmap_bufs(struct ionic_queue *q,
         struct ionic_tx_desc_info *desc_info)
{
 struct ionic_buf_info *buf_info = desc_info->bufs;
 struct device *dev = q->dev;
 unsigned int i;

 if (!desc_info->nbufs)
  return;

 dma_unmap_single(dev, buf_info->dma_addr,
    buf_info->len, DMA_TO_DEVICE);
 buf_info++;
 for (i = 1; i < desc_info->nbufs; i++, buf_info++)
  dma_unmap_page(dev, buf_info->dma_addr,
          buf_info->len, DMA_TO_DEVICE);

 desc_info->nbufs = 0;
}

static void ionic_tx_clean(struct ionic_queue *q,
      struct ionic_tx_desc_info *desc_info,
      struct ionic_txq_comp *comp,
      bool in_napi)
{
 struct ionic_tx_stats *stats = q_to_tx_stats(q);
 struct ionic_qcq *qcq = q_to_qcq(q);
 struct sk_buff *skb;

 if (desc_info->xdpf) {
  ionic_xdp_tx_desc_clean(q->partner, desc_info, in_napi);
  stats->clean++;

  if (unlikely(__netif_subqueue_stopped(q->lif->netdev, q->index)))
   netif_wake_subqueue(q->lif->netdev, q->index);

  return;
 }

 ionic_tx_desc_unmap_bufs(q, desc_info);

 skb = desc_info->skb;
 if (!skb)
  return;

 if (unlikely(ionic_txq_hwstamp_enabled(q))) {
  if (comp) {
   struct skb_shared_hwtstamps hwts = {};
   __le64 *cq_desc_hwstamp;
   u64 hwstamp;

   cq_desc_hwstamp =
    (void *)comp +
    qcq->cq.desc_size -
    sizeof(struct ionic_txq_comp) -
    IONIC_HWSTAMP_CQ_NEGOFFSET;

   hwstamp = le64_to_cpu(*cq_desc_hwstamp);

   if (hwstamp != IONIC_HWSTAMP_INVALID) {
    hwts.hwtstamp = ionic_lif_phc_ktime(q->lif, hwstamp);

    skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
    skb_tstamp_tx(skb, &hwts);

    stats->hwstamp_valid++;
   } else {
    stats->hwstamp_invalid++;
   }
  }
 }

 desc_info->bytes = skb->len;
 stats->clean++;

 napi_consume_skb(skb, likely(in_napi) ? 1 : 0);
}

static bool ionic_tx_service(struct ionic_cq *cq,
        unsigned int *total_pkts,
        unsigned int *total_bytes,
        bool in_napi)
{
 struct ionic_tx_desc_info *desc_info;
 struct ionic_queue *q = cq->bound_q;
 struct ionic_txq_comp *comp;
 unsigned int bytes = 0;
 unsigned int pkts = 0;
 u16 index;

 comp = &((struct ionic_txq_comp *)cq->base)[cq->tail_idx];

 if (!color_match(comp->color, cq->done_color))
  return false;

 /* clean the related q entries, there could be
 * several q entries completed for each cq completion
 */

 do {
  desc_info = &q->tx_info[q->tail_idx];
  desc_info->bytes = 0;
  index = q->tail_idx;
  q->tail_idx = (q->tail_idx + 1) & (q->num_descs - 1);
  ionic_tx_clean(q, desc_info, comp, in_napi);
  if (desc_info->skb) {
   pkts++;
   bytes += desc_info->bytes;
   desc_info->skb = NULL;
  }
 } while (index != le16_to_cpu(comp->comp_index));

 (*total_pkts) += pkts;
 (*total_bytes) += bytes;

 return true;
}

unsigned int ionic_tx_cq_service(struct ionic_cq *cq,
     unsigned int work_to_do,
     bool in_napi)
{
 unsigned int work_done = 0;
 unsigned int bytes = 0;
 unsigned int pkts = 0;

 if (work_to_do == 0)
  return 0;

 while (ionic_tx_service(cq, &pkts, &bytes, in_napi)) {
  if (cq->tail_idx == cq->num_descs - 1)
   cq->done_color = !cq->done_color;
  cq->tail_idx = (cq->tail_idx + 1) & (cq->num_descs - 1);

  if (++work_done >= work_to_do)
   break;
 }

 if (work_done) {
  struct ionic_queue *q = cq->bound_q;

  if (likely(!ionic_txq_hwstamp_enabled(q)))
   netif_txq_completed_wake(q_to_ndq(q->lif->netdev, q),
       pkts, bytes,
       ionic_q_space_avail(q),
       IONIC_TSO_DESCS_NEEDED);
 }

 return work_done;
}

void ionic_tx_flush(struct ionic_cq *cq)
{
 u32 work_done;

 work_done = ionic_tx_cq_service(cq, cq->num_descs, false);
 if (work_done)
  ionic_intr_credits(cq->idev->intr_ctrl, cq->bound_intr->index,
       work_done, IONIC_INTR_CRED_RESET_COALESCE);
}

void ionic_tx_empty(struct ionic_queue *q)
{
 struct ionic_tx_desc_info *desc_info;
 int bytes = 0;
 int pkts = 0;

 /* walk the not completed tx entries, if any */
 while (q->head_idx != q->tail_idx) {
  desc_info = &q->tx_info[q->tail_idx];
  desc_info->bytes = 0;
  q->tail_idx = (q->tail_idx + 1) & (q->num_descs - 1);
  ionic_tx_clean(q, desc_info, NULL, false);
  if (desc_info->skb) {
   pkts++;
   bytes += desc_info->bytes;
   desc_info->skb = NULL;
  }
 }

 if (likely(!ionic_txq_hwstamp_enabled(q))) {
  struct netdev_queue *ndq = q_to_ndq(q->lif->netdev, q);

  netdev_tx_completed_queue(ndq, pkts, bytes);
  netdev_tx_reset_queue(ndq);
 }
}

static int ionic_tx_tcp_inner_pseudo_csum(struct sk_buff *skb)
{
 int err;

 err = skb_cow_head(skb, 0);
 if (unlikely(err))
  return err;

 if (skb->protocol == cpu_to_be16(ETH_P_IP)) {
  inner_ip_hdr(skb)->check = 0;
  inner_tcp_hdr(skb)->check =
   ~csum_tcpudp_magic(inner_ip_hdr(skb)->saddr,
        inner_ip_hdr(skb)->daddr,
        0, IPPROTO_TCP, 0);
 } else if (skb->protocol == cpu_to_be16(ETH_P_IPV6)) {
  inner_tcp_hdr(skb)->check =
   ~csum_ipv6_magic(&inner_ipv6_hdr(skb)->saddr,
      &inner_ipv6_hdr(skb)->daddr,
      0, IPPROTO_TCP, 0);
 }

 return 0;
}

static int ionic_tx_tcp_pseudo_csum(struct sk_buff *skb)
{
 int err;

 err = skb_cow_head(skb, 0);
 if (unlikely(err))
  return err;

 if (skb->protocol == cpu_to_be16(ETH_P_IP)) {
  ip_hdr(skb)->check = 0;
  tcp_hdr(skb)->check =
   ~csum_tcpudp_magic(ip_hdr(skb)->saddr,
        ip_hdr(skb)->daddr,
        0, IPPROTO_TCP, 0);
 } else if (skb->protocol == cpu_to_be16(ETH_P_IPV6)) {
  tcp_v6_gso_csum_prep(skb);
 }

 return 0;
}

static void ionic_tx_tso_post(struct net_device *netdev, struct ionic_queue *q,
         struct ionic_txq_desc *desc,
         struct sk_buff *skb,
         dma_addr_t addr, u8 nsge, u16 len,
         unsigned int hdrlen, unsigned int mss,
         bool outer_csum,
         u16 vlan_tci, bool has_vlan,
         bool start, bool done)
{
 u8 flags = 0;
 u64 cmd;

 flags |= has_vlan ? IONIC_TXQ_DESC_FLAG_VLAN : 0;
 flags |= outer_csum ? IONIC_TXQ_DESC_FLAG_ENCAP : 0;
 flags |= start ? IONIC_TXQ_DESC_FLAG_TSO_SOT : 0;
 flags |= done ? IONIC_TXQ_DESC_FLAG_TSO_EOT : 0;

 cmd = encode_txq_desc_cmd(IONIC_TXQ_DESC_OPCODE_TSO, flags, nsge, addr);
 desc->cmd = cpu_to_le64(cmd);
 desc->len = cpu_to_le16(len);
 desc->vlan_tci = cpu_to_le16(vlan_tci);
 desc->hdr_len = cpu_to_le16(hdrlen);
 desc->mss = cpu_to_le16(mss);

 ionic_write_cmb_desc(q, desc);

 if (start) {
  skb_tx_timestamp(skb);
  if (likely(!ionic_txq_hwstamp_enabled(q)))
   netdev_tx_sent_queue(q_to_ndq(netdev, q), skb->len);
  ionic_txq_post(q, false);
 } else {
  ionic_txq_post(q, done);
 }
}

static int ionic_tx_tso(struct net_device *netdev, struct ionic_queue *q,
   struct sk_buff *skb)
{
 struct ionic_tx_stats *stats = q_to_tx_stats(q);
 struct ionic_tx_desc_info *desc_info;
 struct ionic_buf_info *buf_info;
 struct ionic_txq_sg_elem *elem;
 struct ionic_txq_desc *desc;
 unsigned int chunk_len;
 unsigned int frag_rem;
 unsigned int tso_rem;
 unsigned int seg_rem;
 dma_addr_t desc_addr;
 dma_addr_t frag_addr;
 unsigned int hdrlen;
 unsigned int len;
 unsigned int mss;
 bool start, done;
 bool outer_csum;
 bool has_vlan;
 u16 desc_len;
 u8 desc_nsge;
 u16 vlan_tci;
 bool encap;
 int err;

 has_vlan = !!skb_vlan_tag_present(skb);
 vlan_tci = skb_vlan_tag_get(skb);
 encap = skb->encapsulation;

 /* Preload inner-most TCP csum field with IP pseudo hdr
 * calculated with IP length set to zero.  HW will later
 * add in length to each TCP segment resulting from the TSO.
 */


 if (encap)
  err = ionic_tx_tcp_inner_pseudo_csum(skb);
 else
  err = ionic_tx_tcp_pseudo_csum(skb);
 if (unlikely(err))
  return err;

 desc_info = &q->tx_info[q->head_idx];
 if (unlikely(ionic_tx_map_skb(q, skb, desc_info)))
  return -EIO;

 len = skb->len;
 mss = skb_shinfo(skb)->gso_size;
 outer_csum = (skb_shinfo(skb)->gso_type & (SKB_GSO_GRE |
         SKB_GSO_GRE_CSUM |
         SKB_GSO_IPXIP4 |
         SKB_GSO_IPXIP6 |
         SKB_GSO_UDP_TUNNEL |
         SKB_GSO_UDP_TUNNEL_CSUM));
 if (encap)
  hdrlen = skb_inner_tcp_all_headers(skb);
 else
  hdrlen = skb_tcp_all_headers(skb);

 desc_info->skb = skb;
 buf_info = desc_info->bufs;
 tso_rem = len;
 seg_rem = min(tso_rem, hdrlen + mss);

 frag_addr = 0;
 frag_rem = 0;

 start = true;

 while (tso_rem > 0) {
  desc = NULL;
  elem = NULL;
  desc_addr = 0;
  desc_len = 0;
  desc_nsge = 0;
  /* use fragments until we have enough to post a single descriptor */
  while (seg_rem > 0) {
   /* if the fragment is exhausted then move to the next one */
   if (frag_rem == 0) {
    /* grab the next fragment */
    frag_addr = buf_info->dma_addr;
    frag_rem = buf_info->len;
    buf_info++;
   }
   chunk_len = min(frag_rem, seg_rem);
   if (!desc) {
    /* fill main descriptor */
    desc = &q->txq[q->head_idx];
    elem = ionic_tx_sg_elems(q);
    desc_addr = frag_addr;
    desc_len = chunk_len;
   } else {
    /* fill sg descriptor */
    elem->addr = cpu_to_le64(frag_addr);
    elem->len = cpu_to_le16(chunk_len);
    elem++;
    desc_nsge++;
   }
   frag_addr += chunk_len;
   frag_rem -= chunk_len;
   tso_rem -= chunk_len;
   seg_rem -= chunk_len;
  }
  seg_rem = min(tso_rem, mss);
  done = (tso_rem == 0);
  /* post descriptor */
  ionic_tx_tso_post(netdev, q, desc, skb, desc_addr, desc_nsge,
      desc_len, hdrlen, mss, outer_csum, vlan_tci,
      has_vlan, start, done);
  start = false;
  /* Buffer information is stored with the first tso descriptor */
  desc_info = &q->tx_info[q->head_idx];
  desc_info->nbufs = 0;
 }

 stats->pkts += DIV_ROUND_UP(len - hdrlen, mss);
 stats->bytes += len;
 stats->tso++;
 stats->tso_bytes = len;

 return 0;
}

static void ionic_tx_calc_csum(struct ionic_queue *q, struct sk_buff *skb,
          struct ionic_tx_desc_info *desc_info)
{
 struct ionic_txq_desc *desc = &q->txq[q->head_idx];
 struct ionic_buf_info *buf_info = desc_info->bufs;
 struct ionic_tx_stats *stats = q_to_tx_stats(q);
 bool has_vlan;
 u8 flags = 0;
 bool encap;
 u64 cmd;

 has_vlan = !!skb_vlan_tag_present(skb);
 encap = skb->encapsulation;

 flags |= has_vlan ? IONIC_TXQ_DESC_FLAG_VLAN : 0;
 flags |= encap ? IONIC_TXQ_DESC_FLAG_ENCAP : 0;

 cmd = encode_txq_desc_cmd(IONIC_TXQ_DESC_OPCODE_CSUM_PARTIAL,
      flags, skb_shinfo(skb)->nr_frags,
      buf_info->dma_addr);
 desc->cmd = cpu_to_le64(cmd);
 desc->len = cpu_to_le16(buf_info->len);
 if (has_vlan) {
  desc->vlan_tci = cpu_to_le16(skb_vlan_tag_get(skb));
  stats->vlan_inserted++;
 } else {
  desc->vlan_tci = 0;
 }
 desc->csum_start = cpu_to_le16(skb_checksum_start_offset(skb));
 desc->csum_offset = cpu_to_le16(skb->csum_offset);

 ionic_write_cmb_desc(q, desc);

 if (skb_csum_is_sctp(skb))
  stats->crc32_csum++;
 else
  stats->csum++;
}

static void ionic_tx_calc_no_csum(struct ionic_queue *q, struct sk_buff *skb,
      struct ionic_tx_desc_info *desc_info)
{
 struct ionic_txq_desc *desc = &q->txq[q->head_idx];
 struct ionic_buf_info *buf_info = desc_info->bufs;
 struct ionic_tx_stats *stats = q_to_tx_stats(q);
 bool has_vlan;
 u8 flags = 0;
 bool encap;
 u64 cmd;

 has_vlan = !!skb_vlan_tag_present(skb);
 encap = skb->encapsulation;

 flags |= has_vlan ? IONIC_TXQ_DESC_FLAG_VLAN : 0;
 flags |= encap ? IONIC_TXQ_DESC_FLAG_ENCAP : 0;

 cmd = encode_txq_desc_cmd(IONIC_TXQ_DESC_OPCODE_CSUM_NONE,
      flags, skb_shinfo(skb)->nr_frags,
      buf_info->dma_addr);
 desc->cmd = cpu_to_le64(cmd);
 desc->len = cpu_to_le16(buf_info->len);
 if (has_vlan) {
  desc->vlan_tci = cpu_to_le16(skb_vlan_tag_get(skb));
  stats->vlan_inserted++;
 } else {
  desc->vlan_tci = 0;
 }
 desc->csum_start = 0;
 desc->csum_offset = 0;

 ionic_write_cmb_desc(q, desc);

 stats->csum_none++;
}

static void ionic_tx_skb_frags(struct ionic_queue *q, struct sk_buff *skb,
          struct ionic_tx_desc_info *desc_info)
{
 struct ionic_buf_info *buf_info = &desc_info->bufs[1];
 struct ionic_tx_stats *stats = q_to_tx_stats(q);
 struct ionic_txq_sg_elem *elem;
 unsigned int i;

 elem = ionic_tx_sg_elems(q);
 for (i = 0; i < skb_shinfo(skb)->nr_frags; i++, buf_info++, elem++) {
  elem->addr = cpu_to_le64(buf_info->dma_addr);
  elem->len = cpu_to_le16(buf_info->len);
 }

 stats->frags += skb_shinfo(skb)->nr_frags;
}

static int ionic_tx(struct net_device *netdev, struct ionic_queue *q,
      struct sk_buff *skb)
{
 struct ionic_tx_desc_info *desc_info = &q->tx_info[q->head_idx];
 struct ionic_tx_stats *stats = q_to_tx_stats(q);
 bool ring_dbell = true;

 if (unlikely(ionic_tx_map_skb(q, skb, desc_info)))
  return -EIO;

 desc_info->skb = skb;

 /* set up the initial descriptor */
 if (skb->ip_summed == CHECKSUM_PARTIAL)
  ionic_tx_calc_csum(q, skb, desc_info);
 else
  ionic_tx_calc_no_csum(q, skb, desc_info);

 /* add frags */
 ionic_tx_skb_frags(q, skb, desc_info);

 skb_tx_timestamp(skb);
 stats->pkts++;
 stats->bytes += skb->len;

 if (likely(!ionic_txq_hwstamp_enabled(q))) {
  struct netdev_queue *ndq = q_to_ndq(netdev, q);

  if (unlikely(!ionic_q_has_space(q, MAX_SKB_FRAGS + 1)))
   netif_tx_stop_queue(ndq);
  ring_dbell = __netdev_tx_sent_queue(ndq, skb->len,
          netdev_xmit_more());
 }
 ionic_txq_post(q, ring_dbell);

 return 0;
}

static int ionic_tx_descs_needed(struct ionic_queue *q, struct sk_buff *skb)
{
 int nr_frags = skb_shinfo(skb)->nr_frags;
 bool too_many_frags = false;
 skb_frag_t *frag;
 int desc_bufs;
 int chunk_len;
 int frag_rem;
 int tso_rem;
 int seg_rem;
 bool encap;
 int hdrlen;
 int ndescs;
 int err;

 /* Each desc is mss long max, so a descriptor for each gso_seg */
 if (skb_is_gso(skb)) {
  ndescs = skb_shinfo(skb)->gso_segs;
  if (!nr_frags)
   return ndescs;
 } else {
  ndescs = 1;
  if (!nr_frags)
   return ndescs;

  if (unlikely(nr_frags > q->max_sg_elems)) {
   too_many_frags = true;
   goto linearize;
  }

  return ndescs;
 }

 /* We need to scan the skb to be sure that none of the MTU sized
 * packets in the TSO will require more sgs per descriptor than we
 * can support.  We loop through the frags, add up the lengths for
 * a packet, and count the number of sgs used per packet.
 */

 tso_rem = skb->len;
 frag = skb_shinfo(skb)->frags;
 encap = skb->encapsulation;

 /* start with just hdr in first part of first descriptor */
 if (encap)
  hdrlen = skb_inner_tcp_all_headers(skb);
 else
  hdrlen = skb_tcp_all_headers(skb);
 seg_rem = min_t(int, tso_rem, hdrlen + skb_shinfo(skb)->gso_size);
 frag_rem = hdrlen;

 while (tso_rem > 0) {
  desc_bufs = 0;
  while (seg_rem > 0) {
   desc_bufs++;

   /* We add the +1 because we can take buffers for one
 * more than we have SGs: one for the initial desc data
 * in addition to the SG segments that might follow.
 */

   if (desc_bufs > q->max_sg_elems + 1) {
    too_many_frags = true;
    goto linearize;
   }

   if (frag_rem == 0) {
    frag_rem = skb_frag_size(frag);
    frag++;
   }
   chunk_len = min(frag_rem, seg_rem);
   frag_rem -= chunk_len;
   tso_rem -= chunk_len;
   seg_rem -= chunk_len;
  }

  seg_rem = min_t(int, tso_rem, skb_shinfo(skb)->gso_size);
 }

linearize:
 if (too_many_frags) {
  err = skb_linearize(skb);
  if (unlikely(err))
   return err;
  q_to_tx_stats(q)->linearize++;
 }

 return ndescs;
}

static netdev_tx_t ionic_start_hwstamp_xmit(struct sk_buff *skb,
         struct net_device *netdev)
{
 struct ionic_lif *lif = netdev_priv(netdev);
 struct ionic_queue *q;
 int err, ndescs;

 /* Does not stop/start txq, because we post to a separate tx queue
 * for timestamping, and if a packet can't be posted immediately to
 * the timestamping queue, it is dropped.
 */


 q = &lif->hwstamp_txq->q;
 ndescs = ionic_tx_descs_needed(q, skb);
 if (unlikely(ndescs < 0))
  goto err_out_drop;

 if (unlikely(!ionic_q_has_space(q, ndescs)))
  goto err_out_drop;

 skb_shinfo(skb)->tx_flags |= SKBTX_HW_TSTAMP;
 if (skb_is_gso(skb))
  err = ionic_tx_tso(netdev, q, skb);
 else
  err = ionic_tx(netdev, q, skb);

 if (unlikely(err))
  goto err_out_drop;

 return NETDEV_TX_OK;

err_out_drop:
 q->drop++;
 dev_kfree_skb(skb);
 return NETDEV_TX_OK;
}

netdev_tx_t ionic_start_xmit(struct sk_buff *skb, struct net_device *netdev)
{
 u16 queue_index = skb_get_queue_mapping(skb);
 struct ionic_lif *lif = netdev_priv(netdev);
 struct ionic_queue *q;
 int ndescs;
 int err;

 if (unlikely(!test_bit(IONIC_LIF_F_UP, lif->state))) {
  dev_kfree_skb(skb);
  return NETDEV_TX_OK;
 }

 if (unlikely(skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP))
  if (lif->hwstamp_txq && lif->phc->ts_config_tx_mode)
   return ionic_start_hwstamp_xmit(skb, netdev);

 if (unlikely(queue_index >= lif->nxqs))
  queue_index = 0;
 q = &lif->txqcqs[queue_index]->q;

 ndescs = ionic_tx_descs_needed(q, skb);
 if (ndescs < 0)
  goto err_out_drop;

 if (!netif_txq_maybe_stop(q_to_ndq(netdev, q),
      ionic_q_space_avail(q),
      ndescs, ndescs))
  return NETDEV_TX_BUSY;

 if (skb_is_gso(skb))
  err = ionic_tx_tso(netdev, q, skb);
 else
  err = ionic_tx(netdev, q, skb);

 if (unlikely(err))
  goto err_out_drop;

 return NETDEV_TX_OK;

err_out_drop:
 q->drop++;
 dev_kfree_skb(skb);
 return NETDEV_TX_OK;
}

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

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