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

Quelle  dpaa2-eth.c   Sprache: C

 
// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
/* Copyright 2014-2016 Freescale Semiconductor Inc.
 * Copyright 2016-2022 NXP
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/etherdevice.h>
#include <linux/of_net.h>
#include <linux/interrupt.h>
#include <linux/kthread.h>
#include <linux/iommu.h>
#include <linux/fsl/mc.h>
#include <linux/bpf.h>
#include <linux/bpf_trace.h>
#include <linux/fsl/ptp_qoriq.h>
#include <linux/ptp_classify.h>
#include <net/pkt_cls.h>
#include <net/sock.h>
#include <net/tso.h>
#include <net/xdp_sock_drv.h>

#include "dpaa2-eth.h"

/* CREATE_TRACE_POINTS only needs to be defined once. Other dpa files
 * using trace events only need to #include <trace/events/sched.h>
 */

#define CREATE_TRACE_POINTS
#include "dpaa2-eth-trace.h"

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Freescale Semiconductor, Inc");
MODULE_DESCRIPTION("Freescale DPAA2 Ethernet Driver");

struct ptp_qoriq *dpaa2_ptp;
EXPORT_SYMBOL(dpaa2_ptp);

static void dpaa2_eth_detect_features(struct dpaa2_eth_priv *priv)
{
 priv->features = 0;

 if (dpaa2_eth_cmp_dpni_ver(priv, DPNI_PTP_ONESTEP_VER_MAJOR,
       DPNI_PTP_ONESTEP_VER_MINOR) >= 0)
  priv->features |= DPAA2_ETH_FEATURE_ONESTEP_CFG_DIRECT;
}

static void dpaa2_update_ptp_onestep_indirect(struct dpaa2_eth_priv *priv,
           u32 offset, u8 udp)
{
 struct dpni_single_step_cfg cfg;

 cfg.en = 1;
 cfg.ch_update = udp;
 cfg.offset = offset;
 cfg.peer_delay = 0;

 if (dpni_set_single_step_cfg(priv->mc_io, 0, priv->mc_token, &cfg))
  WARN_ONCE(1, "Failed to set single step register");
}

static void dpaa2_update_ptp_onestep_direct(struct dpaa2_eth_priv *priv,
         u32 offset, u8 udp)
{
 u32 val = 0;

 val = DPAA2_PTP_SINGLE_STEP_ENABLE |
        DPAA2_PTP_SINGLE_CORRECTION_OFF(offset);

 if (udp)
  val |= DPAA2_PTP_SINGLE_STEP_CH;

 if (priv->onestep_reg_base)
  writel(val, priv->onestep_reg_base);
}

static void dpaa2_ptp_onestep_reg_update_method(struct dpaa2_eth_priv *priv)
{
 struct device *dev = priv->net_dev->dev.parent;
 struct dpni_single_step_cfg ptp_cfg;

 priv->dpaa2_set_onestep_params_cb = dpaa2_update_ptp_onestep_indirect;

 if (!(priv->features & DPAA2_ETH_FEATURE_ONESTEP_CFG_DIRECT))
  return;

 if (dpni_get_single_step_cfg(priv->mc_io, 0,
         priv->mc_token, &ptp_cfg)) {
  dev_err(dev, "dpni_get_single_step_cfg cannot retrieve onestep reg, falling back to indirect update\n");
  return;
 }

 if (!ptp_cfg.ptp_onestep_reg_base) {
  dev_err(dev, "1588 onestep reg not available, falling back to indirect update\n");
  return;
 }

 priv->onestep_reg_base = ioremap(ptp_cfg.ptp_onestep_reg_base,
      sizeof(u32));
 if (!priv->onestep_reg_base) {
  dev_err(dev, "1588 onestep reg cannot be mapped, falling back to indirect update\n");
  return;
 }

 priv->dpaa2_set_onestep_params_cb = dpaa2_update_ptp_onestep_direct;
}

void *dpaa2_iova_to_virt(struct iommu_domain *domain,
    dma_addr_t iova_addr)
{
 phys_addr_t phys_addr;

 phys_addr = domain ? iommu_iova_to_phys(domain, iova_addr) : iova_addr;

 return phys_to_virt(phys_addr);
}

static void dpaa2_eth_validate_rx_csum(struct dpaa2_eth_priv *priv,
           u32 fd_status,
           struct sk_buff *skb)
{
 skb_checksum_none_assert(skb);

 /* HW checksum validation is disabled, nothing to do here */
 if (!(priv->net_dev->features & NETIF_F_RXCSUM))
  return;

 /* Read checksum validation bits */
 if (!((fd_status & DPAA2_FAS_L3CV) &&
       (fd_status & DPAA2_FAS_L4CV)))
  return;

 /* Inform the stack there's no need to compute L3/L4 csum anymore */
 skb->ip_summed = CHECKSUM_UNNECESSARY;
}

/* Free a received FD.
 * Not to be used for Tx conf FDs or on any other paths.
 */

static void dpaa2_eth_free_rx_fd(struct dpaa2_eth_priv *priv,
     const struct dpaa2_fd *fd,
     void *vaddr)
{
 struct device *dev = priv->net_dev->dev.parent;
 dma_addr_t addr = dpaa2_fd_get_addr(fd);
 u8 fd_format = dpaa2_fd_get_format(fd);
 struct dpaa2_sg_entry *sgt;
 void *sg_vaddr;
 int i;

 /* If single buffer frame, just free the data buffer */
 if (fd_format == dpaa2_fd_single)
  goto free_buf;
 else if (fd_format != dpaa2_fd_sg)
  /* We don't support any other format */
  return;

 /* For S/G frames, we first need to free all SG entries
 * except the first one, which was taken care of already
 */

 sgt = vaddr + dpaa2_fd_get_offset(fd);
 for (i = 1; i < DPAA2_ETH_MAX_SG_ENTRIES; i++) {
  addr = dpaa2_sg_get_addr(&sgt[i]);
  sg_vaddr = dpaa2_iova_to_virt(priv->iommu_domain, addr);
  dma_unmap_page(dev, addr, priv->rx_buf_size,
          DMA_BIDIRECTIONAL);

  free_pages((unsigned long)sg_vaddr, 0);
  if (dpaa2_sg_is_final(&sgt[i]))
   break;
 }

free_buf:
 free_pages((unsigned long)vaddr, 0);
}

/* Build a linear skb based on a single-buffer frame descriptor */
static struct sk_buff *dpaa2_eth_build_linear_skb(struct dpaa2_eth_channel *ch,
        const struct dpaa2_fd *fd,
        void *fd_vaddr)
{
 struct sk_buff *skb = NULL;
 u16 fd_offset = dpaa2_fd_get_offset(fd);
 u32 fd_length = dpaa2_fd_get_len(fd);

 ch->buf_count--;

 skb = build_skb(fd_vaddr, DPAA2_ETH_RX_BUF_RAW_SIZE);
 if (unlikely(!skb))
  return NULL;

 skb_reserve(skb, fd_offset);
 skb_put(skb, fd_length);

 return skb;
}

/* Build a non linear (fragmented) skb based on a S/G table */
static struct sk_buff *dpaa2_eth_build_frag_skb(struct dpaa2_eth_priv *priv,
      struct dpaa2_eth_channel *ch,
      struct dpaa2_sg_entry *sgt)
{
 struct sk_buff *skb = NULL;
 struct device *dev = priv->net_dev->dev.parent;
 void *sg_vaddr;
 dma_addr_t sg_addr;
 u16 sg_offset;
 u32 sg_length;
 struct page *page, *head_page;
 int page_offset;
 int i;

 for (i = 0; i < DPAA2_ETH_MAX_SG_ENTRIES; i++) {
  struct dpaa2_sg_entry *sge = &sgt[i];

  /* NOTE: We only support SG entries in dpaa2_sg_single format,
 * but this is the only format we may receive from HW anyway
 */


  /* Get the address and length from the S/G entry */
  sg_addr = dpaa2_sg_get_addr(sge);
  sg_vaddr = dpaa2_iova_to_virt(priv->iommu_domain, sg_addr);
  dma_unmap_page(dev, sg_addr, priv->rx_buf_size,
          DMA_BIDIRECTIONAL);

  sg_length = dpaa2_sg_get_len(sge);

  if (i == 0) {
   /* We build the skb around the first data buffer */
   skb = build_skb(sg_vaddr, DPAA2_ETH_RX_BUF_RAW_SIZE);
   if (unlikely(!skb)) {
    /* Free the first SG entry now, since we already
 * unmapped it and obtained the virtual address
 */

    free_pages((unsigned long)sg_vaddr, 0);

    /* We still need to subtract the buffers used
 * by this FD from our software counter
 */

    while (!dpaa2_sg_is_final(&sgt[i]) &&
           i < DPAA2_ETH_MAX_SG_ENTRIES)
     i++;
    break;
   }

   sg_offset = dpaa2_sg_get_offset(sge);
   skb_reserve(skb, sg_offset);
   skb_put(skb, sg_length);
  } else {
   /* Rest of the data buffers are stored as skb frags */
   page = virt_to_page(sg_vaddr);
   head_page = virt_to_head_page(sg_vaddr);

   /* Offset in page (which may be compound).
 * Data in subsequent SG entries is stored from the
 * beginning of the buffer, so we don't need to add the
 * sg_offset.
 */

   page_offset = ((unsigned long)sg_vaddr &
    (PAGE_SIZE - 1)) +
    (page_address(page) - page_address(head_page));

   skb_add_rx_frag(skb, i - 1, head_page, page_offset,
     sg_length, priv->rx_buf_size);
  }

  if (dpaa2_sg_is_final(sge))
   break;
 }

 WARN_ONCE(i == DPAA2_ETH_MAX_SG_ENTRIES, "Final bit not set in SGT");

 /* Count all data buffers + SG table buffer */
 ch->buf_count -= i + 2;

 return skb;
}

/* Free buffers acquired from the buffer pool or which were meant to
 * be released in the pool
 */

static void dpaa2_eth_free_bufs(struct dpaa2_eth_priv *priv, u64 *buf_array,
    int count, bool xsk_zc)
{
 struct device *dev = priv->net_dev->dev.parent;
 struct dpaa2_eth_swa *swa;
 struct xdp_buff *xdp_buff;
 void *vaddr;
 int i;

 for (i = 0; i < count; i++) {
  vaddr = dpaa2_iova_to_virt(priv->iommu_domain, buf_array[i]);

  if (!xsk_zc) {
   dma_unmap_page(dev, buf_array[i], priv->rx_buf_size,
           DMA_BIDIRECTIONAL);
   free_pages((unsigned long)vaddr, 0);
  } else {
   swa = (struct dpaa2_eth_swa *)
    (vaddr + DPAA2_ETH_RX_HWA_SIZE);
   xdp_buff = swa->xsk.xdp_buff;
   xsk_buff_free(xdp_buff);
  }
 }
}

void dpaa2_eth_recycle_buf(struct dpaa2_eth_priv *priv,
      struct dpaa2_eth_channel *ch,
      dma_addr_t addr)
{
 int retries = 0;
 int err;

 ch->recycled_bufs[ch->recycled_bufs_cnt++] = addr;
 if (ch->recycled_bufs_cnt < DPAA2_ETH_BUFS_PER_CMD)
  return;

 while ((err = dpaa2_io_service_release(ch->dpio, ch->bp->bpid,
            ch->recycled_bufs,
            ch->recycled_bufs_cnt)) == -EBUSY) {
  if (retries++ >= DPAA2_ETH_SWP_BUSY_RETRIES)
   break;
  cpu_relax();
 }

 if (err) {
  dpaa2_eth_free_bufs(priv, ch->recycled_bufs,
        ch->recycled_bufs_cnt, ch->xsk_zc);
  ch->buf_count -= ch->recycled_bufs_cnt;
 }

 ch->recycled_bufs_cnt = 0;
}

static int dpaa2_eth_xdp_flush(struct dpaa2_eth_priv *priv,
          struct dpaa2_eth_fq *fq,
          struct dpaa2_eth_xdp_fds *xdp_fds)
{
 int total_enqueued = 0, retries = 0, enqueued;
 struct dpaa2_eth_drv_stats *percpu_extras;
 int num_fds, err, max_retries;
 struct dpaa2_fd *fds;

 percpu_extras = this_cpu_ptr(priv->percpu_extras);

 /* try to enqueue all the FDs until the max number of retries is hit */
 fds = xdp_fds->fds;
 num_fds = xdp_fds->num;
 max_retries = num_fds * DPAA2_ETH_ENQUEUE_RETRIES;
 while (total_enqueued < num_fds && retries < max_retries) {
  err = priv->enqueue(priv, fq, &fds[total_enqueued],
        0, num_fds - total_enqueued, &enqueued);
  if (err == -EBUSY) {
   percpu_extras->tx_portal_busy += ++retries;
   continue;
  }
  total_enqueued += enqueued;
 }
 xdp_fds->num = 0;

 return total_enqueued;
}

static void dpaa2_eth_xdp_tx_flush(struct dpaa2_eth_priv *priv,
       struct dpaa2_eth_channel *ch,
       struct dpaa2_eth_fq *fq)
{
 struct rtnl_link_stats64 *percpu_stats;
 struct dpaa2_fd *fds;
 int enqueued, i;

 percpu_stats = this_cpu_ptr(priv->percpu_stats);

 // enqueue the array of XDP_TX frames
 enqueued = dpaa2_eth_xdp_flush(priv, fq, &fq->xdp_tx_fds);

 /* update statistics */
 percpu_stats->tx_packets += enqueued;
 fds = fq->xdp_tx_fds.fds;
 for (i = 0; i < enqueued; i++) {
  percpu_stats->tx_bytes += dpaa2_fd_get_len(&fds[i]);
  ch->stats.xdp_tx++;
 }
 for (i = enqueued; i < fq->xdp_tx_fds.num; i++) {
  dpaa2_eth_recycle_buf(priv, ch, dpaa2_fd_get_addr(&fds[i]));
  percpu_stats->tx_errors++;
  ch->stats.xdp_tx_err++;
 }
 fq->xdp_tx_fds.num = 0;
}

void dpaa2_eth_xdp_enqueue(struct dpaa2_eth_priv *priv,
      struct dpaa2_eth_channel *ch,
      struct dpaa2_fd *fd,
      void *buf_start, u16 queue_id)
{
 struct dpaa2_faead *faead;
 struct dpaa2_fd *dest_fd;
 struct dpaa2_eth_fq *fq;
 u32 ctrl, frc;

 /* Mark the egress frame hardware annotation area as valid */
 frc = dpaa2_fd_get_frc(fd);
 dpaa2_fd_set_frc(fd, frc | DPAA2_FD_FRC_FAEADV);
 dpaa2_fd_set_ctrl(fd, DPAA2_FD_CTRL_ASAL);

 /* Instruct hardware to release the FD buffer directly into
 * the buffer pool once transmission is completed, instead of
 * sending a Tx confirmation frame to us
 */

 ctrl = DPAA2_FAEAD_A4V | DPAA2_FAEAD_A2V | DPAA2_FAEAD_EBDDV;
 faead = dpaa2_get_faead(buf_start, false);
 faead->ctrl = cpu_to_le32(ctrl);
 faead->conf_fqid = 0;

 fq = &priv->fq[queue_id];
 dest_fd = &fq->xdp_tx_fds.fds[fq->xdp_tx_fds.num++];
 memcpy(dest_fd, fd, sizeof(*dest_fd));

 if (fq->xdp_tx_fds.num < DEV_MAP_BULK_SIZE)
  return;

 dpaa2_eth_xdp_tx_flush(priv, ch, fq);
}

static u32 dpaa2_eth_run_xdp(struct dpaa2_eth_priv *priv,
        struct dpaa2_eth_channel *ch,
        struct dpaa2_eth_fq *rx_fq,
        struct dpaa2_fd *fd, void *vaddr)
{
 dma_addr_t addr = dpaa2_fd_get_addr(fd);
 struct bpf_prog *xdp_prog;
 struct xdp_buff xdp;
 u32 xdp_act = XDP_PASS;
 int err, offset;

 xdp_prog = READ_ONCE(ch->xdp.prog);
 if (!xdp_prog)
  goto out;

 offset = dpaa2_fd_get_offset(fd) - XDP_PACKET_HEADROOM;
 xdp_init_buff(&xdp, DPAA2_ETH_RX_BUF_RAW_SIZE - offset, &ch->xdp_rxq);
 xdp_prepare_buff(&xdp, vaddr + offset, XDP_PACKET_HEADROOM,
    dpaa2_fd_get_len(fd), false);

 xdp_act = bpf_prog_run_xdp(xdp_prog, &xdp);

 /* xdp.data pointer may have changed */
 dpaa2_fd_set_offset(fd, xdp.data - vaddr);
 dpaa2_fd_set_len(fd, xdp.data_end - xdp.data);

 switch (xdp_act) {
 case XDP_PASS:
  break;
 case XDP_TX:
  dpaa2_eth_xdp_enqueue(priv, ch, fd, vaddr, rx_fq->flowid);
  break;
 default:
  bpf_warn_invalid_xdp_action(priv->net_dev, xdp_prog, xdp_act);
  fallthrough;
 case XDP_ABORTED:
  trace_xdp_exception(priv->net_dev, xdp_prog, xdp_act);
  fallthrough;
 case XDP_DROP:
  dpaa2_eth_recycle_buf(priv, ch, addr);
  ch->stats.xdp_drop++;
  break;
 case XDP_REDIRECT:
  dma_unmap_page(priv->net_dev->dev.parent, addr,
          priv->rx_buf_size, DMA_BIDIRECTIONAL);
  ch->buf_count--;

  /* Allow redirect use of full headroom */
  xdp.data_hard_start = vaddr;
  xdp.frame_sz = DPAA2_ETH_RX_BUF_RAW_SIZE;

  err = xdp_do_redirect(priv->net_dev, &xdp, xdp_prog);
  if (unlikely(err)) {
   addr = dma_map_page(priv->net_dev->dev.parent,
         virt_to_page(vaddr), 0,
         priv->rx_buf_size, DMA_BIDIRECTIONAL);
   if (unlikely(dma_mapping_error(priv->net_dev->dev.parent, addr))) {
    free_pages((unsigned long)vaddr, 0);
   } else {
    ch->buf_count++;
    dpaa2_eth_recycle_buf(priv, ch, addr);
   }
   ch->stats.xdp_drop++;
  } else {
   ch->stats.xdp_redirect++;
  }
  break;
 }

 ch->xdp.res |= xdp_act;
out:
 return xdp_act;
}

struct sk_buff *dpaa2_eth_alloc_skb(struct dpaa2_eth_priv *priv,
        struct dpaa2_eth_channel *ch,
        const struct dpaa2_fd *fd, u32 fd_length,
        void *fd_vaddr)
{
 u16 fd_offset = dpaa2_fd_get_offset(fd);
 struct sk_buff *skb = NULL;
 unsigned int skb_len;

 skb_len = fd_length + dpaa2_eth_needed_headroom(NULL);

 skb = napi_alloc_skb(&ch->napi, skb_len);
 if (!skb)
  return NULL;

 skb_reserve(skb, dpaa2_eth_needed_headroom(NULL));
 skb_put(skb, fd_length);

 memcpy(skb->data, fd_vaddr + fd_offset, fd_length);

 return skb;
}

static struct sk_buff *dpaa2_eth_copybreak(struct dpaa2_eth_channel *ch,
        const struct dpaa2_fd *fd,
        void *fd_vaddr)
{
 struct dpaa2_eth_priv *priv = ch->priv;
 u32 fd_length = dpaa2_fd_get_len(fd);

 if (fd_length > priv->rx_copybreak)
  return NULL;

 return dpaa2_eth_alloc_skb(priv, ch, fd, fd_length, fd_vaddr);
}

void dpaa2_eth_receive_skb(struct dpaa2_eth_priv *priv,
      struct dpaa2_eth_channel *ch,
      const struct dpaa2_fd *fd, void *vaddr,
      struct dpaa2_eth_fq *fq,
      struct rtnl_link_stats64 *percpu_stats,
      struct sk_buff *skb)
{
 struct dpaa2_fas *fas;
 u32 status = 0;

 fas = dpaa2_get_fas(vaddr, false);
 prefetch(fas);
 prefetch(skb->data);

 /* Get the timestamp value */
 if (priv->rx_tstamp) {
  struct skb_shared_hwtstamps *shhwtstamps = skb_hwtstamps(skb);
  __le64 *ts = dpaa2_get_ts(vaddr, false);
  u64 ns;

  memset(shhwtstamps, 0, sizeof(*shhwtstamps));

  ns = DPAA2_PTP_CLK_PERIOD_NS * le64_to_cpup(ts);
  shhwtstamps->hwtstamp = ns_to_ktime(ns);
 }

 /* Check if we need to validate the L4 csum */
 if (likely(dpaa2_fd_get_frc(fd) & DPAA2_FD_FRC_FASV)) {
  status = le32_to_cpu(fas->status);
  dpaa2_eth_validate_rx_csum(priv, status, skb);
 }

 skb->protocol = eth_type_trans(skb, priv->net_dev);
 skb_record_rx_queue(skb, fq->flowid);

 percpu_stats->rx_packets++;
 percpu_stats->rx_bytes += dpaa2_fd_get_len(fd);
 ch->stats.bytes_per_cdan += dpaa2_fd_get_len(fd);

 list_add_tail(&skb->list, ch->rx_list);
}

/* Main Rx frame processing routine */
void dpaa2_eth_rx(struct dpaa2_eth_priv *priv,
    struct dpaa2_eth_channel *ch,
    const struct dpaa2_fd *fd,
    struct dpaa2_eth_fq *fq)
{
 dma_addr_t addr = dpaa2_fd_get_addr(fd);
 u8 fd_format = dpaa2_fd_get_format(fd);
 void *vaddr;
 struct sk_buff *skb;
 struct rtnl_link_stats64 *percpu_stats;
 struct dpaa2_eth_drv_stats *percpu_extras;
 struct device *dev = priv->net_dev->dev.parent;
 bool recycle_rx_buf = false;
 void *buf_data;
 u32 xdp_act;

 /* Tracing point */
 trace_dpaa2_rx_fd(priv->net_dev, fd);

 vaddr = dpaa2_iova_to_virt(priv->iommu_domain, addr);
 dma_sync_single_for_cpu(dev, addr, priv->rx_buf_size,
    DMA_BIDIRECTIONAL);

 buf_data = vaddr + dpaa2_fd_get_offset(fd);
 prefetch(buf_data);

 percpu_stats = this_cpu_ptr(priv->percpu_stats);
 percpu_extras = this_cpu_ptr(priv->percpu_extras);

 if (fd_format == dpaa2_fd_single) {
  xdp_act = dpaa2_eth_run_xdp(priv, ch, fq, (struct dpaa2_fd *)fd, vaddr);
  if (xdp_act != XDP_PASS) {
   percpu_stats->rx_packets++;
   percpu_stats->rx_bytes += dpaa2_fd_get_len(fd);
   return;
  }

  skb = dpaa2_eth_copybreak(ch, fd, vaddr);
  if (!skb) {
   dma_unmap_page(dev, addr, priv->rx_buf_size,
           DMA_BIDIRECTIONAL);
   skb = dpaa2_eth_build_linear_skb(ch, fd, vaddr);
  } else {
   recycle_rx_buf = true;
  }
 } else if (fd_format == dpaa2_fd_sg) {
  WARN_ON(priv->xdp_prog);

  dma_unmap_page(dev, addr, priv->rx_buf_size,
          DMA_BIDIRECTIONAL);
  skb = dpaa2_eth_build_frag_skb(priv, ch, buf_data);
  free_pages((unsigned long)vaddr, 0);
  percpu_extras->rx_sg_frames++;
  percpu_extras->rx_sg_bytes += dpaa2_fd_get_len(fd);
 } else {
  /* We don't support any other format */
  goto err_frame_format;
 }

 if (unlikely(!skb))
  goto err_build_skb;

 dpaa2_eth_receive_skb(priv, ch, fd, vaddr, fq, percpu_stats, skb);

 if (recycle_rx_buf)
  dpaa2_eth_recycle_buf(priv, ch, dpaa2_fd_get_addr(fd));
 return;

err_build_skb:
 dpaa2_eth_free_rx_fd(priv, fd, vaddr);
err_frame_format:
 percpu_stats->rx_dropped++;
}

/* Processing of Rx frames received on the error FQ
 * We check and print the error bits and then free the frame
 */

static void dpaa2_eth_rx_err(struct dpaa2_eth_priv *priv,
        struct dpaa2_eth_channel *ch,
        const struct dpaa2_fd *fd,
        struct dpaa2_eth_fq *fq __always_unused)
{
 struct device *dev = priv->net_dev->dev.parent;
 dma_addr_t addr = dpaa2_fd_get_addr(fd);
 u8 fd_format = dpaa2_fd_get_format(fd);
 struct rtnl_link_stats64 *percpu_stats;
 struct dpaa2_eth_trap_item *trap_item;
 struct dpaa2_fapr *fapr;
 struct sk_buff *skb;
 void *buf_data;
 void *vaddr;

 vaddr = dpaa2_iova_to_virt(priv->iommu_domain, addr);
 dma_sync_single_for_cpu(dev, addr, priv->rx_buf_size,
    DMA_BIDIRECTIONAL);

 buf_data = vaddr + dpaa2_fd_get_offset(fd);

 if (fd_format == dpaa2_fd_single) {
  dma_unmap_page(dev, addr, priv->rx_buf_size,
          DMA_BIDIRECTIONAL);
  skb = dpaa2_eth_build_linear_skb(ch, fd, vaddr);
 } else if (fd_format == dpaa2_fd_sg) {
  dma_unmap_page(dev, addr, priv->rx_buf_size,
          DMA_BIDIRECTIONAL);
  skb = dpaa2_eth_build_frag_skb(priv, ch, buf_data);
  free_pages((unsigned long)vaddr, 0);
 } else {
  /* We don't support any other format */
  dpaa2_eth_free_rx_fd(priv, fd, vaddr);
  goto err_frame_format;
 }

 fapr = dpaa2_get_fapr(vaddr, false);
 trap_item = dpaa2_eth_dl_get_trap(priv, fapr);
 if (trap_item)
  devlink_trap_report(priv->devlink, skb, trap_item->trap_ctx,
        &priv->devlink_port, NULL);
 consume_skb(skb);

err_frame_format:
 percpu_stats = this_cpu_ptr(priv->percpu_stats);
 percpu_stats->rx_errors++;
 ch->buf_count--;
}

/* Consume all frames pull-dequeued into the store. This is the simplest way to
 * make sure we don't accidentally issue another volatile dequeue which would
 * overwrite (leak) frames already in the store.
 *
 * Observance of NAPI budget is not our concern, leaving that to the caller.
 */

static int dpaa2_eth_consume_frames(struct dpaa2_eth_channel *ch,
        struct dpaa2_eth_fq **src)
{
 struct dpaa2_eth_priv *priv = ch->priv;
 struct dpaa2_eth_fq *fq = NULL;
 struct dpaa2_dq *dq;
 const struct dpaa2_fd *fd;
 int cleaned = 0, retries = 0;
 int is_last;

 do {
  dq = dpaa2_io_store_next(ch->store, &is_last);
  if (unlikely(!dq)) {
   /* If we're here, we *must* have placed a
 * volatile dequeue comnmand, so keep reading through
 * the store until we get some sort of valid response
 * token (either a valid frame or an "empty dequeue")
 */

   if (retries++ >= DPAA2_ETH_SWP_BUSY_RETRIES) {
    netdev_err_once(priv->net_dev,
      "Unable to read a valid dequeue response\n");
    return -ETIMEDOUT;
   }
   continue;
  }

  fd = dpaa2_dq_fd(dq);
  fq = (struct dpaa2_eth_fq *)(uintptr_t)dpaa2_dq_fqd_ctx(dq);

  fq->consume(priv, ch, fd, fq);
  cleaned++;
  retries = 0;
 } while (!is_last);

 if (!cleaned)
  return 0;

 fq->stats.frames += cleaned;
 ch->stats.frames += cleaned;
 ch->stats.frames_per_cdan += cleaned;

 /* A dequeue operation only pulls frames from a single queue
 * into the store. Return the frame queue as an out param.
 */

 if (src)
  *src = fq;

 return cleaned;
}

static int dpaa2_eth_ptp_parse(struct sk_buff *skb,
          u8 *msgtype, u8 *twostep, u8 *udp,
          u16 *correction_offset,
          u16 *origintimestamp_offset)
{
 unsigned int ptp_class;
 struct ptp_header *hdr;
 unsigned int type;
 u8 *base;

 ptp_class = ptp_classify_raw(skb);
 if (ptp_class == PTP_CLASS_NONE)
  return -EINVAL;

 hdr = ptp_parse_header(skb, ptp_class);
 if (!hdr)
  return -EINVAL;

 *msgtype = ptp_get_msgtype(hdr, ptp_class);
 *twostep = hdr->flag_field[0] & 0x2;

 type = ptp_class & PTP_CLASS_PMASK;
 if (type == PTP_CLASS_IPV4 ||
     type == PTP_CLASS_IPV6)
  *udp = 1;
 else
  *udp = 0;

 base = skb_mac_header(skb);
 *correction_offset = (u8 *)&hdr->correction - base;
 *origintimestamp_offset = (u8 *)hdr + sizeof(struct ptp_header) - base;

 return 0;
}

/* Configure the egress frame annotation for timestamp update */
static void dpaa2_eth_enable_tx_tstamp(struct dpaa2_eth_priv *priv,
           struct dpaa2_fd *fd,
           void *buf_start,
           struct sk_buff *skb)
{
 struct ptp_tstamp origin_timestamp;
 u8 msgtype, twostep, udp;
 struct dpaa2_faead *faead;
 struct dpaa2_fas *fas;
 struct timespec64 ts;
 u16 offset1, offset2;
 u32 ctrl, frc;
 __le64 *ns;
 u8 *data;

 /* Mark the egress frame annotation area as valid */
 frc = dpaa2_fd_get_frc(fd);
 dpaa2_fd_set_frc(fd, frc | DPAA2_FD_FRC_FAEADV);

 /* Set hardware annotation size */
 ctrl = dpaa2_fd_get_ctrl(fd);
 dpaa2_fd_set_ctrl(fd, ctrl | DPAA2_FD_CTRL_ASAL);

 /* enable UPD (update prepanded data) bit in FAEAD field of
 * hardware frame annotation area
 */

 ctrl = DPAA2_FAEAD_A2V | DPAA2_FAEAD_UPDV | DPAA2_FAEAD_UPD;
 faead = dpaa2_get_faead(buf_start, true);
 faead->ctrl = cpu_to_le32(ctrl);

 if (skb->cb[0] == TX_TSTAMP_ONESTEP_SYNC) {
  if (dpaa2_eth_ptp_parse(skb, &msgtype, &twostep, &udp,
     &offset1, &offset2) ||
      msgtype != PTP_MSGTYPE_SYNC || twostep) {
   WARN_ONCE(1, "Bad packet for one-step timestamping\n");
   return;
  }

  /* Mark the frame annotation status as valid */
  frc = dpaa2_fd_get_frc(fd);
  dpaa2_fd_set_frc(fd, frc | DPAA2_FD_FRC_FASV);

  /* Mark the PTP flag for one step timestamping */
  fas = dpaa2_get_fas(buf_start, true);
  fas->status = cpu_to_le32(DPAA2_FAS_PTP);

  dpaa2_ptp->caps.gettime64(&dpaa2_ptp->caps, &ts);
  ns = dpaa2_get_ts(buf_start, true);
  *ns = cpu_to_le64(timespec64_to_ns(&ts) /
      DPAA2_PTP_CLK_PERIOD_NS);

  /* Update current time to PTP message originTimestamp field */
  ns_to_ptp_tstamp(&origin_timestamp, le64_to_cpup(ns));
  data = skb_mac_header(skb);
  *(__be16 *)(data + offset2) = htons(origin_timestamp.sec_msb);
  *(__be32 *)(data + offset2 + 2) =
   htonl(origin_timestamp.sec_lsb);
  *(__be32 *)(data + offset2 + 6) = htonl(origin_timestamp.nsec);

  if (priv->ptp_correction_off == offset1)
   return;

  priv->dpaa2_set_onestep_params_cb(priv, offset1, udp);
  priv->ptp_correction_off = offset1;

 }
}

void *dpaa2_eth_sgt_get(struct dpaa2_eth_priv *priv)
{
 struct dpaa2_eth_sgt_cache *sgt_cache;
 void *sgt_buf = NULL;
 int sgt_buf_size;

 sgt_cache = this_cpu_ptr(priv->sgt_cache);
 sgt_buf_size = priv->tx_data_offset +
  DPAA2_ETH_SG_ENTRIES_MAX * sizeof(struct dpaa2_sg_entry);

 if (sgt_cache->count == 0)
  sgt_buf = napi_alloc_frag_align(sgt_buf_size, DPAA2_ETH_TX_BUF_ALIGN);
 else
  sgt_buf = sgt_cache->buf[--sgt_cache->count];
 if (!sgt_buf)
  return NULL;

 memset(sgt_buf, 0, sgt_buf_size);

 return sgt_buf;
}

void dpaa2_eth_sgt_recycle(struct dpaa2_eth_priv *priv, void *sgt_buf)
{
 struct dpaa2_eth_sgt_cache *sgt_cache;

 sgt_cache = this_cpu_ptr(priv->sgt_cache);
 if (sgt_cache->count >= DPAA2_ETH_SGT_CACHE_SIZE)
  skb_free_frag(sgt_buf);
 else
  sgt_cache->buf[sgt_cache->count++] = sgt_buf;
}

/* Create a frame descriptor based on a fragmented skb */
static int dpaa2_eth_build_sg_fd(struct dpaa2_eth_priv *priv,
     struct sk_buff *skb,
     struct dpaa2_fd *fd,
     void **swa_addr)
{
 struct device *dev = priv->net_dev->dev.parent;
 void *sgt_buf = NULL;
 dma_addr_t addr;
 int nr_frags = skb_shinfo(skb)->nr_frags;
 struct dpaa2_sg_entry *sgt;
 int i, err;
 int sgt_buf_size;
 struct scatterlist *scl, *crt_scl;
 int num_sg;
 int num_dma_bufs;
 struct dpaa2_eth_swa *swa;

 /* Create and map scatterlist.
 * We don't advertise NETIF_F_FRAGLIST, so skb_to_sgvec() will not have
 * to go beyond nr_frags+1.
 * Note: We don't support chained scatterlists
 */

 if (unlikely(PAGE_SIZE / sizeof(struct scatterlist) < nr_frags + 1))
  return -EINVAL;

 scl = kmalloc_array(nr_frags + 1, sizeof(struct scatterlist), GFP_ATOMIC);
 if (unlikely(!scl))
  return -ENOMEM;

 sg_init_table(scl, nr_frags + 1);
 num_sg = skb_to_sgvec(skb, scl, 0, skb->len);
 if (unlikely(num_sg < 0)) {
  err = -ENOMEM;
  goto dma_map_sg_failed;
 }
 num_dma_bufs = dma_map_sg(dev, scl, num_sg, DMA_BIDIRECTIONAL);
 if (unlikely(!num_dma_bufs)) {
  err = -ENOMEM;
  goto dma_map_sg_failed;
 }

 /* Prepare the HW SGT structure */
 sgt_buf_size = priv->tx_data_offset +
         sizeof(struct dpaa2_sg_entry) *  num_dma_bufs;
 sgt_buf = dpaa2_eth_sgt_get(priv);
 if (unlikely(!sgt_buf)) {
  err = -ENOMEM;
  goto sgt_buf_alloc_failed;
 }

 sgt = (struct dpaa2_sg_entry *)(sgt_buf + priv->tx_data_offset);

 /* Fill in the HW SGT structure.
 *
 * sgt_buf is zeroed out, so the following fields are implicit
 * in all sgt entries:
 *   - offset is 0
 *   - format is 'dpaa2_sg_single'
 */

 for_each_sg(scl, crt_scl, num_dma_bufs, i) {
  dpaa2_sg_set_addr(&sgt[i], sg_dma_address(crt_scl));
  dpaa2_sg_set_len(&sgt[i], sg_dma_len(crt_scl));
 }
 dpaa2_sg_set_final(&sgt[i - 1], true);

 /* Store the skb backpointer in the SGT buffer.
 * Fit the scatterlist and the number of buffers alongside the
 * skb backpointer in the software annotation area. We'll need
 * all of them on Tx Conf.
 */

 *swa_addr = (void *)sgt_buf;
 swa = (struct dpaa2_eth_swa *)sgt_buf;
 swa->type = DPAA2_ETH_SWA_SG;
 swa->sg.skb = skb;
 swa->sg.scl = scl;
 swa->sg.num_sg = num_sg;
 swa->sg.sgt_size = sgt_buf_size;

 /* Separately map the SGT buffer */
 addr = dma_map_single(dev, sgt_buf, sgt_buf_size, DMA_BIDIRECTIONAL);
 if (unlikely(dma_mapping_error(dev, addr))) {
  err = -ENOMEM;
  goto dma_map_single_failed;
 }
 memset(fd, 0, sizeof(struct dpaa2_fd));
 dpaa2_fd_set_offset(fd, priv->tx_data_offset);
 dpaa2_fd_set_format(fd, dpaa2_fd_sg);
 dpaa2_fd_set_addr(fd, addr);
 dpaa2_fd_set_len(fd, skb->len);
 dpaa2_fd_set_ctrl(fd, FD_CTRL_PTA);

 return 0;

dma_map_single_failed:
 dpaa2_eth_sgt_recycle(priv, sgt_buf);
sgt_buf_alloc_failed:
 dma_unmap_sg(dev, scl, num_sg, DMA_BIDIRECTIONAL);
dma_map_sg_failed:
 kfree(scl);
 return err;
}

/* Create a SG frame descriptor based on a linear skb.
 *
 * This function is used on the Tx path when the skb headroom is not large
 * enough for the HW requirements, thus instead of realloc-ing the skb we
 * create a SG frame descriptor with only one entry.
 */

static int dpaa2_eth_build_sg_fd_single_buf(struct dpaa2_eth_priv *priv,
         struct sk_buff *skb,
         struct dpaa2_fd *fd,
         void **swa_addr)
{
 struct device *dev = priv->net_dev->dev.parent;
 struct dpaa2_sg_entry *sgt;
 struct dpaa2_eth_swa *swa;
 dma_addr_t addr, sgt_addr;
 void *sgt_buf = NULL;
 int sgt_buf_size;
 int err;

 /* Prepare the HW SGT structure */
 sgt_buf_size = priv->tx_data_offset + sizeof(struct dpaa2_sg_entry);
 sgt_buf = dpaa2_eth_sgt_get(priv);
 if (unlikely(!sgt_buf))
  return -ENOMEM;
 sgt = (struct dpaa2_sg_entry *)(sgt_buf + priv->tx_data_offset);

 addr = dma_map_single(dev, skb->data, skb->len, DMA_BIDIRECTIONAL);
 if (unlikely(dma_mapping_error(dev, addr))) {
  err = -ENOMEM;
  goto data_map_failed;
 }

 /* Fill in the HW SGT structure */
 dpaa2_sg_set_addr(sgt, addr);
 dpaa2_sg_set_len(sgt, skb->len);
 dpaa2_sg_set_final(sgt, true);

 /* Store the skb backpointer in the SGT buffer */
 *swa_addr = (void *)sgt_buf;
 swa = (struct dpaa2_eth_swa *)sgt_buf;
 swa->type = DPAA2_ETH_SWA_SINGLE;
 swa->single.skb = skb;
 swa->single.sgt_size = sgt_buf_size;

 /* Separately map the SGT buffer */
 sgt_addr = dma_map_single(dev, sgt_buf, sgt_buf_size, DMA_BIDIRECTIONAL);
 if (unlikely(dma_mapping_error(dev, sgt_addr))) {
  err = -ENOMEM;
  goto sgt_map_failed;
 }

 memset(fd, 0, sizeof(struct dpaa2_fd));
 dpaa2_fd_set_offset(fd, priv->tx_data_offset);
 dpaa2_fd_set_format(fd, dpaa2_fd_sg);
 dpaa2_fd_set_addr(fd, sgt_addr);
 dpaa2_fd_set_len(fd, skb->len);
 dpaa2_fd_set_ctrl(fd, FD_CTRL_PTA);

 return 0;

sgt_map_failed:
 dma_unmap_single(dev, addr, skb->len, DMA_BIDIRECTIONAL);
data_map_failed:
 dpaa2_eth_sgt_recycle(priv, sgt_buf);

 return err;
}

/* Create a frame descriptor based on a linear skb */
static int dpaa2_eth_build_single_fd(struct dpaa2_eth_priv *priv,
         struct sk_buff *skb,
         struct dpaa2_fd *fd,
         void **swa_addr)
{
 struct device *dev = priv->net_dev->dev.parent;
 u8 *buffer_start, *aligned_start;
 struct dpaa2_eth_swa *swa;
 dma_addr_t addr;

 buffer_start = skb->data - dpaa2_eth_needed_headroom(skb);
 aligned_start = PTR_ALIGN(buffer_start, DPAA2_ETH_TX_BUF_ALIGN);
 if (aligned_start >= skb->head)
  buffer_start = aligned_start;
 else
  return -ENOMEM;

 /* Store a backpointer to the skb at the beginning of the buffer
 * (in the private data area) such that we can release it
 * on Tx confirm
 */

 *swa_addr = (void *)buffer_start;
 swa = (struct dpaa2_eth_swa *)buffer_start;
 swa->type = DPAA2_ETH_SWA_SINGLE;
 swa->single.skb = skb;

 addr = dma_map_single(dev, buffer_start,
         skb_tail_pointer(skb) - buffer_start,
         DMA_BIDIRECTIONAL);
 if (unlikely(dma_mapping_error(dev, addr)))
  return -ENOMEM;

 memset(fd, 0, sizeof(struct dpaa2_fd));
 dpaa2_fd_set_addr(fd, addr);
 dpaa2_fd_set_offset(fd, (u16)(skb->data - buffer_start));
 dpaa2_fd_set_len(fd, skb->len);
 dpaa2_fd_set_format(fd, dpaa2_fd_single);
 dpaa2_fd_set_ctrl(fd, FD_CTRL_PTA);

 return 0;
}

/* FD freeing routine on the Tx path
 *
 * DMA-unmap and free FD and possibly SGT buffer allocated on Tx. The skb
 * back-pointed to is also freed.
 * This can be called either from dpaa2_eth_tx_conf() or on the error path of
 * dpaa2_eth_tx().
 */

void dpaa2_eth_free_tx_fd(struct dpaa2_eth_priv *priv,
     struct dpaa2_eth_channel *ch,
     struct dpaa2_eth_fq *fq,
     const struct dpaa2_fd *fd, bool in_napi)
{
 struct device *dev = priv->net_dev->dev.parent;
 dma_addr_t fd_addr, sg_addr;
 struct sk_buff *skb = NULL;
 unsigned char *buffer_start;
 struct dpaa2_eth_swa *swa;
 u8 fd_format = dpaa2_fd_get_format(fd);
 u32 fd_len = dpaa2_fd_get_len(fd);
 struct dpaa2_sg_entry *sgt;
 int should_free_skb = 1;
 void *tso_hdr;
 int i;

 fd_addr = dpaa2_fd_get_addr(fd);
 buffer_start = dpaa2_iova_to_virt(priv->iommu_domain, fd_addr);
 swa = (struct dpaa2_eth_swa *)buffer_start;

 if (fd_format == dpaa2_fd_single) {
  if (swa->type == DPAA2_ETH_SWA_SINGLE) {
   skb = swa->single.skb;
   /* Accessing the skb buffer is safe before dma unmap,
 * because we didn't map the actual skb shell.
 */

   dma_unmap_single(dev, fd_addr,
      skb_tail_pointer(skb) - buffer_start,
      DMA_BIDIRECTIONAL);
  } else {
   WARN_ONCE(swa->type != DPAA2_ETH_SWA_XDP, "Wrong SWA type");
   dma_unmap_single(dev, fd_addr, swa->xdp.dma_size,
      DMA_BIDIRECTIONAL);
  }
 } else if (fd_format == dpaa2_fd_sg) {
  if (swa->type == DPAA2_ETH_SWA_SG) {
   skb = swa->sg.skb;

   /* Unmap the scatterlist */
   dma_unmap_sg(dev, swa->sg.scl, swa->sg.num_sg,
         DMA_BIDIRECTIONAL);
   kfree(swa->sg.scl);

   /* Unmap the SGT buffer */
   dma_unmap_single(dev, fd_addr, swa->sg.sgt_size,
      DMA_BIDIRECTIONAL);
  } else if (swa->type == DPAA2_ETH_SWA_SW_TSO) {
   skb = swa->tso.skb;

   sgt = (struct dpaa2_sg_entry *)(buffer_start +
       priv->tx_data_offset);

   /* Unmap the SGT buffer */
   dma_unmap_single(dev, fd_addr, swa->tso.sgt_size,
      DMA_BIDIRECTIONAL);

   /* Unmap and free the header */
   tso_hdr = dpaa2_iova_to_virt(priv->iommu_domain, dpaa2_sg_get_addr(sgt));
   dma_unmap_single(dev, dpaa2_sg_get_addr(sgt), TSO_HEADER_SIZE,
      DMA_TO_DEVICE);
   kfree(tso_hdr);

   /* Unmap the other SG entries for the data */
   for (i = 1; i < swa->tso.num_sg; i++)
    dma_unmap_single(dev, dpaa2_sg_get_addr(&sgt[i]),
       dpaa2_sg_get_len(&sgt[i]), DMA_TO_DEVICE);

   if (!swa->tso.is_last_fd)
    should_free_skb = 0;
  } else if (swa->type == DPAA2_ETH_SWA_XSK) {
   /* Unmap the SGT Buffer */
   dma_unmap_single(dev, fd_addr, swa->xsk.sgt_size,
      DMA_BIDIRECTIONAL);
  } else {
   skb = swa->single.skb;

   /* Unmap the SGT Buffer */
   dma_unmap_single(dev, fd_addr, swa->single.sgt_size,
      DMA_BIDIRECTIONAL);

   sgt = (struct dpaa2_sg_entry *)(buffer_start +
       priv->tx_data_offset);
   sg_addr = dpaa2_sg_get_addr(sgt);
   dma_unmap_single(dev, sg_addr, skb->len, DMA_BIDIRECTIONAL);
  }
 } else {
  netdev_dbg(priv->net_dev, "Invalid FD format\n");
  return;
 }

 if (swa->type == DPAA2_ETH_SWA_XSK) {
  ch->xsk_tx_pkts_sent++;
  dpaa2_eth_sgt_recycle(priv, buffer_start);
  return;
 }

 if (swa->type != DPAA2_ETH_SWA_XDP && in_napi) {
  fq->dq_frames++;
  fq->dq_bytes += fd_len;
 }

 if (swa->type == DPAA2_ETH_SWA_XDP) {
  xdp_return_frame(swa->xdp.xdpf);
  return;
 }

 /* Get the timestamp value */
 if (swa->type != DPAA2_ETH_SWA_SW_TSO) {
  if (skb->cb[0] == TX_TSTAMP) {
   struct skb_shared_hwtstamps shhwtstamps;
   __le64 *ts = dpaa2_get_ts(buffer_start, true);
   u64 ns;

   memset(&shhwtstamps, 0, sizeof(shhwtstamps));

   ns = DPAA2_PTP_CLK_PERIOD_NS * le64_to_cpup(ts);
   shhwtstamps.hwtstamp = ns_to_ktime(ns);
   skb_tstamp_tx(skb, &shhwtstamps);
  } else if (skb->cb[0] == TX_TSTAMP_ONESTEP_SYNC) {
   mutex_unlock(&priv->onestep_tstamp_lock);
  }
 }

 /* Free SGT buffer allocated on tx */
 if (fd_format != dpaa2_fd_single)
  dpaa2_eth_sgt_recycle(priv, buffer_start);

 /* Move on with skb release. If we are just confirming multiple FDs
 * from the same TSO skb then only the last one will need to free the
 * skb.
 */

 if (should_free_skb)
  napi_consume_skb(skb, in_napi);
}

static int dpaa2_eth_build_gso_fd(struct dpaa2_eth_priv *priv,
      struct sk_buff *skb, struct dpaa2_fd *fd,
      int *num_fds, u32 *total_fds_len)
{
 struct device *dev = priv->net_dev->dev.parent;
 int hdr_len, total_len, data_left, fd_len;
 int num_sge, err, i, sgt_buf_size;
 struct dpaa2_fd *fd_start = fd;
 struct dpaa2_sg_entry *sgt;
 struct dpaa2_eth_swa *swa;
 dma_addr_t sgt_addr, addr;
 dma_addr_t tso_hdr_dma;
 unsigned int index = 0;
 struct tso_t tso;
 char *tso_hdr;
 void *sgt_buf;

 /* Initialize the TSO handler, and prepare the first payload */
 hdr_len = tso_start(skb, &tso);
 *total_fds_len = 0;

 total_len = skb->len - hdr_len;
 while (total_len > 0) {
  /* Prepare the HW SGT structure for this frame */
  sgt_buf = dpaa2_eth_sgt_get(priv);
  if (unlikely(!sgt_buf)) {
   netdev_err(priv->net_dev, "dpaa2_eth_sgt_get() failed\n");
   err = -ENOMEM;
   goto err_sgt_get;
  }
  sgt = (struct dpaa2_sg_entry *)(sgt_buf + priv->tx_data_offset);

  /* Determine the data length of this frame */
  data_left = min_t(int, skb_shinfo(skb)->gso_size, total_len);
  total_len -= data_left;
  fd_len = data_left + hdr_len;

  /* Prepare packet headers: MAC + IP + TCP */
  tso_hdr = kmalloc(TSO_HEADER_SIZE, GFP_ATOMIC);
  if (!tso_hdr) {
   err =  -ENOMEM;
   goto err_alloc_tso_hdr;
  }

  tso_build_hdr(skb, tso_hdr, &tso, data_left, total_len == 0);
  tso_hdr_dma = dma_map_single(dev, tso_hdr, TSO_HEADER_SIZE, DMA_TO_DEVICE);
  if (dma_mapping_error(dev, tso_hdr_dma)) {
   netdev_err(priv->net_dev, "dma_map_single(tso_hdr) failed\n");
   err = -ENOMEM;
   goto err_map_tso_hdr;
  }

  /* Setup the SG entry for the header */
  dpaa2_sg_set_addr(sgt, tso_hdr_dma);
  dpaa2_sg_set_len(sgt, hdr_len);
  dpaa2_sg_set_final(sgt, data_left <= 0);

  /* Compose the SG entries for each fragment of data */
  num_sge = 1;
  while (data_left > 0) {
   int size;

   /* Move to the next SG entry */
   sgt++;
   size = min_t(int, tso.size, data_left);

   addr = dma_map_single(dev, tso.data, size, DMA_TO_DEVICE);
   if (dma_mapping_error(dev, addr)) {
    netdev_err(priv->net_dev, "dma_map_single(tso.data) failed\n");
    err = -ENOMEM;
    goto err_map_data;
   }
   dpaa2_sg_set_addr(sgt, addr);
   dpaa2_sg_set_len(sgt, size);
   dpaa2_sg_set_final(sgt, size == data_left);

   num_sge++;

   /* Build the data for the __next__ fragment */
   data_left -= size;
   tso_build_data(skb, &tso, size);
  }

  /* Store the skb backpointer in the SGT buffer */
  sgt_buf_size = priv->tx_data_offset + num_sge * sizeof(struct dpaa2_sg_entry);
  swa = (struct dpaa2_eth_swa *)sgt_buf;
  swa->type = DPAA2_ETH_SWA_SW_TSO;
  swa->tso.skb = skb;
  swa->tso.num_sg = num_sge;
  swa->tso.sgt_size = sgt_buf_size;
  swa->tso.is_last_fd = total_len == 0 ? 1 : 0;

  /* Separately map the SGT buffer */
  sgt_addr = dma_map_single(dev, sgt_buf, sgt_buf_size, DMA_BIDIRECTIONAL);
  if (unlikely(dma_mapping_error(dev, sgt_addr))) {
   netdev_err(priv->net_dev, "dma_map_single(sgt_buf) failed\n");
   err = -ENOMEM;
   goto err_map_sgt;
  }

  /* Setup the frame descriptor */
  memset(fd, 0, sizeof(struct dpaa2_fd));
  dpaa2_fd_set_offset(fd, priv->tx_data_offset);
  dpaa2_fd_set_format(fd, dpaa2_fd_sg);
  dpaa2_fd_set_addr(fd, sgt_addr);
  dpaa2_fd_set_len(fd, fd_len);
  dpaa2_fd_set_ctrl(fd, FD_CTRL_PTA);

  *total_fds_len += fd_len;
  /* Advance to the next frame descriptor */
  fd++;
  index++;
 }

 *num_fds = index;

 return 0;

err_map_sgt:
err_map_data:
 /* Unmap all the data S/G entries for the current FD */
 sgt = (struct dpaa2_sg_entry *)(sgt_buf + priv->tx_data_offset);
 for (i = 1; i < num_sge; i++)
  dma_unmap_single(dev, dpaa2_sg_get_addr(&sgt[i]),
     dpaa2_sg_get_len(&sgt[i]), DMA_TO_DEVICE);

 /* Unmap the header entry */
 dma_unmap_single(dev, tso_hdr_dma, TSO_HEADER_SIZE, DMA_TO_DEVICE);
err_map_tso_hdr:
 kfree(tso_hdr);
err_alloc_tso_hdr:
 dpaa2_eth_sgt_recycle(priv, sgt_buf);
err_sgt_get:
 /* Free all the other FDs that were already fully created */
 for (i = 0; i < index; i++)
  dpaa2_eth_free_tx_fd(priv, NULL, NULL, &fd_start[i], false);

 return err;
}

static netdev_tx_t __dpaa2_eth_tx(struct sk_buff *skb,
      struct net_device *net_dev)
{
 struct dpaa2_eth_priv *priv = netdev_priv(net_dev);
 int total_enqueued = 0, retries = 0, enqueued;
 struct dpaa2_eth_drv_stats *percpu_extras;
 struct rtnl_link_stats64 *percpu_stats;
 unsigned int needed_headroom;
 int num_fds = 1, max_retries;
 struct dpaa2_eth_fq *fq;
 struct netdev_queue *nq;
 struct dpaa2_fd *fd;
 u16 queue_mapping;
 void *swa = NULL;
 u8 prio = 0;
 int err, i;
 u32 fd_len;

 percpu_stats = this_cpu_ptr(priv->percpu_stats);
 percpu_extras = this_cpu_ptr(priv->percpu_extras);
 fd = (this_cpu_ptr(priv->fd))->array;

 needed_headroom = dpaa2_eth_needed_headroom(skb);

 /* We'll be holding a back-reference to the skb until Tx Confirmation;
 * we don't want that overwritten by a concurrent Tx with a cloned skb.
 */

 skb = skb_unshare(skb, GFP_ATOMIC);
 if (unlikely(!skb)) {
  /* skb_unshare() has already freed the skb */
  percpu_stats->tx_dropped++;
  return NETDEV_TX_OK;
 }

 /* Setup the FD fields */

 if (skb_is_gso(skb)) {
  err = dpaa2_eth_build_gso_fd(priv, skb, fd, &num_fds, &fd_len);
  percpu_extras->tx_sg_frames += num_fds;
  percpu_extras->tx_sg_bytes += fd_len;
  percpu_extras->tx_tso_frames += num_fds;
  percpu_extras->tx_tso_bytes += fd_len;
 } else if (skb_is_nonlinear(skb)) {
  err = dpaa2_eth_build_sg_fd(priv, skb, fd, &swa);
  percpu_extras->tx_sg_frames++;
  percpu_extras->tx_sg_bytes += skb->len;
  fd_len = dpaa2_fd_get_len(fd);
 } else if (skb_headroom(skb) < needed_headroom) {
  err = dpaa2_eth_build_sg_fd_single_buf(priv, skb, fd, &swa);
  percpu_extras->tx_sg_frames++;
  percpu_extras->tx_sg_bytes += skb->len;
  percpu_extras->tx_converted_sg_frames++;
  percpu_extras->tx_converted_sg_bytes += skb->len;
  fd_len = dpaa2_fd_get_len(fd);
 } else {
  err = dpaa2_eth_build_single_fd(priv, skb, fd, &swa);
  fd_len = dpaa2_fd_get_len(fd);
 }

 if (unlikely(err)) {
  percpu_stats->tx_dropped++;
  goto err_build_fd;
 }

 if (swa && skb->cb[0])
  dpaa2_eth_enable_tx_tstamp(priv, fd, swa, skb);

 /* Tracing point */
 for (i = 0; i < num_fds; i++)
  trace_dpaa2_tx_fd(net_dev, &fd[i]);

 /* TxConf FQ selection relies on queue id from the stack.
 * In case of a forwarded frame from another DPNI interface, we choose
 * a queue affined to the same core that processed the Rx frame
 */

 queue_mapping = skb_get_queue_mapping(skb);

 if (net_dev->num_tc) {
  prio = netdev_txq_to_tc(net_dev, queue_mapping);
  /* Hardware interprets priority level 0 as being the highest,
 * so we need to do a reverse mapping to the netdev tc index
 */

  prio = net_dev->num_tc - prio - 1;
  /* We have only one FQ array entry for all Tx hardware queues
 * with the same flow id (but different priority levels)
 */

  queue_mapping %= dpaa2_eth_queue_count(priv);
 }
 fq = &priv->fq[queue_mapping];
 nq = netdev_get_tx_queue(net_dev, queue_mapping);
 netdev_tx_sent_queue(nq, fd_len);

 /* Everything that happens after this enqueues might race with
 * the Tx confirmation callback for this frame
 */

 max_retries = num_fds * DPAA2_ETH_ENQUEUE_RETRIES;
 while (total_enqueued < num_fds && retries < max_retries) {
  err = priv->enqueue(priv, fq, &fd[total_enqueued],
        prio, num_fds - total_enqueued, &enqueued);
  if (err == -EBUSY) {
   retries++;
   continue;
  }

  total_enqueued += enqueued;
 }
 percpu_extras->tx_portal_busy += retries;

 if (unlikely(err < 0)) {
  percpu_stats->tx_errors++;
  /* Clean up everything, including freeing the skb */
  dpaa2_eth_free_tx_fd(priv, NULL, fq, fd, false);
  netdev_tx_completed_queue(nq, 1, fd_len);
 } else {
  percpu_stats->tx_packets += total_enqueued;
  percpu_stats->tx_bytes += fd_len;
 }

 return NETDEV_TX_OK;

err_build_fd:
 dev_kfree_skb(skb);

 return NETDEV_TX_OK;
}

static void dpaa2_eth_tx_onestep_tstamp(struct work_struct *work)
{
 struct dpaa2_eth_priv *priv = container_of(work, struct dpaa2_eth_priv,
         tx_onestep_tstamp);
 struct sk_buff *skb;

 while (true) {
  skb = skb_dequeue(&priv->tx_skbs);
  if (!skb)
   return;

  /* Lock just before TX one-step timestamping packet,
 * and release the lock in dpaa2_eth_free_tx_fd when
 * confirm the packet has been sent on hardware, or
 * when clean up during transmit failure.
 */

  mutex_lock(&priv->onestep_tstamp_lock);
  __dpaa2_eth_tx(skb, priv->net_dev);
 }
}

static netdev_tx_t dpaa2_eth_tx(struct sk_buff *skb, struct net_device *net_dev)
{
 struct dpaa2_eth_priv *priv = netdev_priv(net_dev);
 u8 msgtype, twostep, udp;
 u16 offset1, offset2;

 /* Utilize skb->cb[0] for timestamping request per skb */
 skb->cb[0] = 0;

 if ((skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) && dpaa2_ptp) {
  if (priv->tx_tstamp_type == HWTSTAMP_TX_ON)
   skb->cb[0] = TX_TSTAMP;
  else if (priv->tx_tstamp_type == HWTSTAMP_TX_ONESTEP_SYNC)
   skb->cb[0] = TX_TSTAMP_ONESTEP_SYNC;
 }

 /* TX for one-step timestamping PTP Sync packet */
 if (skb->cb[0] == TX_TSTAMP_ONESTEP_SYNC) {
  if (!dpaa2_eth_ptp_parse(skb, &msgtype, &twostep, &udp,
      &offset1, &offset2))
   if (msgtype == PTP_MSGTYPE_SYNC && twostep == 0) {
    skb_queue_tail(&priv->tx_skbs, skb);
    queue_work(priv->dpaa2_ptp_wq,
        &priv->tx_onestep_tstamp);
    return NETDEV_TX_OK;
   }
  /* Use two-step timestamping if not one-step timestamping
 * PTP Sync packet
 */

  skb->cb[0] = TX_TSTAMP;
 }

 /* TX for other packets */
 return __dpaa2_eth_tx(skb, net_dev);
}

/* Tx confirmation frame processing routine */
static void dpaa2_eth_tx_conf(struct dpaa2_eth_priv *priv,
         struct dpaa2_eth_channel *ch,
         const struct dpaa2_fd *fd,
         struct dpaa2_eth_fq *fq)
{
 struct rtnl_link_stats64 *percpu_stats;
 struct dpaa2_eth_drv_stats *percpu_extras;
 u32 fd_len = dpaa2_fd_get_len(fd);
 u32 fd_errors;

 /* Tracing point */
 trace_dpaa2_tx_conf_fd(priv->net_dev, fd);

 percpu_extras = this_cpu_ptr(priv->percpu_extras);
 percpu_extras->tx_conf_frames++;
 percpu_extras->tx_conf_bytes += fd_len;
 ch->stats.bytes_per_cdan += fd_len;

 /* Check frame errors in the FD field */
 fd_errors = dpaa2_fd_get_ctrl(fd) & DPAA2_FD_TX_ERR_MASK;
 dpaa2_eth_free_tx_fd(priv, ch, fq, fd, true);

 if (likely(!fd_errors))
  return;

 if (net_ratelimit())
  netdev_dbg(priv->net_dev, "TX frame FD error: 0x%08x\n",
      fd_errors);

 percpu_stats = this_cpu_ptr(priv->percpu_stats);
 /* Tx-conf logically pertains to the egress path. */
 percpu_stats->tx_errors++;
}

static int dpaa2_eth_set_rx_vlan_filtering(struct dpaa2_eth_priv *priv,
        bool enable)
{
 int err;

 err = dpni_enable_vlan_filter(priv->mc_io, 0, priv->mc_token, enable);

 if (err) {
  netdev_err(priv->net_dev,
      "dpni_enable_vlan_filter failed\n");
  return err;
 }

 return 0;
}

static int dpaa2_eth_set_rx_csum(struct dpaa2_eth_priv *priv, bool enable)
{
 int err;

 err = dpni_set_offload(priv->mc_io, 0, priv->mc_token,
          DPNI_OFF_RX_L3_CSUM, enable);
 if (err) {
  netdev_err(priv->net_dev,
      "dpni_set_offload(RX_L3_CSUM) failed\n");
  return err;
 }

 err = dpni_set_offload(priv->mc_io, 0, priv->mc_token,
          DPNI_OFF_RX_L4_CSUM, enable);
 if (err) {
  netdev_err(priv->net_dev,
      "dpni_set_offload(RX_L4_CSUM) failed\n");
  return err;
 }

 return 0;
}

static int dpaa2_eth_set_tx_csum(struct dpaa2_eth_priv *priv, bool enable)
{
 int err;

 err = dpni_set_offload(priv->mc_io, 0, priv->mc_token,
          DPNI_OFF_TX_L3_CSUM, enable);
 if (err) {
  netdev_err(priv->net_dev, "dpni_set_offload(TX_L3_CSUM) failed\n");
  return err;
 }

 err = dpni_set_offload(priv->mc_io, 0, priv->mc_token,
          DPNI_OFF_TX_L4_CSUM, enable);
 if (err) {
  netdev_err(priv->net_dev, "dpni_set_offload(TX_L4_CSUM) failed\n");
  return err;
 }

 return 0;
}

/* Perform a single release command to add buffers
 * to the specified buffer pool
 */

static int dpaa2_eth_add_bufs(struct dpaa2_eth_priv *priv,
         struct dpaa2_eth_channel *ch)
{
 struct xdp_buff *xdp_buffs[DPAA2_ETH_BUFS_PER_CMD];
 struct device *dev = priv->net_dev->dev.parent;
 u64 buf_array[DPAA2_ETH_BUFS_PER_CMD];
 struct dpaa2_eth_swa *swa;
 struct page *page;
 dma_addr_t addr;
 int retries = 0;
 int i = 0, err;
 u32 batch;

 /* Allocate buffers visible to WRIOP */
 if (!ch->xsk_zc) {
  for (i = 0; i < DPAA2_ETH_BUFS_PER_CMD; i++) {
   /* Also allocate skb shared info and alignment padding.
 * There is one page for each Rx buffer. WRIOP sees
 * the entire page except for a tailroom reserved for
 * skb shared info
 */

   page = dev_alloc_pages(0);
   if (!page)
    goto err_alloc;

   addr = dma_map_page(dev, page, 0, priv->rx_buf_size,
         DMA_BIDIRECTIONAL);
   if (unlikely(dma_mapping_error(dev, addr)))
    goto err_map;

   buf_array[i] = addr;

   /* tracing point */
   trace_dpaa2_eth_buf_seed(priv->net_dev,
       page_address(page),
       DPAA2_ETH_RX_BUF_RAW_SIZE,
       addr, priv->rx_buf_size,
       ch->bp->bpid);
  }
 } else if (xsk_buff_can_alloc(ch->xsk_pool, DPAA2_ETH_BUFS_PER_CMD)) {
  /* Allocate XSK buffers for AF_XDP fast path in batches
 * of DPAA2_ETH_BUFS_PER_CMD. Bail out if the UMEM cannot
 * provide enough buffers at the moment
 */

  batch = xsk_buff_alloc_batch(ch->xsk_pool, xdp_buffs,
          DPAA2_ETH_BUFS_PER_CMD);
  if (!batch)
   goto err_alloc;

  for (i = 0; i < batch; i++) {
   swa = (struct dpaa2_eth_swa *)(xdp_buffs[i]->data_hard_start +
             DPAA2_ETH_RX_HWA_SIZE);
   swa->xsk.xdp_buff = xdp_buffs[i];

   addr = xsk_buff_xdp_get_frame_dma(xdp_buffs[i]);
   if (unlikely(dma_mapping_error(dev, addr)))
    goto err_map;

   buf_array[i] = addr;

   trace_dpaa2_xsk_buf_seed(priv->net_dev,
       xdp_buffs[i]->data_hard_start,
       DPAA2_ETH_RX_BUF_RAW_SIZE,
       addr, priv->rx_buf_size,
       ch->bp->bpid);
  }
 }

release_bufs:
 /* In case the portal is busy, retry until successful */
 while ((err = dpaa2_io_service_release(ch->dpio, ch->bp->bpid,
            buf_array, i)) == -EBUSY) {
  if (retries++ >= DPAA2_ETH_SWP_BUSY_RETRIES)
   break;
  cpu_relax();
 }

 /* If release command failed, clean up and bail out;
 * not much else we can do about it
 */

 if (err) {
  dpaa2_eth_free_bufs(priv, buf_array, i, ch->xsk_zc);
  return 0;
 }

 return i;

err_map:
 if (!ch->xsk_zc) {
  __free_pages(page, 0);
 } else {
  for (; i < batch; i++)
   xsk_buff_free(xdp_buffs[i]);
 }
err_alloc:
 /* If we managed to allocate at least some buffers,
 * release them to hardware
 */

 if (i)
  goto release_bufs;

 return 0;
}

static int dpaa2_eth_seed_pool(struct dpaa2_eth_priv *priv,
          struct dpaa2_eth_channel *ch)
{
 int i;
 int new_count;

 for (i = 0; i < DPAA2_ETH_NUM_BUFS; i += DPAA2_ETH_BUFS_PER_CMD) {
  new_count = dpaa2_eth_add_bufs(priv, ch);
  ch->buf_count += new_count;

  if (new_count < DPAA2_ETH_BUFS_PER_CMD)
   return -ENOMEM;
 }

 return 0;
}

static void dpaa2_eth_seed_pools(struct dpaa2_eth_priv *priv)
{
 struct net_device *net_dev = priv->net_dev;
 struct dpaa2_eth_channel *channel;
 int i, err = 0;

 for (i = 0; i < priv->num_channels; i++) {
  channel = priv->channel[i];

  err = dpaa2_eth_seed_pool(priv, channel);

  /* Not much to do; the buffer pool, though not filled up,
 * may still contain some buffers which would enable us
 * to limp on.
 */

  if (err)
   netdev_err(net_dev, "Buffer seeding failed for DPBP %d (bpid=%d)\n",
       channel->bp->dev->obj_desc.id,
       channel->bp->bpid);
 }
}

/*
 * Drain the specified number of buffers from one of the DPNI's private buffer
 * pools.
 * @count must not exceeed DPAA2_ETH_BUFS_PER_CMD
 */

static void dpaa2_eth_drain_bufs(struct dpaa2_eth_priv *priv, int bpid,
     int count)
{
 u64 buf_array[DPAA2_ETH_BUFS_PER_CMD];
 bool xsk_zc = false;
 int retries = 0;
 int i, ret;

 for (i = 0; i < priv->num_channels; i++)
  if (priv->channel[i]->bp->bpid == bpid)
   xsk_zc = priv->channel[i]->xsk_zc;

 do {
  ret = dpaa2_io_service_acquire(NULL, bpid, buf_array, count);
  if (ret < 0) {
   if (ret == -EBUSY &&
       retries++ < DPAA2_ETH_SWP_BUSY_RETRIES)
    continue;
   netdev_err(priv->net_dev, "dpaa2_io_service_acquire() failed\n");
   return;
  }
  dpaa2_eth_free_bufs(priv, buf_array, ret, xsk_zc);
  retries = 0;
 } while (ret);
}

static void dpaa2_eth_drain_pool(struct dpaa2_eth_priv *priv, int bpid)
{
 int i;

 /* Drain the buffer pool */
 dpaa2_eth_drain_bufs(priv, bpid, DPAA2_ETH_BUFS_PER_CMD);
 dpaa2_eth_drain_bufs(priv, bpid, 1);

 /* Setup to zero the buffer count of all channels which were
 * using this buffer pool.
 */

 for (i = 0; i < priv->num_channels; i++)
  if (priv->channel[i]->bp->bpid == bpid)
   priv->channel[i]->buf_count = 0;
}

static void dpaa2_eth_drain_pools(struct dpaa2_eth_priv *priv)
{
 int i;

 for (i = 0; i < priv->num_bps; i++)
  dpaa2_eth_drain_pool(priv, priv->bp[i]->bpid);
}

/* Function is called from softirq context only, so we don't need to guard
 * the access to percpu count
 */

static int dpaa2_eth_refill_pool(struct dpaa2_eth_priv *priv,
     struct dpaa2_eth_channel *ch)
{
 int new_count;

 if (likely(ch->buf_count >= DPAA2_ETH_REFILL_THRESH))
  return 0;

 do {
  new_count = dpaa2_eth_add_bufs(priv, ch);
  if (unlikely(!new_count)) {
   /* Out of memory; abort for now, we'll try later on */
   break;
  }
  ch->buf_count += new_count;
 } while (ch->buf_count < DPAA2_ETH_NUM_BUFS);

 if (unlikely(ch->buf_count < DPAA2_ETH_NUM_BUFS))
  return -ENOMEM;

 return 0;
}

static void dpaa2_eth_sgt_cache_drain(struct dpaa2_eth_priv *priv)
{
 struct dpaa2_eth_sgt_cache *sgt_cache;
 u16 count;
 int k, i;

 for_each_possible_cpu(k) {
  sgt_cache = per_cpu_ptr(priv->sgt_cache, k);
  count = sgt_cache->count;

  for (i = 0; i < count; i++)
   skb_free_frag(sgt_cache->buf[i]);
  sgt_cache->count = 0;
 }
}

static int dpaa2_eth_pull_channel(struct dpaa2_eth_channel *ch)
{
 int err;
 int dequeues = -1;

 /* Retry while portal is busy */
 do {
  err = dpaa2_io_service_pull_channel(ch->dpio, ch->ch_id,
          ch->store);
  dequeues++;
  cpu_relax();
 } while (err == -EBUSY && dequeues < DPAA2_ETH_SWP_BUSY_RETRIES);

 ch->stats.dequeue_portal_busy += dequeues;
 if (unlikely(err))
  ch->stats.pull_err++;

 return err;
}

/* NAPI poll routine
 *
 * Frames are dequeued from the QMan channel associated with this NAPI context.
 * Rx, Tx confirmation and (if configured) Rx error frames all count
 * towards the NAPI budget.
 */

static int dpaa2_eth_poll(struct napi_struct *napi, int budget)
{
 struct dpaa2_eth_channel *ch;
 struct dpaa2_eth_priv *priv;
 int rx_cleaned = 0, txconf_cleaned = 0;
 struct dpaa2_eth_fq *fq, *txc_fq = NULL;
 struct netdev_queue *nq;
 int store_cleaned, work_done;
 bool work_done_zc = false;
 struct list_head rx_list;
 int retries = 0;
 u16 flowid;
 int err;

 ch = container_of(napi, struct dpaa2_eth_channel, napi);
 ch->xdp.res = 0;
 priv = ch->priv;

 INIT_LIST_HEAD(&rx_list);
 ch->rx_list = &rx_list;

 if (ch->xsk_zc) {
  work_done_zc = dpaa2_xsk_tx(priv, ch);
  /* If we reached the XSK Tx per NAPI threshold, we're done */
  if (work_done_zc) {
   work_done = budget;
   goto out;
  }
 }

 do {
  err = dpaa2_eth_pull_channel(ch);
  if (unlikely(err))
   break;

  /* Refill pool if appropriate */
  dpaa2_eth_refill_pool(priv, ch);

  store_cleaned = dpaa2_eth_consume_frames(ch, &fq);
  if (store_cleaned <= 0)
   break;
  if (fq->type == DPAA2_RX_FQ) {
   rx_cleaned += store_cleaned;
   flowid = fq->flowid;
  } else {
   txconf_cleaned += store_cleaned;
   /* We have a single Tx conf FQ on this channel */
   txc_fq = fq;
  }

  /* If we either consumed the whole NAPI budget with Rx frames
 * or we reached the Tx confirmations threshold, we're done.
 */

  if (rx_cleaned >= budget ||
      txconf_cleaned >= DPAA2_ETH_TXCONF_PER_NAPI) {
   work_done = budget;
   if (ch->xdp.res & XDP_REDIRECT)
    xdp_do_flush();
   goto out;
  }
 } while (store_cleaned);

 if (ch->xdp.res & XDP_REDIRECT)
  xdp_do_flush();

 /* Update NET DIM with the values for this CDAN */
 dpaa2_io_update_net_dim(ch->dpio, ch->stats.frames_per_cdan,
    ch->stats.bytes_per_cdan);
 ch->stats.frames_per_cdan = 0;
 ch->stats.bytes_per_cdan = 0;

 /* We didn't consume the entire budget, so finish napi and
 * re-enable data availability notifications
 */

 napi_complete_done(napi, rx_cleaned);
 do {
  err = dpaa2_io_service_rearm(ch->dpio, &ch->nctx);
  cpu_relax();
 } while (err == -EBUSY && retries++ < DPAA2_ETH_SWP_BUSY_RETRIES);
 WARN_ONCE(err, "CDAN notifications rearm failed on core %d",
    ch->nctx.desired_cpu);

 work_done = max(rx_cleaned, 1);

out:
 netif_receive_skb_list(ch->rx_list);

 if (ch->xsk_tx_pkts_sent) {
  xsk_tx_completed(ch->xsk_pool, ch->xsk_tx_pkts_sent);
  ch->xsk_tx_pkts_sent = 0;
 }

 if (txc_fq && txc_fq->dq_frames) {
  nq = netdev_get_tx_queue(priv->net_dev, txc_fq->flowid);
  netdev_tx_completed_queue(nq, txc_fq->dq_frames,
       txc_fq->dq_bytes);
  txc_fq->dq_frames = 0;
  txc_fq->dq_bytes = 0;
 }

 if (rx_cleaned && ch->xdp.res & XDP_TX)
  dpaa2_eth_xdp_tx_flush(priv, ch, &priv->fq[flowid]);

 return work_done;
}

static void dpaa2_eth_enable_ch_napi(struct dpaa2_eth_priv *priv)
{
 struct dpaa2_eth_channel *ch;
 int i;

 for (i = 0; i < priv->num_channels; i++) {
  ch = priv->channel[i];
  napi_enable(&ch->napi);
 }
}

static void dpaa2_eth_disable_ch_napi(struct dpaa2_eth_priv *priv)
{
 struct dpaa2_eth_channel *ch;
 int i;

 for (i = 0; i < priv->num_channels; i++) {
  ch = priv->channel[i];
  napi_disable(&ch->napi);
 }
}

void dpaa2_eth_set_rx_taildrop(struct dpaa2_eth_priv *priv,
          bool tx_pause, bool pfc)
{
 struct dpni_taildrop td = {0};
 struct dpaa2_eth_fq *fq;
 int i, err;

 /* FQ taildrop: threshold is in bytes, per frame queue. Enabled if
 * flow control is disabled (as it might interfere with either the
 * buffer pool depletion trigger for pause frames or with the group
 * congestion trigger for PFC frames)
 */

 td.enable = !tx_pause;
 if (priv->rx_fqtd_enabled == td.enable)
  goto set_cgtd;

 td.threshold = DPAA2_ETH_FQ_TAILDROP_THRESH;
 td.units = DPNI_CONGESTION_UNIT_BYTES;

 for (i = 0; i < priv->num_fqs; i++) {
  fq = &priv->fq[i];
  if (fq->type != DPAA2_RX_FQ)
   continue;
  err = dpni_set_taildrop(priv->mc_io, 0, priv->mc_token,
     DPNI_CP_QUEUE, DPNI_QUEUE_RX,
     fq->tc, fq->flowid, &td);
  if (err) {
   netdev_err(priv->net_dev,
       "dpni_set_taildrop(FQ) failed\n");
   return;
  }
 }

 priv->rx_fqtd_enabled = td.enable;

set_cgtd:
 /* Congestion group taildrop: threshold is in frames, per group
 * of FQs belonging to the same traffic class
 * Enabled if general Tx pause disabled or if PFCs are enabled
 * (congestion group threhsold for PFC generation is lower than the
 * CG taildrop threshold, so it won't interfere with it; we also
 * want frames in non-PFC enabled traffic classes to be kept in check)
 */

 td.enable = !tx_pause || pfc;
 if (priv->rx_cgtd_enabled == td.enable)
  return;

 td.threshold = DPAA2_ETH_CG_TAILDROP_THRESH(priv);
 td.units = DPNI_CONGESTION_UNIT_FRAMES;
 for (i = 0; i < dpaa2_eth_tc_count(priv); i++) {
  err = dpni_set_taildrop(priv->mc_io, 0, priv->mc_token,
     DPNI_CP_GROUP, DPNI_QUEUE_RX,
     i, 0, &td);
  if (err) {
   netdev_err(priv->net_dev,
       "dpni_set_taildrop(CG) failed\n");
   return;
  }
 }

 priv->rx_cgtd_enabled = td.enable;
}

static int dpaa2_eth_link_state_update(struct dpaa2_eth_priv *priv)
{
 struct dpni_link_state state = {0};
 bool tx_pause;
 int err;

 err = dpni_get_link_state(priv->mc_io, 0, priv->mc_token, &state);
 if (unlikely(err)) {
  netdev_err(priv->net_dev,
      "dpni_get_link_state() failed\n");
  return err;
 }

 /* If Tx pause frame settings have changed, we need to update
 * Rx FQ taildrop configuration as well. We configure taildrop
 * only when pause frame generation is disabled.
 */

 tx_pause = dpaa2_eth_tx_pause_enabled(state.options);
 dpaa2_eth_set_rx_taildrop(priv, tx_pause, priv->pfc_enabled);

 /* When we manage the MAC/PHY using phylink there is no need
 * to manually update the netif_carrier.
 * We can avoid locking because we are called from the "link changed"
 * IRQ handler, which is the same as the "endpoint changed" IRQ handler
 * (the writer to priv->mac), so we cannot race with it.
 */

 if (dpaa2_mac_is_type_phy(priv->mac))
  goto out;

 /* Chech link state; speed / duplex changes are not treated yet */
 if (priv->link_state.up == state.up)
  goto out;

 if (state.up) {
  netif_carrier_on(priv->net_dev);
  netif_tx_start_all_queues(priv->net_dev);
 } else {
  netif_tx_stop_all_queues(priv->net_dev);
  netif_carrier_off(priv->net_dev);
 }

 netdev_info(priv->net_dev, "Link Event: state %s\n",
      state.up ? "up" : "down");

out:
 priv->link_state = state;

 return 0;
}

static int dpaa2_eth_open(struct net_device *net_dev)
{
 struct dpaa2_eth_priv *priv = netdev_priv(net_dev);
 int err;

 dpaa2_eth_seed_pools(priv);

 mutex_lock(&priv->mac_lock);

 if (!dpaa2_eth_is_type_phy(priv)) {
  /* We'll only start the txqs when the link is actually ready;
 * make sure we don't race against the link up notification,
 * which may come immediately after dpni_enable();
 */

  netif_tx_stop_all_queues(net_dev);

  /* Also, explicitly set carrier off, otherwise
 * netif_carrier_ok() will return true and cause 'ip link show'
 * to report the LOWER_UP flag, even though the link
 * notification wasn't even received.
 */

  netif_carrier_off(net_dev);
 }
 dpaa2_eth_enable_ch_napi(priv);

 err = dpni_enable(priv->mc_io, 0, priv->mc_token);
 if (err < 0) {
  mutex_unlock(&priv->mac_lock);
  netdev_err(net_dev, "dpni_enable() failed\n");
  goto enable_err;
 }

 if (dpaa2_eth_is_type_phy(priv))
  dpaa2_mac_start(priv->mac);

 mutex_unlock(&priv->mac_lock);

 return 0;

enable_err:
 dpaa2_eth_disable_ch_napi(priv);
 dpaa2_eth_drain_pools(priv);
 return err;
}

/* Total number of in-flight frames on ingress queues */
static u32 dpaa2_eth_ingress_fq_count(struct dpaa2_eth_priv *priv)
{
 struct dpaa2_eth_fq *fq;
 u32 fcnt = 0, bcnt = 0, total = 0;
 int i, err;

 for (i = 0; i < priv->num_fqs; i++) {
  fq = &priv->fq[i];
  err = dpaa2_io_query_fq_count(NULL, fq->fqid, &fcnt, &bcnt);
  if (err) {
   netdev_warn(priv->net_dev, "query_fq_count failed");
   break;
  }
  total += fcnt;
 }

 return total;
}

static void dpaa2_eth_wait_for_ingress_fq_empty(struct dpaa2_eth_priv *priv)
{
 int retries = 10;
 u32 pending;

 do {
  pending = dpaa2_eth_ingress_fq_count(priv);
  if (pending)
   msleep(100);
 } while (pending && --retries);
}

#define DPNI_TX_PENDING_VER_MAJOR 7
#define DPNI_TX_PENDING_VER_MINOR 13
static void dpaa2_eth_wait_for_egress_fq_empty(struct dpaa2_eth_priv *priv)
{
 union dpni_statistics stats;
 int retries = 10;
 int err;

 if (dpaa2_eth_cmp_dpni_ver(priv, DPNI_TX_PENDING_VER_MAJOR,
       DPNI_TX_PENDING_VER_MINOR) < 0)
  goto out;

 do {
  err = dpni_get_statistics(priv->mc_io, 0, priv->mc_token, 6,
       &stats);
  if (err)
   goto out;
  if (stats.page_6.tx_pending_frames == 0)
   return;
 } while (--retries);

out:
 msleep(500);
}

static int dpaa2_eth_stop(struct net_device *net_dev)
{
 struct dpaa2_eth_priv *priv = netdev_priv(net_dev);
 int dpni_enabled = 0;
 int retries = 10;

 mutex_lock(&priv->mac_lock);

 if (dpaa2_eth_is_type_phy(priv)) {
  dpaa2_mac_stop(priv->mac);
 } else {
  netif_tx_stop_all_queues(net_dev);
  netif_carrier_off(net_dev);
 }

 mutex_unlock(&priv->mac_lock);

 /* On dpni_disable(), the MC firmware will:
 * - stop MAC Rx and wait for all Rx frames to be enqueued to software
 * - cut off WRIOP dequeues from egress FQs and wait until transmission
 * of all in flight Tx frames is finished (and corresponding Tx conf
 * frames are enqueued back to software)
 *
 * Before calling dpni_disable(), we wait for all Tx frames to arrive
 * on WRIOP. After it finishes, wait until all remaining frames on Rx
 * and Tx conf queues are consumed on NAPI poll.
 */

 dpaa2_eth_wait_for_egress_fq_empty(priv);

 do {
  dpni_disable(priv->mc_io, 0, priv->mc_token);
  dpni_is_enabled(priv->mc_io, 0, priv->mc_token, &dpni_enabled);
  if (dpni_enabled)
   /* Allow the hardware some slack */
   msleep(100);
 } while (dpni_enabled && --retries);
 if (!retries) {
  netdev_warn(net_dev, "Retry count exceeded disabling DPNI\n");
  /* Must go on and disable NAPI nonetheless, so we don't crash at
 * the next "ifconfig up"
 */

 }

 dpaa2_eth_wait_for_ingress_fq_empty(priv);
 dpaa2_eth_disable_ch_napi(priv);

 /* Empty the buffer pool */
 dpaa2_eth_drain_pools(priv);

 /* Empty the Scatter-Gather Buffer cache */
 dpaa2_eth_sgt_cache_drain(priv);

 return 0;
}

static int dpaa2_eth_set_addr(struct net_device *net_dev, void *addr)
{
 struct dpaa2_eth_priv *priv = netdev_priv(net_dev);
 struct device *dev = net_dev->dev.parent;
 int err;

 err = eth_mac_addr(net_dev, addr);
 if (err < 0) {
  dev_err(dev, "eth_mac_addr() failed (%d)\n", err);
  return err;
 }

 err = dpni_set_primary_mac_addr(priv->mc_io, 0, priv->mc_token,
     net_dev->dev_addr);
 if (err) {
  dev_err(dev, "dpni_set_primary_mac_addr() failed (%d)\n", err);
  return err;
 }

 return 0;
}

/** Fill in counters maintained by the GPP driver. These may be different from
 * the hardware counters obtained by ethtool.
 */

static void dpaa2_eth_get_stats(struct net_device *net_dev,
    struct rtnl_link_stats64 *stats)
{
 struct dpaa2_eth_priv *priv = netdev_priv(net_dev);
 struct rtnl_link_stats64 *percpu_stats;
 u64 *cpustats;
 u64 *netstats = (u64 *)stats;
 int i, j;
 int num = sizeof(struct rtnl_link_stats64) / sizeof(u64);

 for_each_possible_cpu(i) {
  percpu_stats = per_cpu_ptr(priv->percpu_stats, i);
  cpustats = (u64 *)percpu_stats;
  for (j = 0; j < num; j++)
   netstats[j] += cpustats[j];
 }
}

/* Copy mac unicast addresses from @net_dev to @priv.
 * Its sole purpose is to make dpaa2_eth_set_rx_mode() more readable.
 */

static void dpaa2_eth_add_uc_hw_addr(const struct net_device *net_dev,
         struct dpaa2_eth_priv *priv)
{
 struct netdev_hw_addr *ha;
 int err;

 netdev_for_each_uc_addr(ha, net_dev) {
  err = dpni_add_mac_addr(priv->mc_io, 0, priv->mc_token,
     ha->addr);
  if (err)
   netdev_warn(priv->net_dev,
        "Could not add ucast MAC %pM to the filtering table (err %d)\n",
        ha->addr, err);
 }
}

/* Copy mac multicast addresses from @net_dev to @priv
 * Its sole purpose is to make dpaa2_eth_set_rx_mode() more readable.
 */

static void dpaa2_eth_add_mc_hw_addr(const struct net_device *net_dev,
         struct dpaa2_eth_priv *priv)
{
 struct netdev_hw_addr *ha;
 int err;

 netdev_for_each_mc_addr(ha, net_dev) {
  err = dpni_add_mac_addr(priv->mc_io, 0, priv->mc_token,
     ha->addr);
  if (err)
--> --------------------

--> maximum size reached

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

Messung V0.5
C=95 H=92 G=93

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