All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH net] net: dsa: mv88e6xxx: avoid reg_lock deadlock in mv88e6xxx_setup_port()
@ 2022-12-14 11:01 Vladimir Oltean
  2022-12-14 11:39 ` Maxim Kiselev
  2022-12-15 15:00 ` patchwork-bot+netdevbpf
  0 siblings, 2 replies; 3+ messages in thread
From: Vladimir Oltean @ 2022-12-14 11:01 UTC (permalink / raw)
  To: netdev
  Cc: Andrew Lunn, Florian Fainelli, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Marcin Wojtas, Maksim Kiselev,
	Maxim Kochetkov, Russell King, Marek Behún

In the blamed commit, it was not noticed that one implementation of
chip->info->ops->phylink_get_caps(), called by mv88e6xxx_get_caps(),
may access hardware registers, and in doing so, it takes the
mv88e6xxx_reg_lock(). Namely, this is mv88e6352_phylink_get_caps().

This is a problem because mv88e6xxx_get_caps(), apart from being
a top-level function (method invoked by dsa_switch_ops), is now also
directly called from mv88e6xxx_setup_port(), which runs under the
mv88e6xxx_reg_lock() taken by mv88e6xxx_setup(). Therefore, when running
on mv88e6352, the reg_lock would be acquired a second time and the
system would deadlock on driver probe.

The things that mv88e6xxx_setup() can compete with in terms of register
access with are the IRQ handlers and MDIO bus operations registered by
mv88e6xxx_probe(). So there is a real need to acquire the register lock.

The register lock can, in principle, be dropped and re-acquired pretty
much at will within the driver, as long as no operations that involve
waiting for indirect access to complete (essentially, callers of
mv88e6xxx_smi_direct_wait() and mv88e6xxx_wait_mask()) are interrupted
with the lock released. However, I would guess that in mv88e6xxx_setup(),
the critical section is kept open for such a long time just in order to
optimize away multiple lock/unlock operations on the registers.

We could, in principle, drop the reg_lock right before the
mv88e6xxx_setup_port() -> mv88e6xxx_get_caps() call, and
re-acquire it immediately afterwards. But this would look ugly, because
mv88e6xxx_setup_port() would release a lock which it didn't acquire, but
the caller did.

A cleaner solution to this issue comes from the observation that struct
mv88e6xxxx_ops methods generally assume they are called with the
reg_lock already acquired. Whereas mv88e6352_phylink_get_caps() is more
the exception rather than the norm, in that it acquires the lock itself.

Let's enforce the same locking pattern/convention for
chip->info->ops->phylink_get_caps() as well, and make
mv88e6xxx_get_caps(), the top-level function, acquire the register lock
explicitly, for this one implementation that will access registers for
port 4 to work properly.

This means that mv88e6xxx_setup_port() will no longer call the top-level
function, but the low-level mv88e6xxx_ops method which expects the
correct calling context (register lock held).

Compared to chip->info->ops->phylink_get_caps(), mv88e6xxx_get_caps()
also fixes up the supported_interfaces bitmap for internal ports, since
that can be done generically and does not require per-switch knowledge.
That's code which will no longer execute, however mv88e6xxx_setup_port()
doesn't need that. It just needs to look at the mac_capabilities bitmap.

Fixes: cc1049ccee20 ("net: dsa: mv88e6xxx: fix speed setting for CPU/DSA ports")
Reported-by: Maksim Kiselev <bigunclemax@gmail.com>
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
 drivers/net/dsa/mv88e6xxx/chip.c | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index ba4fff8690aa..242b8b325504 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -689,13 +689,12 @@ static void mv88e6352_phylink_get_caps(struct mv88e6xxx_chip *chip, int port,
 
 	/* Port 4 supports automedia if the serdes is associated with it. */
 	if (port == 4) {
-		mv88e6xxx_reg_lock(chip);
 		err = mv88e6352_g2_scratch_port_has_serdes(chip, port);
 		if (err < 0)
 			dev_err(chip->dev, "p%d: failed to read scratch\n",
 				port);
 		if (err <= 0)
-			goto unlock;
+			return;
 
 		cmode = mv88e6352_get_port4_serdes_cmode(chip);
 		if (cmode < 0)
@@ -703,8 +702,6 @@ static void mv88e6352_phylink_get_caps(struct mv88e6xxx_chip *chip, int port,
 				port);
 		else
 			mv88e6xxx_translate_cmode(cmode, supported);
-unlock:
-		mv88e6xxx_reg_unlock(chip);
 	}
 }
 
@@ -831,7 +828,9 @@ static void mv88e6xxx_get_caps(struct dsa_switch *ds, int port,
 {
 	struct mv88e6xxx_chip *chip = ds->priv;
 
+	mv88e6xxx_reg_lock(chip);
 	chip->info->ops->phylink_get_caps(chip, port, config);
+	mv88e6xxx_reg_unlock(chip);
 
 	if (mv88e6xxx_phy_is_internal(ds, port)) {
 		__set_bit(PHY_INTERFACE_MODE_INTERNAL,
@@ -3307,7 +3306,7 @@ static int mv88e6xxx_setup_port(struct mv88e6xxx_chip *chip, int port)
 		struct phylink_config pl_config = {};
 		unsigned long caps;
 
-		mv88e6xxx_get_caps(ds, port, &pl_config);
+		chip->info->ops->phylink_get_caps(chip, port, &pl_config);
 
 		caps = pl_config.mac_capabilities;
 
-- 
2.34.1


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

* Re: [PATCH net] net: dsa: mv88e6xxx: avoid reg_lock deadlock in mv88e6xxx_setup_port()
  2022-12-14 11:01 [PATCH net] net: dsa: mv88e6xxx: avoid reg_lock deadlock in mv88e6xxx_setup_port() Vladimir Oltean
@ 2022-12-14 11:39 ` Maxim Kiselev
  2022-12-15 15:00 ` patchwork-bot+netdevbpf
  1 sibling, 0 replies; 3+ messages in thread
From: Maxim Kiselev @ 2022-12-14 11:39 UTC (permalink / raw)
  To: Vladimir Oltean
  Cc: netdev, Andrew Lunn, Florian Fainelli, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Marcin Wojtas,
	Maxim Kochetkov, Russell King, Marek Behún

> Fixes: cc1049ccee20 ("net: dsa: mv88e6xxx: fix speed setting for CPU/DSA ports")
> Reported-by: Maksim Kiselev <bigunclemax@gmail.com>
> Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>

Tested-by: Maksim Kiselev <bigunclemax@gmail.com>

ср, 14 дек. 2022 г. в 14:01, Vladimir Oltean <vladimir.oltean@nxp.com>:
>
> In the blamed commit, it was not noticed that one implementation of
> chip->info->ops->phylink_get_caps(), called by mv88e6xxx_get_caps(),
> may access hardware registers, and in doing so, it takes the
> mv88e6xxx_reg_lock(). Namely, this is mv88e6352_phylink_get_caps().
>
> This is a problem because mv88e6xxx_get_caps(), apart from being
> a top-level function (method invoked by dsa_switch_ops), is now also
> directly called from mv88e6xxx_setup_port(), which runs under the
> mv88e6xxx_reg_lock() taken by mv88e6xxx_setup(). Therefore, when running
> on mv88e6352, the reg_lock would be acquired a second time and the
> system would deadlock on driver probe.
>
> The things that mv88e6xxx_setup() can compete with in terms of register
> access with are the IRQ handlers and MDIO bus operations registered by
> mv88e6xxx_probe(). So there is a real need to acquire the register lock.
>
> The register lock can, in principle, be dropped and re-acquired pretty
> much at will within the driver, as long as no operations that involve
> waiting for indirect access to complete (essentially, callers of
> mv88e6xxx_smi_direct_wait() and mv88e6xxx_wait_mask()) are interrupted
> with the lock released. However, I would guess that in mv88e6xxx_setup(),
> the critical section is kept open for such a long time just in order to
> optimize away multiple lock/unlock operations on the registers.
>
> We could, in principle, drop the reg_lock right before the
> mv88e6xxx_setup_port() -> mv88e6xxx_get_caps() call, and
> re-acquire it immediately afterwards. But this would look ugly, because
> mv88e6xxx_setup_port() would release a lock which it didn't acquire, but
> the caller did.
>
> A cleaner solution to this issue comes from the observation that struct
> mv88e6xxxx_ops methods generally assume they are called with the
> reg_lock already acquired. Whereas mv88e6352_phylink_get_caps() is more
> the exception rather than the norm, in that it acquires the lock itself.
>
> Let's enforce the same locking pattern/convention for
> chip->info->ops->phylink_get_caps() as well, and make
> mv88e6xxx_get_caps(), the top-level function, acquire the register lock
> explicitly, for this one implementation that will access registers for
> port 4 to work properly.
>
> This means that mv88e6xxx_setup_port() will no longer call the top-level
> function, but the low-level mv88e6xxx_ops method which expects the
> correct calling context (register lock held).
>
> Compared to chip->info->ops->phylink_get_caps(), mv88e6xxx_get_caps()
> also fixes up the supported_interfaces bitmap for internal ports, since
> that can be done generically and does not require per-switch knowledge.
> That's code which will no longer execute, however mv88e6xxx_setup_port()
> doesn't need that. It just needs to look at the mac_capabilities bitmap.
>
> Fixes: cc1049ccee20 ("net: dsa: mv88e6xxx: fix speed setting for CPU/DSA ports")
> Reported-by: Maksim Kiselev <bigunclemax@gmail.com>
> Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
> ---
>  drivers/net/dsa/mv88e6xxx/chip.c | 9 ++++-----
>  1 file changed, 4 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
> index ba4fff8690aa..242b8b325504 100644
> --- a/drivers/net/dsa/mv88e6xxx/chip.c
> +++ b/drivers/net/dsa/mv88e6xxx/chip.c
> @@ -689,13 +689,12 @@ static void mv88e6352_phylink_get_caps(struct mv88e6xxx_chip *chip, int port,
>
>         /* Port 4 supports automedia if the serdes is associated with it. */
>         if (port == 4) {
> -               mv88e6xxx_reg_lock(chip);
>                 err = mv88e6352_g2_scratch_port_has_serdes(chip, port);
>                 if (err < 0)
>                         dev_err(chip->dev, "p%d: failed to read scratch\n",
>                                 port);
>                 if (err <= 0)
> -                       goto unlock;
> +                       return;
>
>                 cmode = mv88e6352_get_port4_serdes_cmode(chip);
>                 if (cmode < 0)
> @@ -703,8 +702,6 @@ static void mv88e6352_phylink_get_caps(struct mv88e6xxx_chip *chip, int port,
>                                 port);
>                 else
>                         mv88e6xxx_translate_cmode(cmode, supported);
> -unlock:
> -               mv88e6xxx_reg_unlock(chip);
>         }
>  }
>
> @@ -831,7 +828,9 @@ static void mv88e6xxx_get_caps(struct dsa_switch *ds, int port,
>  {
>         struct mv88e6xxx_chip *chip = ds->priv;
>
> +       mv88e6xxx_reg_lock(chip);
>         chip->info->ops->phylink_get_caps(chip, port, config);
> +       mv88e6xxx_reg_unlock(chip);
>
>         if (mv88e6xxx_phy_is_internal(ds, port)) {
>                 __set_bit(PHY_INTERFACE_MODE_INTERNAL,
> @@ -3307,7 +3306,7 @@ static int mv88e6xxx_setup_port(struct mv88e6xxx_chip *chip, int port)
>                 struct phylink_config pl_config = {};
>                 unsigned long caps;
>
> -               mv88e6xxx_get_caps(ds, port, &pl_config);
> +               chip->info->ops->phylink_get_caps(chip, port, &pl_config);
>
>                 caps = pl_config.mac_capabilities;
>
> --
> 2.34.1
>

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

* Re: [PATCH net] net: dsa: mv88e6xxx: avoid reg_lock deadlock in mv88e6xxx_setup_port()
  2022-12-14 11:01 [PATCH net] net: dsa: mv88e6xxx: avoid reg_lock deadlock in mv88e6xxx_setup_port() Vladimir Oltean
  2022-12-14 11:39 ` Maxim Kiselev
@ 2022-12-15 15:00 ` patchwork-bot+netdevbpf
  1 sibling, 0 replies; 3+ messages in thread
From: patchwork-bot+netdevbpf @ 2022-12-15 15:00 UTC (permalink / raw)
  To: Vladimir Oltean
  Cc: netdev, andrew, f.fainelli, davem, edumazet, kuba, pabeni, mw,
	bigunclemax, fido_max, linux, kabel

Hello:

This patch was applied to netdev/net.git (master)
by Paolo Abeni <pabeni@redhat.com>:

On Wed, 14 Dec 2022 13:01:20 +0200 you wrote:
> In the blamed commit, it was not noticed that one implementation of
> chip->info->ops->phylink_get_caps(), called by mv88e6xxx_get_caps(),
> may access hardware registers, and in doing so, it takes the
> mv88e6xxx_reg_lock(). Namely, this is mv88e6352_phylink_get_caps().
> 
> This is a problem because mv88e6xxx_get_caps(), apart from being
> a top-level function (method invoked by dsa_switch_ops), is now also
> directly called from mv88e6xxx_setup_port(), which runs under the
> mv88e6xxx_reg_lock() taken by mv88e6xxx_setup(). Therefore, when running
> on mv88e6352, the reg_lock would be acquired a second time and the
> system would deadlock on driver probe.
> 
> [...]

Here is the summary with links:
  - [net] net: dsa: mv88e6xxx: avoid reg_lock deadlock in mv88e6xxx_setup_port()
    https://git.kernel.org/netdev/net/c/a7d82367daa6

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



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

end of thread, other threads:[~2022-12-15 15:00 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-12-14 11:01 [PATCH net] net: dsa: mv88e6xxx: avoid reg_lock deadlock in mv88e6xxx_setup_port() Vladimir Oltean
2022-12-14 11:39 ` Maxim Kiselev
2022-12-15 15:00 ` patchwork-bot+netdevbpf

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.