This patch change the API for ndo_xdp_xmit to support bulking
xdp_frames.

When kernel is compiled with CONFIG_RETPOLINE, XDP sees a huge slowdown.
Most of the slowdown is caused by DMA API indirect function calls, but
also the net_device->ndo_xdp_xmit() call.

Benchmarked patch with CONFIG_RETPOLINE, using xdp_redirect_map with
single flow/core test (CPU E5-1650 v4 @ 3.60GHz), showed
performance improved:
 for driver ixgbe: 6,042,682 pps -> 6,853,768 pps = +811,086 pps
 for driver i40e : 6,187,169 pps -> 6,724,519 pps = +537,350 pps

With frames avail as a bulk inside the driver ndo_xdp_xmit call,
further optimizations are possible, like bulk DMA-mapping for TX.

Testing without CONFIG_RETPOLINE show the same performance for
physical NIC drivers.

The virtual NIC driver tun sees a huge performance boost, as it can
avoid doing per frame producer locking, but instead amortize the
locking cost over the bulk.

Signed-off-by: Jesper Dangaard Brouer <bro...@redhat.com>
---
 drivers/net/ethernet/intel/i40e/i40e_txrx.c   |   26 +++++++---
 drivers/net/ethernet/intel/i40e/i40e_txrx.h   |    2 -
 drivers/net/ethernet/intel/ixgbe/ixgbe_main.c |   21 ++++++--
 drivers/net/tun.c                             |   37 +++++++++-----
 drivers/net/virtio_net.c                      |   66 +++++++++++++++++++------
 include/linux/netdevice.h                     |   14 +++--
 include/net/xdp.h                             |    1 
 include/trace/events/xdp.h                    |   10 ++--
 kernel/bpf/devmap.c                           |   33 ++++++++-----
 net/core/filter.c                             |    4 +-
 net/core/xdp.c                                |   14 ++++-
 samples/bpf/xdp_monitor_kern.c                |   10 ++++
 samples/bpf/xdp_monitor_user.c                |   35 +++++++++++--
 13 files changed, 197 insertions(+), 76 deletions(-)

diff --git a/drivers/net/ethernet/intel/i40e/i40e_txrx.c 
b/drivers/net/ethernet/intel/i40e/i40e_txrx.c
index 87fb27ab9c24..27e69fdd0a11 100644
--- a/drivers/net/ethernet/intel/i40e/i40e_txrx.c
+++ b/drivers/net/ethernet/intel/i40e/i40e_txrx.c
@@ -3686,14 +3686,19 @@ netdev_tx_t i40e_lan_xmit_frame(struct sk_buff *skb, 
struct net_device *netdev)
  * @dev: netdev
  * @xdp: XDP buffer
  *
- * Returns Zero if sent, else an error code
+ * Returns number of frames successfully sent. Frames that fail are
+ * free'ed via XDP return API.
+ *
+ * For error cases, a negative errno code is returned and no-frames
+ * are transmitted (caller must handle freeing frames).
  **/
-int i40e_xdp_xmit(struct net_device *dev, struct xdp_frame *xdpf)
+int i40e_xdp_xmit(struct net_device *dev, int n, struct xdp_frame **frames)
 {
        struct i40e_netdev_priv *np = netdev_priv(dev);
        unsigned int queue_index = smp_processor_id();
        struct i40e_vsi *vsi = np->vsi;
-       int err;
+       int drops = 0;
+       int i;
 
        if (test_bit(__I40E_VSI_DOWN, vsi->state))
                return -ENETDOWN;
@@ -3701,11 +3706,18 @@ int i40e_xdp_xmit(struct net_device *dev, struct 
xdp_frame *xdpf)
        if (!i40e_enabled_xdp_vsi(vsi) || queue_index >= vsi->num_queue_pairs)
                return -ENXIO;
 
-       err = i40e_xmit_xdp_ring(xdpf, vsi->xdp_rings[queue_index]);
-       if (err != I40E_XDP_TX)
-               return -ENOSPC;
+       for (i = 0; i < n; i++) {
+               struct xdp_frame *xdpf = frames[i];
+               int err;
 
-       return 0;
+               err = i40e_xmit_xdp_ring(xdpf, vsi->xdp_rings[queue_index]);
+               if (err != I40E_XDP_TX) {
+                       xdp_return_frame_rx_napi(xdpf);
+                       drops++;
+               }
+       }
+
+       return n - drops;
 }
 
 /**
diff --git a/drivers/net/ethernet/intel/i40e/i40e_txrx.h 
b/drivers/net/ethernet/intel/i40e/i40e_txrx.h
index 4bf318b8be85..da6697c0407f 100644
--- a/drivers/net/ethernet/intel/i40e/i40e_txrx.h
+++ b/drivers/net/ethernet/intel/i40e/i40e_txrx.h
@@ -511,7 +511,7 @@ u32 i40e_get_tx_pending(struct i40e_ring *ring, bool in_sw);
 void i40e_detect_recover_hung(struct i40e_vsi *vsi);
 int __i40e_maybe_stop_tx(struct i40e_ring *tx_ring, int size);
 bool __i40e_chk_linearize(struct sk_buff *skb);
-int i40e_xdp_xmit(struct net_device *dev, struct xdp_frame *xdpf);
+int i40e_xdp_xmit(struct net_device *dev, int n, struct xdp_frame **frames);
 void i40e_xdp_flush(struct net_device *dev);
 
 /**
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c 
b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
index b6e5cea84949..e64f71bc04c2 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
@@ -10042,11 +10042,13 @@ static int ixgbe_xdp(struct net_device *dev, struct 
netdev_bpf *xdp)
        }
 }
 
-static int ixgbe_xdp_xmit(struct net_device *dev, struct xdp_frame *xdpf)
+static int ixgbe_xdp_xmit(struct net_device *dev, int n,
+                         struct xdp_frame **frames)
 {
        struct ixgbe_adapter *adapter = netdev_priv(dev);
        struct ixgbe_ring *ring;
-       int err;
+       int drops = 0;
+       int i;
 
        if (unlikely(test_bit(__IXGBE_DOWN, &adapter->state)))
                return -ENETDOWN;
@@ -10058,11 +10060,18 @@ static int ixgbe_xdp_xmit(struct net_device *dev, 
struct xdp_frame *xdpf)
        if (unlikely(!ring))
                return -ENXIO;
 
-       err = ixgbe_xmit_xdp_ring(adapter, xdpf);
-       if (err != IXGBE_XDP_TX)
-               return -ENOSPC;
+       for (i = 0; i < n; i++) {
+               struct xdp_frame *xdpf = frames[i];
+               int err;
 
-       return 0;
+               err = ixgbe_xmit_xdp_ring(adapter, xdpf);
+               if (err != IXGBE_XDP_TX) {
+                       xdp_return_frame_rx_napi(xdpf);
+                       drops++;
+               }
+       }
+
+       return n - drops;
 }
 
 static void ixgbe_xdp_flush(struct net_device *dev)
diff --git a/drivers/net/tun.c b/drivers/net/tun.c
index d3c04ab9752a..4fe0c75c5e0b 100644
--- a/drivers/net/tun.c
+++ b/drivers/net/tun.c
@@ -70,6 +70,7 @@
 #include <net/netns/generic.h>
 #include <net/rtnetlink.h>
 #include <net/sock.h>
+#include <net/xdp.h>
 #include <linux/seq_file.h>
 #include <linux/uio.h>
 #include <linux/skb_array.h>
@@ -1290,34 +1291,44 @@ static const struct net_device_ops tun_netdev_ops = {
        .ndo_get_stats64        = tun_net_get_stats64,
 };
 
-static int tun_xdp_xmit(struct net_device *dev, struct xdp_frame *frame)
+static int tun_xdp_xmit(struct net_device *dev, int n, struct xdp_frame 
**frames)
 {
        struct tun_struct *tun = netdev_priv(dev);
        struct tun_file *tfile;
        u32 numqueues;
-       int ret = 0;
+       int drops = 0;
+       int cnt = n;
+       int i;
 
        rcu_read_lock();
 
        numqueues = READ_ONCE(tun->numqueues);
        if (!numqueues) {
-               ret = -ENOSPC;
-               goto out;
+               rcu_read_unlock();
+               return -ENXIO; /* Caller will free/return all frames */
        }
 
        tfile = rcu_dereference(tun->tfiles[smp_processor_id() %
                                            numqueues]);
-       /* Encode the XDP flag into lowest bit for consumer to differ
-        * XDP buffer from sk_buff.
-        */
-       if (ptr_ring_produce(&tfile->tx_ring, tun_xdp_to_ptr(frame))) {
-               this_cpu_inc(tun->pcpu_stats->tx_dropped);
-               ret = -ENOSPC;
+
+       spin_lock(&tfile->tx_ring.producer_lock);
+       for (i = 0; i < n; i++) {
+               struct xdp_frame *xdp = frames[i];
+               /* Encode the XDP flag into lowest bit for consumer to differ
+                * XDP buffer from sk_buff.
+                */
+               void *frame = tun_xdp_to_ptr(xdp);
+
+               if (__ptr_ring_produce(&tfile->tx_ring, frame)) {
+                       this_cpu_inc(tun->pcpu_stats->tx_dropped);
+                       xdp_return_frame_rx_napi(xdp);
+                       drops++;
+               }
        }
+       spin_unlock(&tfile->tx_ring.producer_lock);
 
-out:
        rcu_read_unlock();
-       return ret;
+       return cnt - drops;
 }
 
 static int tun_xdp_tx(struct net_device *dev, struct xdp_buff *xdp)
@@ -1327,7 +1338,7 @@ static int tun_xdp_tx(struct net_device *dev, struct 
xdp_buff *xdp)
        if (unlikely(!frame))
                return -EOVERFLOW;
 
-       return tun_xdp_xmit(dev, frame);
+       return tun_xdp_xmit(dev, 1, &frame);
 }
 
 static void tun_xdp_flush(struct net_device *dev)
diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c
index f34794a76c4d..39a0783d1cde 100644
--- a/drivers/net/virtio_net.c
+++ b/drivers/net/virtio_net.c
@@ -419,23 +419,13 @@ static void virtnet_xdp_flush(struct net_device *dev)
        virtqueue_kick(sq->vq);
 }
 
-static int __virtnet_xdp_xmit(struct virtnet_info *vi,
-                              struct xdp_frame *xdpf)
+static int __virtnet_xdp_xmit_one(struct virtnet_info *vi,
+                                  struct send_queue *sq,
+                                  struct xdp_frame *xdpf)
 {
        struct virtio_net_hdr_mrg_rxbuf *hdr;
-       struct xdp_frame *xdpf_sent;
-       struct send_queue *sq;
-       unsigned int len;
-       unsigned int qp;
        int err;
 
-       qp = vi->curr_queue_pairs - vi->xdp_queue_pairs + smp_processor_id();
-       sq = &vi->sq[qp];
-
-       /* Free up any pending old buffers before queueing new ones. */
-       while ((xdpf_sent = virtqueue_get_buf(sq->vq, &len)) != NULL)
-               xdp_return_frame(xdpf_sent);
-
        /* virtqueue want to use data area in-front of packet */
        if (unlikely(xdpf->metasize > 0))
                return -EOPNOTSUPP;
@@ -459,11 +449,40 @@ static int __virtnet_xdp_xmit(struct virtnet_info *vi,
        return 0;
 }
 
-static int virtnet_xdp_xmit(struct net_device *dev, struct xdp_frame *xdpf)
+static int __virtnet_xdp_tx_xmit(struct virtnet_info *vi,
+                                  struct xdp_frame *xdpf)
+{
+       struct xdp_frame *xdpf_sent;
+       struct send_queue *sq;
+       unsigned int len;
+       unsigned int qp;
+
+       qp = vi->curr_queue_pairs - vi->xdp_queue_pairs + smp_processor_id();
+       sq = &vi->sq[qp];
+
+       /* Free up any pending old buffers before queueing new ones. */
+       while ((xdpf_sent = virtqueue_get_buf(sq->vq, &len)) != NULL)
+               xdp_return_frame(xdpf_sent);
+
+       return __virtnet_xdp_xmit_one(vi, sq, xdpf);
+}
+
+static int virtnet_xdp_xmit(struct net_device *dev,
+                           int n, struct xdp_frame **frames)
 {
        struct virtnet_info *vi = netdev_priv(dev);
        struct receive_queue *rq = vi->rq;
+       struct xdp_frame *xdpf_sent;
        struct bpf_prog *xdp_prog;
+       struct send_queue *sq;
+       unsigned int len;
+       unsigned int qp;
+       int drops = 0;
+       int err;
+       int i;
+
+       qp = vi->curr_queue_pairs - vi->xdp_queue_pairs + smp_processor_id();
+       sq = &vi->sq[qp];
 
        /* Only allow ndo_xdp_xmit if XDP is loaded on dev, as this
         * indicate XDP resources have been successfully allocated.
@@ -472,7 +491,20 @@ static int virtnet_xdp_xmit(struct net_device *dev, struct 
xdp_frame *xdpf)
        if (!xdp_prog)
                return -ENXIO;
 
-       return __virtnet_xdp_xmit(vi, xdpf);
+       /* Free up any pending old buffers before queueing new ones. */
+       while ((xdpf_sent = virtqueue_get_buf(sq->vq, &len)) != NULL)
+               xdp_return_frame(xdpf_sent);
+
+       for (i = 0; i < n; i++) {
+               struct xdp_frame *xdpf = frames[i];
+
+               err = __virtnet_xdp_xmit_one(vi, sq, xdpf);
+               if (err) {
+                       xdp_return_frame_rx_napi(xdpf);
+                       drops++;
+               }
+       }
+       return n - drops;
 }
 
 static unsigned int virtnet_get_headroom(struct virtnet_info *vi)
@@ -616,7 +648,7 @@ static struct sk_buff *receive_small(struct net_device *dev,
                        xdpf = convert_to_xdp_frame(&xdp);
                        if (unlikely(!xdpf))
                                goto err_xdp;
-                       err = __virtnet_xdp_xmit(vi, xdpf);
+                       err = __virtnet_xdp_tx_xmit(vi, xdpf);
                        if (unlikely(err)) {
                                trace_xdp_exception(vi->dev, xdp_prog, act);
                                goto err_xdp;
@@ -779,7 +811,7 @@ static struct sk_buff *receive_mergeable(struct net_device 
*dev,
                        xdpf = convert_to_xdp_frame(&xdp);
                        if (unlikely(!xdpf))
                                goto err_xdp;
-                       err = __virtnet_xdp_xmit(vi, xdpf);
+                       err = __virtnet_xdp_tx_xmit(vi, xdpf);
                        if (unlikely(err)) {
                                trace_xdp_exception(vi->dev, xdp_prog, act);
                                if (unlikely(xdp_page != page))
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index a30435118530..1c92f3b63c70 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -1165,9 +1165,13 @@ struct dev_ifalias {
  *     This function is used to set or query state related to XDP on the
  *     netdevice and manage BPF offload. See definition of
  *     enum bpf_netdev_command for details.
- * int (*ndo_xdp_xmit)(struct net_device *dev, struct xdp_frame *xdp);
- *     This function is used to submit a XDP packet for transmit on a
- *     netdevice.
+ * int (*ndo_xdp_xmit)(struct net_device *dev, int n, struct xdp_frame **xdp);
+ *     This function is used to submit @n XDP packets for transmit on a
+ *     netdevice. Returns number of frames successfully transmitted, frames
+ *     that got dropped are freed/returned via xdp_return_frame().
+ *     Returns negative number, means general error invoking ndo, meaning
+ *     no frames were xmit'ed and core-caller will free all frames.
+ *     TODO: Consider add flag to allow sending flush operation.
  * void (*ndo_xdp_flush)(struct net_device *dev);
  *     This function is used to inform the driver to flush a particular
  *     xdp tx queue. Must be called on same CPU as xdp_xmit.
@@ -1355,8 +1359,8 @@ struct net_device_ops {
                                                       int needed_headroom);
        int                     (*ndo_bpf)(struct net_device *dev,
                                           struct netdev_bpf *bpf);
-       int                     (*ndo_xdp_xmit)(struct net_device *dev,
-                                               struct xdp_frame *xdp);
+       int                     (*ndo_xdp_xmit)(struct net_device *dev, int n,
+                                               struct xdp_frame **xdp);
        void                    (*ndo_xdp_flush)(struct net_device *dev);
 };
 
diff --git a/include/net/xdp.h b/include/net/xdp.h
index 0b689cf561c7..7ad779237ae8 100644
--- a/include/net/xdp.h
+++ b/include/net/xdp.h
@@ -104,6 +104,7 @@ struct xdp_frame *convert_to_xdp_frame(struct xdp_buff *xdp)
 }
 
 void xdp_return_frame(struct xdp_frame *xdpf);
+void xdp_return_frame_rx_napi(struct xdp_frame *xdpf);
 void xdp_return_buff(struct xdp_buff *xdp);
 
 int xdp_rxq_info_reg(struct xdp_rxq_info *xdp_rxq,
diff --git a/include/trace/events/xdp.h b/include/trace/events/xdp.h
index 2e9ef0650144..1ecf4c67fcf7 100644
--- a/include/trace/events/xdp.h
+++ b/include/trace/events/xdp.h
@@ -234,9 +234,9 @@ TRACE_EVENT(xdp_devmap_xmit,
        TP_PROTO(const struct bpf_map *map, u32 map_index,
                 int sent, int drops,
                 const struct net_device *from_dev,
-                const struct net_device *to_dev),
+                const struct net_device *to_dev, int err),
 
-       TP_ARGS(map, map_index, sent, drops, from_dev, to_dev),
+       TP_ARGS(map, map_index, sent, drops, from_dev, to_dev, err),
 
        TP_STRUCT__entry(
                __field(int, map_id)
@@ -246,6 +246,7 @@ TRACE_EVENT(xdp_devmap_xmit,
                __field(int, sent)
                __field(int, from_ifindex)
                __field(int, to_ifindex)
+               __field(int, err)
        ),
 
        TP_fast_assign(
@@ -256,16 +257,17 @@ TRACE_EVENT(xdp_devmap_xmit,
                __entry->sent           = sent;
                __entry->from_ifindex   = from_dev->ifindex;
                __entry->to_ifindex     = to_dev->ifindex;
+               __entry->err            = err;
        ),
 
        TP_printk("ndo_xdp_xmit"
                  " map_id=%d map_index=%d action=%s"
                  " sent=%d drops=%d"
-                 " from_ifindex=%d to_ifindex=%d",
+                 " from_ifindex=%d to_ifindex=%d err=%d",
                  __entry->map_id, __entry->map_index,
                  __print_symbolic(__entry->act, __XDP_ACT_SYM_TAB),
                  __entry->sent, __entry->drops,
-                 __entry->from_ifindex, __entry->to_ifindex)
+                 __entry->from_ifindex, __entry->to_ifindex, __entry->err)
 );
 
 #endif /* _TRACE_XDP_H */
diff --git a/kernel/bpf/devmap.c b/kernel/bpf/devmap.c
index 6f84100723b0..4dd8f0e3a8d9 100644
--- a/kernel/bpf/devmap.c
+++ b/kernel/bpf/devmap.c
@@ -222,7 +222,7 @@ static int bq_xmit_all(struct bpf_dtab_netdev *obj,
                         struct xdp_bulk_queue *bq)
 {
        struct net_device *dev = obj->dev;
-       int sent = 0, drops = 0;
+       int sent = 0, drops = 0, err = 0;
        int i;
 
        if (unlikely(!bq->count))
@@ -234,23 +234,32 @@ static int bq_xmit_all(struct bpf_dtab_netdev *obj,
                prefetch(xdpf);
        }
 
-       for (i = 0; i < bq->count; i++) {
-               struct xdp_frame *xdpf = bq->q[i];
-               int err;
-
-               err = dev->netdev_ops->ndo_xdp_xmit(dev, xdpf);
-               if (err) {
-                       drops++;
-                       xdp_return_frame(xdpf);
-               }
-               sent++;
+       sent = dev->netdev_ops->ndo_xdp_xmit(dev, bq->count, bq->q);
+       if (sent < 0) {
+               err = sent;
+               sent = 0;
+               goto error;
        }
+       drops = bq->count - sent;
+out:
        bq->count = 0;
 
        trace_xdp_devmap_xmit(&obj->dtab->map, obj->bit,
-                             sent, drops, bq->dev_rx, dev);
+                             sent, drops, bq->dev_rx, dev, err);
        bq->dev_rx = NULL;
        return 0;
+error:
+       /* If ndo_xdp_xmit fails with an errno, no frames have been
+        * xmit'ed and it's our responsibility to them free all.
+        */
+       for (i = 0; i < bq->count; i++) {
+               struct xdp_frame *xdpf = bq->q[i];
+
+               /* RX path under NAPI protection, can return frames faster */
+               xdp_return_frame_rx_napi(xdpf);
+               drops++;
+       }
+       goto out;
 }
 
 /* __dev_map_flush is called from xdp_do_flush_map() which _must_ be signaled
diff --git a/net/core/filter.c b/net/core/filter.c
index 8b7924368dc1..547174d37f66 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -3004,8 +3004,8 @@ static int __bpf_tx_xdp(struct net_device *dev,
        if (unlikely(!xdpf))
                return -EOVERFLOW;
 
-       err = dev->netdev_ops->ndo_xdp_xmit(dev, xdpf);
-       if (err)
+       err = dev->netdev_ops->ndo_xdp_xmit(dev, 1, &xdpf);
+       if (err <= 0)
                return err;
        dev->netdev_ops->ndo_xdp_flush(dev);
        return 0;
diff --git a/net/core/xdp.c b/net/core/xdp.c
index bf6758f74339..237d374e6ef7 100644
--- a/net/core/xdp.c
+++ b/net/core/xdp.c
@@ -308,7 +308,7 @@ int xdp_rxq_info_reg_mem_model(struct xdp_rxq_info *xdp_rxq,
 }
 EXPORT_SYMBOL_GPL(xdp_rxq_info_reg_mem_model);
 
-static void xdp_return(void *data, struct xdp_mem_info *mem)
+static void __xdp_return(void *data, struct xdp_mem_info *mem, bool 
napi_direct)
 {
        struct xdp_mem_allocator *xa;
        struct page *page;
@@ -320,7 +320,7 @@ static void xdp_return(void *data, struct xdp_mem_info *mem)
                xa = rhashtable_lookup(mem_id_ht, &mem->id, mem_id_rht_params);
                page = virt_to_head_page(data);
                if (xa)
-                       page_pool_put_page(xa->page_pool, page);
+                       __page_pool_put_page(xa->page_pool, page, napi_direct);
                else
                        put_page(page);
                rcu_read_unlock();
@@ -340,12 +340,18 @@ static void xdp_return(void *data, struct xdp_mem_info 
*mem)
 
 void xdp_return_frame(struct xdp_frame *xdpf)
 {
-       xdp_return(xdpf->data, &xdpf->mem);
+       __xdp_return(xdpf->data, &xdpf->mem, false);
 }
 EXPORT_SYMBOL_GPL(xdp_return_frame);
 
+void xdp_return_frame_rx_napi(struct xdp_frame *xdpf)
+{
+       __xdp_return(xdpf->data, &xdpf->mem, true);
+}
+EXPORT_SYMBOL_GPL(xdp_return_frame_rx_napi);
+
 void xdp_return_buff(struct xdp_buff *xdp)
 {
-       xdp_return(xdp->data, &xdp->rxq->mem);
+       __xdp_return(xdp->data, &xdp->rxq->mem, true);
 }
 EXPORT_SYMBOL_GPL(xdp_return_buff);
diff --git a/samples/bpf/xdp_monitor_kern.c b/samples/bpf/xdp_monitor_kern.c
index 2854aa0665ea..ad10fe700d7d 100644
--- a/samples/bpf/xdp_monitor_kern.c
+++ b/samples/bpf/xdp_monitor_kern.c
@@ -125,6 +125,7 @@ struct datarec {
        u64 processed;
        u64 dropped;
        u64 info;
+       u64 err;
 };
 #define MAX_CPUS 64
 
@@ -228,6 +229,7 @@ struct devmap_xmit_ctx {
        int sent;               //      offset:24; size:4; signed:1;
        int from_ifindex;       //      offset:28; size:4; signed:1;
        int to_ifindex;         //      offset:32; size:4; signed:1;
+       int err;                //      offset:36; size:4; signed:1;
 };
 
 SEC("tracepoint/xdp/xdp_devmap_xmit")
@@ -245,5 +247,13 @@ int trace_xdp_devmap_xmit(struct devmap_xmit_ctx *ctx)
        /* Record bulk events, then userspace can calc average bulk size */
        rec->info += 1;
 
+       /* Record error cases, where no frame were sent */
+       if (ctx->err)
+               rec->err++;
+
+       /* Catch API error of drv ndo_xdp_xmit sent more than count */
+       if (ctx->drops < 0)
+               rec->err++;
+
        return 1;
 }
diff --git a/samples/bpf/xdp_monitor_user.c b/samples/bpf/xdp_monitor_user.c
index 4aaf1ab1927d..0ec7967dd32a 100644
--- a/samples/bpf/xdp_monitor_user.c
+++ b/samples/bpf/xdp_monitor_user.c
@@ -117,6 +117,7 @@ struct datarec {
        __u64 processed;
        __u64 dropped;
        __u64 info;
+       __u64 err;
 };
 #define MAX_CPUS 64
 
@@ -152,6 +153,7 @@ static bool map_collect_record(int fd, __u32 key, struct 
record *rec)
        __u64 sum_processed = 0;
        __u64 sum_dropped = 0;
        __u64 sum_info = 0;
+       __u64 sum_err = 0;
        int i;
 
        if ((bpf_map_lookup_elem(fd, &key, values)) != 0) {
@@ -170,10 +172,13 @@ static bool map_collect_record(int fd, __u32 key, struct 
record *rec)
                sum_dropped        += values[i].dropped;
                rec->cpu[i].info = values[i].info;
                sum_info        += values[i].info;
+               rec->cpu[i].err = values[i].err;
+               sum_err        += values[i].err;
        }
        rec->total.processed = sum_processed;
        rec->total.dropped   = sum_dropped;
        rec->total.info      = sum_info;
+       rec->total.err       = sum_err;
        return true;
 }
 
@@ -274,6 +279,18 @@ static double calc_info(struct datarec *r, struct datarec 
*p, double period)
        return pps;
 }
 
+static double calc_err(struct datarec *r, struct datarec *p, double period)
+{
+       __u64 packets = 0;
+       double pps = 0;
+
+       if (period > 0) {
+               packets = r->err - p->err;
+               pps = packets / period;
+       }
+       return pps;
+}
+
 static void stats_print(struct stats_record *stats_rec,
                        struct stats_record *stats_prev,
                        bool err_only)
@@ -412,11 +429,12 @@ static void stats_print(struct stats_record *stats_rec,
 
        /* devmap ndo_xdp_xmit stats */
        {
-               char *fmt1 = "%-15s %-7d %'-12.0f %'-12.0f %'-10.2f %s\n";
-               char *fmt2 = "%-15s %-7s %'-12.0f %'-12.0f %'-10.2f %s\n";
+               char *fmt1 = "%-15s %-7d %'-12.0f %'-12.0f %'-10.2f %s %s\n";
+               char *fmt2 = "%-15s %-7s %'-12.0f %'-12.0f %'-10.2f %s %s\n";
                struct record *rec, *prev;
-               double drop, info;
+               double drop, info, err;
                char *i_str = "";
+               char *err_str = "";
 
                rec  =  &stats_rec->xdp_devmap_xmit;
                prev = &stats_prev->xdp_devmap_xmit;
@@ -428,22 +446,29 @@ static void stats_print(struct stats_record *stats_rec,
                        pps  = calc_pps(r, p, t);
                        drop = calc_drop(r, p, t);
                        info = calc_info(r, p, t);
+                       err  = calc_err(r, p, t);
                        if (info > 0) {
                                i_str = "bulk-average";
                                info = (pps+drop) / info; /* calc avg bulk */
                        }
+                       if (err > 0)
+                               err_str = "drv-err";
                        if (pps > 0 || drop > 0)
                                printf(fmt1, "devmap-xmit",
-                                      i, pps, drop, info, i_str);
+                                      i, pps, drop, info, i_str, err_str);
                }
                pps = calc_pps(&rec->total, &prev->total, t);
                drop = calc_drop(&rec->total, &prev->total, t);
                info = calc_info(&rec->total, &prev->total, t);
+               err  = calc_err(&rec->total, &prev->total, t);
                if (info > 0) {
                        i_str = "bulk-average";
                        info = (pps+drop) / info; /* calc avg bulk */
                }
-               printf(fmt2, "devmap-xmit", "total", pps, drop, info, i_str);
+               if (err > 0)
+                       err_str = "drv-err";
+               printf(fmt2, "devmap-xmit", "total", pps, drop,
+                      info, i_str, err_str);
        }
 
        printf("\n");

Reply via email to