Thread (32 messages) 32 messages, 4 authors, 12d ago

Re: [RFC net-next 13/15] eth: fbnic: Support AF_XDP zero-copy receive

flat view

From: Björn Töpel <bjorn@kernel.org>
Date: 2026-10-12 14:16:20
Also in: bpf, io-uring, linux-doc, lkml

Stanislav Fomichev [off-list ref] writes:
On 10/02, Björn Töpel wrote:
quoted
fbnic gets RX buffers from page_pool. Use the XSK buffer pool as the
RX queue's page-pool memory provider, so allocation, DMA sync,
recycling and refill stay in page_pool. Only aligned 4 KiB UMEM
chunks are supported.

Each UMEM frame on the header queue must hold one packet. Raise the
header-split threshold to at least 3072 bytes, more than half of a
4 KiB page, so the device finishes the frame after one header. Cap
it to the space left in the frame after the headroom. An MTU above
the threshold needs a socket with XDP_USE_SG and an XDP program with
frags support. With XDP_USE_SG, the payload queue also uses UMEM
frames, and the device uses each frame for one descriptor only.
Without it, the payload queue uses a normal page pool. If the device
does not finish a provider frame after one descriptor, drop the
packet.

The BDQ refill loop now alternates header and payload buffers on all
queues, so two rings that share one finite provider stay balanced.
It keeps NAPI scheduled while the provider still has buffers.

Provider frames are never split, so they get no fragment bias. A
redirected frame can be recycled before its old BDQ slot is cleaned,
and a stale bias would corrupt the new allocation.

The headroom comes from the queue configuration. It must be at least
XDP_PACKET_HEADROOM, a multiple of 128 bytes, and fit the hardware
field; otherwise queue validation fails. MTU, ring parameter and XDP
program changes that do not fit an active provider are rejected.

fbnic did not support XDP_REDIRECT. Add it, with xdp_do_flush(), and
advertise it. This also enables copy-mode AF_XDP and the other
redirect targets on fbnic. Do not advertise AF_XDP zero copy until
TX support is added.

Signed-off-by: Björn Töpel <bjorn@kernel.org>
---
 .../net/ethernet/meta/fbnic/fbnic_ethtool.c   |   5 +
 .../net/ethernet/meta/fbnic/fbnic_netdev.c    | 140 ++++++-
 .../net/ethernet/meta/fbnic/fbnic_netdev.h    |   4 +
 drivers/net/ethernet/meta/fbnic/fbnic_txrx.c  | 350 +++++++++++++-----
 drivers/net/ethernet/meta/fbnic/fbnic_txrx.h  |   6 +
 5 files changed, 402 insertions(+), 103 deletions(-)
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c
index e774740fcb5d..188abe0ead14 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c
@@ -385,6 +385,11 @@ fbnic_set_ringparam(struct net_device *netdev, struct ethtool_ringparam *ring,
 		return -EINVAL;
 	}
 
+	err = fbnic_xsk_validate(fbn, fbn->xdp_prog, netdev->mtu,
+				 kernel_ring->hds_thresh, extack);
+	if (err)
+		return err;
+
 	if (!netif_running(netdev)) {
 		fbnic_set_rings(fbn, ring, kernel_ring);
 		return 0;
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c b/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c
index 8a6703afca38..038e91ef14e4 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c
@@ -5,6 +5,8 @@
 #include <linux/ipv6.h>
 #include <linux/types.h>
 #include <net/netdev_queues.h>
+#include <net/netdev_rx_queue.h>
+#include <net/xdp_sock_drv.h>
 
 #include "fbnic.h"
 #include "fbnic_netdev.h"
@@ -276,6 +278,8 @@ static int fbnic_set_mac(struct net_device *netdev, void *p)
 static int fbnic_change_mtu(struct net_device *dev, int new_mtu)
 {
 	struct fbnic_net *fbn = netdev_priv(dev);
+	struct netlink_ext_ack extack = {};
+	int err;
 
 	if (fbnic_check_split_frames(fbn->xdp_prog, new_mtu, fbn->hds_thresh)) {
 		dev_err(&dev->dev,
@@ -285,6 +289,15 @@ static int fbnic_change_mtu(struct net_device *dev, int new_mtu)
 		return -EINVAL;
 	}
 
+	err = fbnic_xsk_validate(fbn, fbn->xdp_prog, new_mtu,
+				 fbn->hds_thresh, &extack);
+	if (err) {
+		if (extack._msg)
+			netdev_err(dev, "%s\n", extack._msg);
+
+		return err;
+	}
+
 	WRITE_ONCE(dev->mtu, new_mtu);
 
 	return 0;
@@ -534,26 +547,123 @@ bool fbnic_check_split_frames(struct bpf_prog *prog, unsigned int mtu,
 	return mtu + ETH_HLEN > hds_thresh;
 }
 
+u32 fbnic_xsk_hds_thresh(u32 headroom, u32 hds_thresh)
+{
+	u32 max_hds;
+
+	/* Reserve more than half a device page for each header so the device
+	 * retires the provider frame after one descriptor.
+	 */
+	static_assert(2 * FBNIC_XSK_HDS_THRESH > FBNIC_BD_PAGE_SIZE);
+
+	max_hds = FBNIC_BD_PAGE_SIZE - headroom - FBNIC_RX_TROOM -
+		  FBNIC_RX_PAD;
+
+	return min(max_t(u32, hds_thresh, FBNIC_XSK_HDS_THRESH), max_hds);
+}
+
+static int fbnic_xsk_validate_frames(struct xsk_buff_pool *pool,
+				     struct bpf_prog *prog,
+				     unsigned int mtu, u32 hds_thresh,
+				     struct netlink_ext_ack *extack)
+{
+	u32 xsk_hds_thresh;
+
+	xsk_hds_thresh = fbnic_xsk_hds_thresh(xsk_pool_get_headroom(pool),
+					      hds_thresh);
+
+	if (mtu + ETH_HLEN <= xsk_hds_thresh)
+		return 0;
+
+	if (!xsk_pool_uses_sg(pool)) {
+		NL_SET_ERR_MSG_MOD(extack,
+				   "AF_XDP zero-copy socket requires XDP_USE_SG");
+		return -EOPNOTSUPP;
+	}
+
+	if (fbnic_check_split_frames(prog, mtu, xsk_hds_thresh)) {
+		NL_SET_ERR_MSG_MOD(extack,
+				   "AF_XDP zero-copy socket requires XDP frags");
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+int fbnic_xsk_validate(struct fbnic_net *fbn, struct bpf_prog *prog,
+		       unsigned int mtu, u32 hds_thresh,
+		       struct netlink_ext_ack *extack)
+{
+	struct net_device *netdev = fbn->netdev;
+	struct xsk_buff_pool *pool;
+	unsigned int qid;
+	int err;
+
+	for (qid = 0; qid < fbn->num_rx_queues; qid++) {
+		pool = xsk_get_pool_from_rxq(netdev, qid);
+		if (!pool)
+			continue;
+
+		err = fbnic_xsk_validate_frames(pool, prog, mtu, hds_thresh,
+						extack);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int fbnic_xsk_setup_pool(struct net_device *netdev,
+				struct xsk_buff_pool *pool, u16 queue_id,
+				struct netlink_ext_ack *extack)
+{
+	struct fbnic_net *fbn = netdev_priv(netdev);
+	int err;
+
+	if (queue_id >= fbn->num_rx_queues)
+		return -EINVAL;
+
+	if (!pool)
+		return xsk_pool_setup_page_pool(netdev, NULL, queue_id, extack);
+
+	err = fbnic_xsk_validate_frames(pool, fbn->xdp_prog, netdev->mtu,
+					fbn->hds_thresh, extack);
+	if (err)
+		return err;
+
+	return xsk_pool_setup_page_pool(netdev, pool, queue_id, extack);
+}
+
 static int fbnic_bpf(struct net_device *netdev, struct netdev_bpf *bpf)
 {
 	struct bpf_prog *prog = bpf->prog, *prev_prog;
 	struct fbnic_net *fbn = netdev_priv(netdev);
+	int err;
 
-	if (bpf->command != XDP_SETUP_PROG)
+	switch (bpf->command) {
+	case XDP_SETUP_PROG:
+		if (fbnic_check_split_frames(prog, netdev->mtu,
+					     fbn->hds_thresh)) {
+			NL_SET_ERR_MSG_MOD(bpf->extack,
+					   "MTU too high, or HDS threshold is too low for single buffer XDP");
+			return -EOPNOTSUPP;
+		}
+
+		err = fbnic_xsk_validate(fbn, prog, netdev->mtu,
+					 fbn->hds_thresh, bpf->extack);
+		if (err)
+			return err;
+
+		prev_prog = xchg(&fbn->xdp_prog, prog);
+		if (prev_prog)
+			bpf_prog_put(prev_prog);
+		return 0;
[..]
quoted
+	case XDP_SETUP_XSK_POOL:
+		return fbnic_xsk_setup_pool(netdev, bpf->xsk.pool,
+					    bpf->xsk.queue_id, bpf->extack);
Is there a good reason to ask drivers to call us back with
xsk_pool_setup_page_pool? For the PP mode, maybe we should add new
XSK_VALIDATE_POOL or something? So most of that fbnic_xsk_setup_pool stays
in core?
Makes sense. I'll try core-owned setup with driver opt-in and
validation. Existing direct drivers can keep their setup.
I guess I'm mentally trying to have a clear separation between
pp vs non-pp modes. Even that whole xsk_buff_pool might be legacy non-pp
artifact? New drivers shouldn't bother with it? Or is it too much?
Hmm. New RX code shouldn't need allocator internals, but rings, DMA and
TX state are still shared. I need to noodle a bit on it...
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help