* [PATCH net] net: ntb_netdev: Fix statistics races
@ 2026-08-24 2:57 Koichiro Den
2026-08-25 2:57 ` sashiko-bot
2026-08-27 11:04 ` Simon Horman
0 siblings, 2 replies; 5+ messages in thread
From: Koichiro Den @ 2026-08-24 2:57 UTC (permalink / raw)
To: Jakub Kicinski, Jon Mason, Dave Jiang, Allen Hubbe, Andrew Lunn,
David S. Miller, Eric Dumazet, Paolo Abeni
Cc: netdev, ntb, linux-kernel
ntb_netdev updates shared net_device stats from per-QP RX and TX
callbacks. Once multiple queues are enabled, concurrent updates can be
lost.
Use core-managed per-CPU dstats for packet, byte and drop counters.
Keep infrequent error counters in net_device_stats with atomic
DEV_STATS_INC(). The core handles allocation and aggregation.
Transport queues can still complete after ndo_stop. Tear them down from
ndo_uninit before the core frees dstats.
Fixes: 24d9e73c7e00 ("net: ntb_netdev: Support ethtool channels for multi-queue")
Cc: stable@vger.kernel.org
Signed-off-by: Koichiro Den <den@valinux.co.jp>
---
This is the follow-up mentioned here:
https://lore.kernel.org/r/20260819172539.1450821-1-den@valinux.co.jp/
The related TX and RX fixes have now landed in net.
drivers/net/ntb_netdev.c | 44 ++++++++++++++++++++++++----------------
1 file changed, 27 insertions(+), 17 deletions(-)
diff --git a/drivers/net/ntb_netdev.c b/drivers/net/ntb_netdev.c
index 9c171697e762..6a1e58d5d7d6 100644
--- a/drivers/net/ntb_netdev.c
+++ b/drivers/net/ntb_netdev.c
@@ -139,17 +139,16 @@ static void ntb_netdev_rx_handler(struct ntb_transport_qp *qp, void *qp_data,
netdev_dbg(ndev, "%s: %d byte payload received\n", __func__, len);
if (len < 0) {
- ndev->stats.rx_errors++;
- ndev->stats.rx_length_errors++;
+ DEV_STATS_INC(ndev, rx_errors);
+ DEV_STATS_INC(ndev, rx_length_errors);
goto enqueue_again;
}
- ndev->stats.rx_packets++;
- ndev->stats.rx_bytes += len;
+ dev_dstats_rx_add(ndev, len);
new_skb = netdev_alloc_skb(ndev, ndev->mtu + ETH_HLEN);
if (!new_skb) {
- ndev->stats.rx_dropped++;
+ dev_dstats_rx_dropped(ndev);
goto enqueue_again;
}
@@ -166,8 +165,8 @@ static void ntb_netdev_rx_handler(struct ntb_transport_qp *qp, void *qp_data,
rc = ntb_transport_rx_enqueue(qp, skb, skb->data, ndev->mtu + ETH_HLEN);
if (rc) {
dev_kfree_skb_any(skb);
- ndev->stats.rx_errors++;
- ndev->stats.rx_fifo_errors++;
+ DEV_STATS_INC(ndev, rx_errors);
+ DEV_STATS_INC(ndev, rx_fifo_errors);
}
}
@@ -219,11 +218,13 @@ static void ntb_netdev_tx_handler(struct ntb_transport_qp *qp, void *qp_data,
return;
if (len > 0) {
- ndev->stats.tx_packets++;
- ndev->stats.tx_bytes += skb->len;
+ /* TX completion may run from the memcpy kthread. */
+ local_bh_disable();
+ dev_dstats_tx_add(ndev, skb->len);
+ local_bh_enable();
} else {
- ndev->stats.tx_errors++;
- ndev->stats.tx_aborted_errors++;
+ DEV_STATS_INC(ndev, tx_errors);
+ DEV_STATS_INC(ndev, tx_aborted_errors);
}
dev_kfree_skb_any(skb);
@@ -277,7 +278,7 @@ static netdev_tx_t ntb_netdev_start_xmit(struct sk_buff *skb,
drop:
dev_kfree_skb_any(skb);
- ndev->stats.tx_dropped++;
+ dev_dstats_tx_dropped(ndev);
return NETDEV_TX_OK;
}
@@ -427,7 +428,19 @@ static int ntb_netdev_change_mtu(struct net_device *ndev, int new_mtu)
return rc;
}
+static void ntb_netdev_uninit(struct net_device *ndev)
+{
+ struct ntb_netdev *dev = netdev_priv(ndev);
+ unsigned int q;
+
+ for (q = 0; q < dev->num_queues; q++) {
+ ntb_transport_free_queue(dev->queues[q].qp);
+ dev->queues[q].qp = NULL;
+ }
+}
+
static const struct net_device_ops ntb_netdev_ops = {
+ .ndo_uninit = ntb_netdev_uninit,
.ndo_open = ntb_netdev_open,
.ndo_stop = ntb_netdev_close,
.ndo_start_xmit = ntb_netdev_start_xmit,
@@ -647,6 +660,7 @@ static int ntb_netdev_probe(struct device *client_dev)
}
ndev->features = NETIF_F_HIGHDMA;
+ ndev->pcpu_stat_type = NETDEV_PCPU_STAT_DSTATS;
ndev->priv_flags |= IFF_LIVE_ADDR_CHANGE;
@@ -696,8 +710,7 @@ static int ntb_netdev_probe(struct device *client_dev)
return 0;
err_free_qps:
- for (q = 0; q < dev->num_queues; q++)
- ntb_transport_free_queue(dev->queues[q].qp);
+ ntb_netdev_uninit(ndev);
err_free_queues:
kfree(dev->queues);
@@ -711,11 +724,8 @@ static void ntb_netdev_remove(struct device *client_dev)
{
struct net_device *ndev = dev_get_drvdata(client_dev);
struct ntb_netdev *dev = netdev_priv(ndev);
- unsigned int q;
unregister_netdev(ndev);
- for (q = 0; q < dev->num_queues; q++)
- ntb_transport_free_queue(dev->queues[q].qp);
kfree(dev->queues);
free_netdev(ndev);
base-commit: 7cbfb180945ce529608e4d4e24a6d483699fab1e
--
2.51.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* Re: [PATCH net] net: ntb_netdev: Fix statistics races 2026-08-24 2:57 [PATCH net] net: ntb_netdev: Fix statistics races Koichiro Den @ 2026-08-25 2:57 ` sashiko-bot 2026-08-27 11:07 ` Simon Horman 2026-08-27 11:04 ` Simon Horman 1 sibling, 1 reply; 5+ messages in thread From: sashiko-bot @ 2026-08-25 2:57 UTC (permalink / raw) To: Koichiro Den; +Cc: ntb Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: Pre-existing issues: - [High] Pending RX and TX entries are leaked because their associated SKB buffers are not freed when the transport queue is destroyed. -- --- Patch [1]: [PATCH net] net: ntb_netdev: Fix statistics races --- commit fc558d0a991ec95505df001446270417159e2eaf Author: Koichiro Den <den@valinux.co.jp> net: ntb_netdev: Fix statistics races ntb_netdev updates shared net_device stats from per-QP RX and TX callbacks. This commit shifts the driver to use core-managed per-CPU dstats. It also tears down transport queues from ndo_uninit before the core frees dstats. > diff --git a/drivers/net/ntb_netdev.c b/drivers/net/ntb_netdev.c > index 9c171697e7620..6a1e58d5d7d67 100644 > --- a/drivers/net/ntb_netdev.c > +++ b/drivers/net/ntb_netdev.c [ ... ] > @@ -427,7 +428,19 @@ static int ntb_netdev_change_mtu(struct net_device *ndev, int new_mtu) > return rc; > } > > +static void ntb_netdev_uninit(struct net_device *ndev) > +{ > + struct ntb_netdev *dev = netdev_priv(ndev); > + unsigned int q; > + > + for (q = 0; q < dev->num_queues; q++) { > + ntb_transport_free_queue(dev->queues[q].qp); [Severity: High] This is a pre-existing issue, but does calling ntb_transport_free_queue() leak the SKBs attached to pending packets? Looking at ntb_transport_free_queue() in drivers/ntb/ntb_transport.c, it cleans up pending items from the queues like this: drivers/ntb/ntb_transport.c:ntb_transport_free_queue() { ... while ((entry = ntb_list_rm(&qp->ntb_rx_q_lock, &qp->rx_post_q))) { dev_warn(&pdev->dev, "Freeing item from non-empty rx_post_q\n"); kfree(entry); } ... } It frees the entry, but it doesn't seem to free or return the attached client buffer (entry->cb_data). Since ntb_netdev uses cb_data to store allocated SKBs, does this unconditionally leak any in-flight packets when the queue is destroyed? > + dev->queues[q].qp = NULL; > + } > +} > + > static const struct net_device_ops ntb_netdev_ops = { > + .ndo_uninit = ntb_netdev_uninit, > .ndo_open = ntb_netdev_open, > .ndo_stop = ntb_netdev_close, -- Sashiko AI review · https://sashiko.dev/#/patchset/20260824025720.3520288-1-den@valinux.co.jp?part=1 ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net] net: ntb_netdev: Fix statistics races 2026-08-25 2:57 ` sashiko-bot @ 2026-08-27 11:07 ` Simon Horman 0 siblings, 0 replies; 5+ messages in thread From: Simon Horman @ 2026-08-27 11:07 UTC (permalink / raw) To: sashiko-reviews; +Cc: Koichiro Den, ntb On Tue, Aug 25, 2026 at 02:57:39AM +0000, sashiko-bot@kernel.org wrote: > Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: > > Pre-existing issues: > - [High] Pending RX and TX entries are leaked because their associated SKB buffers are not freed when the transport queue is destroyed. > -- > > --- Patch [1]: [PATCH net] net: ntb_netdev: Fix statistics races --- > commit fc558d0a991ec95505df001446270417159e2eaf > Author: Koichiro Den <den@valinux.co.jp> > > net: ntb_netdev: Fix statistics races > > ntb_netdev updates shared net_device stats from per-QP RX and TX callbacks. > This commit shifts the driver to use core-managed per-CPU dstats. It also > tears down transport queues from ndo_uninit before the core frees dstats. > > > diff --git a/drivers/net/ntb_netdev.c b/drivers/net/ntb_netdev.c > > index 9c171697e7620..6a1e58d5d7d67 100644 > > --- a/drivers/net/ntb_netdev.c > > +++ b/drivers/net/ntb_netdev.c > [ ... ] > > @@ -427,7 +428,19 @@ static int ntb_netdev_change_mtu(struct net_device *ndev, int new_mtu) > > return rc; > > } > > > > +static void ntb_netdev_uninit(struct net_device *ndev) > > +{ > > + struct ntb_netdev *dev = netdev_priv(ndev); > > + unsigned int q; > > + > > + for (q = 0; q < dev->num_queues; q++) { > > + ntb_transport_free_queue(dev->queues[q].qp); > > [Severity: High] > This is a pre-existing issue, but does calling ntb_transport_free_queue() > leak the SKBs attached to pending packets? FWIIW, as a pre-existing issue I lean towards this being out-of-scope for the patch under review. And, rather, a topic for possible follow-up. > > Looking at ntb_transport_free_queue() in drivers/ntb/ntb_transport.c, > it cleans up pending items from the queues like this: > > drivers/ntb/ntb_transport.c:ntb_transport_free_queue() { > ... > while ((entry = ntb_list_rm(&qp->ntb_rx_q_lock, &qp->rx_post_q))) { > dev_warn(&pdev->dev, "Freeing item from non-empty rx_post_q\n"); > kfree(entry); > } > ... > } > > It frees the entry, but it doesn't seem to free or return the attached > client buffer (entry->cb_data). Since ntb_netdev uses cb_data to store > allocated SKBs, does this unconditionally leak any in-flight packets > when the queue is destroyed? > > > + dev->queues[q].qp = NULL; > > + } > > +} > > + > > static const struct net_device_ops ntb_netdev_ops = { > > + .ndo_uninit = ntb_netdev_uninit, > > .ndo_open = ntb_netdev_open, > > .ndo_stop = ntb_netdev_close, > > -- > Sashiko AI review · https://sashiko.dev/#/patchset/20260824025720.3520288-1-den@valinux.co.jp?part=1 ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net] net: ntb_netdev: Fix statistics races 2026-08-24 2:57 [PATCH net] net: ntb_netdev: Fix statistics races Koichiro Den 2026-08-25 2:57 ` sashiko-bot @ 2026-08-27 11:04 ` Simon Horman 2026-08-28 1:06 ` Koichiro Den 1 sibling, 1 reply; 5+ messages in thread From: Simon Horman @ 2026-08-27 11:04 UTC (permalink / raw) To: Koichiro Den Cc: Jakub Kicinski, Jon Mason, Dave Jiang, Allen Hubbe, Andrew Lunn, David S. Miller, Eric Dumazet, Paolo Abeni, netdev, ntb, linux-kernel On Mon, Aug 24, 2026 at 11:57:20AM +0900, Koichiro Den wrote: > ntb_netdev updates shared net_device stats from per-QP RX and TX > callbacks. Once multiple queues are enabled, concurrent updates can be > lost. > > Use core-managed per-CPU dstats for packet, byte and drop counters. > Keep infrequent error counters in net_device_stats with atomic > DEV_STATS_INC(). The core handles allocation and aggregation. > > Transport queues can still complete after ndo_stop. Tear them down from > ndo_uninit before the core frees dstats. > > Fixes: 24d9e73c7e00 ("net: ntb_netdev: Support ethtool channels for multi-queue") > Cc: stable@vger.kernel.org > Signed-off-by: Koichiro Den <den@valinux.co.jp> > --- > This is the follow-up mentioned here: > https://lore.kernel.org/r/20260819172539.1450821-1-den@valinux.co.jp/ > The related TX and RX fixes have now landed in net. > > drivers/net/ntb_netdev.c | 44 ++++++++++++++++++++++++---------------- > 1 file changed, 27 insertions(+), 17 deletions(-) > > diff --git a/drivers/net/ntb_netdev.c b/drivers/net/ntb_netdev.c > index 9c171697e762..6a1e58d5d7d6 100644 > --- a/drivers/net/ntb_netdev.c > +++ b/drivers/net/ntb_netdev.c ... > @@ -219,11 +218,13 @@ static void ntb_netdev_tx_handler(struct ntb_transport_qp *qp, void *qp_data, > return; > > if (len > 0) { > - ndev->stats.tx_packets++; > - ndev->stats.tx_bytes += skb->len; > + /* TX completion may run from the memcpy kthread. */ > + local_bh_disable(); > + dev_dstats_tx_add(ndev, skb->len); > + local_bh_enable(); Hi Den-san, There is an AI-generated review of this patch available at [1]. This is separate to the AI-generated review forwarded elswhere in this thread by sashiko-bot. [1] https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260824025720.3520288-1-den%40valinux.co.jp It raises a concern regarding using local_bh_disable/local_bh_enable. And I wonder if this might be addressed by unrolling dev_dstats_tx_add, something like this: u64_stats_update_begin_irqsave(&dstats->syncp, syncp_flags); u64_stats_inc(&dstats->tx_packets); u64_stats_add(&dstats->tx_bytes, skb->len); u64_stats_update_end_irqrestore(&dstats->syncp, syncp_flags); AI-generated review text: Can ntb_netdev_tx_handler() run with interrupts disabled here? With the ntb_transport defaults (use_dma=false, tx_memcpy_offload=false) the TX completion is not deferred to a kthread at all, it runs inline in the ndo_start_xmit caller's context: ntb_netdev_start_xmit() ntb_transport_tx_enqueue() ntb_process_tx() ntb_async_tx() /* chan == NULL, goto err */ ntb_memcpy_tx() ntb_memcpy_tx_on_stack() ntb_tx_copy_callback() qp->tx_handler() == ntb_netdev_tx_handler() netpoll calls ndo_start_xmit with interrupts off: net/core/netpoll.c:netpoll_send_skb() { ... local_irq_save(flags); ret = __netpoll_send_skb(np, skb); local_irq_restore(flags); ... } and ntb_netdev does not set IFF_DISABLE_NETPOLL, so netconsole can bind to the interface. In that context local_bh_enable() hits: kernel/softirq.c:__local_bh_enable_ip() { WARN_ON_ONCE(in_hardirq()); lockdep_assert_irqs_enabled(); ... if (unlikely(!in_interrupt() && local_softirq_pending())) do_softirq(); ... } so besides the splat, softirqs would be run from inside a netpoll/printk section that expects interrupts to stay disabled. The pre-existing dev_kfree_skb_any() a couple of lines below already assumes this handler can be entered from hardirq or with IRQs disabled, which is the same assumption the plain ndev->stats.tx_packets++ was safe under. There is also a second entry path: ntb_async_tx_submit() installs ntb_tx_copy_callback() as txd->callback_result, and whether a dmaengine provider calls that from hardirq is up to the provider. If it does, a BH-disable is not enough to protect the pcpu_dstats syncp that the RX side writes from the rxc_db tasklet on the same CPU, so on 32-bit the seqcount can be torn. Would an IRQ-safe update (local_irq_save()/local_irq_restore() around the update, or u64_stats_update_begin_irqsave() on this_cpu_ptr(ndev->dstats)) be correct in all of these contexts? > } else { > - ndev->stats.tx_errors++; > - ndev->stats.tx_aborted_errors++; > + DEV_STATS_INC(ndev, tx_errors); > + DEV_STATS_INC(ndev, tx_aborted_errors); > } > > dev_kfree_skb_any(skb); > @@ -277,7 +278,7 @@ static netdev_tx_t ntb_netdev_start_xmit(struct sk_buff *skb, > > drop: > dev_kfree_skb_any(skb); > - ndev->stats.tx_dropped++; > + dev_dstats_tx_dropped(ndev); > return NETDEV_TX_OK; > } > > @@ -427,7 +428,19 @@ static int ntb_netdev_change_mtu(struct net_device *ndev, int new_mtu) > return rc; > } > > +static void ntb_netdev_uninit(struct net_device *ndev) > +{ > + struct ntb_netdev *dev = netdev_priv(ndev); > + unsigned int q; > + > + for (q = 0; q < dev->num_queues; q++) { > + ntb_transport_free_queue(dev->queues[q].qp); > + dev->queues[q].qp = NULL; > + } > +} > + [1] also raises a concern here relating to holding RTNL for too long. I wonder if this can be addressed by quiescing the queues in ntb_netdev_uninit() while leaving the calls ntb_transport_free_queue() until after unregister_netdev() has been called in ntb_netdev_remove(). AI-generated review text: How long can RTNL be held by this new ndo_uninit path? ndo_uninit is called from unregister_netdevice_many_notify() with RTNL held (unregister_netdev() -> rtnl_net_dev_lock()): net/core/dev.c:unregister_netdevice_many_notify() { ... if (dev->netdev_ops->ndo_uninit) dev->netdev_ops->ndo_uninit(dev); ... } ntb_transport_free_queue() then drains the DMA channels for each queue pair, once for TX and once for RX: drivers/ntb/ntb_transport.c:ntb_transport_free_queue() { ... dma_sync_wait(chan, qp->last_cookie); dmaengine_terminate_all(chan); ... } and dma_sync_wait() is a busy-poll with a 5 second timeout and no early abort: drivers/dma/dmaengine.c:dma_sync_wait() { unsigned long dma_sync_wait_timeout = jiffies + msecs_to_jiffies(5000); dma_async_issue_pending(chan); do { status = dma_async_is_tx_complete(chan, cookie, NULL, NULL); if (time_after_eq(jiffies, dma_sync_wait_timeout)) { dev_err(chan->device->dev, "%s: timeout!\n", __func__); return DMA_ERROR; } if (status != DMA_IN_PROGRESS) break; cpu_relax(); } while (1); ... } With use_dma=1 and a wedged channel or a downed peer, that is up to roughly 10 seconds of spinning per queue pair, multiplied by dev->num_queues, plus kthread_stop(), tasklet_kill() and cancel_delayed_work_sync(), all under RTNL. Before this patch the same loop ran in ntb_netdev_remove() after unregister_netdev() returned, with no locks held. Since the stats fix only needs the transport callbacks to stop before the core frees dstats, could the channel drain and release stay in ntb_netdev_remove()? > static const struct net_device_ops ntb_netdev_ops = { > + .ndo_uninit = ntb_netdev_uninit, > .ndo_open = ntb_netdev_open, > .ndo_stop = ntb_netdev_close, > .ndo_start_xmit = ntb_netdev_start_xmit, > @@ -647,6 +660,7 @@ static int ntb_netdev_probe(struct device *client_dev) > } > > ndev->features = NETIF_F_HIGHDMA; > + ndev->pcpu_stat_type = NETDEV_PCPU_STAT_DSTATS; > > ndev->priv_flags |= IFF_LIVE_ADDR_CHANGE; > > @@ -696,8 +710,7 @@ static int ntb_netdev_probe(struct device *client_dev) > return 0; > > err_free_qps: > - for (q = 0; q < dev->num_queues; q++) > - ntb_transport_free_queue(dev->queues[q].qp); > + ntb_netdev_uninit(ndev); > > err_free_queues: > kfree(dev->queues); > @@ -711,11 +724,8 @@ static void ntb_netdev_remove(struct device *client_dev) > { > struct net_device *ndev = dev_get_drvdata(client_dev); > struct ntb_netdev *dev = netdev_priv(ndev); > - unsigned int q; > > unregister_netdev(ndev); > - for (q = 0; q < dev->num_queues; q++) > - ntb_transport_free_queue(dev->queues[q].qp); > > kfree(dev->queues); > free_netdev(ndev); > > base-commit: 7cbfb180945ce529608e4d4e24a6d483699fab1e > -- > 2.51.0 > ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net] net: ntb_netdev: Fix statistics races 2026-08-27 11:04 ` Simon Horman @ 2026-08-28 1:06 ` Koichiro Den 0 siblings, 0 replies; 5+ messages in thread From: Koichiro Den @ 2026-08-28 1:06 UTC (permalink / raw) To: Simon Horman Cc: Jakub Kicinski, Jon Mason, Dave Jiang, Allen Hubbe, Andrew Lunn, David S. Miller, Eric Dumazet, Paolo Abeni, netdev, ntb, linux-kernel On Thu, Aug 27, 2026 at 12:04:45PM +0100, Simon Horman wrote: > On Mon, Aug 24, 2026 at 11:57:20AM +0900, Koichiro Den wrote: > > ntb_netdev updates shared net_device stats from per-QP RX and TX > > callbacks. Once multiple queues are enabled, concurrent updates can be > > lost. > > > > Use core-managed per-CPU dstats for packet, byte and drop counters. > > Keep infrequent error counters in net_device_stats with atomic > > DEV_STATS_INC(). The core handles allocation and aggregation. > > > > Transport queues can still complete after ndo_stop. Tear them down from > > ndo_uninit before the core frees dstats. > > > > Fixes: 24d9e73c7e00 ("net: ntb_netdev: Support ethtool channels for multi-queue") > > Cc: stable@vger.kernel.org > > Signed-off-by: Koichiro Den <den@valinux.co.jp> > > --- > > This is the follow-up mentioned here: > > https://lore.kernel.org/r/20260819172539.1450821-1-den@valinux.co.jp/ > > The related TX and RX fixes have now landed in net. > > > > drivers/net/ntb_netdev.c | 44 ++++++++++++++++++++++++---------------- > > 1 file changed, 27 insertions(+), 17 deletions(-) > > > > diff --git a/drivers/net/ntb_netdev.c b/drivers/net/ntb_netdev.c > > index 9c171697e762..6a1e58d5d7d6 100644 > > --- a/drivers/net/ntb_netdev.c > > +++ b/drivers/net/ntb_netdev.c > > ... > > > @@ -219,11 +218,13 @@ static void ntb_netdev_tx_handler(struct ntb_transport_qp *qp, void *qp_data, > > return; > > > > if (len > 0) { > > - ndev->stats.tx_packets++; > > - ndev->stats.tx_bytes += skb->len; > > + /* TX completion may run from the memcpy kthread. */ > > + local_bh_disable(); > > + dev_dstats_tx_add(ndev, skb->len); > > + local_bh_enable(); > > > Hi Den-san, > > There is an AI-generated review of this patch available at [1]. > This is separate to the AI-generated review forwarded elswhere in this > thread by sashiko-bot. > > [1] https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260824025720.3520288-1-den%40valinux.co.jp Hi Simon, Thanks. I missed both the netdev-ai report (again) and the netpoll context. I'll respin this. > > It raises a concern regarding using local_bh_disable/local_bh_enable. > And I wonder if this might be addressed by unrolling dev_dstats_tx_add, > something like this: > > u64_stats_update_begin_irqsave(&dstats->syncp, syncp_flags); > u64_stats_inc(&dstats->tx_packets); > u64_stats_add(&dstats->tx_bytes, skb->len); > u64_stats_update_end_irqrestore(&dstats->syncp, syncp_flags); Looking around, I'm thinking of following the same pattern as mctp_usblib_tx_stats_update(), using tstats: tstats = get_cpu_ptr(ndev->tstats); flags = u64_stats_update_begin_irqsave(&tstats->syncp); ... u64_stats_update_end_irqrestore(&tstats->syncp, flags); put_cpu_ptr(ndev->tstats); Leaving pcpu_stat_type as NONE makes it driver-owned, so they can remain alive until ntb_transport_free_queue() has finished. This should let me restore the original teardown order, with QP teardown after unregister_netdev(), without moving the DMA waits under RTNL. Best regards, Koichiro > > AI-generated review text: > > Can ntb_netdev_tx_handler() run with interrupts disabled here? > > With the ntb_transport defaults (use_dma=false, tx_memcpy_offload=false) > the TX completion is not deferred to a kthread at all, it runs inline in > the ndo_start_xmit caller's context: > > ntb_netdev_start_xmit() > ntb_transport_tx_enqueue() > ntb_process_tx() > ntb_async_tx() /* chan == NULL, goto err */ > ntb_memcpy_tx() > ntb_memcpy_tx_on_stack() > ntb_tx_copy_callback() > qp->tx_handler() == ntb_netdev_tx_handler() > > netpoll calls ndo_start_xmit with interrupts off: > > net/core/netpoll.c:netpoll_send_skb() { > ... > local_irq_save(flags); > ret = __netpoll_send_skb(np, skb); > local_irq_restore(flags); > ... > } > > and ntb_netdev does not set IFF_DISABLE_NETPOLL, so netconsole can bind > to the interface. In that context local_bh_enable() hits: > > kernel/softirq.c:__local_bh_enable_ip() { > WARN_ON_ONCE(in_hardirq()); > lockdep_assert_irqs_enabled(); > ... > if (unlikely(!in_interrupt() && local_softirq_pending())) > do_softirq(); > ... > } > > so besides the splat, softirqs would be run from inside a netpoll/printk > section that expects interrupts to stay disabled. > > The pre-existing dev_kfree_skb_any() a couple of lines below already > assumes this handler can be entered from hardirq or with IRQs disabled, > which is the same assumption the plain ndev->stats.tx_packets++ was safe > under. > > There is also a second entry path: ntb_async_tx_submit() installs > ntb_tx_copy_callback() as txd->callback_result, and whether a dmaengine > provider calls that from hardirq is up to the provider. If it does, a > BH-disable is not enough to protect the pcpu_dstats syncp that the RX > side writes from the rxc_db tasklet on the same CPU, so on 32-bit the > seqcount can be torn. > > Would an IRQ-safe update (local_irq_save()/local_irq_restore() around > the update, or u64_stats_update_begin_irqsave() on > this_cpu_ptr(ndev->dstats)) be correct in all of these contexts? > > > } else { > > - ndev->stats.tx_errors++; > > - ndev->stats.tx_aborted_errors++; > > + DEV_STATS_INC(ndev, tx_errors); > > + DEV_STATS_INC(ndev, tx_aborted_errors); > > } > > > > dev_kfree_skb_any(skb); > > @@ -277,7 +278,7 @@ static netdev_tx_t ntb_netdev_start_xmit(struct sk_buff *skb, > > > > drop: > > dev_kfree_skb_any(skb); > > - ndev->stats.tx_dropped++; > > + dev_dstats_tx_dropped(ndev); > > return NETDEV_TX_OK; > > } > > > > @@ -427,7 +428,19 @@ static int ntb_netdev_change_mtu(struct net_device *ndev, int new_mtu) > > return rc; > > } > > > > +static void ntb_netdev_uninit(struct net_device *ndev) > > +{ > > + struct ntb_netdev *dev = netdev_priv(ndev); > > + unsigned int q; > > + > > + for (q = 0; q < dev->num_queues; q++) { > > + ntb_transport_free_queue(dev->queues[q].qp); > > + dev->queues[q].qp = NULL; > > + } > > +} > > + > > [1] also raises a concern here relating to holding RTNL for too long. > > I wonder if this can be addressed by quiescing the queues in > ntb_netdev_uninit() while leaving the calls ntb_transport_free_queue() > until after unregister_netdev() has been called in ntb_netdev_remove(). > > AI-generated review text: > > How long can RTNL be held by this new ndo_uninit path? > > ndo_uninit is called from unregister_netdevice_many_notify() with RTNL > held (unregister_netdev() -> rtnl_net_dev_lock()): > > net/core/dev.c:unregister_netdevice_many_notify() { > ... > if (dev->netdev_ops->ndo_uninit) > dev->netdev_ops->ndo_uninit(dev); > ... > } > > ntb_transport_free_queue() then drains the DMA channels for each queue > pair, once for TX and once for RX: > > drivers/ntb/ntb_transport.c:ntb_transport_free_queue() { > ... > dma_sync_wait(chan, qp->last_cookie); > dmaengine_terminate_all(chan); > ... > } > > and dma_sync_wait() is a busy-poll with a 5 second timeout and no early > abort: > > drivers/dma/dmaengine.c:dma_sync_wait() { > unsigned long dma_sync_wait_timeout = jiffies + msecs_to_jiffies(5000); > dma_async_issue_pending(chan); > do { > status = dma_async_is_tx_complete(chan, cookie, NULL, NULL); > if (time_after_eq(jiffies, dma_sync_wait_timeout)) { > dev_err(chan->device->dev, "%s: timeout!\n", __func__); > return DMA_ERROR; > } > if (status != DMA_IN_PROGRESS) > break; > cpu_relax(); > } while (1); > ... > } > > With use_dma=1 and a wedged channel or a downed peer, that is up to > roughly 10 seconds of spinning per queue pair, multiplied by > dev->num_queues, plus kthread_stop(), tasklet_kill() and > cancel_delayed_work_sync(), all under RTNL. Before this patch the same > loop ran in ntb_netdev_remove() after unregister_netdev() returned, with > no locks held. > > Since the stats fix only needs the transport callbacks to stop before > the core frees dstats, could the channel drain and release stay in > ntb_netdev_remove()? > > > static const struct net_device_ops ntb_netdev_ops = { > > + .ndo_uninit = ntb_netdev_uninit, > > .ndo_open = ntb_netdev_open, > > .ndo_stop = ntb_netdev_close, > > .ndo_start_xmit = ntb_netdev_start_xmit, > > @@ -647,6 +660,7 @@ static int ntb_netdev_probe(struct device *client_dev) > > } > > > > ndev->features = NETIF_F_HIGHDMA; > > + ndev->pcpu_stat_type = NETDEV_PCPU_STAT_DSTATS; > > > > ndev->priv_flags |= IFF_LIVE_ADDR_CHANGE; > > > > @@ -696,8 +710,7 @@ static int ntb_netdev_probe(struct device *client_dev) > > return 0; > > > > err_free_qps: > > - for (q = 0; q < dev->num_queues; q++) > > - ntb_transport_free_queue(dev->queues[q].qp); > > + ntb_netdev_uninit(ndev); > > > > err_free_queues: > > kfree(dev->queues); > > @@ -711,11 +724,8 @@ static void ntb_netdev_remove(struct device *client_dev) > > { > > struct net_device *ndev = dev_get_drvdata(client_dev); > > struct ntb_netdev *dev = netdev_priv(ndev); > > - unsigned int q; > > > > unregister_netdev(ndev); > > - for (q = 0; q < dev->num_queues; q++) > > - ntb_transport_free_queue(dev->queues[q].qp); > > > > kfree(dev->queues); > > free_netdev(ndev); > > > > base-commit: 7cbfb180945ce529608e4d4e24a6d483699fab1e > > -- > > 2.51.0 > > ^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-08-28 1:06 UTC | newest] Thread overview: 5+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-08-24 2:57 [PATCH net] net: ntb_netdev: Fix statistics races Koichiro Den 2026-08-25 2:57 ` sashiko-bot 2026-08-27 11:07 ` Simon Horman 2026-08-27 11:04 ` Simon Horman 2026-08-28 1:06 ` Koichiro Den
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox