mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: "Björn Töpel" <bjorn@kernel.org>
To: Magnus Karlsson <magnus.karlsson@intel.com>,
	Maciej Fijalkowski <maciej.fijalkowski@intel.com>,
	Stanislav Fomichev <sdf@fomichev.me>,
	"David S. Miller" <davem@davemloft.net>,
	Eric Dumazet <edumazet@kernel.org>,
	Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
	Simon Horman <horms@kernel.org>, Jonathan Corbet <corbet@lwn.net>,
	Shuah Khan <skhan@linuxfoundation.org>,
	Randy Dunlap <rdunlap@infradead.org>,
	Alexander Duyck <alexanderduyck@fb.com>,
	kernel-team@meta.com, Andrew Lunn <andrew+netdev@lunn.ch>,
	Jesper Dangaard Brouer <hawk@kernel.org>,
	Ilias Apalodimas <ilias.apalodimas@linaro.org>,
	Alexei Starovoitov <ast@kernel.org>,
	Daniel Borkmann <daniel@iogearbox.net>,
	John Fastabend <john.fastabend@gmail.com>,
	Pavel Begunkov <asml.silence@gmail.com>,
	Jens Axboe <axboe@kernel.dk>, Andrii Nakryiko <andrii@kernel.org>,
	Eduard Zingerman <eddyz87@gmail.com>,
	Kumar Kartikeya Dwivedi <memxor@gmail.com>,
	Martin KaFai Lau <martin.lau@linux.dev>,
	Song Liu <song@kernel.org>,
	Yonghong Song <yonghong.song@linux.dev>,
	Jiri Olsa <jolsa@kernel.org>,
	Emil Tsalapatis <emil@etsalapatis.com>,
	Ihor Solodrai <ihor.solodrai@linux.dev>,
	netdev@vger.kernel.org, bpf@vger.kernel.org,
	io-uring@vger.kernel.org, Mina Almasry <almasrymina@google.com>
Cc: "Björn Töpel" <bjorn@kernel.org>,
	"Mike Marciniszyn (Meta)" <mike.marciniszyn@gmail.com>,
	"Weiming Shi" <bestswngs@gmail.com>,
	"Nikolay Aleksandrov" <razor@blackwall.org>,
	"David Wei" <dw@davidwei.uk>,
	"Alexander Lobakin" <aleksander.lobakin@intel.com>,
	linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org,
	"Stuart Clark" <stuclar@meta.com>
Subject: [PATCH net-next v1 05/11] xsk: Install UMEM page pool providers in the core
Date: Tue, 10 Nov 2026 11:20:26 +0100	[thread overview]
Message-ID: <20261110102039.1002605-6-bjorn@kernel.org> (raw)
In-Reply-To: <20261110102039.1002605-1-bjorn@kernel.org>

A page_pool driver should not need its own AF_XDP setup. When it sets
netdev_queue_mgmt_ops.xsk_page_pool, a zero-copy bind installs the
UMEM provider through the queue API, as devmem and io_uring do. The
driver only judges the chunk size, headroom and multi-buffer mode
against its MTU and XDP program, in XDP_VALIDATE_XSK_POOL; MTU and
program changes of a bound device get the same check. The UMEM
headroom belongs to the socket, so the queue gets it as rx_headroom,
next to rx_page_size.

After a queue restart, the core checks that the new page pools took
the provider. A readable provider must back the header pool, where an
unsplit packet lands, so pools declare the buffers they supply in
rx_roles. The old queue's pools carry the same provider during the
check, so a per-queue generation, mp_gen, tells the new ones apart.

Generic XDP runs outside the queue NAPI, so it cannot feed the socket.

The provider needs no xdp_buff_xsk per chunk. The buffer pool
allocates them only for copy mode and driver zero copy, which saves
128 bytes per chunk.

Signed-off-by: Björn Töpel <bjorn@kernel.org>
---
 include/linux/netdevice.h     |   8 ++
 include/net/netdev_queues.h   |  12 ++
 include/net/netdev_rx_queue.h |   2 +
 include/net/page_pool/types.h |  12 ++
 include/net/xdp_sock.h        |   4 +
 include/net/xdp_sock_drv.h    |  45 +++++++
 include/net/xsk_buff_pool.h   |   3 +
 net/core/dev.c                |  16 +++
 net/core/netdev_config.c      |   2 +
 net/core/netdev_rx_queue.c    |   9 +-
 net/core/page_pool.c          |   6 +-
 net/core/page_pool_user.c     |  25 ++--
 net/xdp/xsk.h                 |   7 +-
 net/xdp/xsk_buff_pool.c       |  69 ++++++++---
 net/xdp/xsk_pp.c              | 221 ++++++++++++++++++++++++++++++++++
 15 files changed, 410 insertions(+), 31 deletions(-)

diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 435be7880d7e..11e51e684003 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -77,6 +77,7 @@ struct udp_tunnel_info;
 struct udp_tunnel_nic_info;
 struct udp_tunnel_nic;
 struct bpf_prog;
+struct xsk_rx_check;
 struct xdp_buff;
 struct xdp_frame;
 struct xdp_metadata_ops;
@@ -1010,6 +1011,8 @@ enum bpf_netdev_command {
 	BPF_OFFLOAD_MAP_ALLOC,
 	BPF_OFFLOAD_MAP_FREE,
 	XDP_SETUP_XSK_POOL,
+	/* Check an AF_XDP page pool setup, see netdev_queue_mgmt_ops */
+	XDP_VALIDATE_XSK_POOL,
 };
 
 struct bpf_prog_offload_ops;
@@ -1048,6 +1051,11 @@ struct netdev_bpf {
 			struct xsk_buff_pool *pool;
 			u16 queue_id;
 		} xsk;
+		/* XDP_VALIDATE_XSK_POOL */
+		struct {
+			const struct xsk_rx_check *check;
+			struct netlink_ext_ack *extack;
+		} xsk_check;
 	};
 };
 
diff --git a/include/net/netdev_queues.h b/include/net/netdev_queues.h
index c5335e935b7d..ad5f0420c21d 100644
--- a/include/net/netdev_queues.h
+++ b/include/net/netdev_queues.h
@@ -45,12 +45,15 @@ struct netdev_config {
 /**
  * struct netdev_queue_config - rendered configuration for an RX queue
  * @rx_page_size:	Size of one RX page-pool allocation.
+ * @rx_headroom:	Space before the packet in a head buffer, including
+ *			XDP_PACKET_HEADROOM. Zero selects the driver default.
  * @rx_ring_size:	Configured size of the regular RX ring.
  * @rx_mini_ring_size:	Configured size of the RX mini ring.
  * @rx_jumbo_ring_size:	Configured size of the RX jumbo ring.
  */
 struct netdev_queue_config {
 	u32	rx_page_size;
+	u32	rx_headroom;
 	u32	rx_ring_size;
 	u32	rx_mini_ring_size;
 	u32	rx_jumbo_ring_size;
@@ -156,6 +159,8 @@ void netdev_stat_queue_sum(struct net_device *netdev,
 enum {
 	/* The queue checks and honours the page size qcfg parameter */
 	QCFG_RX_PAGE_SIZE	= 0x1,
+	/* The queue checks and honours the headroom qcfg parameter */
+	QCFG_RX_HEADROOM	= 0x2,
 };
 
 /**
@@ -199,6 +204,12 @@ enum {
  *
  * @supported_params:	Bitmask of supported parameters, see QCFG_*.
  *
+ * @xsk_page_pool:	AF_XDP zero copy installs the UMEM as the page pool
+ *			memory provider of the RX queue. The driver selects it
+ *			with PP_FLAG_MEM_PROVIDER and PP_RX_ROLE_HEAD, honours
+ *			whole buffers and QCFG_RX_HEADROOM, and accepts or
+ *			rejects the setup in XDP_VALIDATE_XSK_POOL.
+ *
  * Note that @ndo_queue_mem_alloc and @ndo_queue_mem_free may be called while
  * the interface is closed. @ndo_queue_start and @ndo_queue_stop will only
  * be called for an interface which is open.
@@ -229,6 +240,7 @@ struct netdev_queue_mgmt_ops {
 				    struct netlink_ext_ack *extack);
 
 	unsigned int supported_params;
+	bool xsk_page_pool;
 };
 
 void netdev_queue_config(struct net_device *dev, int rxq,
diff --git a/include/net/netdev_rx_queue.h b/include/net/netdev_rx_queue.h
index 9415a94d333d..989120cbf452 100644
--- a/include/net/netdev_rx_queue.h
+++ b/include/net/netdev_rx_queue.h
@@ -31,6 +31,8 @@ struct netdev_rx_queue {
 	struct napi_struct		*napi;
 	struct netdev_queue_config	qcfg;
 	struct pp_memory_provider_params mp_params;
+	/* stamps the page pools of each queue allocation */
+	u32				mp_gen;
 
 	/* If a queue is leased, then the lease pointer is always
 	 * valid. From the physical device it points to the virtual
diff --git a/include/net/page_pool/types.h b/include/net/page_pool/types.h
index 0d48c05f1462..e9f3af91e305 100644
--- a/include/net/page_pool/types.h
+++ b/include/net/page_pool/types.h
@@ -40,6 +40,12 @@
  */
 #define PP_FLAG_MEM_PROVIDER	BIT(4)
 
+/* RX buffers a page pool supplies, see page_pool_params.rx_roles */
+enum pp_rx_role {
+	PP_RX_ROLE_HEAD		= BIT(0),	/* packet headers */
+	PP_RX_ROLE_PAYLOAD	= BIT(1),	/* payload after header split */
+};
+
 #define PP_FLAG_ALL		(PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV | \
 				 PP_FLAG_SYSTEM_POOL | \
 				 PP_FLAG_ALLOW_UNREADABLE_NETMEM | \
@@ -94,6 +100,8 @@ struct pp_alloc_cache {
  * @queue_idx:	queue idx this page_pool is being created for.
  * @flags:	PP_FLAG_DMA_MAP, PP_FLAG_DMA_SYNC_DEV, PP_FLAG_SYSTEM_POOL,
  *		PP_FLAG_ALLOW_UNREADABLE_NETMEM, PP_FLAG_MEM_PROVIDER.
+ * @rx_roles:	&enum pp_rx_role mask, for pools that select a memory
+ *		provider. Zero means payload buffers.
  */
 struct page_pool_params {
 	struct_group_tagged(page_pool_params_fast, fast,
@@ -110,6 +118,7 @@ struct page_pool_params {
 		struct net_device *netdev;
 		unsigned int queue_idx;
 		unsigned int	flags;
+		unsigned int	rx_roles;
 /* private: used by test code only */
 		void (*init_callback)(netmem_ref netmem, void *arg);
 		void *init_arg;
@@ -182,6 +191,7 @@ struct pp_memory_provider_params {
 	void *mp_priv;
 	const struct memory_provider_ops *mp_ops;
 	u32 rx_page_size;
+	u32 rx_headroom;
 };
 
 struct page_pool {
@@ -245,6 +255,8 @@ struct page_pool {
 
 	void *mp_priv;
 	const struct memory_provider_ops *mp_ops;
+	/* queue allocation that selected the provider */
+	u32 mp_gen;
 
 	struct xarray dma_mapped;
 
diff --git a/include/net/xdp_sock.h b/include/net/xdp_sock.h
index 6e70b320b399..e0e22ef839e1 100644
--- a/include/net/xdp_sock.h
+++ b/include/net/xdp_sock.h
@@ -19,6 +19,10 @@
 struct net_device;
 struct xsk_queue;
 struct xdp_buff;
+struct memory_provider_ops;
+
+/* The memory provider of AF_XDP page pool zero copy */
+extern const struct memory_provider_ops xsk_pp_ops;
 
 struct xdp_umem {
 	void *addrs;
diff --git a/include/net/xdp_sock_drv.h b/include/net/xdp_sock_drv.h
index d94aeb506379..5bb1949a0aa7 100644
--- a/include/net/xdp_sock_drv.h
+++ b/include/net/xdp_sock_drv.h
@@ -22,8 +22,29 @@ struct xsk_cb_desc {
 	u8 bytes;
 };
 
+/**
+ * struct xsk_rx_check - AF_XDP page pool RX setup a driver must accept
+ * @buf_size: size of each RX buffer
+ * @headroom: space before the packet in a head buffer, including
+ *	XDP_PACKET_HEADROOM. The UMEM headroom is the rest.
+ * @mtu: MTU of the device
+ * @sg: the socket takes packets in more than one buffer
+ * @prog: XDP program to check, or NULL for the current one
+ */
+struct xsk_rx_check {
+	u32 buf_size;
+	u32 headroom;
+	unsigned int mtu;
+	bool sg;
+	struct bpf_prog *prog;
+};
+
 #ifdef CONFIG_XDP_SOCKETS
 
+int xsk_validate_dev(struct net_device *dev, unsigned int mtu,
+		     struct bpf_prog *prog, enum bpf_xdp_mode mode,
+		     struct netlink_ext_ack *extack);
+struct xsk_buff_pool *xsk_rxq_pool(struct net_device *dev, u16 queue_id);
 void xsk_tx_completed(struct xsk_buff_pool *pool, u32 nb_entries);
 bool xsk_tx_peek_desc(struct xsk_buff_pool *pool, struct xdp_desc *desc);
 u32 xsk_tx_peek_release_desc_batch(struct xsk_buff_pool *pool, u32 max);
@@ -36,6 +57,11 @@ void xsk_clear_rx_need_wakeup(struct xsk_buff_pool *pool);
 void xsk_clear_tx_need_wakeup(struct xsk_buff_pool *pool);
 bool xsk_uses_need_wakeup(struct xsk_buff_pool *pool);
 
+static inline bool xsk_pool_uses_sg(struct xsk_buff_pool *pool)
+{
+	return pool->umem->flags & XDP_UMEM_SG_FLAG;
+}
+
 static inline u32 xsk_pool_get_headroom(struct xsk_buff_pool *pool)
 {
 	return XDP_PACKET_HEADROOM + pool->headroom;
@@ -351,6 +377,20 @@ static inline void xsk_buff_raw_dma_sync_for_device(struct xsk_buff_pool *pool,
 
 #else
 
+static inline int xsk_validate_dev(struct net_device *dev, unsigned int mtu,
+				   struct bpf_prog *prog,
+				   enum bpf_xdp_mode mode,
+				   struct netlink_ext_ack *extack)
+{
+	return 0;
+}
+
+static inline struct xsk_buff_pool *xsk_rxq_pool(struct net_device *dev,
+						 u16 queue_id)
+{
+	return NULL;
+}
+
 static inline void xsk_tx_completed(struct xsk_buff_pool *pool, u32 nb_entries)
 {
 }
@@ -397,6 +437,11 @@ static inline bool xsk_uses_need_wakeup(struct xsk_buff_pool *pool)
 	return false;
 }
 
+static inline bool xsk_pool_uses_sg(struct xsk_buff_pool *pool)
+{
+	return false;
+}
+
 static inline u32 xsk_pool_get_headroom(struct xsk_buff_pool *pool)
 {
 	return 0;
diff --git a/include/net/xsk_buff_pool.h b/include/net/xsk_buff_pool.h
index 02e89350faf9..6d7cfdf99a8b 100644
--- a/include/net/xsk_buff_pool.h
+++ b/include/net/xsk_buff_pool.h
@@ -11,6 +11,7 @@
 #include <net/xdp.h>
 
 struct xsk_buff_pool;
+struct xsk_pp;
 struct xdp_rxq_info;
 struct xsk_cb_desc;
 struct xsk_queue;
@@ -54,6 +55,8 @@ struct xsk_buff_pool {
 	spinlock_t xsk_tx_list_lock;
 	refcount_t users;
 	struct xdp_umem *umem;
+	/* page pool memory provider, see net/xdp/xsk_pp.c */
+	struct xsk_pp *pp;
 	struct work_struct work;
 	/* Protects generic receive in shared and non-shared umem mode. */
 	spinlock_t rx_lock;
diff --git a/net/core/dev.c b/net/core/dev.c
index 663fe3e32ea0..7e985fb82488 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -163,6 +163,7 @@
 #include <net/page_pool/types.h>
 #include <net/page_pool/helpers.h>
 #include <net/page_pool/memory_provider.h>
+#include <net/xdp_sock_drv.h>
 #include <net/rps.h>
 #include <linux/phy_link_topology.h>
 
@@ -10535,6 +10536,8 @@ u8 dev_xdp_sb_prog_count(struct net_device *dev)
 
 int netif_xdp_propagate(struct net_device *dev, struct netdev_bpf *bpf)
 {
+	int err;
+
 	if (!dev->netdev_ops->ndo_bpf)
 		return -EOPNOTSUPP;
 
@@ -10552,6 +10555,13 @@ int netif_xdp_propagate(struct net_device *dev, struct netdev_bpf *bpf)
 		return -EBUSY;
 	}
 
+	if (bpf->command == XDP_SETUP_PROG && bpf->prog) {
+		err = xsk_validate_dev(dev, dev->mtu, bpf->prog, XDP_MODE_DRV,
+				       bpf->extack);
+		if (err)
+			return err;
+	}
+
 	return dev->netdev_ops->ndo_bpf(dev, bpf);
 }
 EXPORT_SYMBOL_GPL(netif_xdp_propagate);
@@ -10629,6 +10639,12 @@ static int dev_xdp_install(struct net_device *dev, enum bpf_xdp_mode mode,
 		return -EBUSY;
 	}
 
+	if (mode != XDP_MODE_HW && prog) {
+		err = xsk_validate_dev(dev, dev->mtu, prog, mode, extack);
+		if (err)
+			return err;
+	}
+
 	memset(&xdp, 0, sizeof(xdp));
 	xdp.command = mode == XDP_MODE_HW ? XDP_SETUP_PROG_HW : XDP_SETUP_PROG;
 	xdp.extack = extack;
diff --git a/net/core/netdev_config.c b/net/core/netdev_config.c
index 1975de42a60d..97e33764737b 100644
--- a/net/core/netdev_config.c
+++ b/net/core/netdev_config.c
@@ -88,6 +88,8 @@ static int __netdev_queue_config(struct net_device *dev, int rxq_idx,
 	mpp = &__netif_get_rx_queue(dev, rxq_idx)->mp_params;
 	if (mpp->rx_page_size)
 		qcfg->rx_page_size = mpp->rx_page_size;
+	if (mpp->rx_headroom)
+		qcfg->rx_headroom = mpp->rx_headroom;
 	err = validate_cb(dev, qcfg, extack);
 	if (err)
 		return err;
diff --git a/net/core/netdev_rx_queue.c b/net/core/netdev_rx_queue.c
index 9e14a747f9b5..4e2f906fbc73 100644
--- a/net/core/netdev_rx_queue.c
+++ b/net/core/netdev_rx_queue.c
@@ -6,6 +6,7 @@
 #include <net/netdev_queues.h>
 #include <net/netdev_rx_queue.h>
 #include <net/page_pool/memory_provider.h>
+#include <net/xdp_sock.h>
 
 #include "dev.h"
 #include "page_pool_priv.h"
@@ -141,6 +142,7 @@ static int netdev_rx_queue_reconfig(struct net_device *dev,
 		goto err_free_new_mem;
 	}
 
+	rxq->mp_gen++;
 	err = qops->ndo_queue_mem_alloc(dev, qcfg_new, new_mem, rxq_idx);
 	if (err)
 		goto err_free_old_mem;
@@ -235,6 +237,10 @@ static int __netif_mp_open_rxq(struct net_device *dev, unsigned int rxq_idx,
 		NL_SET_ERR_MSG(extack, "device does not support: rx_page_size");
 		return -EOPNOTSUPP;
 	}
+	if (p->rx_headroom && !(qops->supported_params & QCFG_RX_HEADROOM)) {
+		NL_SET_ERR_MSG(extack, "device does not support: rx_headroom");
+		return -EOPNOTSUPP;
+	}
 
 	rxq = __netif_get_rx_queue(dev, rxq_idx);
 	if (rxq->mp_params.mp_ops) {
@@ -242,7 +248,8 @@ static int __netif_mp_open_rxq(struct net_device *dev, unsigned int rxq_idx,
 		return -EEXIST;
 	}
 #ifdef CONFIG_XDP_SOCKETS
-	if (rxq->pool) {
+	/* An AF_XDP pool owns the queue, unless it installs its provider. */
+	if (rxq->pool && p->mp_ops != &xsk_pp_ops) {
 		NL_SET_ERR_MSG(extack, "designated queue already in use by AF_XDP");
 		return -EBUSY;
 	}
diff --git a/net/core/page_pool.c b/net/core/page_pool.c
index 8237fda598ab..e897e79d31f1 100644
--- a/net/core/page_pool.c
+++ b/net/core/page_pool.c
@@ -215,7 +215,8 @@ bool netmem_can_retain(netmem_ref netmem)
 EXPORT_SYMBOL(netmem_can_retain);
 
 static int page_pool_select_mp(struct page_pool *pool,
-			       const struct pp_memory_provider_params *mpp)
+			       const struct pp_memory_provider_params *mpp,
+			       u32 rxq_mp_gen)
 {
 	const struct memory_provider_ops *ops = mpp->mp_ops;
 	unsigned int flags = pool->slow.flags;
@@ -234,6 +235,7 @@ static int page_pool_select_mp(struct page_pool *pool,
 
 	pool->mp_priv = mpp->mp_priv;
 	pool->mp_ops = ops;
+	pool->mp_gen = rxq_mp_gen;
 	return 0;
 }
 
@@ -326,7 +328,7 @@ static int page_pool_init(struct page_pool *pool,
 		netdev_assert_locked(pool->slow.netdev);
 		rxq = __netif_get_rx_queue(pool->slow.netdev,
 					   pool->slow.queue_idx);
-		err = page_pool_select_mp(pool, &rxq->mp_params);
+		err = page_pool_select_mp(pool, &rxq->mp_params, rxq->mp_gen);
 		if (err)
 			goto free_ptr_ring;
 	}
diff --git a/net/core/page_pool_user.c b/net/core/page_pool_user.c
index f30f3779624c..b69da0f74756 100644
--- a/net/core/page_pool_user.c
+++ b/net/core/page_pool_user.c
@@ -398,28 +398,37 @@ void page_pool_unlist(struct page_pool *pool)
 	mutex_unlock(&page_pools_lock);
 }
 
+/* Check that the queue allocation that just ran put the provider in every
+ * pool role it needs: headers for a readable provider, payload otherwise.
+ * Pools of earlier allocations do not count.
+ */
 int page_pool_check_memory_provider(struct net_device *dev,
 				    struct netdev_rx_queue *rxq)
 {
-	void *binding = rxq->mp_params.mp_priv;
+	const struct memory_provider_ops *ops = rxq->mp_params.mp_ops;
+	void *mp_priv = rxq->mp_params.mp_priv;
+	unsigned int need, have = 0;
 	struct page_pool *pool;
 	struct hlist_node *n;
 
-	if (!binding)
+	if (!mp_priv)
 		return 0;
 
+	need = PP_RX_ROLE_PAYLOAD;
+	if (ops->caps & MP_CAP_READABLE)
+		need = PP_RX_ROLE_HEAD;
+
 	mutex_lock(&page_pools_lock);
 	hlist_for_each_entry_safe(pool, n, &dev->page_pools, user.list) {
-		if (pool->mp_priv != binding)
+		if (pool->mp_priv != mp_priv || pool->mp_gen != rxq->mp_gen ||
+		    pool->slow.queue_idx != get_netdev_rx_queue_index(rxq))
 			continue;
 
-		if (pool->slow.queue_idx == get_netdev_rx_queue_index(rxq)) {
-			mutex_unlock(&page_pools_lock);
-			return 0;
-		}
+		have |= pool->slow.rx_roles ? : PP_RX_ROLE_PAYLOAD;
 	}
 	mutex_unlock(&page_pools_lock);
-	return -ENODATA;
+
+	return (have & need) == need ? 0 : -ENODATA;
 }
 
 static void page_pool_unreg_netdev_wipe(struct net_device *netdev)
diff --git a/net/xdp/xsk.h b/net/xdp/xsk.h
index 4dab7b9a1487..9699e9a499a3 100644
--- a/net/xdp/xsk.h
+++ b/net/xdp/xsk.h
@@ -36,10 +36,13 @@ void xsk_map_try_sock_delete(struct xsk_map *map, struct xdp_sock *xs,
 			     struct xdp_sock __rcu **map_entry);
 struct xsk_pp;
 
-extern const struct memory_provider_ops xsk_pp_ops;
-
 struct xsk_pp *xsk_pp_create(struct xsk_buff_pool *pool);
 void xsk_pp_put(struct xsk_pp *pp);
+bool xsk_pp_supported(struct net_device *dev);
+int xsk_pp_bind(struct xsk_buff_pool *pool, struct net_device *dev,
+		u16 queue_id);
+void xsk_pp_unbind(struct xsk_buff_pool *pool);
+int xp_alloc_heads(struct xsk_buff_pool *pool);
 void xsk_clear_pool_at_qid(struct net_device *dev, u16 queue_id);
 int xsk_reg_pool_at_qid(struct net_device *dev, struct xsk_buff_pool *pool,
 			u16 queue_id);
diff --git a/net/xdp/xsk_buff_pool.c b/net/xdp/xsk_buff_pool.c
index 18966bbdb5b7..c620a0c32c17 100644
--- a/net/xdp/xsk_buff_pool.c
+++ b/net/xdp/xsk_buff_pool.c
@@ -38,6 +38,8 @@ void xp_destroy(struct xsk_buff_pool *pool)
 	if (!pool)
 		return;
 
+	if (pool->pp)
+		xsk_pp_put(pool->pp);
 	kvfree(pool->tx_descs);
 	kvfree(pool->heads);
 	kvfree(pool);
@@ -62,18 +64,13 @@ struct xsk_buff_pool *xp_create_and_assign_umem(struct xdp_sock *xs,
 {
 	bool unaligned = umem->flags & XDP_UMEM_UNALIGNED_CHUNK_FLAG;
 	struct xsk_buff_pool *pool;
-	struct xdp_buff_xsk *xskb;
-	u32 i, entries;
+	u32 entries;
 
 	entries = unaligned ? umem->chunks : 0;
 	pool = kvzalloc_flex(*pool, free_heads, entries);
 	if (!pool)
 		goto out;
 
-	pool->heads = kvzalloc_objs(*pool->heads, umem->chunks);
-	if (!pool->heads)
-		goto out;
-
 	if (xs->tx)
 		if (xp_alloc_tx_descs(pool, xs, max_segs))
 			goto out;
@@ -104,10 +101,30 @@ struct xsk_buff_pool *xp_create_and_assign_umem(struct xdp_sock *xs,
 	pool->fq = xs->fq_tmp;
 	pool->cq = xs->cq_tmp;
 
-	for (i = 0; i < pool->free_heads_cnt; i++) {
+	return pool;
+
+out:
+	xp_destroy(pool);
+	return NULL;
+}
+
+/* Only copy mode and driver zero copy use heads */
+int xp_alloc_heads(struct xsk_buff_pool *pool)
+{
+	struct xdp_buff_xsk *xskb;
+	u32 i;
+
+	if (pool->heads)
+		return 0;
+
+	pool->heads = kvzalloc_objs(*pool->heads, pool->heads_cnt);
+	if (!pool->heads)
+		return -ENOMEM;
+
+	for (i = 0; i < pool->heads_cnt; i++) {
 		xskb = &pool->heads[i];
 		xskb->pool = pool;
-		xskb->xdp.frame_sz = umem->chunk_size - umem->headroom;
+		xskb->xdp.frame_sz = pool->chunk_size - pool->headroom;
 		INIT_LIST_HEAD(&xskb->list_node);
 		if (pool->unaligned)
 			pool->free_heads[i] = xskb;
@@ -115,11 +132,7 @@ struct xsk_buff_pool *xp_create_and_assign_umem(struct xdp_sock *xs,
 			xp_init_xskb_addr(xskb, pool, (u64)i * pool->chunk_size);
 	}
 
-	return pool;
-
-out:
-	xp_destroy(pool);
-	return NULL;
+	return 0;
 }
 
 void xp_set_rxq_info(struct xsk_buff_pool *pool, struct xdp_rxq_info *rxq)
@@ -212,9 +225,24 @@ int xp_assign_dev(struct xsk_buff_pool *pool,
 	 */
 	hlist_add_head(&pool->dev_node, &netdev->xsk_pools);
 
-	if (force_copy)
+	if (force_copy) {
 		/* For copy-mode, we are done. */
+		err = xp_alloc_heads(pool);
+		goto out;
+	}
+
+	if (xsk_pp_supported(netdev)) {
+		err = xsk_pp_bind(pool, netdev, queue_id);
+		if (err)
+			goto err_unreg_pool;
+
+		pool->umem->zc = true;
 		return 0;
+	}
+
+	err = xp_alloc_heads(pool);
+	if (err)
+		goto out;
 
 	if ((netdev->xdp_features & NETDEV_XDP_ACT_XSK) != NETDEV_XDP_ACT_XSK) {
 		err = -EOPNOTSUPP;
@@ -267,8 +295,10 @@ int xp_assign_dev(struct xsk_buff_pool *pool,
 err_unreg_xsk:
 	xp_disable_drv_zc(pool);
 err_unreg_pool:
-	if (!force_zc)
-		err = 0; /* fallback to copy mode */
+	/* An old page pool may still own chunks of a failed provider. */
+	if (!force_zc && !pool->pp)
+		err = xp_alloc_heads(pool); /* fallback to copy mode */
+out:
 	if (err) {
 		pool->umem->flags = umem_flags;
 		xsk_clear_pool_at_qid(netdev, queue_id);
@@ -301,7 +331,10 @@ static void __xp_clear_dev(struct xsk_buff_pool *pool)
 	struct net_device *netdev = pool->netdev;
 
 	netdev_assert_locked_ops_compat(netdev);
-	xp_disable_drv_zc(pool);
+	if (pool->pp)
+		xsk_pp_unbind(pool);
+	else
+		xp_disable_drv_zc(pool);
 	xsk_clear_pool_at_qid(netdev, pool->queue_id);
 	hlist_del(&pool->dev_node);
 	pool->netdev = NULL;
@@ -474,7 +507,7 @@ static void xp_check_dma_contiguity(struct xsk_dma_map *dma_map)
 
 static int xp_init_dma_info(struct xsk_buff_pool *pool, struct xsk_dma_map *dma_map)
 {
-	if (!pool->unaligned) {
+	if (!pool->unaligned && pool->heads) {
 		u32 i;
 
 		for (i = 0; i < pool->heads_cnt; i++) {
diff --git a/net/xdp/xsk_pp.c b/net/xdp/xsk_pp.c
index fdd55265a6cd..c2214e718cdb 100644
--- a/net/xdp/xsk_pp.c
+++ b/net/xdp/xsk_pp.c
@@ -8,6 +8,9 @@
  *       +-----RX ring-------+
  */
 
+#include <net/netdev_lock.h>
+#include <net/netdev_queues.h>
+#include <net/netdev_rx_queue.h>
 #include <net/page_pool/helpers.h>
 #include <net/page_pool/memory_provider.h>
 #include <net/xdp_sock_drv.h>
@@ -15,6 +18,8 @@
 #include "xsk.h"
 #include "xsk_queue.h"
 
+#define XSK_PP_DMA_ATTR	(DMA_ATTR_WEAK_ORDERING | DMA_ATTR_SKIP_CPU_SYNC)
+
 enum xsk_pp_chunk {
 	XSK_PP_USER,	/* owned by userspace */
 	XSK_PP_POOL,	/* owned by a page pool */
@@ -30,6 +35,8 @@ struct xsk_pp {
 	refcount_t refs;
 	u32 nr_pools;
 	bool dma_need_sync;
+	/* RX queue of the provider, NULL once it is removed */
+	struct netdev_rx_queue *rxq;
 	u8 *state;
 	u32 reuse_cnt;
 	u32 reuse[];
@@ -236,11 +243,225 @@ static int xsk_pp_nl_fill(void *mp_priv, struct sk_buff *rsp,
 	return 0;
 }
 
+/* The queue no longer uses the provider, and its DMA stopped */
+static void xsk_pp_detach(struct xsk_pp *pp)
+{
+	pp->rxq = NULL;
+	spin_lock_bh(&pp->lock);
+	pp->fq = NULL;
+	spin_unlock_bh(&pp->lock);
+	xp_dma_unmap(pp->pool, XSK_PP_DMA_ATTR);
+}
+
+/* The device unregisters, and is closed */
+static void xsk_pp_uninstall(void *mp_priv, struct netdev_rx_queue *rxq)
+{
+	xsk_pp_detach(mp_priv);
+}
+
 const struct memory_provider_ops xsk_pp_ops = {
 	.init		= xsk_pp_init,
 	.destroy	= xsk_pp_destroy,
 	.alloc_netmems	= xsk_pp_alloc_netmems,
 	.release_netmem	= xsk_pp_release_netmem,
 	.nl_fill	= xsk_pp_nl_fill,
+	.uninstall	= xsk_pp_uninstall,
 	.caps		= MP_CAP_READABLE,
 };
+
+static bool xsk_pp_abandon(struct xsk_pp *pp)
+{
+	struct xsk_buff_pool *pool = pp->pool;
+	bool busy;
+	u32 idx;
+
+	spin_lock_bh(&pp->lock);
+	busy = pp->nr_pools;
+	while (!busy && pp->reuse_cnt) {
+		idx = pp->reuse[--pp->reuse_cnt];
+		pp->state[idx] = XSK_PP_USER;
+		xp_free(&pool->heads[idx]);
+	}
+	spin_unlock_bh(&pp->lock);
+
+	return !busy;
+}
+
+static int xsk_pp_validate(struct net_device *dev, struct xsk_buff_pool *pool,
+			   unsigned int mtu, struct bpf_prog *prog,
+			   struct netlink_ext_ack *extack)
+{
+	struct xsk_rx_check check = {
+		.buf_size = pool->chunk_size,
+		.headroom = xsk_pool_get_headroom(pool),
+		.mtu = mtu,
+		.sg = xsk_pool_uses_sg(pool),
+		.prog = prog,
+	};
+	struct netdev_bpf bpf = {
+		.command = XDP_VALIDATE_XSK_POOL,
+		.xsk_check.check = &check,
+		.xsk_check.extack = extack,
+	};
+
+	return dev->netdev_ops->ndo_bpf(dev, &bpf);
+}
+
+/**
+ * xsk_validate_dev - check AF_XDP page pool queues against a new setting
+ * @dev: device of the queues
+ * @mtu: MTU to check
+ * @prog: XDP program to check, or NULL for the current one
+ * @mode: XDP mode of @prog
+ * @extack: netlink extended ack
+ *
+ * Context: holds the instance lock of @dev, or RTNL for a device without it.
+ * Return: 0 if the driver accepts all queues of @dev that have an AF_XDP
+ * memory provider, or its error.
+ */
+int xsk_validate_dev(struct net_device *dev, unsigned int mtu,
+		     struct bpf_prog *prog, enum bpf_xdp_mode mode,
+		     struct netlink_ext_ack *extack)
+{
+	struct netdev_rx_queue *rxq;
+	struct xsk_pp *pp;
+	unsigned int i;
+	int err;
+
+	netdev_assert_locked_ops_compat(dev);
+
+	for (i = 0; i < dev->real_num_rx_queues; i++) {
+		rxq = __netif_get_rx_queue(dev, i);
+		if (rxq->mp_params.mp_ops != &xsk_pp_ops)
+			continue;
+
+		/* Generic XDP runs outside the queue NAPI */
+		if (mode == XDP_MODE_SKB) {
+			NL_SET_ERR_MSG(extack,
+				       "generic XDP cannot feed an AF_XDP page pool socket");
+			return -EBUSY;
+		}
+
+		pp = rxq->mp_params.mp_priv;
+		err = xsk_pp_validate(dev, pp->pool, mtu, prog, extack);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(xsk_validate_dev);
+
+/**
+ * xsk_rxq_pool - get the AF_XDP pool that provides memory to an RX queue
+ * @dev: device of the queue
+ * @queue_id: RX queue
+ *
+ * The pool stays valid while the queue that the caller allocates runs.
+ *
+ * Context: holds the instance lock of @dev.
+ * Return: the buffer pool, or NULL.
+ */
+struct xsk_buff_pool *xsk_rxq_pool(struct net_device *dev, u16 queue_id)
+{
+	struct netdev_rx_queue *rxq;
+	struct xsk_pp *pp;
+
+	netdev_assert_locked(dev);
+
+	if (queue_id >= dev->real_num_rx_queues)
+		return NULL;
+
+	rxq = __netif_get_rx_queue(dev, queue_id);
+	if (rxq->mp_params.mp_ops != &xsk_pp_ops)
+		return NULL;
+
+	pp = rxq->mp_params.mp_priv;
+	return pp->pool;
+}
+EXPORT_SYMBOL_GPL(xsk_rxq_pool);
+
+bool xsk_pp_supported(struct net_device *dev)
+{
+	const struct netdev_queue_mgmt_ops *qops = dev->queue_mgmt_ops;
+
+	return qops && qops->xsk_page_pool;
+}
+
+/* Called with RTNL and the instance lock of @dev. On error, @pool->pp stays
+ * set if an old page pool still owns chunks; copy mode must not take the pool.
+ */
+int xsk_pp_bind(struct xsk_buff_pool *pool, struct net_device *dev,
+		u16 queue_id)
+{
+	struct pp_memory_provider_params mp = { .mp_ops = &xsk_pp_ops };
+	struct device *dma_dev;
+	struct xsk_pp *pp;
+	int err;
+
+	if (queue_id >= dev->real_num_rx_queues || pool->unaligned)
+		return -EOPNOTSUPP;
+	if ((dev->xdp_features & NETDEV_XDP_ACT_XSK) != NETDEV_XDP_ACT_XSK ||
+	    (xsk_pool_uses_sg(pool) && dev->xdp_zc_max_segs == 1))
+		return -EOPNOTSUPP;
+	if (dev_xdp_prog_id(dev, XDP_MODE_SKB))
+		return -EOPNOTSUPP;
+	if (!netif_all_mps_have_cap(dev, MP_CAP_READABLE))
+		return -EBUSY;
+
+	err = xsk_pp_validate(dev, pool, dev->mtu, NULL, NULL);
+	if (err)
+		return err;
+
+	dma_dev = netdev_queue_get_dma_dev(dev, queue_id, NETDEV_QUEUE_TYPE_RX);
+	if (!dma_dev)
+		return -EOPNOTSUPP;
+
+	err = xp_dma_map(pool, dma_dev, XSK_PP_DMA_ATTR, pool->umem->pgs,
+			 pool->umem->npgs);
+	if (err)
+		return err;
+
+	pp = xsk_pp_create(pool);
+	if (IS_ERR(pp)) {
+		xp_dma_unmap(pool, XSK_PP_DMA_ATTR);
+		return PTR_ERR(pp);
+	}
+
+	mp.mp_priv = pp;
+	mp.rx_page_size = pool->chunk_size;
+	mp.rx_headroom = xsk_pool_get_headroom(pool);
+	err = netif_mp_open_rxq(dev, queue_id, &mp, NULL);
+	if (!err) {
+		pp->rxq = __netif_get_rx_queue(dev, queue_id);
+		pool->pp = pp;
+		pool->xdp_zc_max_segs = dev->xdp_zc_max_segs;
+		return 0;
+	}
+
+	xp_dma_unmap(pool, XSK_PP_DMA_ATTR);
+	/* Copy mode takes the chunks that the provider holds */
+	if (!xp_alloc_heads(pool) && xsk_pp_abandon(pp))
+		xsk_pp_put(pp);
+	else
+		pool->pp = pp;
+
+	return err;
+}
+
+/* Called with RTNL and the instance lock of the device */
+void xsk_pp_unbind(struct xsk_buff_pool *pool)
+{
+	struct xsk_pp *pp = pool->pp;
+	struct pp_memory_provider_params mp = {
+		.mp_ops = &xsk_pp_ops,
+		.mp_priv = pp,
+	};
+
+	/* Unregister removed the provider already */
+	if (!pp->rxq)
+		return;
+
+	netif_mp_close_rxq(pool->netdev, pool->queue_id, &mp);
+	xsk_pp_detach(pp);
+}
-- 
2.56.0


  parent reply	other threads:[~2026-11-10 10:21 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-10 10:20 [PATCH net-next v1 00/11] xsk: Zero copy through page-pool memory providers Björn Töpel
2026-11-10 10:20 ` [PATCH net-next v1 01/11] net: Get skb fragment addresses through netmem Björn Töpel
2026-11-10 10:20 ` [PATCH net-next v1 02/11] net: Add memory provider capabilities Björn Töpel
2026-11-10 10:20 ` [PATCH net-next v1 03/11] xdp: Copy buffers with restricted lifetime Björn Töpel
2026-11-10 10:20 ` [PATCH net-next v1 04/11] xsk: Allocate UMEM chunks through page_pool Björn Töpel
2026-11-10 15:08   ` Magnus Karlsson
2026-11-10 10:20 ` Björn Töpel [this message]
2026-11-10 10:20 ` [PATCH net-next v1 06/11] page_pool: Coordinate refill with memory providers Björn Töpel
2026-11-10 10:20 ` [PATCH net-next v1 07/11] xsk: Deliver provider chunks without copying Björn Töpel
2026-11-10 10:20 ` [PATCH net-next v1 08/11] eth: fbnic: Support XDP redirect Björn Töpel
2026-11-10 10:20 ` [PATCH net-next v1 09/11] eth: fbnic: Use netmem for RX headers and XDP_TX Björn Töpel
2026-11-10 10:20 ` [PATCH net-next v1 10/11] eth: fbnic: Support RX buffers it cannot split Björn Töpel
2026-11-10 10:20 ` [PATCH net-next v1 11/11] eth: fbnic: Support AF_XDP page pool zero copy Björn Töpel

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261110102039.1002605-6-bjorn@kernel.org \
    --to=bjorn@kernel.org \
    --cc=aleksander.lobakin@intel.com \
    --cc=alexanderduyck@fb.com \
    --cc=almasrymina@google.com \
    --cc=andrew+netdev@lunn.ch \
    --cc=andrii@kernel.org \
    --cc=asml.silence@gmail.com \
    --cc=ast@kernel.org \
    --cc=axboe@kernel.dk \
    --cc=bestswngs@gmail.com \
    --cc=bpf@vger.kernel.org \
    --cc=corbet@lwn.net \
    --cc=daniel@iogearbox.net \
    --cc=davem@davemloft.net \
    --cc=dw@davidwei.uk \
    --cc=eddyz87@gmail.com \
    --cc=edumazet@kernel.org \
    --cc=emil@etsalapatis.com \
    --cc=hawk@kernel.org \
    --cc=horms@kernel.org \
    --cc=ihor.solodrai@linux.dev \
    --cc=ilias.apalodimas@linaro.org \
    --cc=io-uring@vger.kernel.org \
    --cc=john.fastabend@gmail.com \
    --cc=jolsa@kernel.org \
    --cc=kernel-team@meta.com \
    --cc=kuba@kernel.org \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=maciej.fijalkowski@intel.com \
    --cc=magnus.karlsson@intel.com \
    --cc=martin.lau@linux.dev \
    --cc=memxor@gmail.com \
    --cc=mike.marciniszyn@gmail.com \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=razor@blackwall.org \
    --cc=rdunlap@infradead.org \
    --cc=sdf@fomichev.me \
    --cc=skhan@linuxfoundation.org \
    --cc=song@kernel.org \
    --cc=stuclar@meta.com \
    --cc=yonghong.song@linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®