mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH net-next v2 0/6] net: devmem: support leased RX and TX queues on netdev unregister
@ 2026-11-08  5:58 Mina Almasry
  2026-11-08  5:58 ` [PATCH net-next v2 1/6] net: devmem: pass net_device pointer to net_devmem_lock_dev() Mina Almasry
                   ` (5 more replies)
  0 siblings, 6 replies; 12+ messages in thread
From: Mina Almasry @ 2026-11-08  5:58 UTC (permalink / raw)
  To: netdev
  Cc: Mina Almasry, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Shuah Khan, linux-kselftest,
	linux-kernel, Daniel Borkmann, Nikolay Aleksandrov, David Wei,
	Stanislav Fomichev, Bobby Eshleman, Dragos Tatulea

Support leased RX and TX devmem bindings when either the virtual device
(e.g. netkit) or the physical backing net_device is unregistered:

1. Pass struct net_device **dev_ptr to net_devmem_lock_dev() so callers
   can lock either binding->map_dev or binding->bind_dev.
2. Rename binding->dev and binding->vdev to binding->map_dev (physical
   DMA device) and binding->bind_dev (device passed to bind-rx/bind-tx).
3. Guard __netif_mp_open_rxq() and __netif_mp_close_rxq() against an
   unregistering physical device reached through a queue lease.
4. Always populate bind_dev on RX and TX bindings and handle teardown
   and netdev unregistration of both bind_dev and map_dev.
5. Resolve the physical backing map_dev in netdev_nl_get_dma_dev() for
   leased RX bindings, lock map_dev around net_devmem_bind_dmabuf() on
   bind-rx when map_dev differs from netdev, assert both devices are
   locked, and check map_dev->reg_state.
6. Add kselftests in nl_netdev.py covering leased RX and TX devmem
   bindings across both virtual-first and physical-first netdev
   unregistration orders.

v2:
- Split patch 3/4 from v1 into 3 smaller commits (patches 3/6..5/6).
- Clear rxq->mp_params and return 0 in __netif_mp_close_rxq() whenever
  dev is already unregistering, avoiding queue reconfiguration on an
  already-shut-down map_dev during cascaded lease teardown (Sashiko).
- Drop unnecessary xa_erase() in net_devmem_unbind_dmabuf() and use a
  local map_dev variable in net_devmem_get_binding() (Stanislav).
- Restore ASSERT_EXCLUSIVE_WRITER(binding->bind_dev) in
  mp_dmabuf_devmem_uninstall(), drop redundant bind_dev->reg_state check
  in net_devmem_bind_dmabuf(), and expand commit message rationale
  (Dragos, Stanislav).
- v1: https://lore.kernel.org/netdev/20261103232659.1340283-1-almasrymina@google.com/


Mina Almasry (6):
  net: devmem: pass net_device pointer to net_devmem_lock_dev()
  net: devmem: rename binding dev and vdev to map_dev and bind_dev
  net: guard memory provider RX queue open and close on unregister
  net: devmem: always populate bind_dev and detach both devices on
    unregister
  net: devmem: resolve physical map_dev for leased RX bindings
  selftests: net: add leased devmem RX and TX netdev unregister tests

 net/core/dev.c                           |   2 +-
 net/core/devmem.c                        | 110 ++++++++++---------
 net/core/devmem.h                        |  25 +++--
 net/core/netdev-genl.c                   |  58 ++++++----
 net/core/netdev_rx_queue.c               |  17 ++-
 tools/testing/selftests/net/nl_netdev.py | 132 ++++++++++++++++++++++-
 6 files changed, 253 insertions(+), 91 deletions(-)


base-commit: fa5196392691051963726787944db872420dfa75
-- 
2.56.0.455.g6c5a503b38-goog


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH net-next v2 1/6] net: devmem: pass net_device pointer to net_devmem_lock_dev()
  2026-11-08  5:58 [PATCH net-next v2 0/6] net: devmem: support leased RX and TX queues on netdev unregister Mina Almasry
@ 2026-11-08  5:58 ` Mina Almasry
  2026-11-08  5:58 ` [PATCH net-next v2 2/6] net: devmem: rename binding dev and vdev to map_dev and bind_dev Mina Almasry
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 12+ messages in thread
From: Mina Almasry @ 2026-11-08  5:58 UTC (permalink / raw)
  To: netdev
  Cc: Mina Almasry, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Shuah Khan, linux-kselftest,
	linux-kernel, Daniel Borkmann, Nikolay Aleksandrov, David Wei,
	Stanislav Fomichev, Bobby Eshleman, Dragos Tatulea

Upcoming changes for leased queue support will track the physical DMA
device and the bound device in separate net_device pointers on struct
net_devmem_dmabuf_binding, with different teardown paths locking
different pointers.

Generalize net_devmem_lock_dev() to take a struct net_device **dev_ptr
instead of struct net_devmem_dmabuf_binding *. No functional change.

Signed-off-by: Mina Almasry <almasrymina@google.com>
Reviewed-by: Dragos Tatulea <dtatulea@nvidia.com>
---
 net/core/devmem.c      | 36 ++++++++++++++++++------------------
 net/core/devmem.h      |  4 ++--
 net/core/netdev-genl.c |  2 +-
 3 files changed, 21 insertions(+), 21 deletions(-)

diff --git a/net/core/devmem.c b/net/core/devmem.c
index 5c7d9640e9d14..f164c90e90b3b 100644
--- a/net/core/devmem.c
+++ b/net/core/devmem.c
@@ -85,39 +85,39 @@ net_devmem_dmabuf_binding_unmap(struct net_devmem_dmabuf_binding *binding)
 }
 
 /**
- * net_devmem_lock_dev() - lock a binding's net_device if still alive
- * @binding:		devmem binding whose @dev pointer to lock
+ * net_devmem_lock_dev() - lock a binding's net_device pointer if still alive
+ * @dev_ptr:		pointer to the net_device field on a devmem binding
  * @dev_tracker:	reftracker for the held net_device reference
  *
- * Safely dereference @binding->dev, acquire netdev_lock() on the device, and
- * verify that @binding->dev has not been cleared while waiting for the lock.
+ * Safely dereference @dev_ptr, acquire netdev_lock() on the device, and
+ * verify that *@dev_ptr has not been cleared while waiting for the lock.
  *
- * Invariant: @binding->dev is set once at bind time and is only ever cleared to
- * NULL while holding netdev_lock(dev) (in mp_dmabuf_devmem_uninstall(),
- * net_devmem_unbind_dmabuf(), or net_devmem_dev_unregister() during
- * NETDEV_UNREGISTER before synchronize_net() and netdev_run_todo() free the
- * net_device).
+ * Invariant: *@dev_ptr is set once at bind time and is only ever cleared to
+ * NULL while holding netdev_lock() on that device (in
+ * mp_dmabuf_devmem_uninstall(), net_devmem_unbind_dmabuf(), or
+ * net_devmem_dev_unregister() during NETDEV_UNREGISTER before
+ * synchronize_net() and netdev_run_todo() free the net_device).
  *
- * Taking rcu_read_lock() guarantees that a non-NULL @dev read from
- * @binding->dev has not yet completed unregister_netdevice_many_notify()
+ * Taking rcu_read_lock() guarantees that a non-NULL device read from
+ * *@dev_ptr has not yet completed unregister_netdevice_many_notify()
  * (which waits for an RCU grace period via synchronize_net() after
  * NETDEV_UNREGISTER before freeing the net_device), so netdev_hold() can
  * safely acquire a reference before sleeping on netdev_lock(). Once
- * netdev_lock(dev) is held, re-checking that READ_ONCE(binding->dev) == dev
- * guarantees that the device has not yet been detached from @binding and
+ * netdev_lock() is held, re-checking that READ_ONCE(*dev_ptr) == dev
+ * guarantees that the device has not yet been detached from the binding and
  * cannot finish unregistering until net_devmem_unlock_dev().
  *
- * Return: The locked and held net_device if @binding->dev is still non-NULL,
+ * Return: The locked and held net_device if *@dev_ptr is still non-NULL,
  * or NULL if already cleared.
  */
 struct net_device *
-net_devmem_lock_dev(struct net_devmem_dmabuf_binding *binding,
+net_devmem_lock_dev(struct net_device **dev_ptr,
 		    netdevice_tracker *dev_tracker)
 {
 	struct net_device *dev;
 
 	rcu_read_lock();
-	dev = READ_ONCE(binding->dev);
+	dev = READ_ONCE(*dev_ptr);
 	if (!dev) {
 		rcu_read_unlock();
 		return NULL;
@@ -126,7 +126,7 @@ net_devmem_lock_dev(struct net_devmem_dmabuf_binding *binding,
 	rcu_read_unlock();
 
 	netdev_lock(dev);
-	if (READ_ONCE(binding->dev) != dev) {
+	if (READ_ONCE(*dev_ptr) != dev) {
 		netdev_unlock(dev);
 		netdev_put(dev, dev_tracker);
 		return NULL;
@@ -157,7 +157,7 @@ void __net_devmem_dmabuf_binding_free(struct work_struct *wq)
 		     binding->area.num_niovs, binding->free_count);
 
 	kvfree(binding->area.niovs);
-	dev = net_devmem_lock_dev(binding, &dev_tracker);
+	dev = net_devmem_lock_dev(&binding->dev, &dev_tracker);
 	net_devmem_dmabuf_binding_unmap(binding);
 	xa_erase(&net_devmem_dmabuf_bindings, binding->id);
 	net_devmem_unlock_dev(dev, &dev_tracker);
diff --git a/net/core/devmem.h b/net/core/devmem.h
index c4f476947dfaa..c0aaba0041ff6 100644
--- a/net/core/devmem.h
+++ b/net/core/devmem.h
@@ -100,7 +100,7 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
 		       struct netlink_ext_ack *extack);
 struct net_devmem_dmabuf_binding *net_devmem_lookup_dmabuf(u32 id);
 struct net_device *
-net_devmem_lock_dev(struct net_devmem_dmabuf_binding *binding,
+net_devmem_lock_dev(struct net_device **dev_ptr,
 		    netdevice_tracker *dev_tracker);
 void net_devmem_unlock_dev(struct net_device *dev,
 			   netdevice_tracker *dev_tracker);
@@ -189,7 +189,7 @@ static inline struct net_devmem_dmabuf_binding *net_devmem_lookup_dmabuf(u32 id)
 }
 
 static inline struct net_device *
-net_devmem_lock_dev(struct net_devmem_dmabuf_binding *binding,
+net_devmem_lock_dev(struct net_device **dev_ptr,
 		    netdevice_tracker *dev_tracker)
 {
 	return NULL;
diff --git a/net/core/netdev-genl.c b/net/core/netdev-genl.c
index 9c5bf17b04e57..2826f7e328340 100644
--- a/net/core/netdev-genl.c
+++ b/net/core/netdev-genl.c
@@ -1476,7 +1476,7 @@ void netdev_nl_sock_priv_destroy(struct netdev_nl_sock *priv)
 
 	mutex_lock(&priv->lock);
 	list_for_each_entry_safe(binding, temp, &priv->bindings, list) {
-		dev = net_devmem_lock_dev(binding, &dev_tracker);
+		dev = net_devmem_lock_dev(&binding->dev, &dev_tracker);
 		net_devmem_unbind_dmabuf(binding);
 		net_devmem_unlock_dev(dev, &dev_tracker);
 	}
-- 
2.56.0.455.g6c5a503b38-goog


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH net-next v2 2/6] net: devmem: rename binding dev and vdev to map_dev and bind_dev
  2026-11-08  5:58 [PATCH net-next v2 0/6] net: devmem: support leased RX and TX queues on netdev unregister Mina Almasry
  2026-11-08  5:58 ` [PATCH net-next v2 1/6] net: devmem: pass net_device pointer to net_devmem_lock_dev() Mina Almasry
@ 2026-11-08  5:58 ` Mina Almasry
  2026-11-08  5:58 ` [PATCH net-next v2 3/6] net: guard memory provider RX queue open and close on unregister Mina Almasry
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 12+ messages in thread
From: Mina Almasry @ 2026-11-08  5:58 UTC (permalink / raw)
  To: netdev
  Cc: Mina Almasry, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Shuah Khan, linux-kselftest,
	linux-kernel, Daniel Borkmann, Nikolay Aleksandrov, David Wei,
	Stanislav Fomichev, Bobby Eshleman, Dragos Tatulea

struct net_devmem_dmabuf_binding currently tracks two device pointers
under the names 'dev' and 'vdev':

- 'dev' is the physical net_device whose dma_dev holds the DMA mapping.
- 'vdev' is the virtual net_device the user bound to when using leased
  queues.

Rename 'dev' to 'map_dev' and 'vdev' to 'bind_dev' (typed as struct
net_device *), and rename the matching local variable in
netdev_nl_bind_tx_doit() from 'bind_dev' to 'map_dev' to match. No
functional change.

Signed-off-by: Mina Almasry <almasrymina@google.com>
Reviewed-by: Dragos Tatulea <dtatulea@nvidia.com>
---
 net/core/dev.c         |  2 +-
 net/core/devmem.c      | 38 ++++++++++++++++++++------------------
 net/core/devmem.h      | 17 +++++++++--------
 net/core/netdev-genl.c | 31 +++++++++++++++----------------
 4 files changed, 45 insertions(+), 43 deletions(-)

diff --git a/net/core/dev.c b/net/core/dev.c
index 3b60b15e2175e..4b8d656207d9c 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -4186,7 +4186,7 @@ static struct sk_buff *validate_xmit_unreadable_skb(struct sk_buff *skb,
 		niov = netmem_to_net_iov(skb_frag_netmem(&shinfo->frags[0]));
 		if (net_is_devmem_iov(niov) &&
 		    (net_devmem_iov_binding(niov)->direction != DMA_TO_DEVICE ||
-		     READ_ONCE(net_devmem_iov_binding(niov)->dev) != dev))
+		     READ_ONCE(net_devmem_iov_binding(niov)->map_dev) != dev))
 			goto out_free;
 	}
 
diff --git a/net/core/devmem.c b/net/core/devmem.c
index f164c90e90b3b..6446355e787d4 100644
--- a/net/core/devmem.c
+++ b/net/core/devmem.c
@@ -157,7 +157,7 @@ void __net_devmem_dmabuf_binding_free(struct work_struct *wq)
 		     binding->area.num_niovs, binding->free_count);
 
 	kvfree(binding->area.niovs);
-	dev = net_devmem_lock_dev(&binding->dev, &dev_tracker);
+	dev = net_devmem_lock_dev(&binding->map_dev, &dev_tracker);
 	net_devmem_dmabuf_binding_unmap(binding);
 	xa_erase(&net_devmem_dmabuf_bindings, binding->id);
 	net_devmem_unlock_dev(dev, &dev_tracker);
@@ -227,13 +227,14 @@ void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding)
 
 		rxq_idx = get_netdev_rx_queue_index(rxq);
 
-		if (netif_mp_close_rxq(binding->dev, rxq_idx, &mp_params))
+		if (netif_mp_close_rxq(binding->map_dev, rxq_idx, &mp_params))
 			busy = true;
 	}
 
-	if (binding->direction == DMA_FROM_DEVICE && binding->dev && !busy) {
+	if (binding->direction == DMA_FROM_DEVICE && binding->map_dev &&
+	    !busy) {
 		net_devmem_dmabuf_binding_unmap(binding);
-		WRITE_ONCE(binding->dev, NULL);
+		WRITE_ONCE(binding->map_dev, NULL);
 	}
 
 	percpu_ref_kill(&binding->ref);
@@ -252,16 +253,16 @@ void net_devmem_dev_unregister(struct net_device *dev)
 	xa_lock(&net_devmem_dmabuf_bindings);
 	xa_for_each(&net_devmem_dmabuf_bindings, xa_idx, binding) {
 		if (binding->direction == DMA_FROM_DEVICE) {
-			if (READ_ONCE(binding->dev) != dev ||
+			if (READ_ONCE(binding->map_dev) != dev ||
 			    xa_get_mark(&net_devmem_dmabuf_bindings, xa_idx,
 					NET_DEVMEM_BINDING_LIVE))
 				continue;
 		} else {
-			if (READ_ONCE(binding->vdev) == dev)
-				WRITE_ONCE(binding->vdev, NULL);
-			if (READ_ONCE(binding->dev) != dev)
+			if (READ_ONCE(binding->bind_dev) == dev)
+				WRITE_ONCE(binding->bind_dev, NULL);
+			if (READ_ONCE(binding->map_dev) != dev)
 				continue;
-			WRITE_ONCE(binding->vdev, NULL);
+			WRITE_ONCE(binding->bind_dev, NULL);
 			__xa_clear_mark(&net_devmem_dmabuf_bindings, xa_idx,
 					NET_DEVMEM_BINDING_LIVE);
 		}
@@ -270,7 +271,7 @@ void net_devmem_dev_unregister(struct net_device *dev)
 		if (!netif_is_queue_leasee(dev))
 			net_devmem_dmabuf_binding_unmap(binding);
 		xa_lock(&net_devmem_dmabuf_bindings);
-		WRITE_ONCE(binding->dev, NULL);
+		WRITE_ONCE(binding->map_dev, NULL);
 	}
 	xa_unlock(&net_devmem_dmabuf_bindings);
 }
@@ -304,7 +305,8 @@ int net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx,
 }
 
 struct net_devmem_dmabuf_binding *
-net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
+net_devmem_bind_dmabuf(struct net_device *map_dev,
+		       struct net_device *bind_dev,
 		       struct device *dma_dev,
 		       enum dma_data_direction direction,
 		       unsigned int dmabuf_fd, unsigned int niov_shift,
@@ -332,14 +334,14 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
 		return ERR_CAST(dmabuf);
 
 	binding = kzalloc_node(sizeof(*binding), GFP_KERNEL,
-			       dev_to_node(&dev->dev));
+			       dev_to_node(&map_dev->dev));
 	if (!binding) {
 		err = -ENOMEM;
 		goto err_put_dmabuf;
 	}
 
-	binding->dev = dev;
-	binding->vdev = vdev;
+	binding->map_dev = map_dev;
+	binding->bind_dev = bind_dev;
 	write_pnet(&binding->net, sock_net(nl_sk));
 	binding->uid = sk_uid(nl_sk);
 	binding->niov_shift = niov_shift;
@@ -559,8 +561,8 @@ struct net_devmem_dmabuf_binding *net_devmem_get_binding(struct sock *sk,
 	 */
 	dst_dev = dst_dev_rcu(dst);
 	if (unlikely(!dst_dev) ||
-	    unlikely(dst_dev != READ_ONCE(binding->dev) &&
-		     dst_dev != READ_ONCE(binding->vdev))) {
+	    unlikely(dst_dev != READ_ONCE(binding->map_dev) &&
+		     dst_dev != READ_ONCE(binding->bind_dev))) {
 		err = -ENODEV;
 		goto out_unlock;
 	}
@@ -684,8 +686,8 @@ static void mp_dmabuf_devmem_uninstall(void *mp_priv,
 		if (xa_empty(&binding->bound_rxqs)) {
 			if (!rxq->lease)
 				net_devmem_dmabuf_binding_unmap(binding);
-			ASSERT_EXCLUSIVE_WRITER(binding->dev);
-			WRITE_ONCE(binding->dev, NULL);
+			ASSERT_EXCLUSIVE_WRITER(binding->map_dev);
+			WRITE_ONCE(binding->map_dev, NULL);
 		}
 		break;
 	}
diff --git a/net/core/devmem.h b/net/core/devmem.h
index c0aaba0041ff6..9dcb629e10bfd 100644
--- a/net/core/devmem.h
+++ b/net/core/devmem.h
@@ -22,14 +22,13 @@ struct net_devmem_dmabuf_binding {
 	struct dma_buf_attachment *attachment;
 	struct sg_table *sgt;
 	/* Physical NIC that does the actual DMA for this binding. Set at bind
-	 * time and cleared to NULL under netdev_lock(dev).
+	 * time and cleared to NULL under netdev_lock(map_dev).
 	 */
-	struct net_device *dev;
-	/* Opaque cookie identifying the virtual device (e.g. netkit) the user
-	 * called bind-tx on. Used only for pointer comparison via READ_ONCE()
-	 * and cleared with WRITE_ONCE() on unregistration. Never dereferenced.
+	struct net_device *map_dev;
+	/* Device the user called bind-rx or bind-tx on. Set at bind time and
+	 * cleared to NULL on unregistration.
 	 */
-	void *vdev;
+	struct net_device *bind_dev;
 	possible_net_t net;
 	kuid_t uid;
 	/* Serialize net_devmem_dmabuf_binding_unmap() on attachment and sgt */
@@ -91,7 +90,8 @@ struct net_devmem_dmabuf_binding {
 #if defined(CONFIG_NET_DEVMEM)
 void __net_devmem_dmabuf_binding_free(struct work_struct *wq);
 struct net_devmem_dmabuf_binding *
-net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
+net_devmem_bind_dmabuf(struct net_device *map_dev,
+		       struct net_device *bind_dev,
 		       struct device *dma_dev,
 		       enum dma_data_direction direction,
 		       unsigned int dmabuf_fd, unsigned int niov_shift,
@@ -171,7 +171,8 @@ static inline void net_devmem_net_iov_dec_ref(struct net_iov *niov)
 }
 
 static inline struct net_devmem_dmabuf_binding *
-net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
+net_devmem_bind_dmabuf(struct net_device *map_dev,
+		       struct net_device *bind_dev,
 		       struct device *dma_dev,
 		       enum dma_data_direction direction,
 		       unsigned int dmabuf_fd,
diff --git a/net/core/netdev-genl.c b/net/core/netdev-genl.c
index 2826f7e328340..962d7a8c75e9a 100644
--- a/net/core/netdev-genl.c
+++ b/net/core/netdev-genl.c
@@ -1195,9 +1195,8 @@ netdev_find_netmem_tx_dev(struct net_device *dev)
 int netdev_nl_bind_tx_doit(struct sk_buff *skb, struct genl_info *info)
 {
 	struct net_devmem_dmabuf_binding *binding;
-	struct net_device *bind_dev;
+	struct net_device *netdev, *map_dev;
 	struct netdev_nl_sock *priv;
-	struct net_device *netdev;
 	struct device *dma_dev;
 	u32 ifindex, dmabuf_fd;
 	struct sk_buff *rsp;
@@ -1245,27 +1244,27 @@ int netdev_nl_bind_tx_doit(struct sk_buff *skb, struct genl_info *info)
 		goto err_unlock_netdev;
 	}
 
-	bind_dev = netdev_find_netmem_tx_dev(netdev);
-	if (!bind_dev) {
+	map_dev = netdev_find_netmem_tx_dev(netdev);
+	if (!map_dev) {
 		err = -EOPNOTSUPP;
 		NL_SET_ERR_MSG(info->extack,
 			       "No DMA-capable device found for netmem TX");
 		goto err_unlock_netdev;
 	}
 
-	if (bind_dev != netdev)
-		netdev_lock(bind_dev);
+	if (map_dev != netdev)
+		netdev_lock(map_dev);
 
-	dma_dev = netdev_queue_get_dma_dev(bind_dev, 0, NETDEV_QUEUE_TYPE_TX);
+	dma_dev = netdev_queue_get_dma_dev(map_dev, 0, NETDEV_QUEUE_TYPE_TX);
 
-	binding = net_devmem_bind_dmabuf(bind_dev,
-					 bind_dev != netdev ? netdev : NULL,
+	binding = net_devmem_bind_dmabuf(map_dev,
+					 map_dev != netdev ? netdev : NULL,
 					 dma_dev, DMA_TO_DEVICE, dmabuf_fd,
 					 PAGE_SHIFT, priv, NETLINK_CB(skb).sk,
 					 info->extack);
 	if (IS_ERR(binding)) {
 		err = PTR_ERR(binding);
-		goto err_unlock_bind_dev;
+		goto err_unlock_map_dev;
 	}
 
 	/* rsp was allocated large enough */
@@ -1273,16 +1272,16 @@ int netdev_nl_bind_tx_doit(struct sk_buff *skb, struct genl_info *info)
 
 	genlmsg_end(rsp, hdr);
 
-	if (bind_dev != netdev)
-		netdev_unlock(bind_dev);
+	if (map_dev != netdev)
+		netdev_unlock(map_dev);
 	netdev_unlock(netdev);
 	mutex_unlock(&priv->lock);
 
 	return genlmsg_reply(rsp, info);
 
-err_unlock_bind_dev:
-	if (bind_dev != netdev)
-		netdev_unlock(bind_dev);
+err_unlock_map_dev:
+	if (map_dev != netdev)
+		netdev_unlock(map_dev);
 err_unlock_netdev:
 	netdev_unlock(netdev);
 err_unlock_sock:
@@ -1476,7 +1475,7 @@ void netdev_nl_sock_priv_destroy(struct netdev_nl_sock *priv)
 
 	mutex_lock(&priv->lock);
 	list_for_each_entry_safe(binding, temp, &priv->bindings, list) {
-		dev = net_devmem_lock_dev(&binding->dev, &dev_tracker);
+		dev = net_devmem_lock_dev(&binding->map_dev, &dev_tracker);
 		net_devmem_unbind_dmabuf(binding);
 		net_devmem_unlock_dev(dev, &dev_tracker);
 	}
-- 
2.56.0.455.g6c5a503b38-goog


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH net-next v2 3/6] net: guard memory provider RX queue open and close on unregister
  2026-11-08  5:58 [PATCH net-next v2 0/6] net: devmem: support leased RX and TX queues on netdev unregister Mina Almasry
  2026-11-08  5:58 ` [PATCH net-next v2 1/6] net: devmem: pass net_device pointer to net_devmem_lock_dev() Mina Almasry
  2026-11-08  5:58 ` [PATCH net-next v2 2/6] net: devmem: rename binding dev and vdev to map_dev and bind_dev Mina Almasry
@ 2026-11-08  5:58 ` Mina Almasry
  2026-11-09  6:00   ` netdev-bot+sashiko
  2026-11-09 19:50   ` Stanislav Fomichev
  2026-11-08  5:58 ` [PATCH net-next v2 4/6] net: devmem: always populate bind_dev and detach both devices " Mina Almasry
                   ` (2 subsequent siblings)
  5 siblings, 2 replies; 12+ messages in thread
From: Mina Almasry @ 2026-11-08  5:58 UTC (permalink / raw)
  To: netdev
  Cc: Mina Almasry, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Shuah Khan, linux-kselftest,
	linux-kernel, Daniel Borkmann, Nikolay Aleksandrov, David Wei,
	Stanislav Fomichev, Bobby Eshleman, Dragos Tatulea

When a memory provider RX queue is leased to a virtual netdev,
netif_mp_open_rxq() and netif_mp_close_rxq() redirect through
rxq->lease and lock the physical backing netdev directly rather than via
netdev_get_by_index_lock(). This raises two issues during netdev
unregistration:

1. In __netif_mp_open_rxq(), the physical backing netdev may already be
   in NETREG_UNREGISTERING (after dev_shutdown() and
   dev_memory_provider_uninstall() have run on it, before cascaded
   unregistration of the virtual leasee tears down the lease). Reject
   opening an RX queue if dev->reg_state != NETREG_REGISTERED.
2. In __netif_mp_close_rxq(), when the physical backing netdev
   unregisters before the virtual leasee, dev_memory_provider_uninstall()
   on the physical netdev does not uninstall the virtual queues, so
   phys_rxq->mp_params remains populated until the virtual leasee's
   netif_rxq_cleanup_unlease() or socket teardown runs. At that point
   the physical netdev has already gone through dev_shutdown() and must
   not call netdev_rx_queue_reconfig(). When dev->reg_state >
   NETREG_REGISTERED, clear rxq->mp_params and return 0.

Signed-off-by: Mina Almasry <almasrymina@google.com>
---
 net/core/netdev_rx_queue.c | 17 +++++++++++++----
 1 file changed, 13 insertions(+), 4 deletions(-)

diff --git a/net/core/netdev_rx_queue.c b/net/core/netdev_rx_queue.c
index a7261fedd5e76..85ae5abf16b8e 100644
--- a/net/core/netdev_rx_queue.c
+++ b/net/core/netdev_rx_queue.c
@@ -193,6 +193,14 @@ static int __netif_mp_open_rxq(struct net_device *dev, unsigned int rxq_idx,
 	struct netdev_rx_queue *rxq;
 	int ret;
 
+	/* A leased physical dev is locked via rxq->lease rather than
+	 * netdev_get_by_index_lock(), so check reg_state under its lock.
+	 */
+	if (dev->reg_state != NETREG_REGISTERED) {
+		NL_SET_ERR_MSG(extack, "Device is being unregistered");
+		return -ENODEV;
+	}
+
 	if (!qops)
 		return -EOPNOTSUPP;
 
@@ -284,12 +292,13 @@ static int __netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,
 
 	rxq = __netif_get_rx_queue(dev, rxq_idx);
 
-	/* Callers holding a netdev ref may get here after we already
-	 * went thru shutdown via dev_memory_provider_uninstall().
+	/* Callers holding a netdev ref or tearing down a queue lease may get
+	 * here after dev already went through shutdown.
 	 */
-	if (dev->reg_state > NETREG_REGISTERED &&
-	    !rxq->mp_params.mp_ops)
+	if (dev->reg_state > NETREG_REGISTERED) {
+		memset(&rxq->mp_params, 0, sizeof(rxq->mp_params));
 		return 0;
+	}
 
 	if (WARN_ON_ONCE(rxq->mp_params.mp_ops != old_p->mp_ops ||
 			 rxq->mp_params.mp_priv != old_p->mp_priv))
-- 
2.56.0.455.g6c5a503b38-goog


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH net-next v2 4/6] net: devmem: always populate bind_dev and detach both devices on unregister
  2026-11-08  5:58 [PATCH net-next v2 0/6] net: devmem: support leased RX and TX queues on netdev unregister Mina Almasry
                   ` (2 preceding siblings ...)
  2026-11-08  5:58 ` [PATCH net-next v2 3/6] net: guard memory provider RX queue open and close on unregister Mina Almasry
@ 2026-11-08  5:58 ` Mina Almasry
  2026-11-08  5:58 ` [PATCH net-next v2 5/6] net: devmem: resolve physical map_dev for leased RX bindings Mina Almasry
  2026-11-08  5:58 ` [PATCH net-next v2 6/6] selftests: net: add leased devmem RX and TX netdev unregister tests Mina Almasry
  5 siblings, 0 replies; 12+ messages in thread
From: Mina Almasry @ 2026-11-08  5:58 UTC (permalink / raw)
  To: netdev
  Cc: Mina Almasry, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Shuah Khan, linux-kselftest,
	linux-kernel, Daniel Borkmann, Nikolay Aleksandrov, David Wei,
	Stanislav Fomichev, Bobby Eshleman, Dragos Tatulea

Previously, bind_dev was only populated when bind-tx was called on a
virtual netdev (where map_dev != netdev), and socket teardown, queue
uninstall, and net_devmem_dev_unregister() did not uniformly track both
bind_dev and map_dev across RX and TX bindings.

Always populate bind_dev with the net_device the user called bind-rx or
bind-tx on (equal to map_dev for direct bindings, or the virtual device
for leased bindings), and handle teardown and unregistration of both
pointers in either order:

- Lock &binding->bind_dev during socket teardown in
  netdev_nl_sock_priv_destroy() and pass binding->bind_dev to
  netif_mp_close_rxq() in net_devmem_unbind_dmabuf(), because bound_rxqs
  stores queues belonging to bind_dev and netif_mp_close_rxq() expects
  the leasee device so it can redirect through the lease and lock
  map_dev.
- Require binding->map_dev to remain non-NULL in
  net_devmem_get_binding() so TX lookups fail once map_dev unregisters
  even if bind_dev is still registered.
- Clear binding->bind_dev in mp_dmabuf_devmem_uninstall() once
  bound_rxqs is empty, leaving leased bindings mapped until the binding
  is freed or map_dev is unregistered, because
  netif_rxq_cleanup_unlease() runs while map_dev may still be up and
  ignores __netif_mp_close_rxq() failures.
- In net_devmem_dev_unregister(dev), if binding->bind_dev == dev, clear
  NET_DEVMEM_BINDING_LIVE to stop new lookups, and clear
  binding->bind_dev only when bound_rxqs is empty or dev is not a queue
  leasee so leased RX bindings keep binding->bind_dev valid until
  ndo_uninit's netif_rxq_cleanup_unlease() uninstalls the virtual
  queues. If binding->map_dev == dev, clear NET_DEVMEM_BINDING_LIVE,
  unmap the dmabuf while map_dev is still valid, and clear
  binding->map_dev.

Signed-off-by: Mina Almasry <almasrymina@google.com>
---
 net/core/devmem.c      | 62 ++++++++++++++++++++----------------------
 net/core/devmem.h      |  8 ++++--
 net/core/netdev-genl.c | 16 +++++------
 3 files changed, 43 insertions(+), 43 deletions(-)

diff --git a/net/core/devmem.c b/net/core/devmem.c
index 6446355e787d4..bb785a871d1c4 100644
--- a/net/core/devmem.c
+++ b/net/core/devmem.c
@@ -86,7 +86,7 @@ net_devmem_dmabuf_binding_unmap(struct net_devmem_dmabuf_binding *binding)
 
 /**
  * net_devmem_lock_dev() - lock a binding's net_device pointer if still alive
- * @dev_ptr:		pointer to the net_device field on a devmem binding
+ * @dev_ptr:		pointer to &binding->map_dev or &binding->bind_dev
  * @dev_tracker:	reftracker for the held net_device reference
  *
  * Safely dereference @dev_ptr, acquire netdev_lock() on the device, and
@@ -95,17 +95,18 @@ net_devmem_dmabuf_binding_unmap(struct net_devmem_dmabuf_binding *binding)
  * Invariant: *@dev_ptr is set once at bind time and is only ever cleared to
  * NULL while holding netdev_lock() on that device (in
  * mp_dmabuf_devmem_uninstall(), net_devmem_unbind_dmabuf(), or
- * net_devmem_dev_unregister() during NETDEV_UNREGISTER before
- * synchronize_net() and netdev_run_todo() free the net_device).
+ * net_devmem_dev_unregister(), all before synchronize_net() and
+ * netdev_run_todo() free the net_device).
  *
  * Taking rcu_read_lock() guarantees that a non-NULL device read from
  * *@dev_ptr has not yet completed unregister_netdevice_many_notify()
  * (which waits for an RCU grace period via synchronize_net() after
- * NETDEV_UNREGISTER before freeing the net_device), so netdev_hold() can
- * safely acquire a reference before sleeping on netdev_lock(). Once
- * netdev_lock() is held, re-checking that READ_ONCE(*dev_ptr) == dev
- * guarantees that the device has not yet been detached from the binding and
- * cannot finish unregistering until net_devmem_unlock_dev().
+ * NETDEV_UNREGISTER and ndo_uninit before freeing the net_device), so
+ * netdev_hold() can safely acquire a reference before sleeping on
+ * netdev_lock(). Once netdev_lock() is held, re-checking that
+ * READ_ONCE(*dev_ptr) == dev guarantees that the device has not yet been
+ * detached from the binding and cannot finish unregistering until
+ * net_devmem_unlock_dev().
  *
  * Return: The locked and held net_device if *@dev_ptr is still non-NULL,
  * or NULL if already cleared.
@@ -227,14 +228,14 @@ void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding)
 
 		rxq_idx = get_netdev_rx_queue_index(rxq);
 
-		if (netif_mp_close_rxq(binding->map_dev, rxq_idx, &mp_params))
+		if (netif_mp_close_rxq(binding->bind_dev, rxq_idx, &mp_params))
 			busy = true;
 	}
 
-	if (binding->direction == DMA_FROM_DEVICE && binding->map_dev &&
-	    !busy) {
-		net_devmem_dmabuf_binding_unmap(binding);
-		WRITE_ONCE(binding->map_dev, NULL);
+	if (binding->bind_dev) {
+		if (binding->direction == DMA_FROM_DEVICE && !busy)
+			net_devmem_dmabuf_binding_unmap(binding);
+		WRITE_ONCE(binding->bind_dev, NULL);
 	}
 
 	percpu_ref_kill(&binding->ref);
@@ -252,24 +253,20 @@ void net_devmem_dev_unregister(struct net_device *dev)
 
 	xa_lock(&net_devmem_dmabuf_bindings);
 	xa_for_each(&net_devmem_dmabuf_bindings, xa_idx, binding) {
-		if (binding->direction == DMA_FROM_DEVICE) {
-			if (READ_ONCE(binding->map_dev) != dev ||
-			    xa_get_mark(&net_devmem_dmabuf_bindings, xa_idx,
-					NET_DEVMEM_BINDING_LIVE))
-				continue;
-		} else {
-			if (READ_ONCE(binding->bind_dev) == dev)
-				WRITE_ONCE(binding->bind_dev, NULL);
-			if (READ_ONCE(binding->map_dev) != dev)
-				continue;
-			WRITE_ONCE(binding->bind_dev, NULL);
+		if (READ_ONCE(binding->bind_dev) == dev) {
 			__xa_clear_mark(&net_devmem_dmabuf_bindings, xa_idx,
 					NET_DEVMEM_BINDING_LIVE);
+			if (xa_empty(&binding->bound_rxqs) ||
+			    !netif_is_queue_leasee(dev))
+				WRITE_ONCE(binding->bind_dev, NULL);
 		}
+		if (READ_ONCE(binding->map_dev) != dev)
+			continue;
 
+		__xa_clear_mark(&net_devmem_dmabuf_bindings, xa_idx,
+				NET_DEVMEM_BINDING_LIVE);
 		xa_unlock(&net_devmem_dmabuf_bindings);
-		if (!netif_is_queue_leasee(dev))
-			net_devmem_dmabuf_binding_unmap(binding);
+		net_devmem_dmabuf_binding_unmap(binding);
 		xa_lock(&net_devmem_dmabuf_bindings);
 		WRITE_ONCE(binding->map_dev, NULL);
 	}
@@ -529,7 +526,7 @@ struct net_devmem_dmabuf_binding *net_devmem_get_binding(struct sock *sk,
 							 unsigned int dmabuf_id)
 {
 	struct net_devmem_dmabuf_binding *binding;
-	struct net_device *dst_dev;
+	struct net_device *dst_dev, *map_dev;
 	struct dst_entry *dst;
 	int err = 0;
 
@@ -560,9 +557,10 @@ struct net_devmem_dmabuf_binding *net_devmem_get_binding(struct sock *sk,
 	 * net_device.
 	 */
 	dst_dev = dst_dev_rcu(dst);
-	if (unlikely(!dst_dev) ||
-	    unlikely(dst_dev != READ_ONCE(binding->map_dev) &&
-		     dst_dev != READ_ONCE(binding->bind_dev))) {
+	map_dev = READ_ONCE(binding->map_dev);
+	if (unlikely(!dst_dev || !map_dev ||
+		     (dst_dev != map_dev &&
+		      dst_dev != READ_ONCE(binding->bind_dev)))) {
 		err = -ENODEV;
 		goto out_unlock;
 	}
@@ -686,8 +684,8 @@ static void mp_dmabuf_devmem_uninstall(void *mp_priv,
 		if (xa_empty(&binding->bound_rxqs)) {
 			if (!rxq->lease)
 				net_devmem_dmabuf_binding_unmap(binding);
-			ASSERT_EXCLUSIVE_WRITER(binding->map_dev);
-			WRITE_ONCE(binding->map_dev, NULL);
+			ASSERT_EXCLUSIVE_WRITER(binding->bind_dev);
+			WRITE_ONCE(binding->bind_dev, NULL);
 		}
 		break;
 	}
diff --git a/net/core/devmem.h b/net/core/devmem.h
index 9dcb629e10bfd..ff431e5e398e0 100644
--- a/net/core/devmem.h
+++ b/net/core/devmem.h
@@ -25,8 +25,10 @@ struct net_devmem_dmabuf_binding {
 	 * time and cleared to NULL under netdev_lock(map_dev).
 	 */
 	struct net_device *map_dev;
-	/* Device the user called bind-rx or bind-tx on. Set at bind time and
-	 * cleared to NULL on unregistration.
+	/* Device the user called bind-rx or bind-tx on. Equal to map_dev for
+	 * direct bindings, or the virtual device (e.g. netkit) for leased
+	 * bindings. Set at bind time and cleared to NULL under
+	 * netdev_lock(bind_dev).
 	 */
 	struct net_device *bind_dev;
 	possible_net_t net;
@@ -44,7 +46,7 @@ struct net_devmem_dmabuf_binding {
 	 * those refs are dropped and the binding is no longer desired or in
 	 * use. RX bindings are unmapped earlier when their queues are closed or
 	 * uninstalled, unless a leased or busy queue may still use the binding.
-	 * TX bindings are also unmapped earlier if their device is
+	 * Both RX and TX bindings are also unmapped earlier if map_dev is
 	 * unregistered.
 	 *
 	 * net_devmem_net_iov_inc_ref() on dmabuf net_iovs will increment this
diff --git a/net/core/netdev-genl.c b/net/core/netdev-genl.c
index 962d7a8c75e9a..9e56c26fc65f9 100644
--- a/net/core/netdev-genl.c
+++ b/net/core/netdev-genl.c
@@ -1110,9 +1110,10 @@ int netdev_nl_bind_rx_doit(struct sk_buff *skb, struct genl_info *info)
 		goto err_rxq_bitmap;
 	}
 
-	binding = net_devmem_bind_dmabuf(netdev, NULL, dma_dev, DMA_FROM_DEVICE,
-					 dmabuf_fd, niov_shift, priv,
-					 NETLINK_CB(skb).sk, info->extack);
+	binding = net_devmem_bind_dmabuf(netdev, netdev, dma_dev,
+					 DMA_FROM_DEVICE, dmabuf_fd,
+					 niov_shift, priv, NETLINK_CB(skb).sk,
+					 info->extack);
 	if (IS_ERR(binding)) {
 		err = PTR_ERR(binding);
 		goto err_rxq_bitmap;
@@ -1257,10 +1258,9 @@ int netdev_nl_bind_tx_doit(struct sk_buff *skb, struct genl_info *info)
 
 	dma_dev = netdev_queue_get_dma_dev(map_dev, 0, NETDEV_QUEUE_TYPE_TX);
 
-	binding = net_devmem_bind_dmabuf(map_dev,
-					 map_dev != netdev ? netdev : NULL,
-					 dma_dev, DMA_TO_DEVICE, dmabuf_fd,
-					 PAGE_SHIFT, priv, NETLINK_CB(skb).sk,
+	binding = net_devmem_bind_dmabuf(map_dev, netdev, dma_dev,
+					 DMA_TO_DEVICE, dmabuf_fd, PAGE_SHIFT,
+					 priv, NETLINK_CB(skb).sk,
 					 info->extack);
 	if (IS_ERR(binding)) {
 		err = PTR_ERR(binding);
@@ -1475,7 +1475,7 @@ void netdev_nl_sock_priv_destroy(struct netdev_nl_sock *priv)
 
 	mutex_lock(&priv->lock);
 	list_for_each_entry_safe(binding, temp, &priv->bindings, list) {
-		dev = net_devmem_lock_dev(&binding->map_dev, &dev_tracker);
+		dev = net_devmem_lock_dev(&binding->bind_dev, &dev_tracker);
 		net_devmem_unbind_dmabuf(binding);
 		net_devmem_unlock_dev(dev, &dev_tracker);
 	}
-- 
2.56.0.455.g6c5a503b38-goog


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH net-next v2 5/6] net: devmem: resolve physical map_dev for leased RX bindings
  2026-11-08  5:58 [PATCH net-next v2 0/6] net: devmem: support leased RX and TX queues on netdev unregister Mina Almasry
                   ` (3 preceding siblings ...)
  2026-11-08  5:58 ` [PATCH net-next v2 4/6] net: devmem: always populate bind_dev and detach both devices " Mina Almasry
@ 2026-11-08  5:58 ` Mina Almasry
  2026-11-09  6:00   ` netdev-bot+sashiko
  2026-11-09 19:52   ` Stanislav Fomichev
  2026-11-08  5:58 ` [PATCH net-next v2 6/6] selftests: net: add leased devmem RX and TX netdev unregister tests Mina Almasry
  5 siblings, 2 replies; 12+ messages in thread
From: Mina Almasry @ 2026-11-08  5:58 UTC (permalink / raw)
  To: netdev
  Cc: Mina Almasry, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Shuah Khan, linux-kselftest,
	linux-kernel, Daniel Borkmann, Nikolay Aleksandrov, David Wei,
	Stanislav Fomichev, Bobby Eshleman, Dragos Tatulea

Previously, netdev_nl_bind_rx_doit() passed netdev as both map_dev and
bind_dev to net_devmem_bind_dmabuf(), so leased RX bindings stored the
virtual netdev in binding->map_dev without tracking the physical backing
netdev at all. Unregistering the physical backing netdev while a leased
RX binding was active failed to detach and unmap the dmabuf.

Return the physical backing net_device from netdev_nl_get_dma_dev() when
the RX queue is leased from another netdev, lock map_dev around
net_devmem_bind_dmabuf() when map_dev != netdev (matching
netdev_nl_bind_tx_doit()), and assert in net_devmem_bind_dmabuf() that
both bind_dev and map_dev are locked.

Because a leased map_dev is reached via rxq->lease->dev and locked with
raw netdev_lock(map_dev) rather than netdev_get_by_index_lock(), it may
already have entered NETREG_UNREGISTERING and completed
net_devmem_dev_unregister(map_dev) before cascaded unregistration of
bind_dev tears down the lease. Reject binding in
net_devmem_bind_dmabuf() if map_dev->reg_state != NETREG_REGISTERED.

Signed-off-by: Mina Almasry <almasrymina@google.com>
---
 net/core/devmem.c      | 12 ++++++++++++
 net/core/netdev-genl.c | 19 ++++++++++++++++---
 2 files changed, 28 insertions(+), 3 deletions(-)

diff --git a/net/core/devmem.c b/net/core/devmem.c
index bb785a871d1c4..2dac581a57e13 100644
--- a/net/core/devmem.c
+++ b/net/core/devmem.c
@@ -321,6 +321,18 @@ net_devmem_bind_dmabuf(struct net_device *map_dev,
 	size_t i;
 	long err;
 
+	netdev_assert_locked(bind_dev);
+	netdev_assert_locked(map_dev);
+
+	/* When map_dev != bind_dev, map_dev is locked via the queue lease
+	 * rather than netdev_get_by_index_lock(), so it may already be
+	 * unregistering before cascaded bind_dev unregistration runs.
+	 */
+	if (map_dev->reg_state != NETREG_REGISTERED) {
+		NL_SET_ERR_MSG(extack, "Device is being unregistered");
+		return ERR_PTR(-ENODEV);
+	}
+
 	if (!dma_dev) {
 		NL_SET_ERR_MSG(extack, "Device doesn't support DMA");
 		return ERR_PTR(-EOPNOTSUPP);
diff --git a/net/core/netdev-genl.c b/net/core/netdev-genl.c
index 9e56c26fc65f9..442362f23fe88 100644
--- a/net/core/netdev-genl.c
+++ b/net/core/netdev-genl.c
@@ -1005,12 +1005,15 @@ static int netdev_nl_read_rxq_bitmap(struct genl_info *info,
 
 static struct device *
 netdev_nl_get_dma_dev(struct net_device *netdev, unsigned long *rxq_bitmap,
+		      struct net_device **map_dev,
 		      struct netlink_ext_ack *extack)
 {
 	struct device *dma_dev = NULL;
 	u32 rxq_idx, prev_rxq_idx;
 
+	*map_dev = netdev;
 	for_each_set_bit(rxq_idx, rxq_bitmap, netdev->real_num_rx_queues) {
+		struct netdev_rx_queue *rxq;
 		struct device *rxq_dma_dev;
 
 		rxq_dma_dev = netdev_queue_get_dma_dev(netdev, rxq_idx,
@@ -1023,6 +1026,11 @@ netdev_nl_get_dma_dev(struct net_device *netdev, unsigned long *rxq_bitmap,
 
 		dma_dev = rxq_dma_dev;
 		prev_rxq_idx = rxq_idx;
+		if (netif_rxq_is_leased(netdev, rxq_idx) &&
+		    netif_is_queue_leasee(netdev)) {
+			rxq = __netif_get_rx_queue(netdev, rxq_idx);
+			*map_dev = rxq->lease->dev;
+		}
 	}
 
 	return dma_dev;
@@ -1032,9 +1040,9 @@ int netdev_nl_bind_rx_doit(struct sk_buff *skb, struct genl_info *info)
 {
 	struct net_devmem_dmabuf_binding *binding;
 	unsigned int niov_shift = PAGE_SHIFT;
+	struct net_device *netdev, *map_dev;
 	u32 ifindex, dmabuf_fd, rxq_idx;
 	struct netdev_nl_sock *priv;
-	struct net_device *netdev;
 	unsigned long *rxq_bitmap;
 	struct device *dma_dev;
 	struct sk_buff *rsp;
@@ -1104,16 +1112,21 @@ int netdev_nl_bind_rx_doit(struct sk_buff *skb, struct genl_info *info)
 	if (err)
 		goto err_rxq_bitmap;
 
-	dma_dev = netdev_nl_get_dma_dev(netdev, rxq_bitmap, info->extack);
+	dma_dev = netdev_nl_get_dma_dev(netdev, rxq_bitmap, &map_dev,
+					info->extack);
 	if (IS_ERR(dma_dev)) {
 		err = PTR_ERR(dma_dev);
 		goto err_rxq_bitmap;
 	}
 
-	binding = net_devmem_bind_dmabuf(netdev, netdev, dma_dev,
+	if (map_dev != netdev)
+		netdev_lock(map_dev);
+	binding = net_devmem_bind_dmabuf(map_dev, netdev, dma_dev,
 					 DMA_FROM_DEVICE, dmabuf_fd,
 					 niov_shift, priv, NETLINK_CB(skb).sk,
 					 info->extack);
+	if (map_dev != netdev)
+		netdev_unlock(map_dev);
 	if (IS_ERR(binding)) {
 		err = PTR_ERR(binding);
 		goto err_rxq_bitmap;
-- 
2.56.0.455.g6c5a503b38-goog


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH net-next v2 6/6] selftests: net: add leased devmem RX and TX netdev unregister tests
  2026-11-08  5:58 [PATCH net-next v2 0/6] net: devmem: support leased RX and TX queues on netdev unregister Mina Almasry
                   ` (4 preceding siblings ...)
  2026-11-08  5:58 ` [PATCH net-next v2 5/6] net: devmem: resolve physical map_dev for leased RX bindings Mina Almasry
@ 2026-11-08  5:58 ` Mina Almasry
  2026-11-09  6:00   ` netdev-bot+sashiko
  5 siblings, 1 reply; 12+ messages in thread
From: Mina Almasry @ 2026-11-08  5:58 UTC (permalink / raw)
  To: netdev
  Cc: Mina Almasry, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Shuah Khan, linux-kselftest,
	linux-kernel, Daniel Borkmann, Nikolay Aleksandrov, David Wei,
	Stanislav Fomichev, Bobby Eshleman, Dragos Tatulea

Extend nl_netdev.py to cover netdev unregistration while devmem RX and
TX bindings are active on a virtual netkit device leasing an RX queue
from netdevsim:

1. devmem_lease_bind_rx_unregister_check: binds an RX dmabuf to a netkit
   device backed by a leased netdevsim queue and verifies clean teardown
   both when the virtual netkit device is deleted first (unleasing the
   queue while netdevsim remains up) and when the underlying netdevsim
   device is unregistered directly (which uninstalls the physical queue
   memory provider and cascades unregistration to the leasing netkit).
2. devmem_lease_bind_tx_unregister_check: binds a TX dmabuf to a netkit
   device backed by a leased netdevsim queue and verifies clean teardown
   both when the virtual netkit device is deleted first and when the
   underlying netdevsim device is unregistered directly.

Signed-off-by: Mina Almasry <almasrymina@google.com>
Reviewed-by: Dragos Tatulea <dtatulea@nvidia.com>
---
 tools/testing/selftests/net/nl_netdev.py | 132 ++++++++++++++++++++++-
 1 file changed, 129 insertions(+), 3 deletions(-)

diff --git a/tools/testing/selftests/net/nl_netdev.py b/tools/testing/selftests/net/nl_netdev.py
index dd0321c0b463a..fa4b5be093257 100755
--- a/tools/testing/selftests/net/nl_netdev.py
+++ b/tools/testing/selftests/net/nl_netdev.py
@@ -12,8 +12,9 @@ import os
 import struct
 from os import system
 from lib.py import ksft_run, ksft_exit, KsftFailEx, KsftSkipEx
-from lib.py import ksft_eq, ksft_ge, ksft_ne, ksft_raises, ksft_busy_wait
-from lib.py import EthtoolFamily, NetdevFamily, NetdevSimDev, NlError, defer, ip
+from lib.py import ksft_eq, ksft_ge, ksft_ne, ksft_not_in, ksft_raises, ksft_busy_wait
+from lib.py import EthtoolFamily, NetdevFamily, NetdevSimDev, Netlink
+from lib.py import NlError, RtnlFamily, cmd, defer, ip
 
 
 def empty_check(nf) -> None:
@@ -483,6 +484,129 @@ def devmem_bind_tx_unregister_check(_nf) -> None:
     nf_priv.close()
 
 
+def _create_netkit_single(rxqueues=2):
+    """Create a single-device netkit interface for queue lease tests."""
+    ifname = f"nk{os.getpid()}"
+    try:
+        with RtnlFamily() as rtnl:
+            rtnl.newlink(
+                {
+                    "ifname": ifname,
+                    "linkinfo": {
+                        "kind": "netkit",
+                        "data": {
+                            "mode": "l2",
+                            "pairing": "single",
+                        },
+                    },
+                    "num-rx-queues": rxqueues,
+                },
+                flags=[Netlink.NLM_F_CREATE, Netlink.NLM_F_EXCL],
+            )
+    except NlError as e:
+        if e.error in (errno.EOPNOTSUPP, errno.ENOENT):
+            raise KsftSkipEx(f"netkit single mode not supported: {e}") from e
+        raise
+    defer(cmd, f"ip link del dev {ifname}", fail=False)
+
+    link = ip(f"-d link show dev {ifname}", json=True)[0]
+    return ifname, link["ifindex"]
+
+
+def devmem_lease_bind_rx_unregister_check(nf) -> None:
+    """Verify leased RX devmem bindings detach cleanly on virt/phys unregister."""
+    for remove_virt_first in (False, True):
+        dmabuf_fd = _create_udmabuf()
+        defer(os.close, dmabuf_fd)
+        nf_priv = NetdevFamily()
+        defer(nf_priv.close)
+        nsimdev = NetdevSimDev(queue_count=2)
+        defer(nsimdev.remove)
+        nsim = nsimdev.nsims[0]
+
+        ip(f"link set dev {nsim.ifname} up")
+        with EthtoolFamily() as ef:
+            ef.rings_set({"header": {"dev-index": nsim.ifindex},
+                          "tcp-data-split": "enabled"})
+
+        nk_ifname, nk_ifindex = _create_netkit_single(rxqueues=2)
+        ip(f"link set dev {nk_ifname} up")
+
+        res = nf.queue_create({
+            "ifindex": nk_ifindex,
+            "type": "rx",
+            "lease": {
+                "ifindex": nsim.ifindex,
+                "queue": {"id": 1, "type": "rx"},
+            },
+        })
+
+        _devmem_bind(nf_priv.bind_rx, {
+            "ifindex": nk_ifindex,
+            "fd": dmabuf_fd,
+            "queues": [{"id": res["id"], "type": "rx"}],
+        })
+        ksft_ge(_dmabuf_attach_count(dmabuf_fd), 1)
+
+        if remove_virt_first:
+            ip(f"link del dev {nk_ifname}")
+            phys_q = nf.queue_get({"ifindex": nsim.ifindex, "id": 1,
+                                   "type": "rx"})
+            ksft_not_in("dmabuf", phys_q)
+            ksft_not_in("lease", phys_q)
+            ksft_ge(_dmabuf_attach_count(dmabuf_fd), 1)
+            nsimdev.remove()
+        else:
+            nsimdev.remove()
+            ksft_ne(cmd(f"ip link show dev {nk_ifname}", fail=False).ret, 0)
+        ksft_eq(_dmabuf_attach_count(dmabuf_fd), 0)
+
+        nf_priv.close()
+
+
+def devmem_lease_bind_tx_unregister_check(nf) -> None:
+    """Verify leased TX devmem bindings detach cleanly on virt/phys unregister."""
+    for remove_virt_first in (False, True):
+        dmabuf_fd = _create_udmabuf()
+        defer(os.close, dmabuf_fd)
+        nf_priv = NetdevFamily()
+        defer(nf_priv.close)
+        nsimdev = NetdevSimDev(queue_count=2)
+        defer(nsimdev.remove)
+        nsim = nsimdev.nsims[0]
+
+        ip(f"link set dev {nsim.ifname} up")
+
+        nk_ifname, nk_ifindex = _create_netkit_single(rxqueues=2)
+        ip(f"link set dev {nk_ifname} up")
+
+        nf.queue_create({
+            "ifindex": nk_ifindex,
+            "type": "rx",
+            "lease": {
+                "ifindex": nsim.ifindex,
+                "queue": {"id": 1, "type": "rx"},
+            },
+        })
+
+        _devmem_bind(nf_priv.bind_tx, {
+            "ifindex": nk_ifindex,
+            "fd": dmabuf_fd,
+        })
+        ksft_ge(_dmabuf_attach_count(dmabuf_fd), 1)
+
+        if remove_virt_first:
+            ip(f"link del dev {nk_ifname}")
+            ksft_ge(_dmabuf_attach_count(dmabuf_fd), 1)
+            nsimdev.remove()
+        else:
+            nsimdev.remove()
+            ksft_ne(cmd(f"ip link show dev {nk_ifname}", fail=False).ret, 0)
+        ksft_eq(_dmabuf_attach_count(dmabuf_fd), 0)
+
+        nf_priv.close()
+
+
 def main() -> None:
     """ Ksft boiler plate main """
     nf = NetdevFamily()
@@ -498,7 +622,9 @@ def main() -> None:
               page_pool_ifindex_leak_check,
               page_pool_stats_ifindex_check,
               devmem_bind_rx_unregister_check,
-              devmem_bind_tx_unregister_check
+              devmem_bind_tx_unregister_check,
+              devmem_lease_bind_rx_unregister_check,
+              devmem_lease_bind_tx_unregister_check
               ],
              args=(nf, ))
     ksft_exit()
-- 
2.56.0.455.g6c5a503b38-goog


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH net-next v2 3/6] net: guard memory provider RX queue open and close on unregister
  2026-11-08  5:58 ` [PATCH net-next v2 3/6] net: guard memory provider RX queue open and close on unregister Mina Almasry
@ 2026-11-09  6:00   ` netdev-bot+sashiko
  2026-11-09 19:50   ` Stanislav Fomichev
  1 sibling, 0 replies; 12+ messages in thread
From: netdev-bot+sashiko @ 2026-11-09  6:00 UTC (permalink / raw)
  To: almasrymina
  Cc: netdev, davem, edumazet, kuba, pabeni, horms, shuah,
	linux-kselftest, linux-kernel, daniel, razor, dw, sdf.kernel,
	bobbyeshleman, dtatulea

Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.

Low priority / nit picks for your consideration:

- [Low] The commit fixes two bugs that came in with the leased-queue
  proxy in commit 5602ad61ebee ("net: Proxy netif_mp_{open,close}_rxq…

--

net: guard memory provider RX queue open and close on unregister

This commit adds a reg_state check to __netif_mp_open_rxq() in
net/core/netdev_rx_queue.c. The check stops a memory provider from being
installed on a physical netdev that is being unregistered when the device
is reached through a leased rxq. It also changes __netif_mp_close_rxq() to
clear rxq->mp_params and return early whenever the device is past
NETREG_REGISTERED, not only when mp_ops is already NULL.

> When a memory provider RX queue is leased to a virtual netdev,
> netif_mp_open_rxq() and netif_mp_close_rxq() redirect through
> rxq->lease and lock the physical backing netdev directly rather than via
> netdev_get_by_index_lock(). This raises two issues during netdev
> unregistration:

[ ... ]

>    phys_rxq->mp_params remains populated until the virtual leasee's
>    netif_rxq_cleanup_unlease() or socket teardown runs. At that point
>    the physical netdev has already gone through dev_shutdown() and must
>    not call netdev_rx_queue_reconfig(). When dev->reg_state >
>    NETREG_REGISTERED, clear rxq->mp_params and return 0.
>
> Signed-off-by: Mina Almasry <almasrymina@google.com>

[Severity: Low]
Should this have a Fixes: tag?

The rxq->lease redirect in netif_mp_open_rxq() was added by the commit
below. So was netif_rxq_cleanup_unlease(), which calls
__netif_mp_close_rxq() on the physical device:

Fixes: 5602ad61ebee ("net: Proxy netif_mp_{open,close}_rxq for leased queues")

Before that commit, every caller reached __netif_mp_open_rxq() and
__netif_mp_close_rxq() through netdev_get_by_index_lock(). That helper
already rejects devices that are being unregistered.

Could the commit message also say what goes wrong without the fix?

On the open side, unregister_netdevice_many_notify() sets the physical
device to NETREG_UNREGISTERING. It then runs dev_shutdown() and
dev_memory_provider_uninstall() on it before NETDEV_UNREGISTER cascades
into the virtual device's teardown. In that window, a bind-rx on the
virtual device, which is still registered, could install a memory
provider on the physical rxq after the uninstall has already run.

On the close side, devmem's uninstall leaves phys_rxq->mp_params
populated. The old early return only fired when mp_ops was NULL, so it
did not trigger here. As a result,
netif_rxq_cleanup_unlease()->__netif_mp_close_rxq() went on to call
netdev_rx_queue_reconfig() on the shut-down device.

Please state the visible result, for example a queue restart on a
dismantled device, a WARN, or a provider left installed after shutdown.
That would let stable maintainers judge which trees need this and how
urgent it is.

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261108055809.2122934-1-almasrymina%40google.com

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH net-next v2 5/6] net: devmem: resolve physical map_dev for leased RX bindings
  2026-11-08  5:58 ` [PATCH net-next v2 5/6] net: devmem: resolve physical map_dev for leased RX bindings Mina Almasry
@ 2026-11-09  6:00   ` netdev-bot+sashiko
  2026-11-09 19:52   ` Stanislav Fomichev
  1 sibling, 0 replies; 12+ messages in thread
From: netdev-bot+sashiko @ 2026-11-09  6:00 UTC (permalink / raw)
  To: almasrymina
  Cc: netdev, davem, edumazet, kuba, pabeni, horms, shuah,
	linux-kselftest, linux-kernel, daniel, razor, dw, sdf.kernel,
	bobbyeshleman, dtatulea

Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.

Critical: 0 · High: 0 · Medium: 2 · Low: 0

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [Medium] This patch fixes a use-after-free that predates the series,
  but the commit message understates it and has no Fixes: tag.
- [Medium] The series is not bisectable: the previous patch breaks leased
  RX bindings, and this patch fixes that without saying so.

--

net: devmem: resolve physical map_dev for leased RX bindings

For leased RX queues, netdev_nl_bind_rx_doit() now passes the physical
backing netdev (rxq->lease->dev) as map_dev, and locks it around
net_devmem_bind_dmabuf(). net_devmem_bind_dmabuf() now asserts that
both devices are locked. It also refuses to bind when map_dev is no
longer NETREG_REGISTERED.

> Previously, netdev_nl_bind_rx_doit() passed netdev as both map_dev and
> bind_dev to net_devmem_bind_dmabuf(), so leased RX bindings stored the
> virtual netdev in binding->map_dev without tracking the physical backing
> netdev at all. Unregistering the physical backing netdev while a leased
> RX binding was active failed to detach and unmap the dmabuf.

[Severity: Medium]
Does "failed to detach and unmap the dmabuf" understate the problem?

Before this series, a leased RX binding only referenced the netkit
device. Unregistering the physical device then goes like this:

net_devmem_dev_unregister(phys)
    binding->dev never matches phys
dev_memory_provider_uninstall(phys)
    mp_dmabuf_devmem_uninstall(phys_rxq)
        bound_rxqs only holds the virtual rxq, so nothing happens

Later, netkit_uninit()->netkit_queue_unlease()->netif_rxq_cleanup_unlease()
calls mp_dmabuf_devmem_uninstall(virt_rxq) while rxq->lease is still set:

    if (!rxq->lease)
        net_devmem_dmabuf_binding_unmap(binding);

The unmap is skipped and binding->dev is set to NULL. On socket close,
net_devmem_unbind_dmabuf() skips the unmap too, because binding->dev is
NULL.

The attachment stays mapped against the physical device's dma_dev
until __net_devmem_dmabuf_binding_free() calls
net_devmem_dmabuf_binding_unmap().

c4466d2c3e77 ("net: devmem: unmap dma_buf synchronously on queue
uninstall") notes that dma_buf_dynamic_attach() takes no reference on
dma_dev. Once the physical device is gone, isn't that final unmap a
use-after-free in dma_unmap_sg_attrs()?

If so, could the commit message name the use-after-free and carry
Fixes: tags? Candidates are 222b5566a02d ("net: Proxy
netdev_queue_get_dma_dev for leased queues"), which enabled devmem
binding through leased queues, and c4466d2c3e77, which kept the leasee
exception in mp_dmabuf_devmem_uninstall(). Without a Fixes: tag, stable
selection would likely miss this.

> diff --git a/net/core/netdev-genl.c b/net/core/netdev-genl.c
> index 9e56c26fc65f9..442362f23fe88 100644
> --- a/net/core/netdev-genl.c
> +++ b/net/core/netdev-genl.c

[ ... ]

> @@ -1104,16 +1112,21 @@ int netdev_nl_bind_rx_doit(struct sk_buff *skb, struct genl_info *info)
>  	if (err)
>  		goto err_rxq_bitmap;
>  
> -	dma_dev = netdev_nl_get_dma_dev(netdev, rxq_bitmap, info->extack);
> +	dma_dev = netdev_nl_get_dma_dev(netdev, rxq_bitmap, &map_dev,
> +					info->extack);
>  	if (IS_ERR(dma_dev)) {
>  		err = PTR_ERR(dma_dev);
>  		goto err_rxq_bitmap;
>  	}
>  
> -	binding = net_devmem_bind_dmabuf(netdev, netdev, dma_dev,
> +	if (map_dev != netdev)
> +		netdev_lock(map_dev);
> +	binding = net_devmem_bind_dmabuf(map_dev, netdev, dma_dev,

[Severity: Medium]
Is the series bisectable at the previous patch, "net: devmem: always
populate bind_dev and detach both devices on unregister"?

That patch dropped the netif_is_queue_leasee(dev) guard from
net_devmem_dev_unregister(). It now unmaps whenever map_dev matches:

    if (READ_ONCE(binding->map_dev) != dev)
        continue;
    ...
    net_devmem_dmabuf_binding_unmap(binding);

At that commit, this call still passed (netdev, netdev). For a leased
RX binding, map_dev == bind_dev == the netkit device.

Now delete the netkit device while the physical NIC stays up:

unregister_netdevice_many_notify()
    NETDEV_UNREGISTER
        netdev_genl_netdevice_event()
            net_devmem_dev_unregister(netkit)
                net_devmem_dmabuf_binding_unmap()  <- unmap, unpin, detach
    ndo_uninit
        netkit_uninit()
            netkit_queue_unlease()
                netif_rxq_cleanup_unlease()
                    __netif_mp_close_rxq(phys)

In that window, can the physical NIC's page pool still hand out
net_iovs whose DMA addresses come from the unmapped, unpinned sgt?

If __netif_mp_close_rxq() fails to restart the queue (the case
c4466d2c3e77 guarded against), the window never closes.

This patch fixes that by moving map_dev to rxq->lease->dev. The commit
message only describes the physical-unregister case, though.

Could the two patches be squashed or reordered, so the leasee guard is
dropped only once map_dev is the physical device? Failing that, could
the commit message mention this fix?

>  					 DMA_FROM_DEVICE, dmabuf_fd,
>  					 niov_shift, priv, NETLINK_CB(skb).sk,
>  					 info->extack);
> +	if (map_dev != netdev)
> +		netdev_unlock(map_dev);
>  	if (IS_ERR(binding)) {
>  		err = PTR_ERR(binding);
>  		goto err_rxq_bitmap;

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261108055809.2122934-1-almasrymina%40google.com

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH net-next v2 6/6] selftests: net: add leased devmem RX and TX netdev unregister tests
  2026-11-08  5:58 ` [PATCH net-next v2 6/6] selftests: net: add leased devmem RX and TX netdev unregister tests Mina Almasry
@ 2026-11-09  6:00   ` netdev-bot+sashiko
  0 siblings, 0 replies; 12+ messages in thread
From: netdev-bot+sashiko @ 2026-11-09  6:00 UTC (permalink / raw)
  To: almasrymina
  Cc: netdev, davem, edumazet, kuba, pabeni, horms, shuah,
	linux-kselftest, linux-kernel, daniel, razor, dw, sdf.kernel,
	bobbyeshleman, dtatulea

Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider.

Critical: 0 · High: 0 · Medium: 1 · Low: 2

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [Medium] The new leased tests bind through `_devmem_bind()`, which
  turns every NlError with EOPNOTSUPP into KsftSkipEx.
- [Low] devmem_lease_bind_rx_unregister_check()
  (tools/testing/selftests/net/nl_netdev.py), remove_virt_first=True…
- [Low] devmem_lease_bind_rx_unregister_check() and
  devmem_lease_bind_tx_unregister_check() each run a two-pass loop…

--

selftests: net: add leased devmem RX and TX netdev unregister tests

This commit adds two tests to nl_netdev.py. Each one binds an RX or TX
dmabuf to a single-mode netkit device that leases RX queue 1 from
netdevsim. The tests then check for clean teardown both when the netkit
device is deleted first and when the netdevsim device is removed
directly.

> diff --git a/tools/testing/selftests/net/nl_netdev.py b/tools/testing/selftests/net/nl_netdev.py
> index dd0321c0b463a..fa4b5be093257 100755
> --- a/tools/testing/selftests/net/nl_netdev.py
> +++ b/tools/testing/selftests/net/nl_netdev.py

[ ... ]

> +def devmem_lease_bind_rx_unregister_check(nf) -> None:
> +    """Verify leased RX devmem bindings detach cleanly on virt/phys unregister."""
> +    for remove_virt_first in (False, True):

[ ... ]

> +        _devmem_bind(nf_priv.bind_rx, {
> +            "ifindex": nk_ifindex,
> +            "fd": dmabuf_fd,
> +            "queues": [{"id": res["id"], "type": "rx"}],
> +        })
> +        ksft_ge(_dmabuf_attach_count(dmabuf_fd), 1)
> +
> +        if remove_virt_first:
> +            ip(f"link del dev {nk_ifname}")
> +            phys_q = nf.queue_get({"ifindex": nsim.ifindex, "id": 1,
> +                                   "type": "rx"})
> +            ksft_not_in("dmabuf", phys_q)
> +            ksft_not_in("lease", phys_q)

[Severity: Low]
Do these two absence checks show that unleasing removed anything?

Nothing earlier checks that the physical queue reported dmabuf or lease
while the binding was active. The only check after the bind is:

    ksft_ge(_dmabuf_attach_count(dmabuf_fd), 1)

That count goes up when net_devmem_bind_dmabuf() maps the dma-buf, which
happens before any queue gets the memory provider. So it says nothing
about the physical rxq.

Suppose netdev_nl_queue_fill_mp() stopped reporting dmabuf on the leased
physical queue, or the lease redirect in netif_mp_open_rxq() never
installed the memory provider. These ksft_not_in() checks would still
pass.

nk_qlease.py checks lease reporting, but no selftest checks dmabuf on a
leased physical queue.

Could a ksft_in() check on the physical queue be added right after the
bind?

> +            ksft_ge(_dmabuf_attach_count(dmabuf_fd), 1)
> +            nsimdev.remove()
> +        else:
> +            nsimdev.remove()
> +            ksft_ne(cmd(f"ip link show dev {nk_ifname}", fail=False).ret, 0)

[Severity: Low]
What happens on the second loop pass if this check fails?

ksft_ne() only records the failure and does not raise. If removing
netdevsim no longer cascades to the netkit device, the loop moves on to
the remove_virt_first=True pass with the netkit device still there.

That pass calls _create_netkit_single() again with the same name and
NLM_F_EXCL:

    ifname = f"nk{os.getpid()}"
    ...
                flags=[Netlink.NLM_F_CREATE, Netlink.NLM_F_EXCL],
    ...
    defer(cmd, f"ip link del dev {ifname}", fail=False)

The deferred ip link del only runs when the whole test case ends, not
after each iteration.

Would RTM_NEWLINK then fail with EEXIST? _create_netkit_single() only
turns EOPNOTSUPP and ENOENT into a skip, so it would re-raise that error.

The real cascade failure would then be hidden under an unrelated NlError
traceback, and the remove_virt_first=True case would not run.
devmem_lease_bind_tx_unregister_check() has the same pattern.

> +        ksft_eq(_dmabuf_attach_count(dmabuf_fd), 0)
> +
> +        nf_priv.close()
> +
> +
> +def devmem_lease_bind_tx_unregister_check(nf) -> None:
> +    """Verify leased TX devmem bindings detach cleanly on virt/phys unregister."""
> +    for remove_virt_first in (False, True):

[ ... ]

> +        _devmem_bind(nf_priv.bind_tx, {
> +            "ifindex": nk_ifindex,
> +            "fd": dmabuf_fd,
> +        })

[Severity: Medium]
Can a broken lease lookup be reported as SKIP here rather than FAIL?

_devmem_bind() turns every EOPNOTSUPP into a skip:

    def _devmem_bind(bind, attrs) -> None:
        try:
            bind(attrs)
        except NlError as e:
            if e.error == errno.EOPNOTSUPP:
                raise KsftSkipEx(f"devmem binding not supported: {e}") from e

netkit sets dev->netmem_tx = NETMEM_TX_NO_DMA. So this bind only succeeds
if netdev_find_netmem_tx_dev() walks the netkit rx queues, finds
rxq->lease, and returns the physical device. If that walk fails,
netdev_nl_bind_tx_doit() returns the same errno:

    map_dev = netdev_find_netmem_tx_dev(netdev);
    if (!map_dev) {
        err = -EOPNOTSUPP;
        NL_SET_ERR_MSG(info->extack,
                       "No DMA-capable device found for netmem TX");

The leased RX bind in devmem_lease_bind_rx_unregister_check() has the
same problem. netif_mp_open_rxq() returns -EOPNOTSUPP when the lease
redirect resolves to a device without a parent ("rx queue belongs to a
virtual netdev"), and __netif_mp_open_rxq() does the same when there are
no qops.

The skip is needed when netdevsim has no DMA mask and so does not set
NETMEM_TX_DMA. But the wrapper cannot tell that case apart from a lease
resolution failure.

Would it work to skip only if a direct bind on the nsim device also
returns EOPNOTSUPP, or to check the nsim DMA capability up front?

> +        ksft_ge(_dmabuf_attach_count(dmabuf_fd), 1)

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261108055809.2122934-1-almasrymina%40google.com

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH net-next v2 3/6] net: guard memory provider RX queue open and close on unregister
  2026-11-08  5:58 ` [PATCH net-next v2 3/6] net: guard memory provider RX queue open and close on unregister Mina Almasry
  2026-11-09  6:00   ` netdev-bot+sashiko
@ 2026-11-09 19:50   ` Stanislav Fomichev
  1 sibling, 0 replies; 12+ messages in thread
From: Stanislav Fomichev @ 2026-11-09 19:50 UTC (permalink / raw)
  To: Mina Almasry
  Cc: netdev, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Shuah Khan, linux-kselftest,
	linux-kernel, Daniel Borkmann, Nikolay Aleksandrov, David Wei,
	Bobby Eshleman, Dragos Tatulea

On 11/08, Mina Almasry wrote:
> When a memory provider RX queue is leased to a virtual netdev,
> netif_mp_open_rxq() and netif_mp_close_rxq() redirect through
> rxq->lease and lock the physical backing netdev directly rather than via
> netdev_get_by_index_lock(). This raises two issues during netdev
> unregistration:
> 
> 1. In __netif_mp_open_rxq(), the physical backing netdev may already be
>    in NETREG_UNREGISTERING (after dev_shutdown() and
>    dev_memory_provider_uninstall() have run on it, before cascaded
>    unregistration of the virtual leasee tears down the lease). Reject
>    opening an RX queue if dev->reg_state != NETREG_REGISTERED.
> 2. In __netif_mp_close_rxq(), when the physical backing netdev
>    unregisters before the virtual leasee, dev_memory_provider_uninstall()
>    on the physical netdev does not uninstall the virtual queues, so
>    phys_rxq->mp_params remains populated until the virtual leasee's
>    netif_rxq_cleanup_unlease() or socket teardown runs. At that point
>    the physical netdev has already gone through dev_shutdown() and must
>    not call netdev_rx_queue_reconfig(). When dev->reg_state >
>    NETREG_REGISTERED, clear rxq->mp_params and return 0.
> 
> Signed-off-by: Mina Almasry <almasrymina@google.com>
> ---
>  net/core/netdev_rx_queue.c | 17 +++++++++++++----
>  1 file changed, 13 insertions(+), 4 deletions(-)
> 
> diff --git a/net/core/netdev_rx_queue.c b/net/core/netdev_rx_queue.c
> index a7261fedd5e76..85ae5abf16b8e 100644
> --- a/net/core/netdev_rx_queue.c
> +++ b/net/core/netdev_rx_queue.c
> @@ -193,6 +193,14 @@ static int __netif_mp_open_rxq(struct net_device *dev, unsigned int rxq_idx,
>  	struct netdev_rx_queue *rxq;
>  	int ret;
>  
> +	/* A leased physical dev is locked via rxq->lease rather than
> +	 * netdev_get_by_index_lock(), so check reg_state under its lock.
> +	 */
> +	if (dev->reg_state != NETREG_REGISTERED) {
> +		NL_SET_ERR_MSG(extack, "Device is being unregistered");
> +		return -ENODEV;
> +	}

nit: wonder if we should keep lease vs non-lease handling in the callers?
netif_mp_open_rxq/netif_mp_close_rxq already handle this, and __ variants
seem to be unaware of the leasing abstractions.

Maybe put them into some new lease-valid helper? So we can keep these
tricky details in a single place, similar to netdev_put_lock?

  netif_lease_valid() { return netif_is_alive && netif_device_present } ?

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH net-next v2 5/6] net: devmem: resolve physical map_dev for leased RX bindings
  2026-11-08  5:58 ` [PATCH net-next v2 5/6] net: devmem: resolve physical map_dev for leased RX bindings Mina Almasry
  2026-11-09  6:00   ` netdev-bot+sashiko
@ 2026-11-09 19:52   ` Stanislav Fomichev
  1 sibling, 0 replies; 12+ messages in thread
From: Stanislav Fomichev @ 2026-11-09 19:52 UTC (permalink / raw)
  To: Mina Almasry
  Cc: netdev, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Shuah Khan, linux-kselftest,
	linux-kernel, Daniel Borkmann, Nikolay Aleksandrov, David Wei,
	Bobby Eshleman, Dragos Tatulea

On 11/08, Mina Almasry wrote:
> Previously, netdev_nl_bind_rx_doit() passed netdev as both map_dev and
> bind_dev to net_devmem_bind_dmabuf(), so leased RX bindings stored the
> virtual netdev in binding->map_dev without tracking the physical backing
> netdev at all. Unregistering the physical backing netdev while a leased
> RX binding was active failed to detach and unmap the dmabuf.
> 
> Return the physical backing net_device from netdev_nl_get_dma_dev() when
> the RX queue is leased from another netdev, lock map_dev around
> net_devmem_bind_dmabuf() when map_dev != netdev (matching
> netdev_nl_bind_tx_doit()), and assert in net_devmem_bind_dmabuf() that
> both bind_dev and map_dev are locked.
> 
> Because a leased map_dev is reached via rxq->lease->dev and locked with
> raw netdev_lock(map_dev) rather than netdev_get_by_index_lock(), it may
> already have entered NETREG_UNREGISTERING and completed
> net_devmem_dev_unregister(map_dev) before cascaded unregistration of
> bind_dev tears down the lease. Reject binding in
> net_devmem_bind_dmabuf() if map_dev->reg_state != NETREG_REGISTERED.
> 
> Signed-off-by: Mina Almasry <almasrymina@google.com>
> ---
>  net/core/devmem.c      | 12 ++++++++++++
>  net/core/netdev-genl.c | 19 ++++++++++++++++---
>  2 files changed, 28 insertions(+), 3 deletions(-)
> 
> diff --git a/net/core/devmem.c b/net/core/devmem.c
> index bb785a871d1c4..2dac581a57e13 100644
> --- a/net/core/devmem.c
> +++ b/net/core/devmem.c
> @@ -321,6 +321,18 @@ net_devmem_bind_dmabuf(struct net_device *map_dev,
>  	size_t i;
>  	long err;
>  
> +	netdev_assert_locked(bind_dev);
> +	netdev_assert_locked(map_dev);
> +
> +	/* When map_dev != bind_dev, map_dev is locked via the queue lease
> +	 * rather than netdev_get_by_index_lock(), so it may already be
> +	 * unregistering before cascaded bind_dev unregistration runs.
> +	 */
> +	if (map_dev->reg_state != NETREG_REGISTERED) {
> +		NL_SET_ERR_MSG(extack, "Device is being unregistered");
> +		return ERR_PTR(-ENODEV);
> +	}

And you can call this new lease-valid here as well? Otherwise, the changes
are too LLM-y, testing some super low-level conditions and are a bit hard
to read/follow?

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2026-11-09 19:53 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-08  5:58 [PATCH net-next v2 0/6] net: devmem: support leased RX and TX queues on netdev unregister Mina Almasry
2026-11-08  5:58 ` [PATCH net-next v2 1/6] net: devmem: pass net_device pointer to net_devmem_lock_dev() Mina Almasry
2026-11-08  5:58 ` [PATCH net-next v2 2/6] net: devmem: rename binding dev and vdev to map_dev and bind_dev Mina Almasry
2026-11-08  5:58 ` [PATCH net-next v2 3/6] net: guard memory provider RX queue open and close on unregister Mina Almasry
2026-11-09  6:00   ` netdev-bot+sashiko
2026-11-09 19:50   ` Stanislav Fomichev
2026-11-08  5:58 ` [PATCH net-next v2 4/6] net: devmem: always populate bind_dev and detach both devices " Mina Almasry
2026-11-08  5:58 ` [PATCH net-next v2 5/6] net: devmem: resolve physical map_dev for leased RX bindings Mina Almasry
2026-11-09  6:00   ` netdev-bot+sashiko
2026-11-09 19:52   ` Stanislav Fomichev
2026-11-08  5:58 ` [PATCH net-next v2 6/6] selftests: net: add leased devmem RX and TX netdev unregister tests Mina Almasry
2026-11-09  6:00   ` netdev-bot+sashiko

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®