* [PATCH net-next v3 0/6] net: devmem: support leased RX and TX queues on netdev unregister
@ 2026-11-15 2:26 Mina Almasry
2026-11-15 2:26 ` [PATCH net-next v3 1/6] net: devmem: pass net_device pointer to net_devmem_lock_dev() Mina Almasry
` (6 more replies)
0 siblings, 7 replies; 8+ messages in thread
From: Mina Almasry @ 2026-11-15 2:26 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, Weiming Shi, 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. Introduce netif_lease_valid() and guard netif_mp_open_rxq(),
netif_mp_close_rxq(), and netif_rxq_cleanup_unlease() 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 netif_lease_valid(map_dev).
6. Add kselftests in nl_netdev.py covering leased RX and TX devmem
bindings across both virtual-first and physical-first netdev
unregistration orders.
v3:
- Rebase onto latest net-next/main.
- Add netif_lease_valid() helper and keep lease validity checks in the
lease-aware callers (netif_mp_open_rxq(), netif_mp_close_rxq(),
netif_rxq_cleanup_unlease(), and net_devmem_bind_dmabuf()) rather than
inside __netif_mp_open_rxq() / __netif_mp_close_rxq(), and document why
mp_params is cleared directly when !netif_lease_valid(dev) (Stanislav,
Sashiko).
- Guard the bound_rxqs loop in net_devmem_unbind_dmabuf() with
if (binding->bind_dev), keep the !netif_is_queue_leasee(dev) guard in
net_devmem_dev_unregister() in patch 4/6 until patch 5/6 resolves
binding->map_dev to the physical device so the series is bisectable at
every commit, and document the use-after-free in dma_unmap_sg_attrs() on
physical netdev teardown in patch 5/6 (Sashiko).
- In nl_netdev.py, verify dmabuf and lease on the physical queue after
binding, skip when queue_create returns EOPNOTSUPP, probe the physical
device on EOPNOTSUPP so a broken lease lookup is not masked as SKIP, and
clean up nk_ifname between loop iterations (Sashiko).
- v2: https://lore.kernel.org/netdev/20261108055809.2122934-1-almasrymina@google.com/
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/dev.h | 7 +
net/core/devmem.c | 123 ++++++++++--------
net/core/devmem.h | 25 ++--
net/core/netdev-genl.c | 58 +++++----
net/core/netdev_rx_queue.c | 31 ++++-
tools/testing/selftests/net/nl_netdev.py | 159 ++++++++++++++++++++++-
7 files changed, 304 insertions(+), 101 deletions(-)
base-commit: 6964ff21c5d8b75649211a52dbfd92753e605649
--
2.56.0.611.g1030f0b940-goog
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH net-next v3 1/6] net: devmem: pass net_device pointer to net_devmem_lock_dev()
2026-11-15 2:26 [PATCH net-next v3 0/6] net: devmem: support leased RX and TX queues on netdev unregister Mina Almasry
@ 2026-11-15 2:26 ` Mina Almasry
2026-11-15 2:26 ` [PATCH net-next v3 2/6] net: devmem: rename binding dev and vdev to map_dev and bind_dev Mina Almasry
` (5 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Mina Almasry @ 2026-11-15 2:26 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, Weiming Shi, 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.
Reviewed-by: Dragos Tatulea <dtatulea@nvidia.com>
Signed-off-by: Mina Almasry <almasrymina@google.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 7eb91782585e3..6fa7b909b86e8 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.611.g1030f0b940-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH net-next v3 2/6] net: devmem: rename binding dev and vdev to map_dev and bind_dev
2026-11-15 2:26 [PATCH net-next v3 0/6] net: devmem: support leased RX and TX queues on netdev unregister Mina Almasry
2026-11-15 2:26 ` [PATCH net-next v3 1/6] net: devmem: pass net_device pointer to net_devmem_lock_dev() Mina Almasry
@ 2026-11-15 2:26 ` Mina Almasry
2026-11-15 2:26 ` [PATCH net-next v3 3/6] net: guard memory provider RX queue open and close on unregister Mina Almasry
` (4 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Mina Almasry @ 2026-11-15 2:26 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, Weiming Shi, 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.
Reviewed-by: Dragos Tatulea <dtatulea@nvidia.com>
Signed-off-by: Mina Almasry <almasrymina@google.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 a230ecce9327d..405811b99589b 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -4192,7 +4192,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 6fa7b909b86e8..db60d1b5ea229 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;
@@ -558,8 +560,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;
}
@@ -683,8 +685,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.611.g1030f0b940-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH net-next v3 3/6] net: guard memory provider RX queue open and close on unregister
2026-11-15 2:26 [PATCH net-next v3 0/6] net: devmem: support leased RX and TX queues on netdev unregister Mina Almasry
2026-11-15 2:26 ` [PATCH net-next v3 1/6] net: devmem: pass net_device pointer to net_devmem_lock_dev() Mina Almasry
2026-11-15 2:26 ` [PATCH net-next v3 2/6] net: devmem: rename binding dev and vdev to map_dev and bind_dev Mina Almasry
@ 2026-11-15 2:26 ` Mina Almasry
2026-11-15 2:26 ` [PATCH net-next v3 4/6] net: devmem: always populate bind_dev and detach both devices " Mina Almasry
` (3 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Mina Almasry @ 2026-11-15 2:26 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, Weiming Shi, Daniel Borkmann, Nikolay Aleksandrov,
David Wei, Stanislav Fomichev, Bobby Eshleman, Dragos Tatulea,
Stanislav Fomichev
When an RX queue is leased from a physical backing net_device to a
virtual net_device (e.g., netkit), memory provider operations reach the
physical net_device through rxq->lease and lock it directly via
netdev_lock(dev) rather than looking it up via
netdev_get_by_index_lock().
Introduce a netif_lease_valid() helper checking netif_is_alive(dev)
under netdev_lock(dev) and use it in the lease-aware callers while
keeping __netif_mp_open_rxq() and __netif_mp_close_rxq() unaware of
queue leasing:
1. In netif_mp_open_rxq(), if the leased physical device is already
unregistering (!netif_lease_valid(dev)), netdev_get_by_index_lock()
on the virtual netdev succeeds before cascaded unregistration runs,
allowing a new memory provider to be installed on the physical device
while it is unregistering. Reject the open with -ENODEV.
2. In netif_mp_close_rxq() and netif_rxq_cleanup_unlease(),
__netif_mp_close_rxq() reconfigures the physical RX queue so that it
can continue operating with a regular page_pool when the physical
netdev outlives the virtual queue or memory provider. When the
physical backing netdev is itself unregistering
(!netif_lease_valid(dev)), its queue will not outlive the lease and,
matching the non-leased unregistration path (where
dev_memory_provider_uninstall() detaches the provider without calling
netdev_rx_queue_reconfig()), the physical queue should not be
reconfigured. However, unlike non-leased queues,
mp_dmabuf_devmem_uninstall() on the physical netdev does not match
bound_rxqs (which stores the virtual rxq), leaving
phys_rxq->mp_params populated until socket teardown or the virtual
leasee's netif_rxq_cleanup_unlease() runs. When
!netif_lease_valid(dev), skip __netif_mp_close_rxq() and clear
mp_params on the physical queue directly so that a subsequent
netif_rxq_cleanup_unlease() or dev_memory_provider_uninstall() sees
!mp_ops and does not uninstall a second time.
Suggested-by: Stanislav Fomichev <sdf@fomichev.me>
Signed-off-by: Mina Almasry <almasrymina@google.com>
---
net/core/dev.h | 7 +++++++
net/core/netdev_rx_queue.c | 31 ++++++++++++++++++++++++++-----
2 files changed, 33 insertions(+), 5 deletions(-)
diff --git a/net/core/dev.h b/net/core/dev.h
index 4e9e8cd156977..50cfede051db1 100644
--- a/net/core/dev.h
+++ b/net/core/dev.h
@@ -119,6 +119,13 @@ bool netif_rxq_has_mp(struct net_device *dev, unsigned int rxq_idx);
bool netif_rxq_is_leased(struct net_device *dev, unsigned int rxq_idx);
bool netif_is_queue_leasee(const struct net_device *dev);
+static inline bool netif_lease_valid(const struct net_device *dev)
+{
+ netdev_assert_locked(dev);
+
+ return netif_is_alive(dev);
+}
+
void __netif_mp_uninstall_rxq(struct netdev_rx_queue *rxq,
const struct pp_memory_provider_params *p);
diff --git a/net/core/netdev_rx_queue.c b/net/core/netdev_rx_queue.c
index a7261fedd5e76..98b51b2ac8ee1 100644
--- a/net/core/netdev_rx_queue.c
+++ b/net/core/netdev_rx_queue.c
@@ -270,7 +270,12 @@ int netif_mp_open_rxq(struct net_device *dev, unsigned int rxq_idx,
}
netdev_lock(dev);
- ret = __netif_mp_open_rxq(dev, rxq_idx, p, extack);
+ if (netif_lease_valid(dev)) {
+ ret = __netif_mp_open_rxq(dev, rxq_idx, p, extack);
+ } else {
+ NL_SET_ERR_MSG(extack, "Device is being unregistered");
+ ret = -ENODEV;
+ }
netdev_unlock(dev);
return ret;
}
@@ -309,19 +314,29 @@ static int __netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,
int netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,
const struct pp_memory_provider_params *old_p)
{
- int ret;
+ struct netdev_rx_queue *rxq;
+ int ret = 0;
if (WARN_ON_ONCE(rxq_idx >= dev->real_num_rx_queues))
return -EINVAL;
if (!netif_rxq_is_leased(dev, rxq_idx))
return __netif_mp_close_rxq(dev, rxq_idx, old_p);
- if (!__netif_get_rx_queue_lease(&dev, &rxq_idx, NETIF_VIRT_TO_PHYS)) {
+ rxq = __netif_get_rx_queue_lease(&dev, &rxq_idx, NETIF_VIRT_TO_PHYS);
+ if (!rxq) {
WARN_ON_ONCE(1);
return -EINVAL;
}
netdev_lock(dev);
- ret = __netif_mp_close_rxq(dev, rxq_idx, old_p);
+ /* If the physical netdev is already unregistering, the queue will not
+ * outlive the lease; only clear mp_params so subsequent
+ * netif_rxq_cleanup_unlease() or dev_memory_provider_uninstall() is a
+ * no-op.
+ */
+ if (netif_lease_valid(dev))
+ ret = __netif_mp_close_rxq(dev, rxq_idx, old_p);
+ else
+ memset(&rxq->mp_params, 0, sizeof(rxq->mp_params));
netdev_unlock(dev);
return ret;
}
@@ -340,6 +355,9 @@ void __netif_mp_uninstall_rxq(struct netdev_rx_queue *rxq,
* the lease redirection. The extra __netif_mp_close_rxq is needed
* since the physical queue can outlive the virtual queue in the lease
* case, so it needs to be reconfigured to clear the memory provider.
+ * If the physical netdev is also unregistering, its queue will not
+ * outlive the lease, so only clear mp_params without reconfiguring the
+ * queue.
*/
void netif_rxq_cleanup_unlease(struct netdev_rx_queue *phys_rxq,
struct netdev_rx_queue *virt_rxq)
@@ -350,6 +368,9 @@ void netif_rxq_cleanup_unlease(struct netdev_rx_queue *phys_rxq,
if (!p.mp_ops)
return;
- __netif_mp_close_rxq(phys_rxq->dev, rxq_idx, &p);
+ if (netif_lease_valid(phys_rxq->dev))
+ __netif_mp_close_rxq(phys_rxq->dev, rxq_idx, &p);
+ else
+ memset(&phys_rxq->mp_params, 0, sizeof(phys_rxq->mp_params));
__netif_mp_uninstall_rxq(virt_rxq, &p);
}
--
2.56.0.611.g1030f0b940-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH net-next v3 4/6] net: devmem: always populate bind_dev and detach both devices on unregister
2026-11-15 2:26 [PATCH net-next v3 0/6] net: devmem: support leased RX and TX queues on netdev unregister Mina Almasry
` (2 preceding siblings ...)
2026-11-15 2:26 ` [PATCH net-next v3 3/6] net: guard memory provider RX queue open and close on unregister Mina Almasry
@ 2026-11-15 2:26 ` Mina Almasry
2026-11-15 2:26 ` [PATCH net-next v3 5/6] net: devmem: resolve physical map_dev for leased RX bindings Mina Almasry
` (2 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Mina Almasry @ 2026-11-15 2:26 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, Weiming Shi, 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 | 76 +++++++++++++++++++++---------------------
net/core/devmem.h | 8 +++--
net/core/netdev-genl.c | 16 ++++-----
3 files changed, 51 insertions(+), 49 deletions(-)
diff --git a/net/core/devmem.c b/net/core/devmem.c
index db60d1b5ea229..a17ee27dc7cef 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.
@@ -219,22 +220,23 @@ void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding)
if (binding->list.next)
list_del(&binding->list);
- xa_for_each(&binding->bound_rxqs, xa_idx, rxq) {
- const struct pp_memory_provider_params mp_params = {
- .mp_priv = binding,
- .mp_ops = &dmabuf_devmem_ops,
- };
+ if (binding->bind_dev) {
+ xa_for_each(&binding->bound_rxqs, xa_idx, rxq) {
+ const struct pp_memory_provider_params mp_params = {
+ .mp_priv = binding,
+ .mp_ops = &dmabuf_devmem_ops,
+ };
- rxq_idx = get_netdev_rx_queue_index(rxq);
+ rxq_idx = get_netdev_rx_queue_index(rxq);
- if (netif_mp_close_rxq(binding->map_dev, rxq_idx, &mp_params))
- busy = true;
- }
+ 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->direction == DMA_FROM_DEVICE && !busy)
+ net_devmem_dmabuf_binding_unmap(binding);
+ WRITE_ONCE(binding->bind_dev, NULL);
}
percpu_ref_kill(&binding->ref);
@@ -252,21 +254,18 @@ 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);
@@ -528,7 +527,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;
@@ -559,9 +558,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;
}
@@ -685,8 +685,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.611.g1030f0b940-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH net-next v3 5/6] net: devmem: resolve physical map_dev for leased RX bindings
2026-11-15 2:26 [PATCH net-next v3 0/6] net: devmem: support leased RX and TX queues on netdev unregister Mina Almasry
` (3 preceding siblings ...)
2026-11-15 2:26 ` [PATCH net-next v3 4/6] net: devmem: always populate bind_dev and detach both devices " Mina Almasry
@ 2026-11-15 2:26 ` Mina Almasry
2026-11-15 2:26 ` [PATCH net-next v3 6/6] selftests: net: add leased devmem RX and TX netdev unregister tests Mina Almasry
2026-11-15 20:08 ` [PATCH net-next v3 0/6] net: devmem: support leased RX and TX queues on netdev unregister netdev-bot+sinfo
6 siblings, 0 replies; 8+ messages in thread
From: Mina Almasry @ 2026-11-15 2:26 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, Weiming Shi, Daniel Borkmann, Nikolay Aleksandrov,
David Wei, Stanislav Fomichev, Bobby Eshleman, Dragos Tatulea,
Stanislav Fomichev
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. When the physical backing netdev unregistered while a
leased RX binding was active, net_devmem_dev_unregister() did not match
the physical netdev and mp_dmabuf_devmem_uninstall() on the virtual
queue skipped unmapping because rxq->lease was set, leaving the dmabuf
attachment mapped against the freed physical dma_dev until
__net_devmem_dmabuf_binding_free() triggered a use-after-free in
dma_unmap_sg_attrs().
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()), assert in net_devmem_bind_dmabuf() that both
bind_dev and map_dev are locked, and reject binding when
!netif_lease_valid(map_dev).
Now that binding->map_dev always points to the physical DMA device for
both RX and TX bindings (never a queue leasee), drop the
!netif_is_queue_leasee(dev) guard in net_devmem_dev_unregister().
Suggested-by: Stanislav Fomichev <sdf@fomichev.me>
Signed-off-by: Mina Almasry <almasrymina@google.com>
---
net/core/devmem.c | 11 +++++++++--
net/core/netdev-genl.c | 19 ++++++++++++++++---
2 files changed, 25 insertions(+), 5 deletions(-)
diff --git a/net/core/devmem.c b/net/core/devmem.c
index a17ee27dc7cef..31ef17641ef19 100644
--- a/net/core/devmem.c
+++ b/net/core/devmem.c
@@ -267,8 +267,7 @@ void net_devmem_dev_unregister(struct net_device *dev)
__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);
}
@@ -323,6 +322,14 @@ net_devmem_bind_dmabuf(struct net_device *map_dev,
size_t i;
long err;
+ netdev_assert_locked(bind_dev);
+ netdev_assert_locked(map_dev);
+
+ if (!netif_lease_valid(map_dev)) {
+ 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.611.g1030f0b940-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH net-next v3 6/6] selftests: net: add leased devmem RX and TX netdev unregister tests
2026-11-15 2:26 [PATCH net-next v3 0/6] net: devmem: support leased RX and TX queues on netdev unregister Mina Almasry
` (4 preceding siblings ...)
2026-11-15 2:26 ` [PATCH net-next v3 5/6] net: devmem: resolve physical map_dev for leased RX bindings Mina Almasry
@ 2026-11-15 2:26 ` Mina Almasry
2026-11-15 20:08 ` [PATCH net-next v3 0/6] net: devmem: support leased RX and TX queues on netdev unregister netdev-bot+sinfo
6 siblings, 0 replies; 8+ messages in thread
From: Mina Almasry @ 2026-11-15 2:26 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, Weiming Shi, 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.
Reviewed-by: Dragos Tatulea <dtatulea@nvidia.com>
Signed-off-by: Mina Almasry <almasrymina@google.com>
---
tools/testing/selftests/net/nl_netdev.py | 159 ++++++++++++++++++++++-
1 file changed, 155 insertions(+), 4 deletions(-)
diff --git a/tools/testing/selftests/net/nl_netdev.py b/tools/testing/selftests/net/nl_netdev.py
index dd0321c0b463a..2e4e34257e873 100755
--- a/tools/testing/selftests/net/nl_netdev.py
+++ b/tools/testing/selftests/net/nl_netdev.py
@@ -12,8 +12,10 @@ 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_in, ksft_ne, ksft_not_in
+from lib.py import 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:
@@ -423,12 +425,22 @@ def _dmabuf_attach_count(dmabuf_fd) -> int:
raise KsftFailEx(f"dma-buf {ino} not found in {path}")
-def _devmem_bind(bind, attrs) -> None:
+def _devmem_bind(bind, attrs, phys_attrs=None) -> None:
"""Run a devmem bind op, skip the test if the device can't do devmem."""
try:
bind(attrs)
except NlError as e:
if e.error == errno.EOPNOTSUPP:
+ if phys_attrs is not None:
+ try:
+ bind(phys_attrs)
+ except NlError as probe_e:
+ if probe_e.error == errno.EOPNOTSUPP:
+ raise KsftSkipEx(
+ f"devmem binding not supported: {probe_e}"
+ ) from probe_e
+ raise e from probe_e
+ raise
raise KsftSkipEx(f"devmem binding not supported: {e}") from e
raise
@@ -483,6 +495,143 @@ 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 _queue_lease(nf, nk_ifindex, phys_ifindex, phys_qid=1):
+ try:
+ return nf.queue_create({
+ "ifindex": nk_ifindex,
+ "type": "rx",
+ "lease": {
+ "ifindex": phys_ifindex,
+ "queue": {"id": phys_qid, "type": "rx"},
+ },
+ })
+ except NlError as e:
+ if e.error == errno.EOPNOTSUPP:
+ raise KsftSkipEx(f"queue lease not supported: {e}") from e
+ raise
+
+
+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 = _queue_lease(nf, nk_ifindex, nsim.ifindex)
+
+ _devmem_bind(nf_priv.bind_rx, {
+ "ifindex": nk_ifindex,
+ "fd": dmabuf_fd,
+ "queues": [{"id": res["id"], "type": "rx"}],
+ }, phys_attrs={
+ "ifindex": nsim.ifindex,
+ "fd": dmabuf_fd,
+ "queues": [{"id": 0, "type": "rx"}],
+ })
+ ksft_ge(_dmabuf_attach_count(dmabuf_fd), 1)
+ phys_q = nf.queue_get({"ifindex": nsim.ifindex, "id": 1, "type": "rx"})
+ ksft_in("dmabuf", phys_q)
+ ksft_in("lease", phys_q)
+
+ 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)
+ cmd(f"ip link del dev {nk_ifname}", fail=False)
+ 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")
+
+ _queue_lease(nf, nk_ifindex, nsim.ifindex)
+
+ _devmem_bind(nf_priv.bind_tx, {
+ "ifindex": nk_ifindex,
+ "fd": dmabuf_fd,
+ }, phys_attrs={
+ "ifindex": nsim.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)
+ cmd(f"ip link del dev {nk_ifname}", fail=False)
+ ksft_eq(_dmabuf_attach_count(dmabuf_fd), 0)
+
+ nf_priv.close()
+
+
def main() -> None:
""" Ksft boiler plate main """
nf = NetdevFamily()
@@ -498,7 +647,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.611.g1030f0b940-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH net-next v3 0/6] net: devmem: support leased RX and TX queues on netdev unregister
2026-11-15 2:26 [PATCH net-next v3 0/6] net: devmem: support leased RX and TX queues on netdev unregister Mina Almasry
` (5 preceding siblings ...)
2026-11-15 2:26 ` [PATCH net-next v3 6/6] selftests: net: add leased devmem RX and TX netdev unregister tests Mina Almasry
@ 2026-11-15 20:08 ` netdev-bot+sinfo
6 siblings, 0 replies; 8+ messages in thread
From: netdev-bot+sinfo @ 2026-11-15 20:08 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, Weiming Shi, Daniel Borkmann, Nikolay Aleksandrov,
David Wei, Stanislav Fomichev, Bobby Eshleman, Dragos Tatulea
Hi!
This is an automated message. This series looks like a fix, but its
commit messages seem to be missing some information:
- How the issue was discovered, e.g. hit in production, hit during
development, syzbot report, manual code inspection, LLM or static
analysis tool scan.
Please do not repost the series just to address the above. Instead,
reply to this email with the missing information, so that reviewers
can take it into account.
*IMPORTANT*: If you do repost (for other reasons) - please include
the relevant info in the cover letter or commit messages.
--
The evaluation is done by an LLM so it may be wrong, if you think
that is the case please reply and explain.
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-11-15 20:08 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-15 2:26 [PATCH net-next v3 0/6] net: devmem: support leased RX and TX queues on netdev unregister Mina Almasry
2026-11-15 2:26 ` [PATCH net-next v3 1/6] net: devmem: pass net_device pointer to net_devmem_lock_dev() Mina Almasry
2026-11-15 2:26 ` [PATCH net-next v3 2/6] net: devmem: rename binding dev and vdev to map_dev and bind_dev Mina Almasry
2026-11-15 2:26 ` [PATCH net-next v3 3/6] net: guard memory provider RX queue open and close on unregister Mina Almasry
2026-11-15 2:26 ` [PATCH net-next v3 4/6] net: devmem: always populate bind_dev and detach both devices " Mina Almasry
2026-11-15 2:26 ` [PATCH net-next v3 5/6] net: devmem: resolve physical map_dev for leased RX bindings Mina Almasry
2026-11-15 2:26 ` [PATCH net-next v3 6/6] selftests: net: add leased devmem RX and TX netdev unregister tests Mina Almasry
2026-11-15 20:08 ` [PATCH net-next v3 0/6] net: devmem: support leased RX and TX queues on netdev unregister netdev-bot+sinfo
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox