All of lore.kernel.org
 help / color / mirror / Atom feed
From: Simon Horman <horms@kernel.org>
To: Koichiro Den <den@valinux.co.jp>
Cc: Jakub Kicinski <kuba@kernel.org>, Jon Mason <jdmason@kudzu.us>,
	Dave Jiang <dave.jiang@intel.com>,
	Allen Hubbe <allenbh@gmail.com>,
	Andrew Lunn <andrew+netdev@lunn.ch>,
	"David S. Miller" <davem@davemloft.net>,
	Eric Dumazet <edumazet@google.com>,
	Paolo Abeni <pabeni@redhat.com>,
	netdev@vger.kernel.org, ntb@lists.linux.dev,
	linux-kernel@vger.kernel.org
Subject: Re: [PATCH net] net: ntb_netdev: Fix statistics races
Date: Thu, 27 Aug 2026 12:04:45 +0100	[thread overview]
Message-ID: <20260827110445.GD396647@horms.kernel.org> (raw)
In-Reply-To: <20260824025720.3520288-1-den@valinux.co.jp>

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
> 

  parent reply	other threads:[~2026-08-27 11:04 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 [this message]
2026-08-28  1:06   ` Koichiro Den

Reply instructions:

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

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

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

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

  git send-email \
    --in-reply-to=20260827110445.GD396647@horms.kernel.org \
    --to=horms@kernel.org \
    --cc=allenbh@gmail.com \
    --cc=andrew+netdev@lunn.ch \
    --cc=dave.jiang@intel.com \
    --cc=davem@davemloft.net \
    --cc=den@valinux.co.jp \
    --cc=edumazet@google.com \
    --cc=jdmason@kudzu.us \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=ntb@lists.linux.dev \
    --cc=pabeni@redhat.com \
    /path/to/YOUR_REPLY

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

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