Netdev List
 help / color / mirror / Atom feed
From: "Théo Lebrun" <theo.lebrun@bootlin.com>
To: "Polak, Leszek" <LPolak@arri.de>,
	"andrew+netdev@lunn.ch" <andrew+netdev@lunn.ch>,
	"davem@davemloft.net" <davem@davemloft.net>,
	"edumazet@google.com" <edumazet@google.com>,
	"kuba@kernel.org" <kuba@kernel.org>,
	"pabeni@redhat.com" <pabeni@redhat.com>
Cc: "conor.dooley@microchip.com" <conor.dooley@microchip.com>,
	"netdev@vger.kernel.org" <netdev@vger.kernel.org>,
	"linux-kernel@vger.kernel.org" <linux-kernel@vger.kernel.org>
Subject: Re: [PATCH] net: macb: fix stale data returned by MDIO reads
Date: Wed, 05 Aug 2026 16:52:55 +0200	[thread overview]
Message-ID: <DKH3G9J1MI8G.24Q6WY2SU93I9@bootlin.com> (raw)
In-Reply-To: <DKH3AOWRPUDD.1IOVFERUUNC1J@bootlin.com>

Hello Théo,

On Wed Aug 5, 2026 at 4:45 PM CEST, Théo Lebrun wrote:
> Hello Leszek,
>
> Your subject is missing the [PATCH net] prefix.
>
> On Wed Aug 5, 2026 at 2:01 PM CEST, Polak, Leszek wrote:
>> macb_mdio_wait_for_idle() samples NSR.IDLE with no delay after MAN has been
>> written. readx_poll_timeout() expands to read_poll_timeout() with
>> sleep_before_read = false, so the very first NSR read is issued immediately
>> after the register write.
>>
>> The controller does not deassert NSR.IDLE until its MDIO state machine
>> actually begins the frame, which takes up to one MDC period. Neither
>> macb_mdc_clk_div() nor gem_mdc_clk_div() ever selects an MDC above 2.5 MHz,
>> so one period is up to ~400 ns - on the order of a hundred pclk cycles, and
>> far longer than a back-to-back register access. The poll therefore observes
>> IDLE still set from the *previous* operation and returns success before the
>> operation just issued has started.
>
> You (or rather the LLM) say that with confidence. How do you know about
> this MDIO state machine implying some delay inbetween the MAN writel
> (which triggers a MDIO management frame) and the time at which NSR.IDLE
> starts becoming 0?
>
> The doc doesn't mention anything of the sort. It explicitely mentions
> NSR.IDLE as being there to signal for completion.
>
> Also it sounds like a massive bug, wouldn't others have discovered it
> before? Just checked and nothing out of the ordinary on EyeQ (first NSR
> is always 0x2 after writel to MAN). Patch at the end, which should
> trigger error logs on your system from what I understood. Please report
> back on that.
>
>> The caller then reads a MAN DATA field that still holds the result of
>> whatever completed before, so every read returns the data of the previously
>> accessed register. For clause 45 the race occurs three times per access: the
>> address operation is still in flight when the read operation is written, and
>> the final MACB_BFEXT(DATA, macb_readl(bp, MAN)) picks up a stale value.
>>
>> Observed on a Versal board with a Marvell 88Q1111 on GEM1, where reading each
>> MMD register twice showed the first read returning the preceding register's
>> value:
>>
>>   31.8002: 0x0149 then 0x002b   (0x0149 is 31.8001)
>>   31.8004: 0x0b21 then 0x1401   (0x0b21 is 31.8003)
>>   31.8011: 0x400e then 0x6000   (0x400e is 31.8010)
>>   31.8010: 0x6000 then 0x400e   (0x6000 is 31.8011)
>
> I don't understand those logs? Can you provide the code that generated
> them and more context? You probably faced some terrible bugs if all
> MDIO reads returned the value from the previous read.
>
>> The lag persists across callers and across driver entry points, so it is a
>> property of the controller path rather than of any PHY driver. It silently
>> corrupts every MMD read. Latch-high, clear-on-read registers are worse than
>> corrupted: their content cannot be recovered by retrying, because the first
>> read still reaches the PHY and clears the latch.
>>
>> Sleep before the first NSR sample so that an operation issued immediately
>> beforehand is guaranteed to be underway. The pre-operation bus-free check
>> pays the same delay, which is immaterial - a single MDIO frame takes over
>> 25 us at 2.5 MHz MDC, so this is well under the cost of the transfer itself.
>>
>> Assisted-by: Claude:claude-opus-5
>> Signed-off-by: Polak, Leszek <LPolak@arri.de>
>
> If this bug gets confirmed it needs (1) to go into net and
> (2) have `Cc: stable...` and `Fixes: ...` trailers.
>
> Docs on (1) are
> Documentation/process/maintainer-netdev.rst
> https://www.kernel.org/doc/html/latest/process/maintainer-netdev.html
>
> Docs on (2) are
> sources are in: Documentation/process/stable-kernel-rules.rst
> https://www.kernel.org/doc/html/latest/process/stable-kernel-rules.html
>
>> ---
>>  drivers/net/ethernet/cadence/macb_main.c | 20 ++++++++++++++++++--
>>  1 file changed, 18 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c
>> --- a/drivers/net/ethernet/cadence/macb_main.c
>> +++ b/drivers/net/ethernet/cadence/macb_main.c
>> @@ -340,10 +340,26 @@ static void macb_get_hwaddr(struct macb *bp)
>>  
>>  static int macb_mdio_wait_for_idle(struct macb *bp)
>>  {
>> +	/*
>> +	 * NSR.IDLE is not deasserted until the MDIO state machine begins the
>> +	 * frame, which takes up to one MDC period after MAN is written. MDC is
>> +	 * never clocked above 2.5 MHz, so one period is at most ~400 ns; allow
>> +	 * an order of magnitude of margin. read_poll_timeout() sleeps at least
>> +	 * (sleep_us >> 2) + 1 us, so the guaranteed minimum here is 6 us.
>> +	 */
>> +	const unsigned int start_delay_us = 20;
>>  	u32 val;
>>  
>> -	return readx_poll_timeout(MACB_READ_NSR, bp, val, val & MACB_BIT(IDLE),
>> -				  1, MACB_MDIO_TIMEOUT);
>> +	/*
>> +	 * Do not sample NSR.IDLE immediately. When this is called straight
>> +	 * after a MAN write, IDLE is still set from the previous operation,
>> +	 * so the poll would return at once and the caller would go on to read
>> +	 * a MAN DATA field that still holds the previous transaction's
>> +	 * result. Sleeping first guarantees the new operation has started and
>> +	 * IDLE has gone low before it is sampled.
>> +	 */
>> +	return read_poll_timeout(MACB_READ_NSR, val, val & MACB_BIT(IDLE),
>> +				 start_delay_us, MACB_MDIO_TIMEOUT, true, bp);
>>  }
>
> Your code is not just adding a 20µs start delay, it changes all delays
> from 1µs to 20µs. I don't think we want that.
> Before: readl(NSR) -> usleep(1) ->  readl(NSR) -> usleep(1)  -> ...
> After:  usleep(20) -> readl(NSR) -> usleep(20) -> readl(NSR) -> ...
>
> Your introduced variable start_delay_us is not needed.
>
> There are way too many comment lines. Your LLM hasn't read enough kernel
> code! Rare comments, mostly terse. Commit messages can be relied upon.
>
> ---
>
> pr_err() should trigger on your system from what I understood.
> Can you confirm?
>
> [...]

Oops this test triggers on the pre-check macb_mdio_wait_for_idle() calls.
Instead this:



diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c
index d58430fe9c41..98e0cafb0de1 100644
--- a/drivers/net/ethernet/cadence/macb_main.c
+++ b/drivers/net/ethernet/cadence/macb_main.c
@@ -308,6 +308,20 @@ static int macb_mdio_wait_for_idle(struct macb *bp)
              1, MACB_MDIO_TIMEOUT);
 }

+static int macb_mdio_wait_for_idle_busy(struct macb *bp)
+{
+  u32 val;
+
+  val = macb_readl(bp, NSR);
+  if (val & MACB_BIT(IDLE)) {
+     pr_err("PHY IDLE after writel(MAN): NSR %#x MAN %#x\n",
+            val, macb_readl(bp, MAN));
+     fsleep(20);
+  }
+
+  return macb_mdio_wait_for_idle(bp);
+}
+
 static int macb_mdio_read_c22(struct mii_bus *bus, int mii_id, int regnum)
 {
   struct macb *bp = bus->priv;
@@ -327,7 +341,7 @@ static int macb_mdio_read_c22(struct mii_bus *bus, int mii_id, int regnum)
               | MACB_BF(REGA, regnum)
               | MACB_BF(CODE, MACB_MAN_C22_CODE)));

-  status = macb_mdio_wait_for_idle(bp);
+  status = macb_mdio_wait_for_idle_busy(bp);
   if (status < 0)
      goto mdio_read_exit;

@@ -362,7 +376,7 @@ static int macb_mdio_read_c45(struct mii_bus *bus, int mii_id, int devad,
               | MACB_BF(DATA, regnum & 0xFFFF)
               | MACB_BF(CODE, MACB_MAN_C45_CODE)));

-  status = macb_mdio_wait_for_idle(bp);
+  status = macb_mdio_wait_for_idle_busy(bp);
   if (status < 0)
      goto mdio_read_exit;

@@ -372,7 +386,7 @@ static int macb_mdio_read_c45(struct mii_bus *bus, int mii_id, int devad,
               | MACB_BF(REGA, devad & 0x1F)
               | MACB_BF(CODE, MACB_MAN_C45_CODE)));

-  status = macb_mdio_wait_for_idle(bp);
+  status = macb_mdio_wait_for_idle_busy(bp);
   if (status < 0)
      goto mdio_read_exit;

@@ -405,7 +419,7 @@ static int macb_mdio_write_c22(struct mii_bus *bus, int mii_id, int regnum,
               | MACB_BF(CODE, MACB_MAN_C22_CODE)
               | MACB_BF(DATA, value)));

-  status = macb_mdio_wait_for_idle(bp);
+  status = macb_mdio_wait_for_idle_busy(bp);
   if (status < 0)
      goto mdio_write_exit;

@@ -439,7 +453,7 @@ static int macb_mdio_write_c45(struct mii_bus *bus, int mii_id,
               | MACB_BF(DATA, regnum & 0xFFFF)
               | MACB_BF(CODE, MACB_MAN_C45_CODE)));

-  status = macb_mdio_wait_for_idle(bp);
+  status = macb_mdio_wait_for_idle_busy(bp);
   if (status < 0)
      goto mdio_write_exit;

@@ -450,7 +464,7 @@ static int macb_mdio_write_c45(struct mii_bus *bus, int mii_id,
               | MACB_BF(CODE, MACB_MAN_C45_CODE)
               | MACB_BF(DATA, value)));

-  status = macb_mdio_wait_for_idle(bp);
+  status = macb_mdio_wait_for_idle_busy(bp);
   if (status < 0)
      goto mdio_write_exit;




Thanks,

--
Théo Lebrun, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com


  reply	other threads:[~2026-08-05 14:53 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-05 12:01 [PATCH] net: macb: fix stale data returned by MDIO reads Polak, Leszek
2026-08-05 14:45 ` Théo Lebrun
2026-08-05 14:52   ` Théo Lebrun [this message]
2026-08-05 15:05     ` Théo Lebrun
2026-08-05 20:57       ` Andrew Lunn

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=DKH3G9J1MI8G.24Q6WY2SU93I9@bootlin.com \
    --to=theo.lebrun@bootlin.com \
    --cc=LPolak@arri.de \
    --cc=andrew+netdev@lunn.ch \
    --cc=conor.dooley@microchip.com \
    --cc=davem@davemloft.net \
    --cc=edumazet@google.com \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox