]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
libeth: xdp: add .ndo_xdp_xmit() helpers
authorAlexander Lobakin <aleksander.lobakin@intel.com>
Thu, 12 Jun 2025 16:02:22 +0000 (18:02 +0200)
committerTony Nguyen <anthony.l.nguyen@intel.com>
Mon, 16 Jun 2025 18:40:14 +0000 (11:40 -0700)
Add helpers for implementing .ndo_xdp_xmit().
Same as for XDP_TX, accumulate up to 16 DMA-mapped frames on the stack,
then flush. If DMA mapping is failed for some reason, don't try mapping
further frames, but still flush what was already prepared.
DMA address of a head frame is stored in its headroom, assuming it
has enough of it for an 8 (or 4) byte value.
In addition to @prep and @xmit driver callbacks in XDP_TX, xmit also
needs @finalize to kick the XDPSQ after filling.

Signed-off-by: Alexander Lobakin <aleksander.lobakin@intel.com>
Reviewed-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
drivers/net/ethernet/intel/libeth/xdp.c
include/net/libeth/tx.h
include/net/libeth/xdp.h

index 444449c72221eafe0b6cb3f2b0515569259d87c0..c65ea5d2746a3540deeb2d97c3e8d83a9ede1c4f 100644 (file)
@@ -42,7 +42,7 @@ static void __cold libeth_trace_xdp_exception(const struct net_device *dev,
  * libeth_xdp_tx_exception - handle Tx exceptions of XDP frames
  * @bq: XDP Tx frame bulk
  * @sent: number of frames sent successfully (from this bulk)
- * @flags: internal libeth_xdp flags
+ * @flags: internal libeth_xdp flags (.ndo_xdp_xmit etc.)
  *
  * Cold helper used by __libeth_xdp_tx_flush_bulk(), do not call directly.
  * Reports XDP Tx exceptions, frees the frames that won't be sent or adjust
@@ -54,7 +54,8 @@ void __cold libeth_xdp_tx_exception(struct libeth_xdp_tx_bulk *bq, u32 sent,
        const struct libeth_xdp_tx_frame *pos = &bq->bulk[sent];
        u32 left = bq->count - sent;
 
-       libeth_trace_xdp_exception(bq->dev, bq->prog, XDP_TX);
+       if (!(flags & LIBETH_XDP_TX_NDO))
+               libeth_trace_xdp_exception(bq->dev, bq->prog, XDP_TX);
 
        if (!(flags & LIBETH_XDP_TX_DROP)) {
                memmove(bq->bulk, pos, left * sizeof(*bq->bulk));
@@ -63,12 +64,42 @@ void __cold libeth_xdp_tx_exception(struct libeth_xdp_tx_bulk *bq, u32 sent,
                return;
        }
 
-       libeth_xdp_tx_return_bulk(pos, left);
+       if (!(flags & LIBETH_XDP_TX_NDO))
+               libeth_xdp_tx_return_bulk(pos, left);
+       else
+               libeth_xdp_xmit_return_bulk(pos, left, bq->dev);
 
        bq->count = 0;
 }
 EXPORT_SYMBOL_GPL(libeth_xdp_tx_exception);
 
+/* .ndo_xdp_xmit() implementation */
+
+u32 __cold libeth_xdp_xmit_return_bulk(const struct libeth_xdp_tx_frame *bq,
+                                      u32 count, const struct net_device *dev)
+{
+       u32 n = 0;
+
+       for (u32 i = 0; i < count; i++) {
+               const struct libeth_xdp_tx_frame *frm = &bq[i];
+               dma_addr_t dma;
+
+               if (frm->flags & LIBETH_XDP_TX_FIRST)
+                       dma = *libeth_xdp_xmit_frame_dma(frm->xdpf);
+               else
+                       dma = dma_unmap_addr(frm, dma);
+
+               dma_unmap_page(dev->dev.parent, dma, dma_unmap_len(frm, len),
+                              DMA_TO_DEVICE);
+
+               /* Actual xdp_frames are freed by the core */
+               n += !!(frm->flags & LIBETH_XDP_TX_FIRST);
+       }
+
+       return n;
+}
+EXPORT_SYMBOL_GPL(libeth_xdp_xmit_return_bulk);
+
 /* Rx polling path */
 
 /**
index 3e68d11914f78e7833abe70b52f7db0ad01acd70..e2b62a8b4c57b730238be97d645491e1db84d00d 100644 (file)
@@ -19,6 +19,8 @@
  * @LIBETH_SQE_SKB: &sk_buff, unmap and napi_consume_skb(), update stats
  * @__LIBETH_SQE_XDP_START: separator between skb and XDP types
  * @LIBETH_SQE_XDP_TX: &skb_shared_info, libeth_xdp_return_buff_bulk(), stats
+ * @LIBETH_SQE_XDP_XMIT: &xdp_frame, unmap and xdp_return_frame_bulk(), stats
+ * @LIBETH_SQE_XDP_XMIT_FRAG: &xdp_frame frag, only unmap DMA
  */
 enum libeth_sqe_type {
        LIBETH_SQE_EMPTY                = 0U,
@@ -29,6 +31,8 @@ enum libeth_sqe_type {
 
        __LIBETH_SQE_XDP_START,
        LIBETH_SQE_XDP_TX               = __LIBETH_SQE_XDP_START,
+       LIBETH_SQE_XDP_XMIT,
+       LIBETH_SQE_XDP_XMIT_FRAG,
 };
 
 /**
@@ -38,6 +42,7 @@ enum libeth_sqe_type {
  * @raw: slab buffer to free via kfree()
  * @skb: &sk_buff to consume
  * @sinfo: skb shared info of an XDP_TX frame
+ * @xdpf: XDP frame from ::ndo_xdp_xmit()
  * @dma: DMA address to unmap
  * @len: length of the mapped region to unmap
  * @nr_frags: number of frags in the frame this buffer belongs to
@@ -53,6 +58,7 @@ struct libeth_sqe {
                void                            *raw;
                struct sk_buff                  *skb;
                struct skb_shared_info          *sinfo;
+               struct xdp_frame                *xdpf;
        };
 
        DEFINE_DMA_UNMAP_ADDR(dma);
index 4988453a3d7062cebfd81816fb40d43f2eed39a8..839001d901b2e69adf54b3fe33d2d8dc1a70c74c 100644 (file)
 #include <net/libeth/tx.h>
 #include <net/xsk_buff_pool.h>
 
+/*
+ * Defined as bits to be able to use them as a mask on Rx.
+ * Also used as internal return values on Tx.
+ */
+enum {
+       LIBETH_XDP_PASS                 = 0U,
+       LIBETH_XDP_DROP                 = BIT(0),
+       LIBETH_XDP_ABORTED              = BIT(1),
+       LIBETH_XDP_TX                   = BIT(2),
+};
+
 /*
  * &xdp_buff_xsk is the largest structure &libeth_xdp_buff gets casted to,
  * pick maximum pointer-compatible alignment.
@@ -56,12 +67,14 @@ static_assert(IS_ALIGNED(sizeof(struct xdp_buff_xsk),
  * @LIBETH_XDP_TX_BULK: one bulk size at which it will be flushed to the queue
  * @LIBETH_XDP_TX_BATCH: batch size for which the queue fill loop is unrolled
  * @LIBETH_XDP_TX_DROP: indicates the send function must drop frames not sent
+ * @LIBETH_XDP_TX_NDO: whether the send function is called from .ndo_xdp_xmit()
  */
 enum {
        LIBETH_XDP_TX_BULK              = DEV_MAP_BULK_SIZE,
        LIBETH_XDP_TX_BATCH             = 8,
 
        LIBETH_XDP_TX_DROP              = BIT(0),
+       LIBETH_XDP_TX_NDO               = BIT(1),
 };
 
 /**
@@ -88,6 +101,11 @@ enum {
  * @len_fl: ``XDP_TX``, combined flags [31:16] and len [15:0] field for speed
  * @soff: ``XDP_TX``, offset from @data to the start of &skb_shared_info
  * @frag: one (non-head) frag for ``XDP_TX``
+ * @xdpf: &xdp_frame for the head frag for .ndo_xdp_xmit()
+ * @dma: DMA address of the non-head frag for .ndo_xdp_xmit()
+ * @len: frag length for .ndo_xdp_xmit()
+ * @flags: Tx flags for the above
+ * @opts: combined @len + @flags for the above for speed
  */
 struct libeth_xdp_tx_frame {
        union {
@@ -100,6 +118,21 @@ struct libeth_xdp_tx_frame {
 
                /* ``XDP_TX`` frag */
                skb_frag_t                      frag;
+
+               /* .ndo_xdp_xmit() */
+               struct {
+                       union {
+                               struct xdp_frame                *xdpf;
+                               dma_addr_t                      dma;
+                       };
+                       union {
+                               struct {
+                                       u32                             len;
+                                       u32                             flags;
+                               };
+                               aligned_u64                     opts;
+                       };
+               };
        };
 } __aligned_largest;
 static_assert(offsetof(struct libeth_xdp_tx_frame, frag.len) ==
@@ -107,7 +140,7 @@ static_assert(offsetof(struct libeth_xdp_tx_frame, frag.len) ==
 
 /**
  * struct libeth_xdp_tx_bulk - XDP Tx frame bulk for bulk sending
- * @prog: corresponding active XDP program
+ * @prog: corresponding active XDP program, %NULL for .ndo_xdp_xmit()
  * @dev: &net_device which the frames are transmitted on
  * @xdpsq: shortcut to the corresponding driver-specific XDPSQ structure
  * @count: current number of frames in @bulk
@@ -445,7 +478,7 @@ void libeth_xdp_tx_exception(struct libeth_xdp_tx_bulk *bq, u32 sent,
 /**
  * __libeth_xdp_tx_flush_bulk - internal helper to flush one XDP Tx bulk
  * @bq: bulk to flush
- * @flags: XDP TX flags
+ * @flags: XDP TX flags (.ndo_xdp_xmit() etc.)
  * @prep: driver-specific callback to prepare the queue for sending
  * @fill: libeth_xdp callback to fill &libeth_sqe and &libeth_xdp_tx_desc
  * @xmit: driver callback to fill a HW descriptor
@@ -495,6 +528,259 @@ __libeth_xdp_tx_flush_bulk(struct libeth_xdp_tx_bulk *bq, u32 flags,
        __libeth_xdp_tx_flush_bulk(bq, flags, prep, libeth_xdp_tx_fill_buf,   \
                                   xmit)
 
+/* .ndo_xdp_xmit() implementation */
+
+/**
+ * libeth_xdp_xmit_frame_dma - internal helper to access DMA of an &xdp_frame
+ * @xf: pointer to the XDP frame
+ *
+ * There's no place in &libeth_xdp_tx_frame to store DMA address for an
+ * &xdp_frame head. The headroom is used then, the address is placed right
+ * after the frame struct, naturally aligned.
+ *
+ * Return: pointer to the DMA address to use.
+ */
+#define libeth_xdp_xmit_frame_dma(xf)                                        \
+       _Generic((xf),                                                        \
+                const struct xdp_frame *:                                    \
+                       (const dma_addr_t *)__libeth_xdp_xmit_frame_dma(xf),  \
+                struct xdp_frame *:                                          \
+                       (dma_addr_t *)__libeth_xdp_xmit_frame_dma(xf)         \
+       )
+
+static inline void *__libeth_xdp_xmit_frame_dma(const struct xdp_frame *xdpf)
+{
+       void *addr = (void *)(xdpf + 1);
+
+       if (!IS_ENABLED(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS) &&
+           __alignof(*xdpf) < sizeof(dma_addr_t))
+               addr = PTR_ALIGN(addr, sizeof(dma_addr_t));
+
+       return addr;
+}
+
+/**
+ * libeth_xdp_xmit_queue_head - internal helper for queueing one XDP xmit head
+ * @bq: XDP Tx bulk to queue the head frag to
+ * @xdpf: XDP frame with the head to queue
+ * @dev: device to perform DMA mapping
+ *
+ * Return: ``LIBETH_XDP_DROP`` on DMA mapping error,
+ *        ``LIBETH_XDP_PASS`` if it's the only frag in the frame,
+ *        ``LIBETH_XDP_TX`` if it's an S/G frame.
+ */
+static inline u32 libeth_xdp_xmit_queue_head(struct libeth_xdp_tx_bulk *bq,
+                                            struct xdp_frame *xdpf,
+                                            struct device *dev)
+{
+       dma_addr_t dma;
+
+       dma = dma_map_single(dev, xdpf->data, xdpf->len, DMA_TO_DEVICE);
+       if (dma_mapping_error(dev, dma))
+               return LIBETH_XDP_DROP;
+
+       *libeth_xdp_xmit_frame_dma(xdpf) = dma;
+
+       bq->bulk[bq->count++] = (typeof(*bq->bulk)){
+               .xdpf   = xdpf,
+               .len    = xdpf->len,
+               .flags  = LIBETH_XDP_TX_FIRST,
+       };
+
+       if (!xdp_frame_has_frags(xdpf))
+               return LIBETH_XDP_PASS;
+
+       bq->bulk[bq->count - 1].flags |= LIBETH_XDP_TX_MULTI;
+
+       return LIBETH_XDP_TX;
+}
+
+/**
+ * libeth_xdp_xmit_queue_frag - internal helper for queueing one XDP xmit frag
+ * @bq: XDP Tx bulk to queue the frag to
+ * @frag: frag to queue
+ * @dev: device to perform DMA mapping
+ *
+ * Return: true on success, false on DMA mapping error.
+ */
+static inline bool libeth_xdp_xmit_queue_frag(struct libeth_xdp_tx_bulk *bq,
+                                             const skb_frag_t *frag,
+                                             struct device *dev)
+{
+       dma_addr_t dma;
+
+       dma = skb_frag_dma_map(dev, frag);
+       if (dma_mapping_error(dev, dma))
+               return false;
+
+       bq->bulk[bq->count++] = (typeof(*bq->bulk)){
+               .dma    = dma,
+               .len    = skb_frag_size(frag),
+       };
+
+       return true;
+}
+
+/**
+ * libeth_xdp_xmit_queue_bulk - internal helper for queueing one XDP xmit frame
+ * @bq: XDP Tx bulk to queue the frame to
+ * @xdpf: XDP frame to queue
+ * @flush_bulk: driver callback to flush the bulk to the HW queue
+ *
+ * Return: ``LIBETH_XDP_TX`` on success,
+ *        ``LIBETH_XDP_DROP`` if the frame should be dropped by the stack,
+ *        ``LIBETH_XDP_ABORTED`` if the frame will be dropped by libeth_xdp.
+ */
+static __always_inline u32
+libeth_xdp_xmit_queue_bulk(struct libeth_xdp_tx_bulk *bq,
+                          struct xdp_frame *xdpf,
+                          bool (*flush_bulk)(struct libeth_xdp_tx_bulk *bq,
+                                             u32 flags))
+{
+       u32 head, nr_frags, i, ret = LIBETH_XDP_TX;
+       struct device *dev = bq->dev->dev.parent;
+       const struct skb_shared_info *sinfo;
+
+       if (unlikely(bq->count == LIBETH_XDP_TX_BULK) &&
+           unlikely(!flush_bulk(bq, LIBETH_XDP_TX_NDO)))
+               return LIBETH_XDP_DROP;
+
+       head = libeth_xdp_xmit_queue_head(bq, xdpf, dev);
+       if (head == LIBETH_XDP_PASS)
+               goto out;
+       else if (head == LIBETH_XDP_DROP)
+               return LIBETH_XDP_DROP;
+
+       sinfo = xdp_get_shared_info_from_frame(xdpf);
+       nr_frags = sinfo->nr_frags;
+
+       for (i = 0; i < nr_frags; i++) {
+               if (unlikely(bq->count == LIBETH_XDP_TX_BULK) &&
+                   unlikely(!flush_bulk(bq, LIBETH_XDP_TX_NDO)))
+                       break;
+
+               if (!libeth_xdp_xmit_queue_frag(bq, &sinfo->frags[i], dev))
+                       break;
+       }
+
+       if (unlikely(i < nr_frags))
+               ret = LIBETH_XDP_ABORTED;
+
+out:
+       bq->bulk[bq->count - 1].flags |= LIBETH_XDP_TX_LAST;
+
+       return ret;
+}
+
+/**
+ * libeth_xdp_xmit_fill_buf - internal helper to fill one XDP xmit &libeth_sqe
+ * @frm: XDP Tx frame from the bulk
+ * @i: index on the HW queue
+ * @sq: XDPSQ abstraction for the queue
+ * @priv: private data
+ *
+ * Return: XDP Tx descriptor with the mapped DMA and other info to pass to
+ * the driver callback.
+ */
+static inline struct libeth_xdp_tx_desc
+libeth_xdp_xmit_fill_buf(struct libeth_xdp_tx_frame frm, u32 i,
+                        const struct libeth_xdpsq *sq, u64 priv)
+{
+       struct libeth_xdp_tx_desc desc;
+       struct libeth_sqe *sqe;
+       struct xdp_frame *xdpf;
+
+       if (frm.flags & LIBETH_XDP_TX_FIRST) {
+               xdpf = frm.xdpf;
+               desc.addr = *libeth_xdp_xmit_frame_dma(xdpf);
+       } else {
+               xdpf = NULL;
+               desc.addr = frm.dma;
+       }
+       desc.opts = frm.opts;
+
+       sqe = &sq->sqes[i];
+       dma_unmap_addr_set(sqe, dma, desc.addr);
+       dma_unmap_len_set(sqe, len, desc.len);
+
+       if (!xdpf) {
+               sqe->type = LIBETH_SQE_XDP_XMIT_FRAG;
+               return desc;
+       }
+
+       sqe->type = LIBETH_SQE_XDP_XMIT;
+       sqe->xdpf = xdpf;
+       libeth_xdp_tx_fill_stats(sqe, &desc,
+                                xdp_get_shared_info_from_frame(xdpf));
+
+       return desc;
+}
+
+/**
+ * libeth_xdp_xmit_flush_bulk - wrapper to define flush of one XDP xmit bulk
+ * @bq: bulk to flush
+ * @flags: Tx flags, see __libeth_xdp_tx_flush_bulk()
+ * @prep: driver callback to prepare the queue
+ * @xmit: driver callback to fill a HW descriptor
+ */
+#define libeth_xdp_xmit_flush_bulk(bq, flags, prep, xmit)                    \
+       __libeth_xdp_tx_flush_bulk(bq, (flags) | LIBETH_XDP_TX_NDO, prep,     \
+                                  libeth_xdp_xmit_fill_buf, xmit)
+
+u32 libeth_xdp_xmit_return_bulk(const struct libeth_xdp_tx_frame *bq,
+                               u32 count, const struct net_device *dev);
+
+/**
+ * __libeth_xdp_xmit_do_bulk - internal function to implement .ndo_xdp_xmit()
+ * @bq: XDP Tx bulk to queue frames to
+ * @frames: XDP frames passed by the stack
+ * @n: number of frames
+ * @flags: flags passed by the stack
+ * @flush_bulk: driver callback to flush an XDP xmit bulk
+ * @finalize: driver callback to finalize sending XDP Tx frames on the queue
+ *
+ * Perform common checks, map the frags and queue them to the bulk, then flush
+ * the bulk to the XDPSQ. If requested by the stack, finalize the queue.
+ *
+ * Return: number of frames send or -errno on error.
+ */
+static __always_inline int
+__libeth_xdp_xmit_do_bulk(struct libeth_xdp_tx_bulk *bq,
+                         struct xdp_frame **frames, u32 n, u32 flags,
+                         bool (*flush_bulk)(struct libeth_xdp_tx_bulk *bq,
+                                            u32 flags),
+                         void (*finalize)(void *xdpsq, bool sent, bool flush))
+{
+       u32 nxmit = 0;
+
+       if (unlikely(flags & ~XDP_XMIT_FLAGS_MASK))
+               return -EINVAL;
+
+       for (u32 i = 0; likely(i < n); i++) {
+               u32 ret;
+
+               ret = libeth_xdp_xmit_queue_bulk(bq, frames[i], flush_bulk);
+               if (unlikely(ret != LIBETH_XDP_TX)) {
+                       nxmit += ret == LIBETH_XDP_ABORTED;
+                       break;
+               }
+
+               nxmit++;
+       }
+
+       if (bq->count) {
+               flush_bulk(bq, LIBETH_XDP_TX_NDO);
+               if (unlikely(bq->count))
+                       nxmit -= libeth_xdp_xmit_return_bulk(bq->bulk,
+                                                            bq->count,
+                                                            bq->dev);
+       }
+
+       finalize(bq->xdpsq, nxmit, flags & XDP_XMIT_FLUSH);
+
+       return nxmit;
+}
+
 /* Rx polling path */
 
 static inline void libeth_xdp_return_va(const void *data, bool napi)