Skip to content

Commit e2a10da

Browse files
vladimirolteankuba-moo
authored andcommitted
net: phy: transfer phy_config_inband() locking responsibility to phylink
Problem description =================== Lockdep reports a possible circular locking dependency (AB/BA) between &pl->state_mutex and &phy->lock, as follows. phylink_resolve() // acquires &pl->state_mutex -> phylink_major_config() -> phy_config_inband() // acquires &pl->phydev->lock whereas all the other call sites where &pl->state_mutex and &pl->phydev->lock have the locking scheme reversed. Everywhere else, &pl->phydev->lock is acquired at the top level, and &pl->state_mutex at the lower level. A clear example is phylink_bringup_phy(). The outlier is the newly introduced phy_config_inband() and the existing lock order is the correct one. To understand why it cannot be the other way around, it is sufficient to consider phylink_phy_change(), phylink's callback from the PHY device's phy->phy_link_change() virtual method, invoked by the PHY state machine. phy_link_up() and phy_link_down(), the (indirect) callers of phylink_phy_change(), are called with &phydev->lock acquired. Then phylink_phy_change() acquires its own &pl->state_mutex, to serialize changes made to its pl->phy_state and pl->link_config. So all other instances of &pl->state_mutex and &phydev->lock must be consistent with this order. Problem impact ============== I think the kernel runs a serious deadlock risk if an existing phylink_resolve() thread, which results in a phy_config_inband() call, is concurrent with a phy_link_up() or phy_link_down() call, which will deadlock on &pl->state_mutex in phylink_phy_change(). Practically speaking, the impact may be limited by the slow speed of the medium auto-negotiation protocol, which makes it unlikely for the current state to still be unresolved when a new one is detected, but I think the problem is there. Nonetheless, the problem was discovered using lockdep. Proposed solution ================= Practically speaking, the phy_config_inband() requirement of having phydev->lock acquired must transfer to the caller (phylink is the only caller). There, it must bubble up until immediately before &pl->state_mutex is acquired, for the cases where that takes place. Solution details, considerations, notes ======================================= This is the phy_config_inband() call graph: sfp_upstream_ops :: connect_phy() | v phylink_sfp_connect_phy() | v phylink_sfp_config_phy() | | sfp_upstream_ops :: module_insert() | | | v | phylink_sfp_module_insert() | | | | sfp_upstream_ops :: module_start() | | | | | v | | phylink_sfp_module_start() | | | | v v | phylink_sfp_config_optical() phylink_start() | | | phylink_resume() v v | | phylink_sfp_set_config() | | | v v v phylink_mac_initial_config() | phylink_resolve() | | phylink_ethtool_ksettings_set() v v v phylink_major_config() | v phy_config_inband() phylink_major_config() caller #1, phylink_mac_initial_config(), does not acquire &pl->state_mutex nor do its callers. It must acquire &pl->phydev->lock prior to calling phylink_major_config(). phylink_major_config() caller #2, phylink_resolve() acquires &pl->state_mutex, thus also needs to acquire &pl->phydev->lock. phylink_major_config() caller #3, phylink_ethtool_ksettings_set(), is completely uninteresting, because it only calls phylink_major_config() if pl->phydev is NULL (otherwise it calls phy_ethtool_ksettings_set()). We need to change nothing there. Other solutions =============== The lock inversion between &pl->state_mutex and &pl->phydev->lock has occurred at least once before, as seen in commit c718af2 ("net: phylink: fix ethtool -A with attached PHYs"). The solution there was to simply not call phy_set_asym_pause() under the &pl->state_mutex. That cannot be extended to our case though, where the phy_config_inband() call is much deeper inside the &pl->state_mutex section. Fixes: 5fd0f1a ("net: phylink: add negotiation of in-band capabilities") Signed-off-by: Vladimir Oltean <[email protected]> Reviewed-by: Russell King (Oracle) <[email protected]> Link: https://patch.msgid.link/[email protected] Signed-off-by: Jakub Kicinski <[email protected]>
1 parent 0ba5b2f commit e2a10da

File tree

2 files changed

+13
-8
lines changed

2 files changed

+13
-8
lines changed

drivers/net/phy/phy.c

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,23 +1065,19 @@ EXPORT_SYMBOL_GPL(phy_inband_caps);
10651065
*/
10661066
int phy_config_inband(struct phy_device *phydev, unsigned int modes)
10671067
{
1068-
int err;
1068+
lockdep_assert_held(&phydev->lock);
10691069

10701070
if (!!(modes & LINK_INBAND_DISABLE) +
10711071
!!(modes & LINK_INBAND_ENABLE) +
10721072
!!(modes & LINK_INBAND_BYPASS) != 1)
10731073
return -EINVAL;
10741074

1075-
mutex_lock(&phydev->lock);
10761075
if (!phydev->drv)
1077-
err = -EIO;
1076+
return -EIO;
10781077
else if (!phydev->drv->config_inband)
1079-
err = -EOPNOTSUPP;
1080-
else
1081-
err = phydev->drv->config_inband(phydev, modes);
1082-
mutex_unlock(&phydev->lock);
1078+
return -EOPNOTSUPP;
10831079

1084-
return err;
1080+
return phydev->drv->config_inband(phydev, modes);
10851081
}
10861082
EXPORT_SYMBOL(phy_config_inband);
10871083

drivers/net/phy/phylink.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1434,6 +1434,7 @@ static void phylink_get_fixed_state(struct phylink *pl,
14341434
static void phylink_mac_initial_config(struct phylink *pl, bool force_restart)
14351435
{
14361436
struct phylink_link_state link_state;
1437+
struct phy_device *phy = pl->phydev;
14371438

14381439
switch (pl->req_link_an_mode) {
14391440
case MLO_AN_PHY:
@@ -1457,7 +1458,11 @@ static void phylink_mac_initial_config(struct phylink *pl, bool force_restart)
14571458
link_state.link = false;
14581459

14591460
phylink_apply_manual_flow(pl, &link_state);
1461+
if (phy)
1462+
mutex_lock(&phy->lock);
14601463
phylink_major_config(pl, force_restart, &link_state);
1464+
if (phy)
1465+
mutex_unlock(&phy->lock);
14611466
}
14621467

14631468
static const char *phylink_pause_to_str(int pause)
@@ -1598,6 +1603,8 @@ static void phylink_resolve(struct work_struct *w)
15981603

15991604
mutex_lock(&pl->phydev_mutex);
16001605
phy = pl->phydev;
1606+
if (phy)
1607+
mutex_lock(&phy->lock);
16011608
mutex_lock(&pl->state_mutex);
16021609
cur_link_state = phylink_link_is_up(pl);
16031610

@@ -1699,6 +1706,8 @@ static void phylink_resolve(struct work_struct *w)
16991706
queue_work(system_power_efficient_wq, &pl->resolve);
17001707
}
17011708
mutex_unlock(&pl->state_mutex);
1709+
if (phy)
1710+
mutex_unlock(&phy->lock);
17021711
mutex_unlock(&pl->phydev_mutex);
17031712
}
17041713

0 commit comments

Comments
 (0)