mbox series

[net-next,0/2] net: dsa: realtek: fix PHY register read corruption

Message ID 20220216160500.2341255-1-alvin@pqrs.dk (mailing list archive)
Headers show
Series net: dsa: realtek: fix PHY register read corruption | expand

Message

Alvin Šipraga Feb. 16, 2022, 4:04 p.m. UTC
From: Alvin Šipraga <alsi@bang-olufsen.dk>

These two patches fix the issue reported by Arınç where PHY register
reads sometimes return garbage data.

MAINTAINERS: Please can you help me with the targetting of these two
patches? This bug is present ca. 5.16, when the SMI version of the
rtl8365mb driver was introduced. But now in net-next we have the MDIO
interface from Luiz, where the issue is also present. I am sending what
I think is an ideal patch series, but should I split it up and send the
SMI-related changes to net and the MDIO changes to net-next? If so, how
would I go about splitting it while preventing merge conflicts and build
errors?

For now I am sending it to net-next so that the whole thing can be
reviewed. If it's applied, I would gladly backport the fix to the stable
tree for 5.16, but I am still confused about what to do for 5.17.

Thanks for your help.


Alvin Šipraga (2):
  net: dsa: realtek: allow subdrivers to externally lock regmap
  net: dsa: realtek: rtl8365mb: serialize indirect PHY register access

 drivers/net/dsa/realtek/realtek-mdio.c | 46 +++++++++++++++++++++-
 drivers/net/dsa/realtek/realtek-smi.c  | 48 +++++++++++++++++++++--
 drivers/net/dsa/realtek/realtek.h      |  2 +
 drivers/net/dsa/realtek/rtl8365mb.c    | 54 ++++++++++++++++----------
 4 files changed, 124 insertions(+), 26 deletions(-)

Comments

Luiz Angelo Daros de Luca Feb. 16, 2022, 5:57 p.m. UTC | #1
> These two patches fix the issue reported by Arınç where PHY register
> reads sometimes return garbage data.
>
> MAINTAINERS: Please can you help me with the targetting of these two
> patches? This bug is present ca. 5.16, when the SMI version of the
> rtl8365mb driver was introduced. But now in net-next we have the MDIO
> interface from Luiz, where the issue is also present. I am sending what
> I think is an ideal patch series, but should I split it up and send the
> SMI-related changes to net and the MDIO changes to net-next? If so, how
> would I go about splitting it while preventing merge conflicts and build
> errors?
>
> For now I am sending it to net-next so that the whole thing can be
> reviewed. If it's applied, I would gladly backport the fix to the stable
> tree for 5.16, but I am still confused about what to do for 5.17.
>
> Thanks for your help.
>
>
> Alvin Šipraga (2):
>   net: dsa: realtek: allow subdrivers to externally lock regmap
>   net: dsa: realtek: rtl8365mb: serialize indirect PHY register access
>
>  drivers/net/dsa/realtek/realtek-mdio.c | 46 +++++++++++++++++++++-
>  drivers/net/dsa/realtek/realtek-smi.c  | 48 +++++++++++++++++++++--
>  drivers/net/dsa/realtek/realtek.h      |  2 +
>  drivers/net/dsa/realtek/rtl8365mb.c    | 54 ++++++++++++++++----------
>  4 files changed, 124 insertions(+), 26 deletions(-)
>
> --
> 2.35.0
>

Thanks for the fix, Alvin.

I still feel like we are trying to go around a regmap limitation
instead of fixing it there. If we control regmap lock (we can define a
custom lock/unlock) and create new regmap_{read,write}_nolock
variants, we'll just need to lock the regmap, do whatever you need,
and unlock it.

BTW, I believe that, for realtek-mdio, a regmap custom lock mechanism
could simply use mdio lock while realtek-smi already has priv->lock.

Regards,
Alvin Šipraga Feb. 16, 2022, 6:23 p.m. UTC | #2
Luiz Angelo Daros de Luca <luizluca@gmail.com> writes:

>> These two patches fix the issue reported by Arınç where PHY register
>> reads sometimes return garbage data.
>>
>> MAINTAINERS: Please can you help me with the targetting of these two
>> patches? This bug is present ca. 5.16, when the SMI version of the
>> rtl8365mb driver was introduced. But now in net-next we have the MDIO
>> interface from Luiz, where the issue is also present. I am sending what
>> I think is an ideal patch series, but should I split it up and send the
>> SMI-related changes to net and the MDIO changes to net-next? If so, how
>> would I go about splitting it while preventing merge conflicts and build
>> errors?
>>
>> For now I am sending it to net-next so that the whole thing can be
>> reviewed. If it's applied, I would gladly backport the fix to the stable
>> tree for 5.16, but I am still confused about what to do for 5.17.
>>
>> Thanks for your help.
>>
>>
>> Alvin Šipraga (2):
>>   net: dsa: realtek: allow subdrivers to externally lock regmap
>>   net: dsa: realtek: rtl8365mb: serialize indirect PHY register access
>>
>>  drivers/net/dsa/realtek/realtek-mdio.c | 46 +++++++++++++++++++++-
>>  drivers/net/dsa/realtek/realtek-smi.c  | 48 +++++++++++++++++++++--
>>  drivers/net/dsa/realtek/realtek.h      |  2 +
>>  drivers/net/dsa/realtek/rtl8365mb.c    | 54 ++++++++++++++++----------
>>  4 files changed, 124 insertions(+), 26 deletions(-)
>>
>> --
>> 2.35.0
>>
>
> Thanks for the fix, Alvin.
>
> I still feel like we are trying to go around a regmap limitation
> instead of fixing it there. If we control regmap lock (we can define a
> custom lock/unlock) and create new regmap_{read,write}_nolock
> variants, we'll just need to lock the regmap, do whatever you need,
> and unlock it.

Can you show me what those regmap_{read,write}_nolock variants would
look like in your example? And what about the other regmap_ APIs we use,
like regmap_read_poll_timeout, regmap_update_bits, etc. - do you propose
to reimplement all of these?

>
> BTW, I believe that, for realtek-mdio, a regmap custom lock mechanism
> could simply use mdio lock while realtek-smi already has priv->lock.

Hmm OK. Actually I'm a bit confused about the mdio_lock: can you explain
what it's guarding against, for someone unfamiliar with MDIO? Currently
realtek-mdio's regmap has an additional lock around it (disable_locking
is 0), so with these patches applied the number of locks remains the
same.

priv->lock is a spinlock which is inappropriate here. I'm not really
sure what the point of it is, besides to handle unlocked calls to the
_noack function. It might be removable altogether but I would prefer not
to touch it for this series.

Kind regards,
Alvin
Andrew Lunn Feb. 16, 2022, 7:11 p.m. UTC | #3
> Hmm OK. Actually I'm a bit confused about the mdio_lock: can you explain
> what it's guarding against, for someone unfamiliar with MDIO?

The more normal use case for MDIO is for PHYs, not switches. There can
be multiple PHYs on one MDIO bus. And these PHYs each have there own
state machine in phylib. At any point in time, that state machine can
request the driver to do something, like poll the PHY status, does it
have link? To prevent two PHY drivers trying to use the MDIO bus at
the same time, there is an MDIO lock. At the beginning of an MDIO
transaction, the lock is taken. And the end of the transaction,
reading or writing one register of a device on the bus, the lock is
released.

So the MDIO lock simply ensures there is only one user of the MDIO bus
at one time, for a single read or write.

For PHYs this is sufficient. For switches, sometimes you need
additional protection. The granularity of an access might not be a
single register read or a write. It could be you need to read or write
a few registers in an atomic way. If that is the case, you need a lock
at a higher level.

   Andrew
Alvin Šipraga Feb. 16, 2022, 7:26 p.m. UTC | #4
Andrew Lunn <andrew@lunn.ch> writes:
>> Hmm OK. Actually I'm a bit confused about the mdio_lock: can you explain
>> what it's guarding against, for someone unfamiliar with MDIO?
>
> The more normal use case for MDIO is for PHYs, not switches. There can
> be multiple PHYs on one MDIO bus. And these PHYs each have there own
> state machine in phylib. At any point in time, that state machine can
> request the driver to do something, like poll the PHY status, does it
> have link? To prevent two PHY drivers trying to use the MDIO bus at
> the same time, there is an MDIO lock. At the beginning of an MDIO
> transaction, the lock is taken. And the end of the transaction,
> reading or writing one register of a device on the bus, the lock is
> released.
>
> So the MDIO lock simply ensures there is only one user of the MDIO bus
> at one time, for a single read or write.
>
> For PHYs this is sufficient. For switches, sometimes you need
> additional protection. The granularity of an access might not be a
> single register read or a write. It could be you need to read or write
> a few registers in an atomic way. If that is the case, you need a lock
> at a higher level.

Thank you Andrew for the clear explanation.

Somewhat unrelated to this series, but are you able to explain to me the
difference between:

	mutex_lock(&bus->mdio_lock);
and
	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);

While looking at other driver examples I noticed the latter form quite a
few times too.

Kind regards,
Alvin
Luiz Angelo Daros de Luca Feb. 17, 2022, 4:28 a.m. UTC | #5
> > I still feel like we are trying to go around a regmap limitation
> > instead of fixing it there. If we control regmap lock (we can define a
> > custom lock/unlock) and create new regmap_{read,write}_nolock
> > variants, we'll just need to lock the regmap, do whatever you need,
> > and unlock it.
>
> Can you show me what those regmap_{read,write}_nolock variants would
> look like in your example? And what about the other regmap_ APIs we use,
> like regmap_read_poll_timeout, regmap_update_bits, etc. - do you propose
> to reimplement all of these?

The option of having two regmaps is a nice way to have "_nolock"
variants for free. It is much cleaner than any solutions I imagined!
Ayway, I don't believe the regmap API expects to have an evil
non-locked clone. It looks like it is being abused.

What regmap API misses is a way to create a "transaction". Mdio, for
example, expects the user to lock the bus before doing a series of
accesses while regmap api assumes a single atomic access is enough.
However, Realtek indirect register access shows that it is not enough.
We could reimplement a mutex for every case where two calls might
share the same register (or indirectly affect others like we saw with
Realtek) but I believe a shared solution would be better, even if it
costs a couple more wrap functions.

It would be even nicer if we have a regmap "manual lock" mode that
will expose the lock/unlock functions but it will never call them by
itself. It would work if it could check if the caller is actually the
same thread/context that locked it. However I doubt there is a clean
solution in a kernel code that can check if the lock was acquired by
the same context that is calling the read.


> > BTW, I believe that, for realtek-mdio, a regmap custom lock mechanism
> > could simply use mdio lock while realtek-smi already has priv->lock.
>
> Hmm OK. Actually I'm a bit confused about the mdio_lock: can you explain
> what it's guarding against, for someone unfamiliar with MDIO? Currently
> realtek-mdio's regmap has an additional lock around it (disable_locking
> is 0), so with these patches applied the number of locks remains the
> same.

Today we already have to redundants locks (mdio and regmap). Your
patch is just replacing the regmap lock.

regmap_read is something like this:

regmap_read
    lock regmap
    realtek_mdio_read()
        lock mdio
        ...
        unlock mdio
   unlock regmap

If you are implementing a custom lock, simply use mdio lock directly.

And the map_nolock you created does not mean "access without locks"
but "you must lock it yourself before using anything here". If that
lock is actually mdio_lock, it would be ok to remove the lock inside
realtek_mdio_{read,write}. You just need a reference to those
lock/unlock functions in realtek_priv.

> priv->lock is a spinlock which is inappropriate here. I'm not really
> sure what the point of it is, besides to handle unlocked calls to the
> _noack function. It might be removable altogether but I would prefer not
> to touch it for this series.

If spinlock is inappropriate, it can be easily converted to a mutex.
Everything else from realtek-mdio might apply.

> Kind regards,
> Alvin
Alvin Šipraga Feb. 17, 2022, 7:53 a.m. UTC | #6
Luiz Angelo Daros de Luca <luizluca@gmail.com> writes:

>> > I still feel like we are trying to go around a regmap limitation
>> > instead of fixing it there. If we control regmap lock (we can define a
>> > custom lock/unlock) and create new regmap_{read,write}_nolock
>> > variants, we'll just need to lock the regmap, do whatever you need,
>> > and unlock it.
>>
>> Can you show me what those regmap_{read,write}_nolock variants would
>> look like in your example? And what about the other regmap_ APIs we use,
>> like regmap_read_poll_timeout, regmap_update_bits, etc. - do you propose
>> to reimplement all of these?
>
> The option of having two regmaps is a nice way to have "_nolock"
> variants for free. It is much cleaner than any solutions I imagined!
> Ayway, I don't believe the regmap API expects to have an evil
> non-locked clone. It looks like it is being abused.
>
> What regmap API misses is a way to create a "transaction". Mdio, for
> example, expects the user to lock the bus before doing a series of
> accesses while regmap api assumes a single atomic access is enough.
> However, Realtek indirect register access shows that it is not enough.
> We could reimplement a mutex for every case where two calls might
> share the same register (or indirectly affect others like we saw with
> Realtek) but I believe a shared solution would be better, even if it
> costs a couple more wrap functions.
>
> It would be even nicer if we have a regmap "manual lock" mode that
> will expose the lock/unlock functions but it will never call them by
> itself. It would work if it could check if the caller is actually the
> same thread/context that locked it. However I doubt there is a clean
> solution in a kernel code that can check if the lock was acquired by
> the same context that is calling the read.

I went through all of this while preparing the patch, so your arguments
are familiar to me ;-)

What I sent was the cleanest solution I could eventually think of. I
don't think it is foul play, but I agree it is a bit funny to have this
kind of "shadow regmap". However, the interface is quite safe, and as I
implied in the commit message, quite foolproof as well.

Basically, rather than reimplementing every regmap API that I want to
use while manually taking the lock, I just use another regmap with
locking disabled. It boils down to exactly the same thing.

>
>
>> > BTW, I believe that, for realtek-mdio, a regmap custom lock mechanism
>> > could simply use mdio lock while realtek-smi already has priv->lock.
>>
>> Hmm OK. Actually I'm a bit confused about the mdio_lock: can you explain
>> what it's guarding against, for someone unfamiliar with MDIO? Currently
>> realtek-mdio's regmap has an additional lock around it (disable_locking
>> is 0), so with these patches applied the number of locks remains the
>> same.
>
> Today we already have to redundants locks (mdio and regmap). Your
> patch is just replacing the regmap lock.

Is that so? Andrew seems to imply that you shouldn't be using the
mdio_lock like this, but only for per-register access, and then
implement your own higher level lock:

> For PHYs this is sufficient. For switches, sometimes you need
> additional protection. The granularity of an access might not be a
> single register read or a write. It could be you need to read or write
> a few registers in an atomic way. If that is the case, you need a lock
> at a higher level.

It seems to me like you should have used mdiobus_{read,write} or even
mdiobus_{read,write}_nested? Although the state of the art in other DSA
drivers seems like a mixed bag, so I don't know.

Since I do not have an MDIO switch in front of me to test with, and
since the existing MDIO code looks a little suspect, again I would
prefer to stick in my lane and just fix the problem without
refactoring.

>
> regmap_read is something like this:
>
> regmap_read
>     lock regmap
>     realtek_mdio_read()
>         lock mdio
>         ...
>         unlock mdio
>    unlock regmap
>
> If you are implementing a custom lock, simply use mdio lock directly.
>
> And the map_nolock you created does not mean "access without locks"
> but "you must lock it yourself before using anything here". If that
> lock is actually mdio_lock, it would be ok to remove the lock inside
> realtek_mdio_{read,write}. You just need a reference to those
> lock/unlock functions in realtek_priv.
>
>> priv->lock is a spinlock which is inappropriate here. I'm not really
>> sure what the point of it is, besides to handle unlocked calls to the
>> _noack function. It might be removable altogether but I would prefer not
>> to touch it for this series.
>
> If spinlock is inappropriate, it can be easily converted to a mutex.
> Everything else from realtek-mdio might apply.

Well, this is a bugfix series, not a refactoring. I am not adding more
locks than were here before. If I start touching old code (this spinlock
predates my engagement with this driver), I will have to answer to that
in the commit message too. If we want to do this, let's do it after the
bugfix has been reviewed and merged. It will be easier to justify as
well.

Kind regards,
Alvin

>
>> Kind regards,
>> Alvin
Andrew Lunn Feb. 17, 2022, 12:12 p.m. UTC | #7
> Thank you Andrew for the clear explanation.
> 
> Somewhat unrelated to this series, but are you able to explain to me the
> difference between:
> 
> 	mutex_lock(&bus->mdio_lock);
> and
> 	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
> 
> While looking at other driver examples I noticed the latter form quite a
> few times too.

This is to do with the debug code for checking for deadlocks,
CONFIG_PROVE_LOCKING. When that feature is enables, each lock/unlock
of a mutex is tracked, and a list is made of what other locks are also
taken, and the order. The code can find deadlocks where one thread
takes A then B, while another thread takes B and then A. It can also
detect when a thread takes lock A and then tries to take lock A again.

Rather than track each individual mutex, it uses classes of mutex. So
bus->mdio_lock is a class of mutex. The code simply tracks that a
bus->mdio_lock has been taken, not a specific bus->mdio_lock. That is
generally sufficient, but not always. The mv88e6xxx switch is like
many switches, accessed over MDIO. But the mv88e6xxx switch offers an
MDIO bus, and there is an MDIO bus driver inside the mv88e6xxx
driver. So you have nested MDIO calls. So this debug code seems the
same class of mutex being taken twice, and thinks it is a
deadlock. You can tell it that nested MDIO calls are actually O.K, it
won't deadlock.

      Andrew
Alvin Šipraga Feb. 17, 2022, 1:09 p.m. UTC | #8
Andrew Lunn <andrew@lunn.ch> writes:

>> Thank you Andrew for the clear explanation.
>> 
>> Somewhat unrelated to this series, but are you able to explain to me the
>> difference between:
>> 
>> 	mutex_lock(&bus->mdio_lock);
>> and
>> 	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
>> 
>> While looking at other driver examples I noticed the latter form quite a
>> few times too.
>
> This is to do with the debug code for checking for deadlocks,
> CONFIG_PROVE_LOCKING. When that feature is enables, each lock/unlock
> of a mutex is tracked, and a list is made of what other locks are also
> taken, and the order. The code can find deadlocks where one thread
> takes A then B, while another thread takes B and then A. It can also
> detect when a thread takes lock A and then tries to take lock A again.
>
> Rather than track each individual mutex, it uses classes of mutex. So
> bus->mdio_lock is a class of mutex. The code simply tracks that a
> bus->mdio_lock has been taken, not a specific bus->mdio_lock. That is
> generally sufficient, but not always. The mv88e6xxx switch is like
> many switches, accessed over MDIO. But the mv88e6xxx switch offers an
> MDIO bus, and there is an MDIO bus driver inside the mv88e6xxx
> driver. So you have nested MDIO calls. So this debug code seems the
> same class of mutex being taken twice, and thinks it is a
> deadlock. You can tell it that nested MDIO calls are actually O.K, it
> won't deadlock.

Thanks for the explanation, the missing piece of the puzzle was the fact
that some switch drivers expose an additional MDIO bus. I can understand
the CONFIG_PROVE_LOCKING rationale.

If you have the patience to answer a few more questions:

1. You mentioned in an earlier mail that the mdio_lock is used mostly by
PHY drivers to synchronize their access to the MDIO bus, for a single
read or write. You also mentioned that for switches which have a more
involved access pattern (for instance to access switch management
registers), a higher lock is required. In realtek-mdio this is the case:
we do a couple of reads and writes over the MDIO bus to access the
switch registers. Moreover, the mdio_lock is held for the duration of
these MDIO bus reads/writes. Do you mean to say that one should rather
take a higher-level lock and only lock/unlock the mdio_lock on a
per-read or per-write basis? Put another way, should this:

static int realtek_mdio_read(void *ctx, u32 reg, u32 *val)
{
	/* ... */
        
	mutex_lock(&bus->mdio_lock);

	bus->write(bus, priv->mdio_addr, ...);
	bus->write(bus, priv->mdio_addr, ...);
	bus->write(bus, priv->mdio_addr, ...);
	bus->read(bus, priv->mdio_addr, ...);

	/* ... */

	mutex_unlock(&bus->mdio_lock);

	return ret;
}

rather look like this?:

static int realtek_mdio_read(void *ctx, u32 reg, u32 *val)
{
	/* ... */
        
	mutex_lock(&my_realtek_driver_lock); /* synchronize concurrent realtek_mdio_{read,write} */

	mdiobus_write(bus, priv->mdio_addr, ...); /* mdio_lock locked/unlocked here */
	mdiobus_write(bus, priv->mdio_addr, ...); /* ditto */
	mdiobus_write(bus, priv->mdio_addr, ...); /* ditto */
	mdiobus_read(bus, priv->mdio_addr, ...);  /* ditto */

	/* ... */

	mutex_unlock(&my_realtek_driver_lock);

	return ret;
}


2. Is the nested locking only relevant for DSA switches which offer
another MDIO bus? Or should all switch drivers do this, on the basis
that, feasibly, one could connect my Realtek switch to the MDIO bus of a
mv88e6xxx switch? In that case, and assuming the latter form of
raeltek_mdio_read above, should one use the mdiobus_{read,write}_nested
functions instead?

Kind regards,
Alvin
Andrew Lunn Feb. 17, 2022, 1:32 p.m. UTC | #9
> If you have the patience to answer a few more questions:
> 
> 1. You mentioned in an earlier mail that the mdio_lock is used mostly by
> PHY drivers to synchronize their access to the MDIO bus, for a single
> read or write. You also mentioned that for switches which have a more
> involved access pattern (for instance to access switch management
> registers), a higher lock is required. In realtek-mdio this is the case:
> we do a couple of reads and writes over the MDIO bus to access the
> switch registers. Moreover, the mdio_lock is held for the duration of
> these MDIO bus reads/writes. Do you mean to say that one should rather
> take a higher-level lock and only lock/unlock the mdio_lock on a
> per-read or per-write basis? Put another way, should this:
> 
> static int realtek_mdio_read(void *ctx, u32 reg, u32 *val)
> {
> 	/* ... */
>         
> 	mutex_lock(&bus->mdio_lock);
> 
> 	bus->write(bus, priv->mdio_addr, ...);

It would be better to use __mdiobus_write()

> 	bus->write(bus, priv->mdio_addr, ...);
> 	bus->write(bus, priv->mdio_addr, ...);
> 	bus->read(bus, priv->mdio_addr, ...);

__mdiobus_read()

> 	/* ... */
> 
> 	mutex_unlock(&bus->mdio_lock);
> 
> 	return ret;
> }

You can do this.


> rather look like this?:
> 
> static int realtek_mdio_read(void *ctx, u32 reg, u32 *val)
> {
> 	/* ... */
>         
> 	mutex_lock(&my_realtek_driver_lock); /* synchronize concurrent realtek_mdio_{read,write} */
> 
> 	mdiobus_write(bus, priv->mdio_addr, ...); /* mdio_lock locked/unlocked here */
> 	mdiobus_write(bus, priv->mdio_addr, ...); /* ditto */
> 	mdiobus_write(bus, priv->mdio_addr, ...); /* ditto */
> 	mdiobus_read(bus, priv->mdio_addr, ...);  /* ditto */
> 
> 	/* ... */
> 
> 	mutex_unlock(&my_realtek_driver_lock);
> 
> 	return ret;
> }

This would also work. The advantage of this is when you have multiple
switches on one MDIO bus, you can allow parallel operations on those
switches. Also, if there are PHYs on the MDIO bus as well as the
switch, the PHYs can be accessed as well. If you are only doing 3
writes and read, it probably does not matter. If you are going to do a
lot of accesses, maybe read all the MIB values, allowing access to the
PHYs at the same time would be nice.

> 2. Is the nested locking only relevant for DSA switches which offer
> another MDIO bus? Or should all switch drivers do this, on the basis
> that, feasibly, one could connect my Realtek switch to the MDIO bus of a
> mv88e6xxx switch? In that case, and assuming the latter form of
> raeltek_mdio_read above, should one use the mdiobus_{read,write}_nested
> functions instead?

I would suggest you start with plain mdiobus_{read,write}. Using the
_nested could potentially hide a deadlock. If somebody does build
hardware with this sort of chaining, we can change to the _nested
calls.

	Andrew