Thread (15 messages) flat view 15 messages, 2 authors, 9d ago

Re: [PATCH net-next v3 4/6] net: dsa: motorcomm: Use u64_stats_t for MIB stats

From: netdev-bot+sashiko@kernel.org
Date: 2026-09-09 15:16:16
Also in: lkml

Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:

Critical: 0 · High: 0 · Medium: 0 (1) · Low: 1 (1)

- [Low] In "net: dsa: motorcomm: Use u64_stats_t for MIB stats" the MIB
  counters are converted from u64 to u64_stats_t while no `struct…

Pre-existing issues:
- [Medium] For the 64-bit MIB counters (desc->size == 2: RX_GOOD_BYTES,
  RX_BAD_BYTES, TX_GOOD_BYTES) yt921x_mib_read() performs two…
- [Low] yt921x_dsa_get_stats64() (drivers/net/dsa/motorcomm/mib.c) reads
  the shared MIB counters with no lock and, at the reviewed commit, no…

--

net: dsa: motorcomm: Use u64_stats_t for MIB stats

This converts every MIB counter in struct yt921x_mib_stats and the derived
rx_frames/tx_frames fields from plain u64 to u64_stats_t, and updates all
readers in mib.c to use u64_stats_read().  The changelog notes that
u64_stats_sync is added in the next commit.
quoted hunk ↗ jump to hunk
diff --git a/drivers/net/dsa/motorcomm/mib.c b/drivers/net/dsa/motorcomm/mib.c
index b1296f224cf69..f5d39d22215d4 100644
--- a/drivers/net/dsa/motorcomm/mib.c
+++ b/drivers/net/dsa/motorcomm/mib.c
@@ -92,22 +92,16 @@ static int yt921x_mib_read(struct yt921x_priv *priv, int port)
 	struct device *dev = to_device(priv);
 	struct yt921x_mib *pm = pp->mib;
 	struct yt921x_mib_stats *mib;
+	u64 rx_frames;
+	u64 tx_frames;
 	int res = 0;
 
 	mib = &pm->stats;
 
-	/* Reading of yt921x_port::mib is not protected by a lock and it's vain
-	 * to keep its consistency, since we have to read registers one by one
-	 * and there is no way to make a snapshot of MIB stats.
-	 *
-	 * Writing (by this function only) is and should be protected by
-	 * reg_lock.
-	 */
-
 	for (size_t i = 0; i < ARRAY_SIZE(yt921x_mib_descs); i++) {
 		const struct yt921x_mib_desc *desc = &yt921x_mib_descs[i];
 		u32 reg = YT921X_MIBn_DATA0(port) + desc->offset;
-		u64 *valp = &((u64 *)mib)[i];
+		u64_stats_t *valp = &((u64_stats_t *)mib)[i];
 		u32 val0;
 		u64 val;
 
@@ -116,7 +110,7 @@ static int yt921x_mib_read(struct yt921x_priv *priv, int port)
 			break;
 
 		if (desc->size <= 1) {
-			u64 old_val = *valp;
+			u64 old_val = u64_stats_read(valp);
 
 			val = (old_val & ~(u64)U32_MAX) | val0;
 			if (val < old_val)
[Severity: Low]
The changelog says the change addresses the case where "64-bit variables
might not be atomic on 32-bit architectures, and could lead to load/store
tearing".  Does this commit on its own change anything on 32-bit?

With BITS_PER_LONG == 32, u64_stats_read() and u64_stats_set() are plain
accesses:

	static inline u64 u64_stats_read(const u64_stats_t *p)
	{
		return p->v;
	}

	static inline void u64_stats_set(u64_stats_t *p, u64 val)
	{
		p->v = val;
	}

struct yt921x_mib gains no struct u64_stats_sync here, yt921x_mib_read()
has no u64_stats_update_begin()/u64_stats_update_end() region, and
yt921x_dsa_get_stats64() has no u64_stats_fetch_begin()/
u64_stats_fetch_retry() loop, so the tearing named in the changelog is
still there after this patch.

Also, the writer store goes from WRITE_ONCE(*valp, val) to
u64_stats_set(valp, val), which on 32-bit is a plain store, and the comment
documenting the "written only here under reg_lock, read locklessly" rule is
deleted with nothing replacing it.  Would it be preferable to fold this
patch together with the follow-up, or at least keep the WRITE_ONCE()
equivalent and the locking comment until the follow-up lands?

For the record, the next patch in this series, "net: dsa: motorcomm: Fix MIB
synchronization", does add struct u64_stats_sync syncp to struct yt921x_mib,
wraps the write-back in u64_stats_update_begin()/u64_stats_update_end(),
turns yt921x_dsa_get_stats64() into a fetch_begin()/fetch_retry() loop,
moves mutex_unlock(&priv->reg_lock) to after the counter reads in
yt921x_dsa_get_eth_mac_stats(), yt921x_dsa_get_eth_ctrl_stats(),
yt921x_dsa_get_rmon_stats(), yt921x_dsa_get_pause_stats() and
yt921x_dsa_get_ethtool_stats(), and restores the access documentation as
/* protected by syncp OR priv->reg_lock */ in mib.h.
quoted hunk ↗ jump to hunk
@@ -130,17 +124,25 @@ static int yt921x_mib_read(struct yt921x_priv *priv, int port)
 			val = ((u64)val1 << 32) | val0;
        ^^^^

[Severity: Medium]
This is a pre-existing issue and not introduced by this patch, but while
yt921x_mib_read() is being touched: for the desc->size == 2 counters
(RX_GOOD_BYTES, RX_BAD_BYTES, TX_GOOD_BYTES) the low word at
YT921X_MIBn_DATA0(port) + desc->offset and the high word at + 4 are fetched
in two separate SMI transactions with nothing latching the pair.  If the low
word wraps between the two reads, does the composed value end up about 4 GiB
too high, and does that then get cached as the base for the 32-bit
wraparound arithmetic above and exported as a non-monotonic rx_bytes/
tx_bytes?

The last patch of this series, "net: dsa: motorcomm: Use safe 64-bit counter
reader", replaces this with yt921x_counter_read(), which re-reads hi and lo
and accepts the pair only when lo >= old_lo, so the window is closed there.
 		}
 
-		WRITE_ONCE(*valp, val);
+		u64_stats_set(valp, val);
 	}
 
-	pm->rx_frames = mib->rx_64byte + mib->rx_65_127byte +
-			mib->rx_128_255byte + mib->rx_256_511byte +
-			mib->rx_512_1023byte + mib->rx_1024_1518byte +
-			mib->rx_jumbo;
-	pm->tx_frames = mib->tx_64byte + mib->tx_65_127byte +
-			mib->tx_128_255byte + mib->tx_256_511byte +
-			mib->tx_512_1023byte + mib->tx_1024_1518byte +
-			mib->tx_jumbo;
+	rx_frames = u64_stats_read(&mib->rx_64byte) +
+		    u64_stats_read(&mib->rx_65_127byte) +
+		    u64_stats_read(&mib->rx_128_255byte) +
+		    u64_stats_read(&mib->rx_256_511byte) +
+		    u64_stats_read(&mib->rx_512_1023byte) +
+		    u64_stats_read(&mib->rx_1024_1518byte) +
+		    u64_stats_read(&mib->rx_jumbo);
+	tx_frames = u64_stats_read(&mib->tx_64byte) +
+		    u64_stats_read(&mib->tx_65_127byte) +
+		    u64_stats_read(&mib->tx_128_255byte) +
+		    u64_stats_read(&mib->tx_256_511byte) +
+		    u64_stats_read(&mib->tx_512_1023byte) +
+		    u64_stats_read(&mib->tx_1024_1518byte) +
+		    u64_stats_read(&mib->tx_jumbo);
+	u64_stats_set(&pm->rx_frames, rx_frames);
+	u64_stats_set(&pm->tx_frames, tx_frames);
 
 	if (res)
 		dev_err(dev, "Failed to %s port %d: %i\n", "read stats for",
[ ... ]
quoted hunk ↗ jump to hunk
@@ -246,28 +248,28 @@ yt921x_dsa_get_eth_mac_stats(struct dsa_switch *ds, int port,
 	yt921x_mib_read(priv, port);
 	mutex_unlock(&priv->reg_lock);
 
-	mac_stats->FramesTransmittedOK = pm->tx_frames;
+	mac_stats->FramesTransmittedOK = u64_stats_read(&pm->tx_frames);
[ ... ]
quoted hunk ↗ jump to hunk
@@ -358,32 +360,34 @@ yt921x_dsa_get_stats64(struct dsa_switch *ds, int port,
 		return;
 	mib = &pm->stats;
 
[ ... ]
+	stats->rx_packets = u64_stats_read(&pm->rx_frames);
+	stats->tx_packets = u64_stats_read(&pm->tx_frames);
+	stats->rx_bytes = u64_stats_read(&mib->rx_good_bytes) -
+			  ETH_FCS_LEN * stats->rx_packets;
+	stats->tx_bytes = u64_stats_read(&mib->tx_good_bytes) -
+			  ETH_FCS_LEN * stats->tx_packets;
[Severity: Low]
This is a pre-existing issue rather than something this patch introduces,
but the derivation here mixes two independently loaded counters while
yt921x_dsa_get_stats64() holds no lock:

	CPU0 yt921x_dsa_get_stats64()
	    stats->rx_packets = u64_stats_read(&pm->rx_frames);
	    /* CPU1: yt921x_mib_poll() -> yt921x_mib_read() rewrites
	     * all counters under priv->reg_lock */
	    stats->rx_bytes = u64_stats_read(&mib->rx_good_bytes) -
			      ETH_FCS_LEN * stats->rx_packets;

If rx_packets is observed fresh and rx_good_bytes stale, can this unsigned
subtraction wrap and report an rx_bytes value close to 2^64 through
rtnl_link_stats64?

The follow-up patch "net: dsa: motorcomm: Fix MIB synchronization" wraps
this whole block in a u64_stats_fetch_begin()/u64_stats_fetch_retry() loop
and publishes all counters plus rx_frames/tx_frames in one writer section,
which gives a consistent snapshot on 32-bit.
quoted hunk ↗ jump to hunk
diff --git a/drivers/net/dsa/motorcomm/mib.h b/drivers/net/dsa/motorcomm/mib.h
index cfad0665baaea..d84ae2f449ac8 100644
--- a/drivers/net/dsa/motorcomm/mib.h
+++ b/drivers/net/dsa/motorcomm/mib.h
[ ... ]
quoted hunk ↗ jump to hunk
@@ -128,8 +130,8 @@ struct yt921x_mib {
 
 	struct delayed_work work;
 	struct yt921x_mib_stats stats;
-	u64 rx_frames;
-	u64 tx_frames;
+	u64_stats_t rx_frames;
+	u64_stats_t tx_frames;
 };
 
 void yt921x_mib_poll(struct work_struct *work);
Would it make sense to add struct u64_stats_sync syncp to struct yt921x_mib
in this same patch, so the u64_stats_t fields are never present without
their companion seqcount?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260906131414.2683362-1-mmyangfl%40gmail.com
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help