[PATCH net-next v2 6/6] net: dsa: motorcomm: Use safe 64-bit counter reader

From: David Yang

Date: Fri Sep 04 2026 - 14:03:28 EST


64-bit counter registers are read as two separate 32-bit transactions.
If the low word wraps from about 0xffffffff to a small value while the
high word increments between the two transactions, the recombined value
becomes ((new_high << 32) | old_low), producing a spurious ~4 GiB jump.

Use a safe counter reader for this.

Signed-off-by: David Yang <mmyangfl@xxxxxxxxx>
---
drivers/net/dsa/motorcomm/mib.c | 19 +++++++++----------
drivers/net/dsa/motorcomm/smi.c | 30 ++++++++++++++++++++++++++++++
drivers/net/dsa/motorcomm/smi.h | 2 ++
3 files changed, 41 insertions(+), 10 deletions(-)

diff --git a/drivers/net/dsa/motorcomm/mib.c b/drivers/net/dsa/motorcomm/mib.c
index 31d86c3122c7..8929476a976f 100644
--- a/drivers/net/dsa/motorcomm/mib.c
+++ b/drivers/net/dsa/motorcomm/mib.c
@@ -102,26 +102,25 @@ static int yt921x_mib_read(struct yt921x_priv *priv, int port)
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;
- u32 val0;
- u64 val;
-
- res = yt921x_reg_read(priv, reg, &val0);
- if (res)
- break;

if (desc->size <= 1) {
u64 old_val = buf[i];
+ u32 val0;
+ u64 val;
+
+ res = yt921x_reg_read(priv, reg, &val0);
+ if (res)
+ break;

val = (old_val & ~(u64)U32_MAX) | val0;
if (val < old_val)
val += 1ull << 32;
- } else {
- u32 val1;

- res = yt921x_reg_read(priv, reg + 4, &val1);
+ buf[i] = val;
+ } else {
+ res = yt921x_counter_read(priv, reg, &buf[i]);
if (res)
break;
- val = ((u64)val1 << 32) | val0;
}

buf[i] = val;
diff --git a/drivers/net/dsa/motorcomm/smi.c b/drivers/net/dsa/motorcomm/smi.c
index bf3adfd64165..27086602997b 100644
--- a/drivers/net/dsa/motorcomm/smi.c
+++ b/drivers/net/dsa/motorcomm/smi.c
@@ -63,6 +63,36 @@ int yt921x_reg_update_bits(struct yt921x_priv *priv, u32 reg, u32 mask, u32 val)
return yt921x_reg_write(priv, reg, u);
}

+/* Reliably read a 64bit counter */
+int yt921x_counter_read(struct yt921x_priv *priv, u32 reg, u64 *valp)
+{
+ u32 old_lo;
+ int res;
+ u32 hi;
+ u32 lo;
+
+ res = yt921x_reg_read(priv, reg, &old_lo);
+ if (res)
+ return res;
+
+ for (int i = 0; i < 16; i++) {
+ res = yt921x_reg_read(priv, reg + 4, &hi);
+ if (res)
+ return res;
+ res = yt921x_reg_read(priv, reg, &lo);
+ if (res)
+ return res;
+
+ if (lo >= old_lo) {
+ *valp = ((u64)hi << 32) | lo;
+ return 0;
+ }
+ old_lo = lo;
+ }
+
+ return -ETIMEDOUT;
+}
+
static int
yt921x_regs_read(struct yt921x_priv *priv, u32 reg, u32 *vals,
unsigned int num_regs)
diff --git a/drivers/net/dsa/motorcomm/smi.h b/drivers/net/dsa/motorcomm/smi.h
index 212e20f71d80..d34240548af7 100644
--- a/drivers/net/dsa/motorcomm/smi.h
+++ b/drivers/net/dsa/motorcomm/smi.h
@@ -35,6 +35,8 @@ yt921x_reg_toggle_bits(struct yt921x_priv *priv, u32 reg, u32 mask, bool set)
return yt921x_reg_update_bits(priv, reg, mask, !set ? 0 : mask);
}

+int yt921x_counter_read(struct yt921x_priv *priv, u32 reg, u64 *valp);
+
/* Some multi-word registers, like VLANn_CTRL, should be treated as a single
* long register. More specifically, writes to parts of its words won't become
* visible, until the last word is written.
--
2.53.0