* [PATCH net v5 0/4] net: devmem: fix RX and TX binding teardown on device unregistration
@ 2026-10-30 8:35 Mina Almasry
2026-10-30 8:35 ` [PATCH net v5 1/4] net: devmem: unmap dma_buf synchronously on queue uninstall Mina Almasry
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Mina Almasry @ 2026-10-30 8:35 UTC (permalink / raw)
To: netdev, linux-kselftest, linux-kernel, linux-media, dri-devel,
Paolo Abeni, Stanislav Fomichev, Mina Almasry, Kaiyuan Zhang,
Jakub Kicinski, Bobby Eshleman, Pavel Begunkov, David Wei,
Taehee Yoo
Cc: David S. Miller, Eric Dumazet, Simon Horman, Shuah Khan,
Sumit Semwal, Christian König, Philipp Stanner, Andrew Lunn,
Stanislav Fomichev, Jesper Dangaard Brouer, Ilias Apalodimas,
Daniel Borkmann, Nikolay Aleksandrov, Tariq Toukan, Kaifeng Wang,
Dragos Tatulea
Fix two device-unregistration lifecycle bugs in devmem TCP bindings and
add netdevsim support plus selftests covering both RX and TX device
unregistration. These issues were found via LLM-assisted code review
(Sashiko) and reproduced and verified with the new nl_netdev.py
selftests on netdevsim (not tested on real hardware):
1. net: devmem: unmap dma_buf synchronously on queue uninstall
Synchronously unmap and detach binding->attachment under binding->lock
when the last RX queue is uninstalled in mp_dmabuf_devmem_uninstall()
or closed in net_devmem_unbind_dmabuf(), and unmap remaining unbound RX
bindings on NETDEV_UNREGISTER, preventing slab-use-after-free in
dma_unmap_sg_attrs() when the binding is freed after dma_dev. Factor
out net_devmem_lock_dev() and net_devmem_unlock_dev() to synchronize
netdev_nl_sock_priv_destroy() and __net_devmem_dmabuf_binding_free()
against NETDEV_UNREGISTER.
2. net: devmem: detach TX bindings on NETDEV_UNREGISTER
Extend net_devmem_dev_unregister() to fully detach and unmap TX
bindings when binding->dev unregisters, or clear binding->vdev alone
when only a virtual device unregisters while binding->dev lives.
3. netdevsim: support devmem RX and TX bindings
Enable PP_FLAG_DMA_MAP | PP_FLAG_ALLOW_UNREADABLE_NETMEM, 64-bit DMA mask
on nsim_bus_dev, and NETMEM_TX_DMA on netdevsim so devmem RX/TX bindings
can be exercised in software selftests.
4. selftests: net: add devmem RX and TX netdev unregister tests
Add devmem_bind_rx_unregister_check and devmem_bind_tx_unregister_check
to tools/testing/selftests/net/nl_netdev.py.
Cc: Tariq Toukan <tariqt@nvidia.com>
Cc: Kaifeng Wang <kaifengw@google.com>
Cc: Dragos Tatulea <dtatulea@nvidia.com>
Cc: Stanislav Fomichev <sdf@fomichev.me>
---
v5:
- Rebase onto net after the net-next merge and state how the issues were
discovered and tested (Jakub).
- 1/4: Inline net_devmem_dev_unregister_rx() into
net_devmem_dev_unregister() (Stanislav). Factor out
net_devmem_lock_dev() and net_devmem_unlock_dev() and clear binding->dev
after unmapping and re-acquiring the xarray lock so
__net_devmem_dmabuf_binding_free() cannot free the binding while
net_devmem_dev_unregister() is still accessing it, and reject RX devmem
net_iovs in validate_xmit_unreadable_skb() (Sashiko).
- 2/4: Inline the TX unregistration check into net_devmem_dev_unregister()
(Stanislav), clear binding->vdev under the xarray lock, and lock
binding->dev in __net_devmem_dmabuf_binding_free() for TX bindings as
well (Sashiko).
- 3/4: Configure the DMA mask and dma_parms in nsim_bus_probe() gated on
dma_pci_p2pdma_supported(), require PP_DMA_INDEX_BITS before setting
PP_FLAG_DMA_MAP, check for NULL ns->rq in nsim_pp_hold_write(), and drop
unreadable or net_iov skbs at the start of nsim_start_xmit() (Sashiko).
- 4/4: Tolerate non-UTF-8 dma-buf names in debugfs bufinfo (Sashiko).
- Link to v4: https://lore.kernel.org/netdev/20261015163915.1166043-1-almasrymina@google.com/
v4:
- 1/4: Unmap RX bindings in net_devmem_unbind_dmabuf() once their queues
are closed, and make netif_mp_close_rxq() report queue restart
failures, so the NETDEV_UNREGISTER walk only handles bindings whose
queue failed to restart. Drop net_devmem_lock: walk the bindings under
the xarray lock and take the instance lock of binding->dev in the
final free. (Stanislav) Record a queue in bound_rxqs before opening
it. (Sashiko)
- 2/4: Detach and unmap TX bindings directly from the walk, without a
grace period or a second pass. (Stanislav)
- Link to v3: https://lore.kernel.org/netdev/20261014161754.3782346-1-almasrymina@google.com/
v3:
- 1/4: Run the NETDEV_UNREGISTER walk under the netdev instance lock,
and skip it when the device only moves to another netns. (Sashiko)
- 2/4: Detach all matching TX bindings, then synchronize_net() once
before unmapping them. (Sashiko)
- 3/4: Only request page pool DMA mapping with CONFIG_HAS_DMA, refuse
pp_hold on memory provider pools, and drop unreadable skbs in the
software PSP checksum path. (Sashiko)
- 4/4: Check only the test's dma-buf in debugfs, skip instead of passing
silently, clean up on errors, compute UDMABUF_CREATE per arch, and
add the needed config options. (Sashiko, Dragos)
- Link to v2: https://lore.kernel.org/netdev/20261012174300.2338784-1-almasrymina@google.com/
v2:
- 1/4: Don't unmap when a leased queue is unleased. Keep bindings in
net_devmem_dmabuf_bindings until freed, hidden from TX lookups by an
xarray mark, and unmap unbound RX bindings on NETDEV_UNREGISTER. Update
the binding->lock and binding->ref comments. (Sashiko)
- 2/4: Detach TX bindings from the same net_devmem_lock-protected walk,
which now also covers unbound TX bindings, and clear the LIVE mark
instead of erasing the ID. The walk no longer uses RCU or takes
binding references (Stanislav).
- Link to v1: https://lore.kernel.org/netdev/20261010025532.839559-1-almasrymina@google.com/
Mina Almasry (4):
net: devmem: unmap dma_buf synchronously on queue uninstall
net: devmem: detach TX bindings on NETDEV_UNREGISTER
netdevsim: support devmem RX and TX bindings
selftests: net: add devmem RX and TX netdev unregister tests
drivers/net/netdevsim/bus.c | 9 ++
drivers/net/netdevsim/netdev.c | 33 ++++-
drivers/net/netdevsim/netdevsim.h | 1 +
include/net/page_pool/memory_provider.h | 4 +-
net/core/dev.c | 4 +-
net/core/devmem.c | 181 +++++++++++++++++++----
net/core/devmem.h | 37 ++++-
net/core/netdev-genl.c | 16 +-
net/core/netdev_rx_queue.c | 26 ++--
tools/testing/selftests/net/config | 4 +
tools/testing/selftests/net/nl_netdev.py | 125 +++++++++++++++-
11 files changed, 373 insertions(+), 67 deletions(-)
base-commit: 5838f10563d58afca6711edb5e4eda968ca181d9
--
2.56.0.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH net v5 1/4] net: devmem: unmap dma_buf synchronously on queue uninstall
2026-10-30 8:35 [PATCH net v5 0/4] net: devmem: fix RX and TX binding teardown on device unregistration Mina Almasry
@ 2026-10-30 8:35 ` Mina Almasry
2026-10-30 8:35 ` [PATCH net v5 2/4] net: devmem: detach TX bindings on NETDEV_UNREGISTER Mina Almasry
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Mina Almasry @ 2026-10-30 8:35 UTC (permalink / raw)
To: netdev, linux-kselftest, linux-kernel, linux-media, dri-devel,
Paolo Abeni, Stanislav Fomichev, Mina Almasry, Kaiyuan Zhang,
Jakub Kicinski, Bobby Eshleman, Pavel Begunkov, David Wei,
Taehee Yoo
Cc: David S. Miller, Eric Dumazet, Simon Horman, Shuah Khan,
Sumit Semwal, Christian König, Philipp Stanner, Andrew Lunn,
Stanislav Fomichev, Jesper Dangaard Brouer, Ilias Apalodimas,
Daniel Borkmann, Nikolay Aleksandrov, Tariq Toukan, Kaifeng Wang,
Dragos Tatulea
When a bound net_device is unregistered while userspace still holds a
netlink or TCP socket reference on a devmem binding,
mp_dmabuf_devmem_uninstall() clears binding->dev to NULL so
netdev_nl_sock_priv_destroy() will not touch the unregistered
net_device, but leaves the dma_buf attachment mapped until
__net_devmem_dmabuf_binding_free() runs later on socket close.
Because dma_buf_dynamic_attach() does not take a reference on dma_dev,
device removal frees dma_dev while the attachment is still mapped.
Closing the netlink socket afterwards triggers a slab use-after-free in
dma_unmap_sg_attrs():
BUG: KASAN: slab-use-after-free in dma_unmap_sg_attrs+0x76/0x2b0
Read of size 8 at addr ffff88810fae64b0 by task kworker/3:1/53
Workqueue: events __net_devmem_dmabuf_binding_free
Call Trace:
dma_unmap_sg_attrs+0x76/0x2b0
unmap_udmabuf+0x8e/0x100
dma_buf_unmap_attachment+0x145/0x330
dma_buf_unmap_attachment_unlocked+0x96/0x130
__net_devmem_dmabuf_binding_free+0x156/0x2e0
Freed by task 9:
kfree+0x1b8/0x550
nsim_bus_dev_release+0xe/0x60
device_release+0xcf/0x250
Add net_devmem_dmabuf_binding_unmap() serialized under binding->lock
and null out binding->sgt and binding->attachment after unmapping so
unmapping is idempotent across queue uninstall and deferred binding
destruction. Call it synchronously when the last bound RX queue is
uninstalled in mp_dmabuf_devmem_uninstall(), except when a leased
queue is unleased: the physical device is still registered and, if
restarting its queue failed, may still be using the binding.
The same use-after-free happens when the netlink socket is closed
before the device is removed but RX tokens keep the binding alive. Once
its queues are closed, nothing DMAs into an RX binding anymore and the
outstanding tokens only need the net_iovs, so unmap it in
net_devmem_unbind_dmabuf(), and reject RX devmem net_iovs in
validate_xmit_unreadable_skb(). Make netif_mp_close_rxq() return an
error when a queue failed to restart and may still be using the binding.
Such a binding stays mapped and keeps binding->dev. Record a queue in
bound_rxqs before opening it, so that a failed bind never leaves a
queue using the binding without unbind knowing about it.
Keep bindings in net_devmem_dmabuf_bindings until they are freed and
hide unbound ones from TX lookups with an xarray mark, so that on
NETDEV_UNREGISTER the remaining unbound RX bindings of the device can be
found, detached and unmapped, unless the device is a virtual device
leasing queues, for the same reason as above. The walk runs under the
device's instance lock and is skipped when the device only moves to
another netns. Factor out net_devmem_lock_dev() and
net_devmem_unlock_dev() and use them in both
netdev_nl_sock_priv_destroy() and __net_devmem_dmabuf_binding_free(), so
a binding the walk finds under the xarray lock stays alive while it is
detached.
This issue was found via LLM-assisted code review (Sashiko) and
reproduced and verified with the new nl_netdev.py selftest on netdevsim
(not tested on real hardware).
Fixes: f8350a4358fc ("net: page_pool: add a mp hook to unregister_netdevice*")
Fixes: 0afc44d8cdf6 ("net: devmem: fix kernel panic when netlink socket close after module unload")
Suggested-by: Stanislav Fomichev <sdf@fomichev.me>
Cc: Taehee Yoo <ap420073@gmail.com>
Cc: Tariq Toukan <tariqt@nvidia.com>
Cc: Kaifeng Wang <kaifengw@google.com>
Signed-off-by: Mina Almasry <almasrymina@google.com>
Reviewed-by: Dragos Tatulea <dtatulea@nvidia.com>
Acked-by: Stanislav Fomichev <sdf@fomichev.me>
---
v5:
- Inline net_devmem_dev_unregister_rx() into net_devmem_dev_unregister()
(Stanislav).
- Factor out net_devmem_lock_dev() and net_devmem_unlock_dev() and clear
binding->dev after unmapping and re-acquiring the xarray lock so
__net_devmem_dmabuf_binding_free() cannot free the binding while
net_devmem_dev_unregister() is still accessing it (Sashiko).
- Reject RX devmem net_iovs in validate_xmit_unreadable_skb() so an RX
binding is not unmapped while its net_iovs are in flight on TX
(Sashiko).
v4:
- Unmap RX bindings in net_devmem_unbind_dmabuf() once their queues are
closed, and make netif_mp_close_rxq() report queue restart failures.
The NETDEV_UNREGISTER walk only handles bindings whose queue failed
to restart (Stanislav).
- Drop net_devmem_lock. Walk the bindings under the xarray lock, and
take the instance lock of binding->dev in the final free (Stanislav).
- Record a queue in bound_rxqs before opening it, so unbind knows about
every queue using the binding (Sashiko).
v3:
- Run the NETDEV_UNREGISTER walk under the netdev instance lock so it
cannot race with net_devmem_unbind_dmabuf() (Sashiko).
- Skip the walk when the device only moves to another netns (Sashiko).
v2:
- Don't unmap when a leased queue is unleased (Sashiko).
- Unmap unbound RX bindings on NETDEV_UNREGISTER; keep bindings in
net_devmem_dmabuf_bindings until freed, hidden from TX lookups by
NET_DEVMEM_BINDING_LIVE (Sashiko).
- Update the binding->lock and binding->ref comments (Sashiko).
---
include/net/page_pool/memory_provider.h | 4 +-
net/core/dev.c | 4 +-
net/core/devmem.c | 173 ++++++++++++++++++++----
net/core/devmem.h | 31 ++++-
net/core/netdev-genl.c | 16 +--
net/core/netdev_rx_queue.c | 26 ++--
6 files changed, 198 insertions(+), 56 deletions(-)
diff --git a/include/net/page_pool/memory_provider.h b/include/net/page_pool/memory_provider.h
index 137cfc50833ac..bceb5f7740e2f 100644
--- a/include/net/page_pool/memory_provider.h
+++ b/include/net/page_pool/memory_provider.h
@@ -36,8 +36,8 @@ void net_mp_niov_clear_page_pool(struct net_iov *niov);
int netif_mp_open_rxq(struct net_device *dev, unsigned int rxq_idx,
const struct pp_memory_provider_params *p,
struct netlink_ext_ack *extack);
-void netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,
- const struct pp_memory_provider_params *old_p);
+int netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,
+ const struct pp_memory_provider_params *old_p);
/**
* net_mp_netmem_place_in_cache() - give a netmem to a page pool
diff --git a/net/core/dev.c b/net/core/dev.c
index 3ac700d37e596..1aece2fe2429f 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -4181,10 +4181,12 @@ static struct sk_buff *validate_xmit_unreadable_skb(struct sk_buff *skb,
shinfo = skb_shinfo(skb);
+ /* All net_iovs in an unreadable skb share the same binding. */
if (shinfo->nr_frags > 0) {
niov = netmem_to_net_iov(skb_frag_netmem(&shinfo->frags[0]));
if (net_is_devmem_iov(niov) &&
- READ_ONCE(net_devmem_iov_binding(niov)->dev) != dev)
+ (net_devmem_iov_binding(niov)->direction != DMA_TO_DEVICE ||
+ READ_ONCE(net_devmem_iov_binding(niov)->dev) != dev))
goto out_free;
}
diff --git a/net/core/devmem.c b/net/core/devmem.c
index 32fafbfa1359e..4610de805ef08 100644
--- a/net/core/devmem.c
+++ b/net/core/devmem.c
@@ -12,6 +12,7 @@
#include <linux/mm.h>
#include <linux/netdevice.h>
#include <linux/types.h>
+#include <net/netdev_lock.h>
#include <net/netdev_queues.h>
#include <net/netdev_rx_queue.h>
#include <net/page_pool/helpers.h>
@@ -19,12 +20,14 @@
#include <net/sock.h>
#include <net/tcp.h>
+#include "dev.h"
#include "devmem.h"
#include "mp_dmabuf_devmem.h"
/* Device memory support */
static DEFINE_XARRAY_FLAGS(net_devmem_dmabuf_bindings, XA_FLAGS_ALLOC1);
+#define NET_DEVMEM_BINDING_LIVE XA_MARK_1
static const struct memory_provider_ops dmabuf_devmem_ops;
@@ -64,9 +67,89 @@ static void net_devmem_dmabuf_binding_release(struct percpu_ref *ref)
schedule_work(&binding->unbind_w);
}
+static void
+net_devmem_dmabuf_binding_unmap(struct net_devmem_dmabuf_binding *binding)
+{
+ mutex_lock(&binding->lock);
+ if (binding->sgt) {
+ dma_resv_lock(binding->dmabuf->resv, NULL);
+ dma_buf_unmap_attachment(binding->attachment, binding->sgt,
+ binding->direction);
+ dma_buf_unpin(binding->attachment);
+ dma_resv_unlock(binding->dmabuf->resv);
+ dma_buf_detach(binding->dmabuf, binding->attachment);
+ binding->sgt = NULL;
+ binding->attachment = NULL;
+ }
+ mutex_unlock(&binding->lock);
+}
+
+/**
+ * net_devmem_lock_dev() - lock a binding's net_device if still alive
+ * @binding: devmem binding whose @dev pointer to lock
+ * @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.
+ *
+ * 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).
+ *
+ * Taking rcu_read_lock() guarantees that a non-NULL @dev read from
+ * @binding->dev 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
+ * cannot finish unregistering until net_devmem_unlock_dev().
+ *
+ * Return: The locked and held net_device if @binding->dev is still non-NULL,
+ * or NULL if already cleared.
+ */
+struct net_device *
+net_devmem_lock_dev(struct net_devmem_dmabuf_binding *binding,
+ netdevice_tracker *dev_tracker)
+{
+ struct net_device *dev;
+
+ rcu_read_lock();
+ dev = READ_ONCE(binding->dev);
+ if (!dev) {
+ rcu_read_unlock();
+ return NULL;
+ }
+ netdev_hold(dev, dev_tracker, GFP_ATOMIC);
+ rcu_read_unlock();
+
+ netdev_lock(dev);
+ if (READ_ONCE(binding->dev) != dev) {
+ netdev_unlock(dev);
+ netdev_put(dev, dev_tracker);
+ return NULL;
+ }
+
+ return dev;
+}
+
+void net_devmem_unlock_dev(struct net_device *dev,
+ netdevice_tracker *dev_tracker)
+{
+ if (!dev)
+ return;
+
+ netdev_unlock(dev);
+ netdev_put(dev, dev_tracker);
+}
+
void __net_devmem_dmabuf_binding_free(struct work_struct *wq)
{
struct net_devmem_dmabuf_binding *binding = container_of(wq, typeof(*binding), unbind_w);
+ netdevice_tracker dev_tracker;
+ struct net_device *dev = NULL;
if (binding->freelist)
WARN(binding->free_count != binding->area.num_niovs,
@@ -74,12 +157,12 @@ void __net_devmem_dmabuf_binding_free(struct work_struct *wq)
binding->area.num_niovs, binding->free_count);
kvfree(binding->area.niovs);
- dma_resv_lock(binding->dmabuf->resv, NULL);
- dma_buf_unmap_attachment(binding->attachment, binding->sgt,
- binding->direction);
- dma_buf_unpin(binding->attachment);
- dma_resv_unlock(binding->dmabuf->resv);
- dma_buf_detach(binding->dmabuf, binding->attachment);
+ if (binding->direction == DMA_FROM_DEVICE)
+ dev = net_devmem_lock_dev(binding, &dev_tracker);
+ net_devmem_dmabuf_binding_unmap(binding);
+ xa_erase(&net_devmem_dmabuf_bindings, binding->id);
+ net_devmem_unlock_dev(dev, &dev_tracker);
+
dma_buf_put(binding->dmabuf);
xa_destroy(&binding->bound_rxqs);
percpu_ref_exit(&binding->ref);
@@ -124,11 +207,13 @@ void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding)
struct netdev_rx_queue *rxq;
unsigned long xa_idx;
unsigned int rxq_idx;
+ bool busy = false;
- xa_erase(&net_devmem_dmabuf_bindings, binding->id);
+ xa_clear_mark(&net_devmem_dmabuf_bindings, binding->id,
+ NET_DEVMEM_BINDING_LIVE);
/* Ensure no tx net_devmem_lookup_dmabuf() are in flight after the
- * erase.
+ * mark is cleared.
*/
synchronize_net();
@@ -143,12 +228,45 @@ void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding)
rxq_idx = get_netdev_rx_queue_index(rxq);
- netif_mp_close_rxq(binding->dev, rxq_idx, &mp_params);
+ if (netif_mp_close_rxq(binding->dev, rxq_idx, &mp_params))
+ busy = true;
+ }
+
+ if (binding->direction == DMA_FROM_DEVICE && binding->dev && !busy) {
+ net_devmem_dmabuf_binding_unmap(binding);
+ WRITE_ONCE(binding->dev, NULL);
}
percpu_ref_kill(&binding->ref);
}
+void net_devmem_dev_unregister(struct net_device *dev)
+{
+ struct net_devmem_dmabuf_binding *binding;
+ unsigned long xa_idx;
+
+ netdev_assert_locked(dev);
+
+ if (dev->reg_state != NETREG_UNREGISTERING)
+ return;
+
+ xa_lock(&net_devmem_dmabuf_bindings);
+ xa_for_each(&net_devmem_dmabuf_bindings, xa_idx, binding) {
+ if (binding->direction != DMA_FROM_DEVICE ||
+ READ_ONCE(binding->dev) != dev ||
+ xa_get_mark(&net_devmem_dmabuf_bindings, xa_idx,
+ NET_DEVMEM_BINDING_LIVE))
+ continue;
+
+ xa_unlock(&net_devmem_dmabuf_bindings);
+ if (!netif_is_queue_leasee(dev))
+ net_devmem_dmabuf_binding_unmap(binding);
+ xa_lock(&net_devmem_dmabuf_bindings);
+ WRITE_ONCE(binding->dev, NULL);
+ }
+ xa_unlock(&net_devmem_dmabuf_bindings);
+}
+
int net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx,
struct net_devmem_dmabuf_binding *binding,
struct netlink_ext_ack *extack)
@@ -164,20 +282,16 @@ int net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx,
if (binding->niov_shift != PAGE_SHIFT)
mp_params.rx_page_size = 1U << binding->niov_shift;
- err = netif_mp_open_rxq(dev, rxq_idx, &mp_params, extack);
- if (err)
- return err;
-
rxq = __netif_get_rx_queue(dev, rxq_idx);
err = xa_alloc(&binding->bound_rxqs, &xa_idx, rxq, xa_limit_32b,
GFP_KERNEL);
if (err)
- goto err_close_rxq;
+ return err;
- return 0;
+ err = netif_mp_open_rxq(dev, rxq_idx, &mp_params, extack);
+ if (err)
+ xa_erase(&binding->bound_rxqs, xa_idx);
-err_close_rxq:
- netif_mp_close_rxq(dev, rxq_idx, &mp_params);
return err;
}
@@ -345,6 +459,8 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
GFP_KERNEL);
if (err < 0)
goto err_free_niovs;
+ xa_set_mark(&net_devmem_dmabuf_bindings, binding->id,
+ NET_DEVMEM_BINDING_LIVE);
list_add(&binding->list, &priv->bindings);
@@ -375,9 +491,11 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
struct net_devmem_dmabuf_binding *net_devmem_lookup_dmabuf(u32 id)
{
struct net_devmem_dmabuf_binding *binding;
+ unsigned long xa_idx = id;
rcu_read_lock();
- binding = xa_load(&net_devmem_dmabuf_bindings, id);
+ binding = xa_find(&net_devmem_dmabuf_bindings, &xa_idx, id,
+ NET_DEVMEM_BINDING_LIVE);
if (binding) {
if (!net_devmem_dmabuf_binding_get(binding))
binding = NULL;
@@ -551,16 +669,17 @@ static void mp_dmabuf_devmem_uninstall(void *mp_priv,
unsigned long xa_idx;
xa_for_each(&binding->bound_rxqs, xa_idx, bound_rxq) {
- if (bound_rxq == rxq) {
- xa_erase(&binding->bound_rxqs, xa_idx);
- if (xa_empty(&binding->bound_rxqs)) {
- mutex_lock(&binding->lock);
- ASSERT_EXCLUSIVE_WRITER(binding->dev);
- WRITE_ONCE(binding->dev, NULL);
- mutex_unlock(&binding->lock);
- }
- break;
+ if (bound_rxq != rxq)
+ continue;
+
+ xa_erase(&binding->bound_rxqs, xa_idx);
+ 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);
}
+ break;
}
}
diff --git a/net/core/devmem.h b/net/core/devmem.h
index 8fa089776610a..a9c38ed6eaa44 100644
--- a/net/core/devmem.h
+++ b/net/core/devmem.h
@@ -21,7 +21,9 @@ struct net_devmem_dmabuf_binding {
struct dma_buf *dmabuf;
struct dma_buf_attachment *attachment;
struct sg_table *sgt;
- /* Physical NIC that does the actual DMA for this binding. */
+ /* Physical NIC that does the actual DMA for this binding. Set at bind
+ * time and cleared to NULL under netdev_lock(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. Never
@@ -30,7 +32,7 @@ struct net_devmem_dmabuf_binding {
void *vdev;
possible_net_t net;
kuid_t uid;
- /* Protect dev */
+ /* Serialize net_devmem_dmabuf_binding_unmap() on attachment and sgt */
struct mutex lock;
/* The user holds a ref (via the netlink API) for as long as they want
@@ -41,7 +43,8 @@ struct net_devmem_dmabuf_binding {
*
* The binding undos itself and unmaps the underlying dmabuf once all
* those refs are dropped and the binding is no longer desired or in
- * use.
+ * use. RX bindings are unmapped earlier when their queues are closed or
+ * uninstalled, unless a leased or busy queue may still use the binding.
*
* net_devmem_net_iov_inc_ref() on dmabuf net_iovs will increment this
* reference, making sure that the binding remains alive until all the
@@ -94,7 +97,13 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
const struct sock *nl_sk,
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,
+ netdevice_tracker *dev_tracker);
+void net_devmem_unlock_dev(struct net_device *dev,
+ netdevice_tracker *dev_tracker);
void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding);
+void net_devmem_dev_unregister(struct net_device *dev);
int net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx,
struct net_devmem_dmabuf_binding *binding,
struct netlink_ext_ack *extack);
@@ -177,11 +186,27 @@ static inline struct net_devmem_dmabuf_binding *net_devmem_lookup_dmabuf(u32 id)
return NULL;
}
+static inline struct net_device *
+net_devmem_lock_dev(struct net_devmem_dmabuf_binding *binding,
+ netdevice_tracker *dev_tracker)
+{
+ return NULL;
+}
+
+static inline void
+net_devmem_unlock_dev(struct net_device *dev, netdevice_tracker *dev_tracker)
+{
+}
+
static inline void
net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding)
{
}
+static inline void net_devmem_dev_unregister(struct net_device *dev)
+{
+}
+
static inline int
net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx,
struct net_devmem_dmabuf_binding *binding,
diff --git a/net/core/netdev-genl.c b/net/core/netdev-genl.c
index 384fdc5ae5145..9c5bf17b04e57 100644
--- a/net/core/netdev-genl.c
+++ b/net/core/netdev-genl.c
@@ -1476,20 +1476,9 @@ 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) {
- mutex_lock(&binding->lock);
- dev = binding->dev;
- if (!dev) {
- mutex_unlock(&binding->lock);
- net_devmem_unbind_dmabuf(binding);
- continue;
- }
- netdev_hold(dev, &dev_tracker, GFP_KERNEL);
- mutex_unlock(&binding->lock);
-
- netdev_lock(dev);
+ dev = net_devmem_lock_dev(binding, &dev_tracker);
net_devmem_unbind_dmabuf(binding);
- netdev_unlock(dev);
- netdev_put(dev, &dev_tracker);
+ net_devmem_unlock_dev(dev, &dev_tracker);
}
mutex_unlock(&priv->lock);
}
@@ -1513,6 +1502,7 @@ static int netdev_genl_netdevice_event(struct notifier_block *nb,
if (READ_ONCE(netdev->reg_state) == NETREG_UNREGISTERED)
break;
netdev_lock(netdev);
+ net_devmem_dev_unregister(netdev);
netdev_genl_dev_notify(netdev, NETDEV_CMD_DEV_DEL_NTF);
netdev_unlock(netdev);
break;
diff --git a/net/core/netdev_rx_queue.c b/net/core/netdev_rx_queue.c
index 00a7011eb4d57..a7261fedd5e76 100644
--- a/net/core/netdev_rx_queue.c
+++ b/net/core/netdev_rx_queue.c
@@ -275,8 +275,8 @@ int netif_mp_open_rxq(struct net_device *dev, unsigned int rxq_idx,
return ret;
}
-static void __netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,
- const struct pp_memory_provider_params *old_p)
+static int __netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,
+ const struct pp_memory_provider_params *old_p)
{
struct netdev_queue_config qcfg[2];
struct netdev_rx_queue *rxq;
@@ -289,35 +289,41 @@ static void __netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,
*/
if (dev->reg_state > NETREG_REGISTERED &&
!rxq->mp_params.mp_ops)
- return;
+ return 0;
if (WARN_ON_ONCE(rxq->mp_params.mp_ops != old_p->mp_ops ||
rxq->mp_params.mp_priv != old_p->mp_priv))
- return;
+ return -EINVAL;
netdev_queue_config(dev, rxq_idx, &qcfg[0]);
memset(&rxq->mp_params, 0, sizeof(rxq->mp_params));
netdev_queue_config(dev, rxq_idx, &qcfg[1]);
err = netdev_rx_queue_reconfig(dev, rxq_idx, &qcfg[0], &qcfg[1]);
- WARN_ON(err && err != -ENETDOWN);
+ if (err == -ENETDOWN)
+ return 0;
+ WARN_ON(err);
+ return err;
}
-void netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,
- const struct pp_memory_provider_params *old_p)
+int netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,
+ const struct pp_memory_provider_params *old_p)
{
+ int ret;
+
if (WARN_ON_ONCE(rxq_idx >= dev->real_num_rx_queues))
- return;
+ 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)) {
WARN_ON_ONCE(1);
- return;
+ return -EINVAL;
}
netdev_lock(dev);
- __netif_mp_close_rxq(dev, rxq_idx, old_p);
+ ret = __netif_mp_close_rxq(dev, rxq_idx, old_p);
netdev_unlock(dev);
+ return ret;
}
void __netif_mp_uninstall_rxq(struct netdev_rx_queue *rxq,
--
2.56.0.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH net v5 2/4] net: devmem: detach TX bindings on NETDEV_UNREGISTER
2026-10-30 8:35 [PATCH net v5 0/4] net: devmem: fix RX and TX binding teardown on device unregistration Mina Almasry
2026-10-30 8:35 ` [PATCH net v5 1/4] net: devmem: unmap dma_buf synchronously on queue uninstall Mina Almasry
@ 2026-10-30 8:35 ` Mina Almasry
2026-10-30 8:35 ` [PATCH net v5 3/4] netdevsim: support devmem RX and TX bindings Mina Almasry
2026-10-30 8:35 ` [PATCH net v5 4/4] selftests: net: add devmem RX and TX netdev unregister tests Mina Almasry
3 siblings, 0 replies; 5+ messages in thread
From: Mina Almasry @ 2026-10-30 8:35 UTC (permalink / raw)
To: netdev, linux-kselftest, linux-kernel, linux-media, dri-devel,
Paolo Abeni, Stanislav Fomichev, Mina Almasry, Kaiyuan Zhang,
Jakub Kicinski, Bobby Eshleman, Pavel Begunkov, David Wei,
Taehee Yoo
Cc: David S. Miller, Eric Dumazet, Simon Horman, Shuah Khan,
Sumit Semwal, Christian König, Philipp Stanner, Andrew Lunn,
Stanislav Fomichev, Jesper Dangaard Brouer, Ilias Apalodimas,
Daniel Borkmann, Nikolay Aleksandrov, Tariq Toukan, Kaifeng Wang,
Dragos Tatulea
TX devmem bindings (NETDEV_CMD_BIND_TX) have no bound RX queues, so
mp_dmabuf_devmem_uninstall() never runs when binding->dev or
binding->vdev unregisters. As a result, binding->dev and binding->vdev
dangle pointing to freed struct net_device memory, and the dma_buf
remains attached and mapped after device removal.
Closing the netlink socket after netdev unregistration dereferences the
freed struct net_device in netdev_nl_sock_priv_destroy():
BUG: unable to handle page fault for address: ffff88824ca9b000
Oops: Oops: 0000 [#1] SMP KASAN NOPTI
RIP: 0010:netdev_nl_sock_priv_destroy+0xbd/0x1e0
Call Trace:
genl_release+0xf5/0x190
netlink_release+0xd01/0x18d0
__sock_release+0xb0/0x270
sock_close+0x18/0x20
Extend net_devmem_dev_unregister() and __net_devmem_dmabuf_binding_free()
to TX bindings, including ones whose netlink socket is already closed
but which TCP retransmits keep alive:
- When binding->dev (the physical DMA-capable device) unregisters,
clear the binding's NET_DEVMEM_BINDING_LIVE mark, unmap the dma_buf,
and clear both binding->vdev and binding->dev. No grace period is
needed: the device is already closed, so nothing hands the DMA
addresses to the hardware anymore, and concurrent senders only compare
against binding->dev.
- When only binding->vdev (virtual device) unregisters while
binding->dev lives, only clear binding->vdev so no new TX sends match
the virtual device while allowing in-flight TX packets on binding->dev
to finish DMA safely.
This issue was found via LLM-assisted code review (Sashiko) and
reproduced and verified with the new nl_netdev.py selftest on netdevsim
(not tested on real hardware).
Fixes: bd61848900bf ("net: devmem: Implement TX path")
Fixes: 1abe839b34ae ("net: devmem: support TX over NETMEM_TX_NO_DMA devices")
Suggested-by: Stanislav Fomichev <sdf@fomichev.me>
Cc: Kaiyuan Zhang <kaiyuanz@google.com>
Cc: Bobby Eshleman <bobbyeshleman@meta.com>
Cc: Tariq Toukan <tariqt@nvidia.com>
Cc: Kaifeng Wang <kaifengw@google.com>
Signed-off-by: Mina Almasry <almasrymina@google.com>
Reviewed-by: Dragos Tatulea <dtatulea@nvidia.com>
Acked-by: Stanislav Fomichev <sdf@fomichev.me>
---
v5:
- Inline the TX unregistration check into net_devmem_dev_unregister()
(Stanislav), and clear binding->vdev under the xarray lock (Sashiko).
- Lock binding->dev in __net_devmem_dmabuf_binding_free() for TX
bindings as well now that net_devmem_dev_unregister() detaches them
(Sashiko).
v4:
- Detach and unmap TX bindings directly from the xarray walk, without a
grace period or a second pass (Stanislav).
v3:
- Detach all matching TX bindings first, then call synchronize_net()
once before unmapping them, instead of once per binding. Don't wait
for a grace period when only binding->vdev is cleared, since vdev is
never dereferenced (Sashiko).
- Don't detach TX bindings when the device only moves to another netns
(Sashiko).
v2:
- Build on the net_devmem_dev_unregister() walk from the previous patch,
so unbound TX bindings are also detached (Sashiko). Clear the
binding's LIVE mark instead of erasing its ID.
- The walk holds net_devmem_lock, which the final free also takes
before erasing the binding, instead of using RCU and
percpu_ref_tryget(), so it no longer takes a binding reference
(Stanislav).
---
net/core/devmem.c | 24 ++++++++++++++++--------
net/core/devmem.h | 6 ++++--
2 files changed, 20 insertions(+), 10 deletions(-)
diff --git a/net/core/devmem.c b/net/core/devmem.c
index 4610de805ef08..5c7d9640e9d14 100644
--- a/net/core/devmem.c
+++ b/net/core/devmem.c
@@ -149,7 +149,7 @@ void __net_devmem_dmabuf_binding_free(struct work_struct *wq)
{
struct net_devmem_dmabuf_binding *binding = container_of(wq, typeof(*binding), unbind_w);
netdevice_tracker dev_tracker;
- struct net_device *dev = NULL;
+ struct net_device *dev;
if (binding->freelist)
WARN(binding->free_count != binding->area.num_niovs,
@@ -157,8 +157,7 @@ void __net_devmem_dmabuf_binding_free(struct work_struct *wq)
binding->area.num_niovs, binding->free_count);
kvfree(binding->area.niovs);
- if (binding->direction == DMA_FROM_DEVICE)
- dev = net_devmem_lock_dev(binding, &dev_tracker);
+ dev = net_devmem_lock_dev(binding, &dev_tracker);
net_devmem_dmabuf_binding_unmap(binding);
xa_erase(&net_devmem_dmabuf_bindings, binding->id);
net_devmem_unlock_dev(dev, &dev_tracker);
@@ -252,11 +251,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 ||
- READ_ONCE(binding->dev) != dev ||
- xa_get_mark(&net_devmem_dmabuf_bindings, xa_idx,
- NET_DEVMEM_BINDING_LIVE))
- continue;
+ if (binding->direction == DMA_FROM_DEVICE) {
+ if (READ_ONCE(binding->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)
+ continue;
+ WRITE_ONCE(binding->vdev, NULL);
+ __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))
diff --git a/net/core/devmem.h b/net/core/devmem.h
index a9c38ed6eaa44..c4f476947dfaa 100644
--- a/net/core/devmem.h
+++ b/net/core/devmem.h
@@ -26,8 +26,8 @@ struct net_devmem_dmabuf_binding {
*/
struct net_device *dev;
/* Opaque cookie identifying the virtual device (e.g. netkit) the user
- * called bind-tx on. Used only for pointer comparison. Never
- * dereferenced.
+ * called bind-tx on. Used only for pointer comparison via READ_ONCE()
+ * and cleared with WRITE_ONCE() on unregistration. Never dereferenced.
*/
void *vdev;
possible_net_t net;
@@ -45,6 +45,8 @@ 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
+ * unregistered.
*
* net_devmem_net_iov_inc_ref() on dmabuf net_iovs will increment this
* reference, making sure that the binding remains alive until all the
--
2.56.0.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH net v5 3/4] netdevsim: support devmem RX and TX bindings
2026-10-30 8:35 [PATCH net v5 0/4] net: devmem: fix RX and TX binding teardown on device unregistration Mina Almasry
2026-10-30 8:35 ` [PATCH net v5 1/4] net: devmem: unmap dma_buf synchronously on queue uninstall Mina Almasry
2026-10-30 8:35 ` [PATCH net v5 2/4] net: devmem: detach TX bindings on NETDEV_UNREGISTER Mina Almasry
@ 2026-10-30 8:35 ` Mina Almasry
2026-10-30 8:35 ` [PATCH net v5 4/4] selftests: net: add devmem RX and TX netdev unregister tests Mina Almasry
3 siblings, 0 replies; 5+ messages in thread
From: Mina Almasry @ 2026-10-30 8:35 UTC (permalink / raw)
To: netdev, linux-kselftest, linux-kernel, linux-media, dri-devel,
Paolo Abeni, Stanislav Fomichev, Mina Almasry, Kaiyuan Zhang,
Jakub Kicinski, Bobby Eshleman, Pavel Begunkov, David Wei,
Taehee Yoo
Cc: David S. Miller, Eric Dumazet, Simon Horman, Shuah Khan,
Sumit Semwal, Christian König, Philipp Stanner, Andrew Lunn,
Stanislav Fomichev, Jesper Dangaard Brouer, Ilias Apalodimas,
Daniel Borkmann, Nikolay Aleksandrov, Tariq Toukan, Kaifeng Wang,
Dragos Tatulea
Set a 64-bit DMA mask and UINT_MAX max segment size on nsim_bus_dev in
nsim_bus_probe() when direct DMA mapping is supported, enable
NETMEM_TX_DMA on the netdevsim net_device, and configure page_pool
instances with PP_FLAG_ALLOW_UNREADABLE_NETMEM and queue_idx, plus
PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV when a DMA mask and
PP_DMA_INDEX_BITS are available, so devmem bind-rx and bind-tx netlink
operations can be tested against netdevsim in software selftests.
The pp_hold debugfs file allocates pages from the queue 0 page pool, so
refuse it when that pool is absent or backed by a memory provider, and
take the netdev instance lock there so a concurrent bind-rx cannot swap
the pool in between. Since netdevsim forwards TX skbs in software
without a DMA engine, drop unreadable or net_iov skbs in
nsim_start_xmit().
Cc: Tariq Toukan <tariqt@nvidia.com>
Cc: Kaifeng Wang <kaifengw@google.com>
Signed-off-by: Mina Almasry <almasrymina@google.com>
Reviewed-by: Dragos Tatulea <dtatulea@nvidia.com>
Acked-by: Stanislav Fomichev <sdf@fomichev.me>
---
v5:
- Configure the DMA mask and dma_parms in nsim_bus_probe() gated on
dma_pci_p2pdma_supported(), and require PP_DMA_INDEX_BITS before
setting PP_FLAG_DMA_MAP so page_pool_scrub() can unmap inflight pages
on 32-bit builds (Sashiko).
- Check for NULL ns->rq in nsim_pp_hold_write() and drop unreadable or
net_iov skbs at the start of nsim_start_xmit() (Sashiko).
v3:
- Only request page pool DMA mapping when CONFIG_HAS_DMA is set
(Sashiko).
- Refuse pp_hold on a page pool backed by a memory provider, under the
netdev instance lock (Sashiko).
- Drop unreadable skbs in the software PSP path when it would have to
checksum the payload (Sashiko).
---
drivers/net/netdevsim/bus.c | 9 +++++++++
drivers/net/netdevsim/netdev.c | 33 +++++++++++++++++++++++++------
drivers/net/netdevsim/netdevsim.h | 1 +
3 files changed, 37 insertions(+), 6 deletions(-)
diff --git a/drivers/net/netdevsim/bus.c b/drivers/net/netdevsim/bus.c
index b792ee10b1c1a..5f8f0c2f76939 100644
--- a/drivers/net/netdevsim/bus.c
+++ b/drivers/net/netdevsim/bus.c
@@ -5,6 +5,7 @@
#include <linux/completion.h>
#include <linux/device.h>
+#include <linux/dma-mapping.h>
#include <linux/idr.h>
#include <linux/kernel.h>
#include <linux/list.h>
@@ -416,6 +417,14 @@ static int nsim_bus_probe(struct device *dev)
{
struct nsim_bus_dev *nsim_bus_dev = to_nsim_bus_dev(dev);
+ if (dma_pci_p2pdma_supported(dev) &&
+ !dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(64))) {
+ dev->dma_parms = &nsim_bus_dev->dma_parms;
+ dma_set_max_seg_size(dev, UINT_MAX);
+ } else {
+ dev->dma_mask = NULL;
+ }
+
return nsim_drv_probe(nsim_bus_dev);
}
diff --git a/drivers/net/netdevsim/netdev.c b/drivers/net/netdevsim/netdev.c
index eebc02ccc4a92..0372a9bd63bbb 100644
--- a/drivers/net/netdevsim/netdev.c
+++ b/drivers/net/netdevsim/netdev.c
@@ -133,6 +133,10 @@ static netdev_tx_t nsim_start_xmit(struct sk_buff *skb, struct net_device *dev)
int dr;
rcu_read_lock();
+ if (!skb_frags_readable(skb) ||
+ (skb_shinfo(skb)->nr_frags &&
+ skb_frag_is_net_iov(&skb_shinfo(skb)->frags[0])))
+ goto out_drop_any;
if (!nsim_ipsec_tx(ns, skb))
goto out_drop_any;
@@ -422,19 +426,27 @@ static int nsim_poll(struct napi_struct *napi, int budget)
return done;
}
-static int nsim_create_page_pool(struct page_pool **p, struct napi_struct *napi)
+static int nsim_create_page_pool(struct net_device *dev, struct page_pool **p,
+ struct napi_struct *napi, int idx)
{
+ struct netdevsim *ns = netdev_priv(dev);
struct page_pool_params params = {
.order = 0,
.pool_size = NSIM_RING_SIZE,
+ .flags = PP_FLAG_ALLOW_UNREADABLE_NETMEM,
+ .max_len = PAGE_SIZE,
.nid = NUMA_NO_NODE,
- .dev = &napi->dev->dev,
+ .dev = &ns->nsim_bus_dev->dev,
.napi = napi,
.dma_dir = DMA_BIDIRECTIONAL,
- .netdev = napi->dev,
+ .netdev = dev,
+ .queue_idx = idx,
};
struct page_pool *pool;
+ if (ns->nsim_bus_dev->dev.dma_mask && PP_DMA_INDEX_BITS)
+ params.flags |= PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV;
+
pool = page_pool_create(¶ms);
if (IS_ERR(pool))
return PTR_ERR(pool);
@@ -458,7 +470,7 @@ static int nsim_init_napi(struct netdevsim *ns)
for (i = 0; i < dev->num_rx_queues; i++) {
rq = ns->rq[i];
- err = nsim_create_page_pool(&rq->page_pool, &rq->napi);
+ err = nsim_create_page_pool(dev, &rq->page_pool, &rq->napi, i);
if (err)
goto err_pp_destroy;
}
@@ -770,14 +782,16 @@ nsim_queue_mem_alloc(struct net_device *dev,
if (ns->rq_reset_mode == 1) {
if (!netif_running(ns->netdev))
return -ENETDOWN;
- return nsim_create_page_pool(&qmem->pp, &ns->rq[idx]->napi);
+ return nsim_create_page_pool(dev, &qmem->pp,
+ &ns->rq[idx]->napi, idx);
}
qmem->rq = nsim_queue_alloc();
if (!qmem->rq)
return -ENOMEM;
- err = nsim_create_page_pool(&qmem->rq->page_pool, &qmem->rq->napi);
+ err = nsim_create_page_pool(dev, &qmem->rq->page_pool, &qmem->rq->napi,
+ idx);
if (err)
goto err_free;
@@ -937,12 +951,16 @@ nsim_pp_hold_write(struct file *file, const char __user *data,
return ret;
rtnl_lock();
+ netdev_lock(ns->netdev);
ret = count;
if (val == !!ns->page)
goto exit;
if (!netif_running(ns->netdev) && val) {
ret = -ENETDOWN;
+ } else if (val && (!ns->rq ||
+ page_pool_is_unreadable(ns->rq[0]->page_pool))) {
+ ret = -EOPNOTSUPP;
} else if (val) {
ns->page = page_pool_dev_alloc_pages(ns->rq[0]->page_pool);
if (!ns->page)
@@ -954,6 +972,7 @@ nsim_pp_hold_write(struct file *file, const char __user *data,
}
exit:
+ netdev_unlock(ns->netdev);
rtnl_unlock();
return ret;
}
@@ -1059,6 +1078,8 @@ static int nsim_init_netdevsim(struct netdevsim *ns)
ns->netdev->netdev_ops = &nsim_netdev_ops;
ns->netdev->stat_ops = &nsim_stat_ops;
ns->netdev->queue_mgmt_ops = &nsim_queue_mgmt_ops;
+ if (ns->nsim_bus_dev->dev.dma_mask)
+ ns->netdev->netmem_tx = NETMEM_TX_DMA;
netdev_lockdep_set_classes(ns->netdev);
err = nsim_udp_tunnels_info_create(ns->nsim_dev, ns->netdev);
diff --git a/drivers/net/netdevsim/netdevsim.h b/drivers/net/netdevsim/netdevsim.h
index fd5599cd3bb9f..2aa7ea924396e 100644
--- a/drivers/net/netdevsim/netdevsim.h
+++ b/drivers/net/netdevsim/netdevsim.h
@@ -476,6 +476,7 @@ int nsim_setup_tc(struct net_device *dev, enum tc_setup_type type,
#define NSIM_BUS_DEV_MAX_VFS 4
struct nsim_bus_dev {
struct device dev;
+ struct device_dma_parameters dma_parms;
struct list_head list;
unsigned int port_count;
unsigned int num_queues; /* Number of queues for each port on this bus */
--
2.56.0.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH net v5 4/4] selftests: net: add devmem RX and TX netdev unregister tests
2026-10-30 8:35 [PATCH net v5 0/4] net: devmem: fix RX and TX binding teardown on device unregistration Mina Almasry
` (2 preceding siblings ...)
2026-10-30 8:35 ` [PATCH net v5 3/4] netdevsim: support devmem RX and TX bindings Mina Almasry
@ 2026-10-30 8:35 ` Mina Almasry
3 siblings, 0 replies; 5+ messages in thread
From: Mina Almasry @ 2026-10-30 8:35 UTC (permalink / raw)
To: netdev, linux-kselftest, linux-kernel, linux-media, dri-devel,
Paolo Abeni, Stanislav Fomichev, Mina Almasry, Kaiyuan Zhang,
Jakub Kicinski, Bobby Eshleman, Pavel Begunkov, David Wei,
Taehee Yoo
Cc: David S. Miller, Eric Dumazet, Simon Horman, Shuah Khan,
Sumit Semwal, Christian König, Philipp Stanner, Andrew Lunn,
Stanislav Fomichev, Jesper Dangaard Brouer, Ilias Apalodimas,
Daniel Borkmann, Nikolay Aleksandrov, Tariq Toukan, Kaifeng Wang,
Dragos Tatulea
Add devmem_bind_rx_unregister_check and devmem_bind_tx_unregister_check
to nl_netdev.py to verify that unregistering a netdevsim device while a
netlink socket holds an active RX or TX devmem binding synchronously
detaches the dma_buf attachment and cleanly closes the netlink socket
without use-after-free or page faults.
Add the dma-buf, udmabuf and devmem options the tests need to
tools/testing/selftests/net/config.
Cc: Tariq Toukan <tariqt@nvidia.com>
Cc: Kaifeng Wang <kaifengw@google.com>
Signed-off-by: Mina Almasry <almasrymina@google.com>
Reviewed-by: Dragos Tatulea <dtatulea@nvidia.com>
Acked-by: Stanislav Fomichev <sdf@fomichev.me>
---
v5:
- Tolerate non-UTF-8 dma-buf names when parsing debugfs bufinfo (Sashiko).
v3:
- Count attachments of the test's own dma-buf only, and skip instead of
passing when debugfs bufinfo is not readable (Sashiko).
- Close the netlink socket after removing the device, clean up with
defer(), and skip on EOPNOTSUPP from bind-rx/bind-tx (Sashiko).
- Replace the hardcoded UDMABUF_CREATE value with a helper that computes
it for the running architecture (Sashiko, Dragos), and close the memfd
and /dev/udmabuf fds on errors (Sashiko).
- Add the needed options to tools/testing/selftests/net/config (Sashiko).
---
tools/testing/selftests/net/config | 4 +
tools/testing/selftests/net/nl_netdev.py | 125 ++++++++++++++++++++++-
2 files changed, 126 insertions(+), 3 deletions(-)
diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config
index d4416f8bdcfd1..7a89e2618d4c4 100644
--- a/tools/testing/selftests/net/config
+++ b/tools/testing/selftests/net/config
@@ -12,6 +12,7 @@ CONFIG_CRYPTO_ARIA=y
CONFIG_CRYPTO_CHACHA20POLY1305=m
CONFIG_CRYPTO_SHA1=y
CONFIG_CRYPTO_SM4_GENERIC=y
+CONFIG_DEBUG_FS=y
CONFIG_DEBUG_INFO_BTF=y
CONFIG_DEBUG_INFO_BTF_MODULES=n
CONFIG_DUMMY=y
@@ -79,6 +80,7 @@ CONFIG_NET_CLS_BPF=m
CONFIG_NET_CLS_FLOWER=m
CONFIG_NET_CLS_MATCHALL=m
CONFIG_NET_CLS_U32=m
+CONFIG_NET_DEVMEM=y
CONFIG_NETDEVSIM=m
CONFIG_NET_DROP_MONITOR=m
CONFIG_NETFILTER=y
@@ -123,6 +125,7 @@ CONFIG_OPENVSWITCH=m
CONFIG_PAGE_POOL_STATS=y
CONFIG_PSAMPLE=m
CONFIG_RPS=y
+CONFIG_SYNC_FILE=y
CONFIG_SYN_COOKIES=y
CONFIG_SYSCTL=y
CONFIG_SYSFS=y
@@ -133,6 +136,7 @@ CONFIG_TEST_BLACKHOLE_DEV=m
CONFIG_TLS=m
CONFIG_TRACEPOINTS=y
CONFIG_TUN=y
+CONFIG_UDMABUF=y
CONFIG_USER_NS=y
CONFIG_VETH=y
CONFIG_VLAN_8021Q=y
diff --git a/tools/testing/selftests/net/nl_netdev.py b/tools/testing/selftests/net/nl_netdev.py
index ceb44c8e1fec5..dd0321c0b463a 100755
--- a/tools/testing/selftests/net/nl_netdev.py
+++ b/tools/testing/selftests/net/nl_netdev.py
@@ -6,10 +6,14 @@ Tests for the netdev netlink family.
"""
import errno
+import fcntl
+import mmap
+import os
+import struct
from os import system
-from lib.py import ksft_run, ksft_exit
+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 NetdevFamily, NetdevSimDev, NlError, defer, ip
+from lib.py import EthtoolFamily, NetdevFamily, NetdevSimDev, NlError, defer, ip
def empty_check(nf) -> None:
@@ -366,6 +370,119 @@ def page_pool_stats_ifindex_check(nf) -> None:
ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.info.id')
+def _udmabuf_create_ioctl() -> int:
+ """Return UDMABUF_CREATE, _IOW('u', 0x42, struct udmabuf_create)."""
+ machine = os.uname().machine
+ if machine.startswith(("alpha", "mips", "ppc", "powerpc", "sparc")):
+ ioc_write = 4 << 29
+ elif machine.startswith("parisc"):
+ ioc_write = 2 << 30
+ else:
+ ioc_write = 1 << 30
+ return ioc_write | 24 << 16 | ord("u") << 8 | 0x42
+
+
+def _create_udmabuf(num_pages=64) -> int:
+ """Create a sealed memfd-backed udmabuf fd for devmem tests."""
+ if not os.path.exists("/dev/udmabuf"):
+ raise KsftSkipEx("/dev/udmabuf is not available")
+
+ size = num_pages * mmap.PAGESIZE
+ memfd = os.memfd_create("devmem-ksft", os.MFD_ALLOW_SEALING)
+ try:
+ os.ftruncate(memfd, size)
+ fcntl.fcntl(memfd, fcntl.F_ADD_SEALS, fcntl.F_SEAL_SHRINK)
+ devfd = os.open("/dev/udmabuf", os.O_RDWR)
+ try:
+ req = bytearray(struct.pack("IIQQ", memfd, 0, 0, size))
+ return fcntl.ioctl(devfd, _udmabuf_create_ioctl(), req)
+ finally:
+ os.close(devfd)
+ finally:
+ os.close(memfd)
+
+
+def _dmabuf_attach_count(dmabuf_fd) -> int:
+ """Return the number of devices attached to dmabuf_fd, from debugfs."""
+ path = "/sys/kernel/debug/dma_buf/bufinfo"
+ try:
+ with open(path, "r", encoding="utf-8", errors="replace") as f:
+ lines = f.read().splitlines()
+ except OSError as e:
+ raise KsftSkipEx(f"{path} is not readable: {e}") from e
+
+ ino = os.fstat(dmabuf_fd).st_ino
+ found = False
+ for line in lines:
+ fields = line.split("\t", 6)
+ if not found:
+ found = (len(fields) == 7 and fields[5].isdigit() and
+ int(fields[5]) == ino)
+ elif line.startswith("Total "):
+ return int(line.split()[1])
+ raise KsftFailEx(f"dma-buf {ino} not found in {path}")
+
+
+def _devmem_bind(bind, attrs) -> 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:
+ raise KsftSkipEx(f"devmem binding not supported: {e}") from e
+ raise
+
+
+def devmem_bind_rx_unregister_check(_nf) -> None:
+ """Verify RX devmem bindings detach dma_buf synchronously on netdev unregister."""
+ 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"})
+ _devmem_bind(nf_priv.bind_rx, {
+ "ifindex": nsim.ifindex,
+ "fd": dmabuf_fd,
+ "queues": [{"id": 1, "type": "rx"}],
+ })
+ ksft_ge(_dmabuf_attach_count(dmabuf_fd), 1)
+
+ nsimdev.remove()
+ ksft_eq(_dmabuf_attach_count(dmabuf_fd), 0)
+
+ nf_priv.close()
+
+
+def devmem_bind_tx_unregister_check(_nf) -> None:
+ """Verify TX devmem bindings detach cleanly on netdev unregister without UAF."""
+ 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")
+ _devmem_bind(nf_priv.bind_tx, {
+ "ifindex": nsim.ifindex,
+ "fd": dmabuf_fd,
+ })
+ ksft_ge(_dmabuf_attach_count(dmabuf_fd), 1)
+
+ nsimdev.remove()
+ ksft_eq(_dmabuf_attach_count(dmabuf_fd), 0)
+
+ nf_priv.close()
+
+
def main() -> None:
""" Ksft boiler plate main """
nf = NetdevFamily()
@@ -379,7 +496,9 @@ def main() -> None:
page_pool_check,
page_pool_dump_ifindex,
page_pool_ifindex_leak_check,
- page_pool_stats_ifindex_check
+ page_pool_stats_ifindex_check,
+ devmem_bind_rx_unregister_check,
+ devmem_bind_tx_unregister_check
],
args=(nf, ))
ksft_exit()
--
2.56.0.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-10-30 8:35 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-30 8:35 [PATCH net v5 0/4] net: devmem: fix RX and TX binding teardown on device unregistration Mina Almasry
2026-10-30 8:35 ` [PATCH net v5 1/4] net: devmem: unmap dma_buf synchronously on queue uninstall Mina Almasry
2026-10-30 8:35 ` [PATCH net v5 2/4] net: devmem: detach TX bindings on NETDEV_UNREGISTER Mina Almasry
2026-10-30 8:35 ` [PATCH net v5 3/4] netdevsim: support devmem RX and TX bindings Mina Almasry
2026-10-30 8:35 ` [PATCH net v5 4/4] selftests: net: add devmem RX and TX netdev unregister tests Mina Almasry
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®