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 17:05:22 +0200 [thread overview]
Message-ID: <DKH3PSLM2LJ8.1S1B51A95T75M@bootlin.com> (raw)
In-Reply-To: <DKH3G9J1MI8G.24Q6WY2SU93I9@bootlin.com>
On Wed Aug 5, 2026 at 4:52 PM CEST, Théo Lebrun wrote:
> 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.
One more thing to add, sorry for the stuttering.
A proper solution would use the IRQ as detection source rather than
sleep-polling. See BIT 0, called MFD in driver.
>>
>> ---
>>
>> 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
next prev parent reply other threads:[~2026-08-05 15:05 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
2026-08-05 15:05 ` Théo Lebrun [this message]
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=DKH3PSLM2LJ8.1S1B51A95T75M@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