[PATCH net-next v2 6/6] net: dsa: motorcomm: Use safe 64-bit counter reader
From: David Yang <mmyangfl@gmail.com>
Date: 2026-09-04 16:31:27
Also in:
lkml
Subsystem:
networking drivers, networking [dsa], the rest · Maintainers:
Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Andrew Lunn, Vladimir Oltean, Linus Torvalds
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@gmail.com> --- 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