netdev.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH net-next] net: dlink: add synchronization for stats update
@ 2025-04-21 19:16 Moon Yeounsu
  2025-04-21 23:23 ` Jacob Keller
  2025-04-24  1:42 ` Jakub Kicinski
  0 siblings, 2 replies; 5+ messages in thread
From: Moon Yeounsu @ 2025-04-21 19:16 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: Moon Yeounsu, netdev, linux-kernel

There are two paths that call `get_stats()`:
    1. From user space via the `ip` command
    2. From interrupt context via `rio_interrupt()`

Case 1 is synchronized by `rtnl_lock()`, so it is safe.
However, the two cases above are not synchronized with each other.
Therefore, `spin_lock` is needed to protect `get_stats()` as it can be
preempted by an interrupt. In this context, `spin_lock_irq()` is required
(using `spin_lock_bh()` may result in a deadlock).

While `spin_lock` protects `get_stats()`, it does not protect updates to
`dev->stats.tx_errors` and `dev->stats.collisions`, which may be
concurrently modified by the interrupt handler and user space.
By using temporary variables in `np->tx_errors` and `np->collisions`,
we can safely update `dev->stats` without additional locking.

Tested-on: D-Link DGE-550T Rev-A3
Signed-off-by: Moon Yeounsu <yyyynoom@gmail.com>
---
Question:
	This might be a bit off-topic, but I don’t fully understand why a single global
	`rtnl_lock` is used for synchronization. While I may not be fully aware of the 
	design rationale, it seems somewhat suboptimal. I believe it could be improved.
---
 drivers/net/ethernet/dlink/dl2k.c | 11 +++++++++--
 drivers/net/ethernet/dlink/dl2k.h |  5 +++++
 2 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/drivers/net/ethernet/dlink/dl2k.c b/drivers/net/ethernet/dlink/dl2k.c
index d0ea92607870..2d929a83e101 100644
--- a/drivers/net/ethernet/dlink/dl2k.c
+++ b/drivers/net/ethernet/dlink/dl2k.c
@@ -865,7 +865,7 @@ tx_error (struct net_device *dev, int tx_status)
 	frame_id = (tx_status & 0xffff0000);
 	printk (KERN_ERR "%s: Transmit error, TxStatus %4.4x, FrameId %d.\n",
 		dev->name, tx_status, frame_id);
-	dev->stats.tx_errors++;
+	np->tx_errors++;
 	/* Ttransmit Underrun */
 	if (tx_status & 0x10) {
 		dev->stats.tx_fifo_errors++;
@@ -904,7 +904,7 @@ tx_error (struct net_device *dev, int tx_status)
 	}
 	/* Maximum Collisions */
 	if (tx_status & 0x08)
-		dev->stats.collisions++;
+		np->collisions++;
 	/* Restart the Tx */
 	dw32(MACCtrl, dr16(MACCtrl) | TxEnable);
 }
@@ -1074,6 +1074,7 @@ get_stats (struct net_device *dev)
 #endif
 	unsigned int stat_reg;
 
+	spin_lock_irq(&np->stats_lock);
 	/* All statistics registers need to be acknowledged,
 	   else statistic overflow could cause problems */
 
@@ -1085,6 +1086,7 @@ get_stats (struct net_device *dev)
 	dev->stats.multicast = dr32(McstFramesRcvdOk);
 	dev->stats.collisions += dr32(SingleColFrames)
 			     +  dr32(MultiColFrames);
+	dev->stats.collisions += np->collisions;
 
 	/* detailed tx errors */
 	stat_reg = dr16(FramesAbortXSColls);
@@ -1095,6 +1097,8 @@ get_stats (struct net_device *dev)
 	dev->stats.tx_carrier_errors += stat_reg;
 	dev->stats.tx_errors += stat_reg;
 
+	dev->stats.tx_errors += np->tx_errors;
+
 	/* Clear all other statistic register. */
 	dr32(McstOctetXmtOk);
 	dr16(BcstFramesXmtdOk);
@@ -1123,6 +1127,9 @@ get_stats (struct net_device *dev)
 	dr16(TCPCheckSumErrors);
 	dr16(UDPCheckSumErrors);
 	dr16(IPCheckSumErrors);
+
+	spin_unlock_irq(&np->stats_lock);
+
 	return &dev->stats;
 }
 
diff --git a/drivers/net/ethernet/dlink/dl2k.h b/drivers/net/ethernet/dlink/dl2k.h
index 195dc6cfd895..dc8755a69b73 100644
--- a/drivers/net/ethernet/dlink/dl2k.h
+++ b/drivers/net/ethernet/dlink/dl2k.h
@@ -372,6 +372,8 @@ struct netdev_private {
 	struct pci_dev *pdev;
 	void __iomem *ioaddr;
 	void __iomem *eeprom_addr;
+	// To ensure synchronization when stats are updated.
+	spinlock_t stats_lock;
 	spinlock_t tx_lock;
 	spinlock_t rx_lock;
 	unsigned int rx_buf_sz;		/* Based on MTU+slack. */
@@ -401,6 +403,9 @@ struct netdev_private {
 	u16 negotiate;		/* Negotiated media */
 	int phy_addr;		/* PHY addresses. */
 	u16 led_mode;		/* LED mode read from EEPROM (IP1000A only) */
+
+	u64 collisions;
+	u64 tx_errors;
 };
 
 /* The station address location in the EEPROM. */
-- 
2.49.0


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

* Re: [PATCH net-next] net: dlink: add synchronization for stats update
  2025-04-21 19:16 [PATCH net-next] net: dlink: add synchronization for stats update Moon Yeounsu
@ 2025-04-21 23:23 ` Jacob Keller
  2025-04-22 22:53   ` Moon Yeounsu
  2025-04-24  1:42 ` Jakub Kicinski
  1 sibling, 1 reply; 5+ messages in thread
From: Jacob Keller @ 2025-04-21 23:23 UTC (permalink / raw)
  To: Moon Yeounsu, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni
  Cc: netdev, linux-kernel



On 4/21/2025 12:16 PM, Moon Yeounsu wrote:
> There are two paths that call `get_stats()`:
>     1. From user space via the `ip` command
>     2. From interrupt context via `rio_interrupt()`
> 
> Case 1 is synchronized by `rtnl_lock()`, so it is safe.
> However, the two cases above are not synchronized with each other.
> Therefore, `spin_lock` is needed to protect `get_stats()` as it can be
> preempted by an interrupt. In this context, `spin_lock_irq()` is required
> (using `spin_lock_bh()` may result in a deadlock).
> 
> While `spin_lock` protects `get_stats()`, it does not protect updates to
> `dev->stats.tx_errors` and `dev->stats.collisions`, which may be
> concurrently modified by the interrupt handler and user space.
> By using temporary variables in `np->tx_errors` and `np->collisions`,
> we can safely update `dev->stats` without additional locking.
> 
> Tested-on: D-Link DGE-550T Rev-A3
> Signed-off-by: Moon Yeounsu <yyyynoom@gmail.com>
> ---
> Question:
> 	This might be a bit off-topic, but I don’t fully understand why a single global
> 	`rtnl_lock` is used for synchronization. While I may not be fully aware of the 
> 	design rationale, it seems somewhat suboptimal. I believe it could be improved.

Its been a long standing effort to reduce the use of RTNL lock, with a
lot of effort going into switching calls to use the per-netdev lock instead.

I think you could switch the driver over to the ops locked method by
setting the relevant flag in netdev to avoid global lock contention,
since 605ef7aec060 ("net: add option to request netdev instance lock")
which is coming in v6.15

Thanks,
Jake

> ---
>  drivers/net/ethernet/dlink/dl2k.c | 11 +++++++++--
>  drivers/net/ethernet/dlink/dl2k.h |  5 +++++
>  2 files changed, 14 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/net/ethernet/dlink/dl2k.c b/drivers/net/ethernet/dlink/dl2k.c
> index d0ea92607870..2d929a83e101 100644
> --- a/drivers/net/ethernet/dlink/dl2k.c
> +++ b/drivers/net/ethernet/dlink/dl2k.c
> @@ -865,7 +865,7 @@ tx_error (struct net_device *dev, int tx_status)
>  	frame_id = (tx_status & 0xffff0000);
>  	printk (KERN_ERR "%s: Transmit error, TxStatus %4.4x, FrameId %d.\n",
>  		dev->name, tx_status, frame_id);
> -	dev->stats.tx_errors++;
> +	np->tx_errors++;
>  	/* Ttransmit Underrun */
>  	if (tx_status & 0x10) {
>  		dev->stats.tx_fifo_errors++;
> @@ -904,7 +904,7 @@ tx_error (struct net_device *dev, int tx_status)
>  	}
>  	/* Maximum Collisions */
>  	if (tx_status & 0x08)
> -		dev->stats.collisions++;
> +		np->collisions++;
>  	/* Restart the Tx */
>  	dw32(MACCtrl, dr16(MACCtrl) | TxEnable);
>  }
> @@ -1074,6 +1074,7 @@ get_stats (struct net_device *dev)
>  #endif
>  	unsigned int stat_reg;
>  
> +	spin_lock_irq(&np->stats_lock);
>  	/* All statistics registers need to be acknowledged,
>  	   else statistic overflow could cause problems */
>  
> @@ -1085,6 +1086,7 @@ get_stats (struct net_device *dev)
>  	dev->stats.multicast = dr32(McstFramesRcvdOk);
>  	dev->stats.collisions += dr32(SingleColFrames)
>  			     +  dr32(MultiColFrames);
> +	dev->stats.collisions += np->collisions;
>  
>  	/* detailed tx errors */
>  	stat_reg = dr16(FramesAbortXSColls);
> @@ -1095,6 +1097,8 @@ get_stats (struct net_device *dev)
>  	dev->stats.tx_carrier_errors += stat_reg;
>  	dev->stats.tx_errors += stat_reg;
>  
> +	dev->stats.tx_errors += np->tx_errors;
> +
>  	/* Clear all other statistic register. */
>  	dr32(McstOctetXmtOk);
>  	dr16(BcstFramesXmtdOk);
> @@ -1123,6 +1127,9 @@ get_stats (struct net_device *dev)
>  	dr16(TCPCheckSumErrors);
>  	dr16(UDPCheckSumErrors);
>  	dr16(IPCheckSumErrors);
> +
> +	spin_unlock_irq(&np->stats_lock);
> +
>  	return &dev->stats;
>  }
>  
> diff --git a/drivers/net/ethernet/dlink/dl2k.h b/drivers/net/ethernet/dlink/dl2k.h
> index 195dc6cfd895..dc8755a69b73 100644
> --- a/drivers/net/ethernet/dlink/dl2k.h
> +++ b/drivers/net/ethernet/dlink/dl2k.h
> @@ -372,6 +372,8 @@ struct netdev_private {
>  	struct pci_dev *pdev;
>  	void __iomem *ioaddr;
>  	void __iomem *eeprom_addr;
> +	// To ensure synchronization when stats are updated.
> +	spinlock_t stats_lock;
>  	spinlock_t tx_lock;
>  	spinlock_t rx_lock;
>  	unsigned int rx_buf_sz;		/* Based on MTU+slack. */
> @@ -401,6 +403,9 @@ struct netdev_private {
>  	u16 negotiate;		/* Negotiated media */
>  	int phy_addr;		/* PHY addresses. */
>  	u16 led_mode;		/* LED mode read from EEPROM (IP1000A only) */
> +
> +	u64 collisions;
> +	u64 tx_errors;
>  };

Code changes seem fine to me.

Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>

>  
>  /* The station address location in the EEPROM. */


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

* Re: [PATCH net-next] net: dlink: add synchronization for stats update
  2025-04-21 23:23 ` Jacob Keller
@ 2025-04-22 22:53   ` Moon Yeounsu
  2025-04-23 16:28     ` Jacob Keller
  0 siblings, 1 reply; 5+ messages in thread
From: Moon Yeounsu @ 2025-04-22 22:53 UTC (permalink / raw)
  To: Jacob Keller
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, netdev, linux-kernel

On Tue, Apr 22, 2025 at 8:24 AM Jacob Keller <jacob.e.keller@intel.com> wrote:
> Thanks,
> Jake

Thank you for replying to the patch!
I think this patch needs a bit more refinement.
Would it be okay if I submit a v2 patch?

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

* Re: [PATCH net-next] net: dlink: add synchronization for stats update
  2025-04-22 22:53   ` Moon Yeounsu
@ 2025-04-23 16:28     ` Jacob Keller
  0 siblings, 0 replies; 5+ messages in thread
From: Jacob Keller @ 2025-04-23 16:28 UTC (permalink / raw)
  To: Moon Yeounsu
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, netdev, linux-kernel



On 4/22/2025 3:53 PM, Moon Yeounsu wrote:
> On Tue, Apr 22, 2025 at 8:24 AM Jacob Keller <jacob.e.keller@intel.com> wrote:
>> Thanks,
>> Jake
> 
> Thank you for replying to the patch!
> I think this patch needs a bit more refinement.
> Would it be okay if I submit a v2 patch?

I would probably switch to request_ops_lock as a separate change than
this synchronization fix/improvement.

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

* Re: [PATCH net-next] net: dlink: add synchronization for stats update
  2025-04-21 19:16 [PATCH net-next] net: dlink: add synchronization for stats update Moon Yeounsu
  2025-04-21 23:23 ` Jacob Keller
@ 2025-04-24  1:42 ` Jakub Kicinski
  1 sibling, 0 replies; 5+ messages in thread
From: Jakub Kicinski @ 2025-04-24  1:42 UTC (permalink / raw)
  To: Moon Yeounsu
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Paolo Abeni, netdev,
	linux-kernel

On Tue, 22 Apr 2025 04:16:44 +0900 Moon Yeounsu wrote:
> -	dev->stats.tx_errors++;
> +	np->tx_errors++;
>  	/* Ttransmit Underrun */
>  	if (tx_status & 0x10) {
>  		dev->stats.tx_fifo_errors++;
> @@ -904,7 +904,7 @@ tx_error (struct net_device *dev, int tx_status)
>  	}
>  	/* Maximum Collisions */
>  	if (tx_status & 0x08)
> -		dev->stats.collisions++;
> +		np->collisions++;

These can be updated concurrently with the reading.
Since they are 64b on 32b machines the update may not be atomic.
So to be safe please take the spin lock around the increments,
or you could convert them to a atomic_t, or you can make them 32 bits
and update them with WRITE_ONCE() read with READ_ONCE()..
-- 
pw-bot: cr

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

end of thread, other threads:[~2025-04-24  1:42 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-04-21 19:16 [PATCH net-next] net: dlink: add synchronization for stats update Moon Yeounsu
2025-04-21 23:23 ` Jacob Keller
2025-04-22 22:53   ` Moon Yeounsu
2025-04-23 16:28     ` Jacob Keller
2025-04-24  1:42 ` Jakub Kicinski

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).