Skip to content

Commit a7d8236

Browse files
vladimirolteanPaolo Abeni
authored andcommitted
net: dsa: mv88e6xxx: avoid reg_lock deadlock in mv88e6xxx_setup_port()
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: cc1049c ("net: dsa: mv88e6xxx: fix speed setting for CPU/DSA ports") Reported-by: Maksim Kiselev <[email protected]> Signed-off-by: Vladimir Oltean <[email protected]> Tested-by: Maksim Kiselev <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Paolo Abeni <[email protected]>
1 parent c72a7e4 commit a7d8236

File tree

1 file changed

+4
-5
lines changed
  • drivers/net/dsa/mv88e6xxx

1 file changed

+4
-5
lines changed

drivers/net/dsa/mv88e6xxx/chip.c

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -689,22 +689,19 @@ static void mv88e6352_phylink_get_caps(struct mv88e6xxx_chip *chip, int port,
689689

690690
/* Port 4 supports automedia if the serdes is associated with it. */
691691
if (port == 4) {
692-
mv88e6xxx_reg_lock(chip);
693692
err = mv88e6352_g2_scratch_port_has_serdes(chip, port);
694693
if (err < 0)
695694
dev_err(chip->dev, "p%d: failed to read scratch\n",
696695
port);
697696
if (err <= 0)
698-
goto unlock;
697+
return;
699698

700699
cmode = mv88e6352_get_port4_serdes_cmode(chip);
701700
if (cmode < 0)
702701
dev_err(chip->dev, "p%d: failed to read serdes cmode\n",
703702
port);
704703
else
705704
mv88e6xxx_translate_cmode(cmode, supported);
706-
unlock:
707-
mv88e6xxx_reg_unlock(chip);
708705
}
709706
}
710707

@@ -831,7 +828,9 @@ static void mv88e6xxx_get_caps(struct dsa_switch *ds, int port,
831828
{
832829
struct mv88e6xxx_chip *chip = ds->priv;
833830

831+
mv88e6xxx_reg_lock(chip);
834832
chip->info->ops->phylink_get_caps(chip, port, config);
833+
mv88e6xxx_reg_unlock(chip);
835834

836835
if (mv88e6xxx_phy_is_internal(ds, port)) {
837836
__set_bit(PHY_INTERFACE_MODE_INTERNAL,
@@ -3307,7 +3306,7 @@ static int mv88e6xxx_setup_port(struct mv88e6xxx_chip *chip, int port)
33073306
struct phylink_config pl_config = {};
33083307
unsigned long caps;
33093308

3310-
mv88e6xxx_get_caps(ds, port, &pl_config);
3309+
chip->info->ops->phylink_get_caps(chip, port, &pl_config);
33113310

33123311
caps = pl_config.mac_capabilities;
33133312

0 commit comments

Comments
 (0)