OSDN Git Service

net: phylink: fix ethtool -A with attached PHYs
authorRussell King <rmk+kernel@armlinux.org.uk>
Tue, 23 Jun 2020 16:47:23 +0000 (17:47 +0100)
committerDavid S. Miller <davem@davemloft.net>
Wed, 24 Jun 2020 03:53:28 +0000 (20:53 -0700)
Fix a phylink's ethtool set_pauseparam support deadlock caused by phylib
interacting with phylink: we must not hold the state lock while calling
phylib functions that may call into phylink_phy_change().

Fixes: f904f15ea9b5 ("net: phylink: allow ethtool -A to change flow control advertisement")
Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/phy/phylink.c

index 0ab65fb..453f9a3 100644 (file)
@@ -1502,18 +1502,20 @@ int phylink_ethtool_set_pauseparam(struct phylink *pl,
        linkmode_set_pause(config->advertising, pause->tx_pause,
                           pause->rx_pause);
 
-       /* If we have a PHY, phylib will call our link state function if the
-        * mode has changed, which will trigger a resolve and update the MAC
-        * configuration.
+       if (!pl->phydev && !test_bit(PHYLINK_DISABLE_STOPPED,
+                                    &pl->phylink_disable_state))
+               phylink_pcs_config(pl, true, &pl->link_config);
+
+       mutex_unlock(&pl->state_mutex);
+
+       /* If we have a PHY, a change of the pause frame advertisement will
+        * cause phylib to renegotiate (if AN is enabled) which will in turn
+        * call our phylink_phy_change() and trigger a resolve.  Note that
+        * we can't hold our state mutex while calling phy_set_asym_pause().
         */
-       if (pl->phydev) {
+       if (pl->phydev)
                phy_set_asym_pause(pl->phydev, pause->rx_pause,
                                   pause->tx_pause);
-       } else if (!test_bit(PHYLINK_DISABLE_STOPPED,
-                            &pl->phylink_disable_state)) {
-               phylink_pcs_config(pl, true, &pl->link_config);
-       }
-       mutex_unlock(&pl->state_mutex);
 
        return 0;
 }