mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH net-next] net: dsa: motorcomm: Resync SMI slave after interrupted MDIO access
@ 2026-11-11 16:39 David Yang
  2026-11-14 15:40 ` Andrew Lunn
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: David Yang @ 2026-11-11 16:39 UTC (permalink / raw)
  To: netdev
  Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel

A register access over MDIO is 4 frames: 2 address writes followed by
2 data reads or writes. The SMI slave tracks the phase in a state
machine, so an access interrupted between frames, e.g. on an MDIO bus
error, leaves the machine in an unknown state, and every following
access fails or acts on a stale address until the machine is realigned.

The driver so far assumed every access succeeds, and relied on manual
resetting when the state machine wedged. However, without a dedicated
reset line, the reset itself goes through the same SMI interface,
causing the chicken-and-egg problem.

Realign the machine before the next access does its own work. Also, the
initial value is unsynced, so any stale state is not inherited.

Signed-off-by: David Yang <mmyangfl@gmail.com>
---
 drivers/net/dsa/motorcomm/chip.c | 106 +++++++++++++++++++++++++++++++
 1 file changed, 106 insertions(+)

diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
index 33e5fc127244..8b6c9739b684 100644
--- a/drivers/net/dsa/motorcomm/chip.c
+++ b/drivers/net/dsa/motorcomm/chip.c
@@ -94,10 +94,98 @@ struct yt921x_reg_mdio {
 	 * Keep this because we need switchid to form MII regaddrs anyway.
 	 */
 	unsigned char switchid;
+
+	bool synced;
 };
 
 /* TODO: SPI/I2C */
 
+/* An SMI register access is 4 MDIO frames. An interrupted access leaves the
+ * machine in an unknown state, and every following access fails or acts on a
+ * stale address.
+ *
+ * The driver used to assume every access succeeds, and relied on manual
+ * resetting when the state machine wedged. However, without a dedicated reset
+ * line, the reset itself goes through the same SMI interface, causing the
+ * chicken-and-egg problem. Furthermore, our driver verifies chip id before
+ * firing the reset, so that can never be triggered reliably.
+ *
+ * (A/D: address/data, H/L: hi/lo byte, r/w: read/write)
+ *
+ * State Input Next Comment
+ * ----- ----- ---- -------
+ * AH    aw    AL
+ * AH    dr    AH   `0xdead` sentinel
+ * AL    aw    DH
+ * AL    dr    AL   `0xdead` sentinel
+ * DHr   aw    AH   abort, frame discarded
+ * DHr   dr    DLr
+ * DLr   aw    AH   abort, frame discarded
+ * DLr   dr    AH   the access is done
+ * DHw   aw    AH   abort, frame discarded
+ * DHw   dw    DLw
+ * DLw   aw    AL   abort, frame consumed as next access's addr-hi
+ * DLw   dr    ?    `0xdead` sentinel; stay or advance undetermined
+ * DLw   dw    AH   the access commits
+ *
+ * The table lists the whitelisted frames only; any other REGAD/op combination
+ * is not characterized and is never emitted here.
+ *
+ * The resync sequence is dr, dr, aw, aw, dr, dr, plus one more dr as a sanity
+ * check: the machine must be back at AH, which reads the `0xdead` sentinel.
+ */
+static int yt921x_reg_mdio_resync(struct yt921x_reg_mdio *mdio)
+{
+	struct mii_bus *bus = mdio->bus;
+	int addr = mdio->addr;
+	u32 reg_addr;
+	u32 reg_data;
+	int res;
+
+	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+
+	reg_addr = YT921X_SMI_SWITCHID(mdio->switchid) | YT921X_SMI_ADDR |
+		   YT921X_SMI_READ;
+	reg_data = YT921X_SMI_SWITCHID(mdio->switchid) | YT921X_SMI_DATA |
+		   YT921X_SMI_READ;
+
+	/* dr */
+	res = __mdiobus_read(bus, addr, reg_data);
+	if (res < 0)
+		goto end;
+	/* dr */
+	res = __mdiobus_read(bus, addr, reg_data);
+	if (res < 0)
+		goto end;
+	/* aw */
+	res = __mdiobus_write(bus, addr, reg_addr, 0);
+	if (res)
+		goto end;
+	/* aw */
+	res = __mdiobus_write(bus, addr, reg_addr, 0);
+	if (res)
+		goto end;
+	/* dr */
+	res = __mdiobus_read(bus, addr, reg_data);
+	if (res < 0)
+		goto end;
+	/* dr */
+	res = __mdiobus_read(bus, addr, reg_data);
+	if (res < 0)
+		goto end;
+	/* AH --dr--> AH */
+	res = __mdiobus_read(bus, addr, reg_data);
+	if (res < 0)
+		goto end;
+	res = res != 0xdead ? -EIO : 0;
+
+end:
+	mutex_unlock(&bus->mdio_lock);
+
+	mdio->synced = !res;
+	return res;
+}
+
 static int yt921x_reg_mdio_read(void *context, u32 reg, u32 *valp)
 {
 	struct yt921x_reg_mdio *mdio = context;
@@ -108,6 +196,12 @@ static int yt921x_reg_mdio_read(void *context, u32 reg, u32 *valp)
 	u32 val;
 	int res;
 
+	if (!mdio->synced) {
+		res = yt921x_reg_mdio_resync(mdio);
+		if (res)
+			return res;
+	}
+
 	/* Hold the mdio bus lock to avoid (un)locking for 4 times */
 	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
 
@@ -136,6 +230,9 @@ static int yt921x_reg_mdio_read(void *context, u32 reg, u32 *valp)
 
 end:
 	mutex_unlock(&bus->mdio_lock);
+
+	if (res)
+		mdio->synced = false;
 	return res;
 }
 
@@ -148,6 +245,12 @@ static int yt921x_reg_mdio_write(void *context, u32 reg, u32 val)
 	u32 reg_data;
 	int res;
 
+	if (!mdio->synced) {
+		res = yt921x_reg_mdio_resync(mdio);
+		if (res)
+			return res;
+	}
+
 	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
 
 	reg_addr = YT921X_SMI_SWITCHID(mdio->switchid) | YT921X_SMI_ADDR |
@@ -172,6 +275,9 @@ static int yt921x_reg_mdio_write(void *context, u32 reg, u32 val)
 
 end:
 	mutex_unlock(&bus->mdio_lock);
+
+	if (res)
+		mdio->synced = false;
 	return res;
 }
 
-- 
2.53.0


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

* Re: [PATCH net-next] net: dsa: motorcomm: Resync SMI slave after interrupted MDIO access
  2026-11-11 16:39 [PATCH net-next] net: dsa: motorcomm: Resync SMI slave after interrupted MDIO access David Yang
@ 2026-11-14 15:40 ` Andrew Lunn
  2026-11-14 16:33   ` David Yang
  2026-11-14 21:32 ` Andrew Lunn
  2026-11-15 16:02 ` netdev-bot+sashiko
  2 siblings, 1 reply; 6+ messages in thread
From: Andrew Lunn @ 2026-11-14 15:40 UTC (permalink / raw)
  To: David Yang
  Cc: netdev, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, linux-kernel

>  static int yt921x_reg_mdio_read(void *context, u32 reg, u32 *valp)
>  {
>  	struct yt921x_reg_mdio *mdio = context;
> @@ -108,6 +196,12 @@ static int yt921x_reg_mdio_read(void *context, u32 reg, u32 *valp)
>  	u32 val;
>  	int res;
>  
> +	if (!mdio->synced) {
> +		res = yt921x_reg_mdio_resync(mdio);
> +		if (res)
> +			return res;
> +	}
> +
>  	/* Hold the mdio bus lock to avoid (un)locking for 4 times */
>  	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
>  
> @@ -136,6 +230,9 @@ static int yt921x_reg_mdio_read(void *context, u32 reg, u32 *valp)
>  
>  end:
>  	mutex_unlock(&bus->mdio_lock);
> +
> +	if (res)
> +		mdio->synced = false;

Why defer the recovery to the next transaction? Why not do it now. And
if the recovery fails, you can report the transaction which actually
killed it, which might be useful for debugging.

       Andrew

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

* Re: [PATCH net-next] net: dsa: motorcomm: Resync SMI slave after interrupted MDIO access
  2026-11-14 15:40 ` Andrew Lunn
@ 2026-11-14 16:33   ` David Yang
  2026-11-14 21:27     ` Andrew Lunn
  0 siblings, 1 reply; 6+ messages in thread
From: David Yang @ 2026-11-14 16:33 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: netdev, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, linux-kernel

On Sat, Nov 14, 2026 at 11:40 PM Andrew Lunn <andrew@lunn.ch> wrote:
>
> >  static int yt921x_reg_mdio_read(void *context, u32 reg, u32 *valp)
> >  {
> >       struct yt921x_reg_mdio *mdio = context;
> > @@ -108,6 +196,12 @@ static int yt921x_reg_mdio_read(void *context, u32 reg, u32 *valp)
> >       u32 val;
> >       int res;
> >
> > +     if (!mdio->synced) {
> > +             res = yt921x_reg_mdio_resync(mdio);
> > +             if (res)
> > +                     return res;
> > +     }
> > +
> >       /* Hold the mdio bus lock to avoid (un)locking for 4 times */
> >       mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
> >
> > @@ -136,6 +230,9 @@ static int yt921x_reg_mdio_read(void *context, u32 reg, u32 *valp)
> >
> >  end:
> >       mutex_unlock(&bus->mdio_lock);
> > +
> > +     if (res)
> > +             mdio->synced = false;
>
> Why defer the recovery to the next transaction? Why not do it now. And
> if the recovery fails, you can report the transaction which actually
> killed it, which might be useful for debugging.
>
>        Andrew

If the recovery fails, you have to, anyway, try to resync in the next
transaction, so recovery here does not help other than adding more
code. And we did not and do not report failure here, instead we let
the caller do it.

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

* Re: [PATCH net-next] net: dsa: motorcomm: Resync SMI slave after interrupted MDIO access
  2026-11-14 16:33   ` David Yang
@ 2026-11-14 21:27     ` Andrew Lunn
  0 siblings, 0 replies; 6+ messages in thread
From: Andrew Lunn @ 2026-11-14 21:27 UTC (permalink / raw)
  To: David Yang
  Cc: netdev, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, linux-kernel

> > > @@ -136,6 +230,9 @@ static int yt921x_reg_mdio_read(void *context, u32 reg, u32 *valp)
> > >
> > >  end:
> > >       mutex_unlock(&bus->mdio_lock);
> > > +
> > > +     if (res)
> > > +             mdio->synced = false;
> >
> > Why defer the recovery to the next transaction? Why not do it now. And
> > if the recovery fails, you can report the transaction which actually
> > killed it, which might be useful for debugging.
> >
> >        Andrew
> 
> If the recovery fails, you have to, anyway, try to resync in the next
> transaction, so recovery here does not help other than adding more
> code.

I don't know why it adds more code. You don't need mdio->synced. You
just call the recovery function. If it fails, it fails, and you assume
the switch is dead, but report the error and keep going. The next
transaction is also likely to fail, probably causing another sync
attempt, which might work, but by then, you have to assume the switch
is wrongly configured, even if the transaction works.

	Andrew

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

* Re: [PATCH net-next] net: dsa: motorcomm: Resync SMI slave after interrupted MDIO access
  2026-11-11 16:39 [PATCH net-next] net: dsa: motorcomm: Resync SMI slave after interrupted MDIO access David Yang
  2026-11-14 15:40 ` Andrew Lunn
@ 2026-11-14 21:32 ` Andrew Lunn
  2026-11-15 16:02 ` netdev-bot+sashiko
  2 siblings, 0 replies; 6+ messages in thread
From: Andrew Lunn @ 2026-11-14 21:32 UTC (permalink / raw)
  To: David Yang
  Cc: netdev, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, linux-kernel

> Realign the machine before the next access does its own work. Also, the
> initial value is unsynced, so any stale state is not inherited.

Seems like you should call the sync function in yt921x_chip_reset
before doing anything else.

       Andrew

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

* Re: [PATCH net-next] net: dsa: motorcomm: Resync SMI slave after interrupted MDIO access
  2026-11-11 16:39 [PATCH net-next] net: dsa: motorcomm: Resync SMI slave after interrupted MDIO access David Yang
  2026-11-14 15:40 ` Andrew Lunn
  2026-11-14 21:32 ` Andrew Lunn
@ 2026-11-15 16:02 ` netdev-bot+sashiko
  2 siblings, 0 replies; 6+ messages in thread
From: netdev-bot+sashiko @ 2026-11-15 16:02 UTC (permalink / raw)
  To: mmyangfl
  Cc: netdev, andrew, olteanv, davem, edumazet, kuba, pabeni, linux-kernel

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

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

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [High] yt921x_reg_mdio_resync() does not reliably realign the SMI slave
  after an interrupted write, even by the patch's own state table.
- [Medium] yt921x_mdio_probe() allocates mdio with devm_kzalloc(), so
  synced starts out false.
- [Low] The commit message presents this as a fix for a user-visible
  defect: one interrupted MDIO access leaves the SMI slave misaligned,…

--

net: dsa: motorcomm: Resync SMI slave after interrupted MDIO access

This patch adds a synced flag to struct yt921x_reg_mdio. It also adds
yt921x_reg_mdio_resync(), which sends a fixed sequence of MDIO frames to
realign the SMI slave state machine before the next register access, and
clears the flag whenever an access fails. The flag starts out false, so
the first access after probe always runs the resync.

> The driver so far assumed every access succeeds, and relied on manual
> resetting when the state machine wedged.

[Severity: Low]
This reads as a fix for a user-visible problem, but there's no Fixes:
tag. The access helpers that assumed every access succeeds appear to come
from:

186623f4aa72 ("net: dsa: yt921x: Add support for Motorcomm YT921x")

Should a Fixes: tag pointing at that commit be added, so stable trees
that carry the driver can pick up this recovery?

> diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
> index 33e5fc127244d..8b6c9739b6841 100644
> --- a/drivers/net/dsa/motorcomm/chip.c
> +++ b/drivers/net/dsa/motorcomm/chip.c
> @@ -94,10 +94,98 @@ struct yt921x_reg_mdio {

[ ... ]

> + * State Input Next Comment
> + * ----- ----- ---- -------
> + * AH    aw    AL
> + * AH    dr    AH   `0xdead` sentinel
> + * AL    aw    DH
> + * AL    dr    AL   `0xdead` sentinel
> + * DHr   aw    AH   abort, frame discarded
> + * DHr   dr    DLr
> + * DLr   aw    AH   abort, frame discarded
> + * DLr   dr    AH   the access is done
> + * DHw   aw    AH   abort, frame discarded
> + * DHw   dw    DLw
> + * DLw   aw    AL   abort, frame consumed as next access's addr-hi
> + * DLw   dr    ?    `0xdead` sentinel; stay or advance undetermined
> + * DLw   dw    AH   the access commits
> + *
> + * The table lists the whitelisted frames only; any other REGAD/op combination
> + * is not characterized and is never emitted here.
> + *
> + * The resync sequence is dr, dr, aw, aw, dr, dr, plus one more dr as a sanity
> + * check: the machine must be back at AH, which reads the `0xdead` sentinel.
> + */
> +static int yt921x_reg_mdio_resync(struct yt921x_reg_mdio *mdio)
> +{

[ ... ]

> +	/* dr */
> +	res = __mdiobus_read(bus, addr, reg_data);
> +	if (res < 0)
> +		goto end;
> +	/* dr */
> +	res = __mdiobus_read(bus, addr, reg_data);
> +	if (res < 0)
> +		goto end;
> +	/* aw */
> +	res = __mdiobus_write(bus, addr, reg_addr, 0);
> +	if (res)
> +		goto end;
> +	/* aw */
> +	res = __mdiobus_write(bus, addr, reg_addr, 0);
> +	if (res)
> +		goto end;
> +	/* dr */
> +	res = __mdiobus_read(bus, addr, reg_data);
> +	if (res < 0)
> +		goto end;
> +	/* dr */
> +	res = __mdiobus_read(bus, addr, reg_data);
> +	if (res < 0)
> +		goto end;
> +	/* AH --dr--> AH */
> +	res = __mdiobus_read(bus, addr, reg_data);
> +	if (res < 0)
> +		goto end;
> +	res = res != 0xdead ? -EIO : 0;
> +
> +end:
> +	mutex_unlock(&bus->mdio_lock);
> +
> +	mdio->synced = !res;
> +	return res;
> +}

[Severity: High]
Does this sequence realign the slave when it starts in DHw?

The table has no row for DHw + dr, and the comment says uncharacterized
combinations are never emitted here. Even so, yt921x_reg_mdio_resync()
always starts with two dr frames, whatever state the slave is in.

DHw can be reached from yt921x_reg_mdio_write() in two ways:

- The aw(lo) frame is latched by the slave, but the controller still
  reports an error.
- aw(lo) succeeds, and the first data write fails before the slave
  latches it:

yt921x_reg_mdio_write() {
    ...
	res = __mdiobus_write(bus, addr, reg_data, (u16)(val >> 16));
	if (res)
		goto end;
    ...
}

Either way synced becomes false, and the next access runs the resync
starting from DHw.

Suppose DHw + dr is a no-op, the same as dr in AH and AL. Then the
sequence would go:

DHw -dr-> DHw -dr-> DHw -aw-> AH (abort) -aw-> AL -dr-> AL -dr-> AL

The final dr in AL then returns 0xdead, per the "AL dr AL" row.

Can the res != 0xdead check tell AH apart from AL? By the table, dr
returns 0xdead in AH, AL and DLw. So the check would pass, and
mdio->synced would be set to true while the slave is still at AL.

What happens on the next access after that? A read would go:

AL -aw-> DHr -aw-> AH (abort) -dr-> 0xdead -dr-> 0xdead

yt921x_reg_mdio_read() would then return success with *valp = 0xdeaddead.
yt921x_reg_update_bits() in smi.c could write (0xdeaddead & ~mask) | val
back to a real register, because by then the slave is at AH and that
write commits.

A write would go AL -aw-> DHw -aw-> AH (abort), followed by dw, dw in
AH. The table doesn't cover that, so the write would be lost while still
returning success.

synced is only cleared when __mdiobus_read() or __mdiobus_write() returns
an error, so neither case would be detected.

Would a sequence built only from whitelisted transitions work better? One
example is aw, dr, dr, aw, aw, dr, dr. The leading aw aborts from DHr, DLr
and DHw, and DLw + aw is a documented abort.

Ending with a full read of a register whose known value is not 0xdead
would also let the check tell AH from AL.

> @@ -108,6 +196,12 @@ static int yt921x_reg_mdio_read(void *context, u32 reg, u32 *valp)
>  	u32 val;
>  	int res;
>  
> +	if (!mdio->synced) {
> +		res = yt921x_reg_mdio_resync(mdio);
> +		if (res)
> +			return res;
> +	}
> +

[Severity: Medium]
yt921x_mdio_probe() allocates mdio with devm_kzalloc(), so synced starts
out false. The first register access at probe therefore goes through the
resync:

yt921x_dsa_setup()
  yt921x_chip_reset()
    yt921x_chip_detect()
      yt921x_reg_read(priv, YT921X_CHIP_ID, &chipid)
        yt921x_reg_mdio_read()
          yt921x_reg_mdio_resync()

A supported part might return something other than exactly 0xdead for a
data read in AH. In that case yt921x_reg_mdio_resync() returns -EIO
without logging anything. yt921x_chip_detect() passes the error straight
up, so its "Unexpected chipid" and "Unsupported chipid" messages can no
longer be reached.

Every later access would then retry the full 7-frame resync, and each
attempt would fail silently with -EIO.

Is the 0xdead sentinel known to hold on all of YT9213, YT9214, YT9215
and YT9218? The driver header mentions only YT9215+SGMII as tested, and
the commit message doesn't mention the sentinel.

Could the resync print something when the sentinel doesn't match, so
this new failure mode can be diagnosed?

Also, each resync does a real read access of register 0x0 (aw 0, aw 0,
dr, dr). The commit message doesn't mention that either.

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261111163954.1132921-1-mmyangfl%40gmail.com

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

end of thread, other threads:[~2026-11-15 16:02 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-11 16:39 [PATCH net-next] net: dsa: motorcomm: Resync SMI slave after interrupted MDIO access David Yang
2026-11-14 15:40 ` Andrew Lunn
2026-11-14 16:33   ` David Yang
2026-11-14 21:27     ` Andrew Lunn
2026-11-14 21:32 ` Andrew Lunn
2026-11-15 16:02 ` netdev-bot+sashiko

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®