diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index f29cd4d7aca2..7641f1f41e39 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -28,6 +28,7 @@ enum { PHYLINK_DISABLE_STOPPED, PHYLINK_DISABLE_LINK, PHYLINK_DISABLE_MAC_WOL, + PHYLINK_DISABLE_REPLAY, PCS_STATE_DOWN = 0, PCS_STATE_STARTING, @@ -77,6 +78,7 @@ struct phylink { bool link_failed; bool suspend_link_up; + bool force_major_config; bool major_config_failed; bool mac_supports_eee_ops; bool mac_supports_eee; @@ -1684,9 +1686,10 @@ static void phylink_resolve(struct work_struct *w) if (pl->act_link_an_mode != MLO_AN_FIXED) phylink_apply_manual_flow(pl, &link_state); - if (mac_config && link_state.interface != pl->link_config.interface) { - /* The interface has changed, so force the link down and then - * reconfigure. + if ((mac_config && link_state.interface != pl->link_config.interface) || + pl->force_major_config) { + /* The interface has changed or a forced major configuration + * was requested, so force the link down and then reconfigure. */ if (cur_link_state) { phylink_link_down(pl); @@ -1694,6 +1697,7 @@ static void phylink_resolve(struct work_struct *w) } phylink_major_config(pl, false, &link_state); pl->link_config.interface = link_state.interface; + pl->force_major_config = false; } /* If configuration of the interface failed, force the link down @@ -4357,6 +4361,57 @@ void phylink_mii_c45_pcs_get_state(struct mdio_device *pcs, } EXPORT_SYMBOL_GPL(phylink_mii_c45_pcs_get_state); +/** + * phylink_replay_link_begin() - begin replay of link callbacks for driver + * which loses state + * @pl: a pointer to a &struct phylink returned from phylink_create() + * + * Helper for MAC drivers which may perform a destructive reset at runtime. + * Both the own driver's mac_link_down() method is called, as well as the + * pcs_link_down() method of the split PCS (if any). + * + * This is similar to phylink_stop(), except it does not alter the state of + * the phylib PHY (it is assumed that it is not affected by the MAC destructive + * reset). + */ +void phylink_replay_link_begin(struct phylink *pl) +{ + ASSERT_RTNL(); + + phylink_run_resolve_and_disable(pl, PHYLINK_DISABLE_REPLAY); +} +EXPORT_SYMBOL_GPL(phylink_replay_link_begin); + +/** + * phylink_replay_link_end() - end replay of link callbacks for driver + * which lost state + * @pl: a pointer to a &struct phylink returned from phylink_create() + * + * Helper for MAC drivers which may perform a destructive reset at runtime. + * Both the own driver's mac_config() and mac_link_up() methods, as well as the + * pcs_config() and pcs_link_up() method of the split PCS (if any), are called. + * + * This is similar to phylink_start(), except it does not alter the state of + * the phylib PHY. + * + * One must call this method only within the same rtnl_lock() critical section + * as a previous phylink_replay_link_start(). + */ +void phylink_replay_link_end(struct phylink *pl) +{ + ASSERT_RTNL(); + + if (WARN(!test_bit(PHYLINK_DISABLE_REPLAY, + &pl->phylink_disable_state), + "phylink_replay_link_end() called without a prior phylink_replay_link_begin()\n")) + return; + + pl->force_major_config = true; + phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_REPLAY); + flush_work(&pl->resolve); +} +EXPORT_SYMBOL_GPL(phylink_replay_link_end); + static int __init phylink_init(void) { for (int i = 0; i < ARRAY_SIZE(phylink_sfp_interface_preference); ++i) diff --git a/include/linux/phylink.h b/include/linux/phylink.h index 20996f5778d1..2bc0db3d52ac 100644 --- a/include/linux/phylink.h +++ b/include/linux/phylink.h @@ -837,4 +837,9 @@ void phylink_mii_c45_pcs_get_state(struct mdio_device *pcs, void phylink_decode_usxgmii_word(struct phylink_link_state *state, uint16_t lpa); + +void phylink_replay_link_begin(struct phylink *pl); + +void phylink_replay_link_end(struct phylink *pl); + #endif