diff options
Diffstat (limited to 'drivers/net/phy/phylink.c')
-rw-r--r-- | drivers/net/phy/phylink.c | 557 |
1 files changed, 309 insertions, 248 deletions
diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 8a3728d423e7..b905a6b93d80 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -56,7 +56,8 @@ struct phylink { struct phy_device *phydev; phy_interface_t link_interface; /* PHY_INTERFACE_xxx */ u8 cfg_link_an_mode; /* MLO_AN_xxx */ - u8 cur_link_an_mode; + u8 req_link_an_mode; /* Requested MLO_AN_xxx mode */ + u8 act_link_an_mode; /* Active MLO_AN_xxx mode */ u8 link_port; /* The current non-phy ethtool port */ __ETHTOOL_DECLARE_LINK_MODE_MASK(supported); @@ -85,7 +86,6 @@ struct phylink { bool sfp_may_have_phy; DECLARE_PHY_INTERFACE_MASK(sfp_interfaces); __ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_support); - u8 sfp_link_an_mode; u8 sfp_port; struct eee_config eee_cfg; @@ -179,6 +179,24 @@ static const char *phylink_an_mode_str(unsigned int mode) return mode < ARRAY_SIZE(modestr) ? modestr[mode] : "unknown"; } +static const char *phylink_pcs_mode_str(unsigned int mode) +{ + if (!mode) + return "none"; + + if (mode & PHYLINK_PCS_NEG_OUTBAND) + return "outband"; + + if (mode & PHYLINK_PCS_NEG_INBAND) { + if (mode & PHYLINK_PCS_NEG_ENABLED) + return "inband/an-enabled"; + else + return "inband/an-disabled"; + } + + return "unknown"; +} + static unsigned int phylink_interface_signal_rate(phy_interface_t interface) { switch (interface) { @@ -651,7 +669,7 @@ static void phylink_validate_mask_caps(unsigned long *supported, linkmode_and(state->advertising, state->advertising, mask); } -static int phylink_validate_mac_and_pcs(struct phylink *pl, unsigned int mode, +static int phylink_validate_mac_and_pcs(struct phylink *pl, unsigned long *supported, struct phylink_link_state *state) { @@ -682,8 +700,7 @@ static int phylink_validate_mac_and_pcs(struct phylink *pl, unsigned int mode, /* Validate the link parameters with the PCS */ if (pcs->ops->pcs_validate) { - ret = pcs->ops->pcs_validate(pcs, mode, supported, - state); + ret = pcs->ops->pcs_validate(pcs, supported, state); if (ret < 0 || phylink_is_empty_linkmode(supported)) return -EINVAL; @@ -708,7 +725,6 @@ static int phylink_validate_mac_and_pcs(struct phylink *pl, unsigned int mode, } static void phylink_validate_one(struct phylink *pl, struct phy_device *phy, - unsigned int mode, const unsigned long *supported, const struct phylink_link_state *state, phy_interface_t interface, @@ -726,8 +742,7 @@ static void phylink_validate_one(struct phylink *pl, struct phy_device *phy, if (phy) tmp_state.rate_matching = phy_get_rate_matching(phy, interface); - if (!phylink_validate_mac_and_pcs(pl, mode, tmp_supported, - &tmp_state)) { + if (!phylink_validate_mac_and_pcs(pl, tmp_supported, &tmp_state)) { phylink_dbg(pl, " interface %u (%s) rate match %s supports %*pbl\n", interface, phy_modes(interface), phy_rate_matching_to_str(tmp_state.rate_matching), @@ -740,7 +755,6 @@ static void phylink_validate_one(struct phylink *pl, struct phy_device *phy, } static int phylink_validate_mask(struct phylink *pl, struct phy_device *phy, - unsigned int mode, unsigned long *supported, struct phylink_link_state *state, const unsigned long *interfaces) @@ -750,7 +764,7 @@ static int phylink_validate_mask(struct phylink *pl, struct phy_device *phy, int interface; for_each_set_bit(interface, interfaces, PHY_INTERFACE_MODE_MAX) - phylink_validate_one(pl, phy, mode, supported, state, interface, + phylink_validate_one(pl, phy, supported, state, interface, all_s, all_adv); linkmode_copy(supported, all_s); @@ -759,20 +773,19 @@ static int phylink_validate_mask(struct phylink *pl, struct phy_device *phy, return phylink_is_empty_linkmode(supported) ? -EINVAL : 0; } -static int phylink_validate(struct phylink *pl, unsigned int mode, - unsigned long *supported, +static int phylink_validate(struct phylink *pl, unsigned long *supported, struct phylink_link_state *state) { const unsigned long *interfaces = pl->config->supported_interfaces; if (state->interface == PHY_INTERFACE_MODE_NA) - return phylink_validate_mask(pl, NULL, mode, supported, state, + return phylink_validate_mask(pl, NULL, supported, state, interfaces); if (!test_bit(state->interface, interfaces)) return -EINVAL; - return phylink_validate_mac_and_pcs(pl, mode, supported, state); + return phylink_validate_mac_and_pcs(pl, supported, state); } static int phylink_parse_fixedlink(struct phylink *pl, @@ -850,7 +863,7 @@ static int phylink_parse_fixedlink(struct phylink *pl, linkmode_fill(pl->supported); linkmode_copy(pl->link_config.advertising, pl->supported); - phylink_validate(pl, MLO_AN_FIXED, pl->supported, &pl->link_config); + phylink_validate(pl, pl->supported, &pl->link_config); pause = phylink_test(pl->supported, Pause); asym_pause = phylink_test(pl->supported, Asym_Pause); @@ -948,8 +961,7 @@ static int phylink_parse_mode(struct phylink *pl, linkmode_copy(pl->link_config.advertising, pl->supported); - if (phylink_validate(pl, pl->cfg_link_an_mode, pl->supported, - &pl->link_config)) { + if (phylink_validate(pl, pl->supported, &pl->link_config)) { phylink_err(pl, "failed to validate link configuration for in-band status\n"); return -EINVAL; @@ -1049,6 +1061,24 @@ static void phylink_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode, pcs->ops->pcs_link_up(pcs, neg_mode, interface, speed, duplex); } +/* Query inband for a specific interface mode, asking the MAC for the + * PCS which will be used to handle the interface mode. + */ +static unsigned int phylink_query_inband(struct phylink *pl, + phy_interface_t interface) +{ + struct phylink_pcs *pcs; + + if (!pl->using_mac_select_pcs) + return 0; + + pcs = pl->mac_ops->mac_select_pcs(pl->config, interface); + if (!pcs) + return 0; + + return phylink_pcs_query_inband(pcs, interface); +} + static void phylink_pcs_poll_stop(struct phylink *pl) { if (phylink_mode_inband(pl->cfg_link_an_mode)) @@ -1076,13 +1106,13 @@ static void phylink_mac_config(struct phylink *pl, phylink_dbg(pl, "%s: mode=%s/%s/%s adv=%*pb pause=%02x\n", - __func__, phylink_an_mode_str(pl->cur_link_an_mode), + __func__, phylink_an_mode_str(pl->act_link_an_mode), phy_modes(st.interface), phy_rate_matching_to_str(st.rate_matching), __ETHTOOL_LINK_MODE_MASK_NBITS, st.advertising, st.pause); - pl->mac_ops->mac_config(pl->config, pl->cur_link_an_mode, &st); + pl->mac_ops->mac_config(pl->config, pl->act_link_an_mode, &st); } static void phylink_pcs_an_restart(struct phylink *pl) @@ -1090,7 +1120,7 @@ static void phylink_pcs_an_restart(struct phylink *pl) if (pl->pcs && linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, pl->link_config.advertising) && phy_interface_mode_is_8023z(pl->link_config.interface) && - phylink_autoneg_inband(pl->cur_link_an_mode)) + phylink_autoneg_inband(pl->act_link_an_mode)) pl->pcs->ops->pcs_an_restart(pl->pcs); } @@ -1158,24 +1188,28 @@ static unsigned int phylink_pcs_neg_mode(struct phylink *pl, } pcs_link_mode = phylink_pcs_query_inband(pcs, interface); - if (pl->phydev) + pcs_link_mode &= LINK_INBAND_DISABLE | LINK_INBAND_ENABLE; + if (pl->phydev) { phy_link_mode = phy_query_inband(pl->phydev, interface); + phy_link_mode &= LINK_INBAND_DISABLE | LINK_INBAND_ENABLE; + } if (!phylink_autoneg_inband(mode)) { + const char *s1, *s2, *s3, *empty = ""; + + s1 = s2 = s3 = empty; + + if (pcs_link_mode == LINK_INBAND_ENABLE) + s1 = "PCS"; + + if (phy_link_mode == LINK_INBAND_ENABLE) + s3 = "PHY"; + /* If either the PCS or PHY requires inband to be enabled, * this is an invalid configuration. Provide a diagnostic * message for this case, but don't try to force the issue. */ - if ((pcs_link_mode | phy_link_mode) & LINK_INBAND_REQUIRED) { - const char *s1, *s2, *s3, *empty = ""; - - s1 = s2 = s3 = empty; - if (pcs_link_mode & LINK_INBAND_REQUIRED) - s1 = "PCS"; - - if (phy_link_mode & LINK_INBAND_REQUIRED) - s3 = "PHY"; - + if (s1 != empty || s3 != empty) { if (s1 != empty && s3 != empty) s2 = " and "; @@ -1188,24 +1222,44 @@ static unsigned int phylink_pcs_neg_mode(struct phylink *pl, } /* For SGMII modes, which are designed to be used with PHYs, we - * try to use inband mode where-ever possible. + * try to use inband mode where-ever possible. However, there are + * some PHYs e.g. BCM84881 which do not support in-band. */ if (type == INBAND_CISCO_SGMII) { - /* If the PCS or PHY can not provide inband, then use - * out of band. + /* PCS PHY + * D E D E + * 0 0 0 0 no information inband enabled + * 1 0 0 0 pcs doesn't support outband + * 0 1 0 0 pcs required inband enabled + * 1 1 0 0 pcs optional inband enabled + * 0 0 1 0 phy doesn't support outband + * 1 0 1 0 pcs+phy doesn't support outband + * 0 1 1 0 pcs required, phy doesn't support, invalid + * 1 1 1 0 pcs optional, phy doesn't support, outband + * 0 0 0 1 phy required inband enabled + * 1 0 0 1 pcs doesn't support, phy required, invalid + * 0 1 0 1 pcs+phy required inband enabled + * 1 1 0 1 pcs optional, phy required inband enabled + * 0 0 1 1 phy optional inband enabled + * 1 0 1 1 pcs doesn't support, phy optional, outband + * 0 1 1 1 pcs required, phy optional inband enabled + * 1 1 1 1 pcs+phy optional inband enabled */ - if (pcs_link_mode == LINK_INBAND_VALID || - phy_link_mode == LINK_INBAND_VALID) { - /* If either require inband, then we have a conflict. */ + if (!pcs_link_mode) + pcs_link_mode = LINK_INBAND_DISABLE | LINK_INBAND_ENABLE; + + if (!phy_link_mode) + phy_link_mode = LINK_INBAND_DISABLE | LINK_INBAND_ENABLE; + + if (pcs_link_mode & phy_link_mode & LINK_INBAND_ENABLE) { + /* inband supported at both ends */ + return PHYLINK_PCS_NEG_INBAND_ENABLED; + } else if (pcs_link_mode & phy_link_mode & LINK_INBAND_DISABLE) { + /* inband not supported at both ends */ return PHYLINK_PCS_NEG_OUTBAND; + } else { + /* invalid */ } - - /* Otherwise, use inband as this is what was requested. - * This covers the case where the supported inband modes - * have not been provided, or the PCS and PHY report that - * they support inband. - */ - return PHYLINK_PCS_NEG_INBAND_ENABLED; } /* For 802.3z, handle the PHY present vs absent cases separately */ @@ -1214,13 +1268,13 @@ static unsigned int phylink_pcs_neg_mode(struct phylink *pl, /* If the PCS doesn't support inband, then inband must be * disabled. */ - if (pcs_link_mode == LINK_INBAND_VALID) + if (pcs_link_mode == LINK_INBAND_DISABLE) return PHYLINK_PCS_NEG_INBAND_DISABLED; /* If the PCS requires inband, then inband must always be * enabled. */ - if (pcs_link_mode & LINK_INBAND_REQUIRED) + if (pcs_link_mode == LINK_INBAND_ENABLE) return PHYLINK_PCS_NEG_INBAND_ENABLED; /* For the possible case, fall through to the "legacy" code. */ @@ -1239,6 +1293,15 @@ static unsigned int phylink_pcs_neg_mode(struct phylink *pl, return neg_mode; } +static u8 phylink_choose_act_link_an_mode(struct phylink *pl) +{ + if (pl->req_link_an_mode == MLO_AN_INBAND && pl->phydev && + pl->pcs_neg_mode == PHYLINK_PCS_NEG_OUTBAND) + return MLO_AN_PHY; + + return pl->req_link_an_mode; +} + static void phylink_major_config(struct phylink *pl, bool restart, const struct phylink_link_state *state) { @@ -1248,7 +1311,9 @@ static void phylink_major_config(struct phylink *pl, bool restart, unsigned int neg_mode; int err; - phylink_dbg(pl, "major config %s\n", phy_modes(state->interface)); + phylink_dbg(pl, "major config, requested %s/%s\n", + phylink_an_mode_str(pl->req_link_an_mode), + phy_modes(state->interface)); if (pl->using_mac_select_pcs) { pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface); @@ -1263,14 +1328,24 @@ static void phylink_major_config(struct phylink *pl, bool restart, } pl->pcs_neg_mode = phylink_pcs_neg_mode(pl, pcs, - pl->cur_link_an_mode, + pl->req_link_an_mode, state->interface, state->advertising); + /* set the active link AN mode, which may end up different from the + * current link AN mode depending on the PCS and PHY capabilities. + */ + pl->act_link_an_mode = phylink_choose_act_link_an_mode(pl); + + phylink_dbg(pl, "major config, active %s/%s/%s\n", + phylink_an_mode_str(pl->act_link_an_mode), + phylink_pcs_mode_str(pl->pcs_neg_mode), + phy_modes(state->interface)); + phylink_pcs_poll_stop(pl); if (pl->mac_ops->mac_prepare) { - err = pl->mac_ops->mac_prepare(pl->config, pl->cur_link_an_mode, + err = pl->mac_ops->mac_prepare(pl->config, pl->act_link_an_mode, state->interface); if (err < 0) { phylink_err(pl, "mac_prepare failed: %pe\n", @@ -1304,7 +1379,7 @@ static void phylink_major_config(struct phylink *pl, bool restart, if (pl->pcs_state == PCS_STATE_STARTING || pcs_changed) phylink_pcs_enable(pl->pcs); - neg_mode = pl->cur_link_an_mode; + neg_mode = pl->act_link_an_mode; if (pl->pcs && pl->pcs->neg_mode) neg_mode = pl->pcs_neg_mode; @@ -1320,7 +1395,7 @@ static void phylink_major_config(struct phylink *pl, bool restart, phylink_pcs_an_restart(pl); if (pl->mac_ops->mac_finish) { - err = pl->mac_ops->mac_finish(pl->config, pl->cur_link_an_mode, + err = pl->mac_ops->mac_finish(pl->config, pl->act_link_an_mode, state->interface); if (err < 0) phylink_err(pl, "mac_finish failed: %pe\n", @@ -1351,18 +1426,23 @@ static int phylink_change_inband_advert(struct phylink *pl) return 0; phylink_dbg(pl, "%s: mode=%s/%s adv=%*pb pause=%02x\n", __func__, - phylink_an_mode_str(pl->cur_link_an_mode), + phylink_an_mode_str(pl->req_link_an_mode), phy_modes(pl->link_config.interface), __ETHTOOL_LINK_MODE_MASK_NBITS, pl->link_config.advertising, pl->link_config.pause); /* Recompute the PCS neg mode */ pl->pcs_neg_mode = phylink_pcs_neg_mode(pl, pl->pcs, - pl->cur_link_an_mode, + pl->req_link_an_mode, pl->link_config.interface, pl->link_config.advertising); - neg_mode = pl->cur_link_an_mode; + /* set the active link AN mode, which may end up different from the + * current link AN mode depending on the PCS and PHY capabilities. + */ + pl->act_link_an_mode = phylink_choose_act_link_an_mode(pl); + + neg_mode = pl->act_link_an_mode; if (pl->pcs->neg_mode) neg_mode = pl->pcs_neg_mode; @@ -1427,7 +1507,7 @@ static void phylink_mac_initial_config(struct phylink *pl, bool force_restart) { struct phylink_link_state link_state; - switch (pl->cur_link_an_mode) { + switch (pl->req_link_an_mode) { case MLO_AN_PHY: link_state = pl->phy_state; break; @@ -1570,14 +1650,14 @@ static void phylink_link_up(struct phylink *pl, pl->cur_interface = link_state.interface; - neg_mode = pl->cur_link_an_mode; + neg_mode = pl->act_link_an_mode; if (pl->pcs && pl->pcs->neg_mode) neg_mode = pl->pcs_neg_mode; phylink_pcs_link_up(pl->pcs, neg_mode, pl->cur_interface, speed, duplex); - pl->mac_ops->mac_link_up(pl->config, pl->phydev, pl->cur_link_an_mode, + pl->mac_ops->mac_link_up(pl->config, pl->phydev, pl->act_link_an_mode, pl->cur_interface, speed, duplex, !!(link_state.pause & MLO_PAUSE_TX), rx_pause); @@ -1603,7 +1683,7 @@ static void phylink_link_down(struct phylink *pl) phylink_deactivate_eee(pl); - pl->mac_ops->mac_link_down(pl->config, pl->cur_link_an_mode, + pl->mac_ops->mac_link_down(pl->config, pl->act_link_an_mode, pl->cur_interface); phylink_info(pl, "Link is Down\n"); } @@ -1630,76 +1710,73 @@ static void phylink_resolve(struct work_struct *w) } else if (pl->mac_link_dropped) { link_state.link = false; retrigger = true; - } else { - switch (pl->cur_link_an_mode) { - case MLO_AN_PHY: + } else if (pl->act_link_an_mode == MLO_AN_FIXED) { + phylink_get_fixed_state(pl, &link_state); + mac_config = link_state.link; + } else if (pl->act_link_an_mode == MLO_AN_PHY) { + /* PHY mode, or PCS configured for outband mode */ + if (pl->phydev) { link_state = pl->phy_state; - phylink_apply_manual_flow(pl, &link_state); - mac_config = link_state.link; - break; + } else { + /* No PHY - assume link is down */ + link_state.link = false; + } + mac_config = link_state.link; + } else { + /* Inband mode */ + phylink_mac_pcs_get_state(pl, &link_state); - case MLO_AN_FIXED: - phylink_get_fixed_state(pl, &link_state); - mac_config = link_state.link; - break; + /* The PCS may have a latching link-fail indicator. + * If the link was up, bring the link down and + * re-trigger the resolve. Otherwise, re-read the + * PCS state to get the current status of the link. + */ + if (!link_state.link) { + if (cur_link_state) + retrigger = true; + else + phylink_mac_pcs_get_state(pl, &link_state); + } - case MLO_AN_INBAND: - phylink_mac_pcs_get_state(pl, &link_state); + /* If we have a phy, the "up" state is the union of + * both the PHY and the MAC + */ + if (pl->phydev) + link_state.link &= pl->phy_state.link; - /* The PCS may have a latching link-fail indicator. - * If the link was up, bring the link down and - * re-trigger the resolve. Otherwise, re-read the - * PCS state to get the current status of the link. + /* Only update if the PHY link is up */ + if (pl->phydev && pl->phy_state.link) { + /* If the interface has changed, force a + * link down event if the link isn't already + * down, and re-resolve. */ - if (!link_state.link) { - if (cur_link_state) - retrigger = true; - else - phylink_mac_pcs_get_state(pl, - &link_state); + if (link_state.interface != pl->phy_state.interface) { + retrigger = true; + link_state.link = false; } + link_state.interface = pl->phy_state.interface; - /* If we have a phy, the "up" state is the union of - * both the PHY and the MAC + /* If we are doing rate matching, then the + * link speed/duplex comes from the PHY */ - if (pl->phydev) - link_state.link &= pl->phy_state.link; - - /* Only update if the PHY link is up */ - if (pl->phydev && pl->phy_state.link) { - /* If the interface has changed, force a - * link down event if the link isn't already - * down, and re-resolve. - */ - if (link_state.interface != - pl->phy_state.interface) { - retrigger = true; - link_state.link = false; - } - link_state.interface = pl->phy_state.interface; - - /* If we are doing rate matching, then the - * link speed/duplex comes from the PHY - */ - if (pl->phy_state.rate_matching) { - link_state.rate_matching = - pl->phy_state.rate_matching; - link_state.speed = pl->phy_state.speed; - link_state.duplex = - pl->phy_state.duplex; - } - - /* If we have a PHY, we need to update with - * the PHY flow control bits. - */ - link_state.pause = pl->phy_state.pause; - mac_config = true; + if (pl->phy_state.rate_matching) { + link_state.rate_matching = + pl->phy_state.rate_matching; + link_state.speed = pl->phy_state.speed; + link_state.duplex = pl->phy_state.duplex; } - phylink_apply_manual_flow(pl, &link_state); - break; + + /* If we have a PHY, we need to update with + * the PHY flow control bits. + */ + link_state.pause = pl->phy_state.pause; + mac_config = true; } } + if (pl->act_link_an_mode != MLO_AN_FIXED) + phylink_apply_manual_flow(pl, &link_state); + if (mac_config) { if (link_state.interface != pl->link_config.interface) { /* The interface has changed, force the link down and @@ -1858,7 +1935,7 @@ struct phylink *phylink_create(struct phylink_config *config, linkmode_fill(pl->supported); linkmode_copy(pl->link_config.advertising, pl->supported); - phylink_validate(pl, MLO_AN_FIXED, pl->supported, &pl->link_config); + phylink_validate(pl, pl->supported, &pl->link_config); /* Set the default EEE configuration */ pl->eee_cfg = pl->config->eee; @@ -1877,7 +1954,8 @@ struct phylink *phylink_create(struct phylink_config *config, } } - pl->cur_link_an_mode = pl->cfg_link_an_mode; + pl->req_link_an_mode = pl->cfg_link_an_mode; + pl->act_link_an_mode = pl->req_link_an_mode; ret = phylink_register_sfp(pl, fwnode); if (ret < 0) { @@ -1963,9 +2041,6 @@ static int phylink_validate_phy(struct phylink *pl, struct phy_device *phy, struct phylink_link_state *state) { DECLARE_PHY_INTERFACE_MASK(interfaces); - unsigned int mode; - - mode = pl->cfg_link_an_mode; /* If the PHY provides a bitmap of the interfaces it will be using * depending on the negotiated media speeds, use this to validate @@ -2002,7 +2077,7 @@ static int phylink_validate_phy(struct phylink *pl, struct phy_device *phy, phy->possible_interfaces, (int)PHY_INTERFACE_MODE_MAX, interfaces); - return phylink_validate_mask(pl, phy, mode, supported, state, + return phylink_validate_mask(pl, phy, supported, state, interfaces); } @@ -2031,7 +2106,7 @@ static int phylink_validate_phy(struct phylink *pl, struct phy_device *phy, state->interface != PHY_INTERFACE_MODE_USXGMII) state->interface = PHY_INTERFACE_MODE_NA; - return phylink_validate(pl, mode, supported, state); + return phylink_validate(pl, supported, state); } static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy, @@ -2334,7 +2409,7 @@ void phylink_start(struct phylink *pl) ASSERT_RTNL(); phylink_info(pl, "configuring for %s/%s link mode\n", - phylink_an_mode_str(pl->cur_link_an_mode), + phylink_an_mode_str(pl->req_link_an_mode), phy_modes(pl->link_config.interface)); /* Always set the carrier off */ @@ -2545,6 +2620,32 @@ int phylink_ethtool_set_wol(struct phylink *pl, struct ethtool_wolinfo *wol) } EXPORT_SYMBOL_GPL(phylink_ethtool_set_wol); +static phy_interface_t phylink_sfp_select_interface(struct phylink *pl, + const unsigned long *link_modes) +{ + phy_interface_t interface; + + interface = sfp_select_interface(pl->sfp_bus, link_modes); + if (interface == PHY_INTERFACE_MODE_NA) { + phylink_err(pl, + "selection of interface failed, advertisement %*pb\n", + __ETHTOOL_LINK_MODE_MASK_NBITS, + link_modes); + return interface; + } + + if (!test_bit(interface, pl->config->supported_interfaces)) { + phylink_err(pl, + "selection of interface failed - SFP selected %s (%u) but MAC supports %*pbl\n", + phy_modes(interface), interface, + (int)PHY_INTERFACE_MODE_MAX, + pl->config->supported_interfaces); + return PHY_INTERFACE_MODE_NA; + } + + return interface; +} + static void phylink_merge_link_mode(unsigned long *dst, const unsigned long *b) { __ETHTOOL_DECLARE_LINK_MODE_MASK(mask); @@ -2593,7 +2694,7 @@ int phylink_ethtool_ksettings_get(struct phylink *pl, linkmode_copy(kset->link_modes.supported, pl->supported); - switch (pl->cur_link_an_mode) { + switch (pl->act_link_an_mode) { case MLO_AN_FIXED: /* We are using fixed settings. Report these as the * current link settings - and note that these also @@ -2624,37 +2725,24 @@ int phylink_ethtool_ksettings_get(struct phylink *pl, } EXPORT_SYMBOL_GPL(phylink_ethtool_ksettings_get); -static int phylink_query_inband(struct phylink *pl, phy_interface_t interface) +static bool phylink_validate_pcs_inband_autoneg(struct phylink *pl, + phy_interface_t interface, + unsigned long *adv) { - struct phylink_pcs *pcs; - - if (!pl->using_mac_select_pcs) - return 0; - - pcs = pl->mac_ops->mac_select_pcs(pl->config, interface); - if (!pcs) - return 0; - - return phylink_pcs_query_inband(pcs, interface); -} - -static bool phylink_validate_pcs_an(struct phylink *pl, - phy_interface_t interface, bool an_state) -{ - int link_inband = phylink_query_inband(pl, interface); + unsigned int inband = phylink_query_inband(pl, interface); + unsigned int mask; /* If the PCS doesn't implement inband support, be permissive. */ - if (!(link_inband & LINK_INBAND_VALID)) + if (!inband) return true; - /* If the PCS can configure the use of inband, allow either state */ - if (link_inband & LINK_INBAND_POSSIBLE) - return true; + if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, adv)) + mask = LINK_INBAND_ENABLE; + else + mask = LINK_INBAND_DISABLE; - /* Otherwise, require the AN state to be fixed according to the - * required flag. - */ - return an_state == !!(link_inband & LINK_INBAND_REQUIRED); + /* Check whether the PCS implements the required mode */ + return !!(inband & mask); } /** @@ -2718,7 +2806,7 @@ int phylink_ethtool_ksettings_set(struct phylink *pl, /* If we have a fixed link, refuse to change link parameters. * If the link parameters match, accept them but do nothing. */ - if (phylink_mode_fixed(pl->cur_link_an_mode)) { + if (phylink_mode_fixed(pl->req_link_an_mode)) { if (s->speed != pl->link_config.speed || s->duplex != pl->link_config.duplex) return -EINVAL; @@ -2734,7 +2822,7 @@ int phylink_ethtool_ksettings_set(struct phylink *pl, * is our default case) but do not allow the advertisement to * be changed. If the advertisement matches, simply return. */ - if (phylink_mode_fixed(pl->cur_link_an_mode)) { + if (phylink_mode_fixed(pl->req_link_an_mode)) { if (!linkmode_equal(config.advertising, pl->link_config.advertising)) return -EINVAL; @@ -2760,22 +2848,16 @@ int phylink_ethtool_ksettings_set(struct phylink *pl, * link can be configured correctly. */ if (pl->sfp_bus) { - config.interface = sfp_select_interface(pl->sfp_bus, + config.interface = phylink_sfp_select_interface(pl, config.advertising); - if (config.interface == PHY_INTERFACE_MODE_NA) { - phylink_err(pl, - "selection of interface failed, advertisement %*pb\n", - __ETHTOOL_LINK_MODE_MASK_NBITS, - config.advertising); + if (config.interface == PHY_INTERFACE_MODE_NA) return -EINVAL; - } /* Revalidate with the selected interface */ linkmode_copy(support, pl->supported); - if (phylink_validate(pl, pl->cur_link_an_mode, support, - &config)) { + if (phylink_validate(pl, support, &config)) { phylink_err(pl, "validation of %s/%s with support %*pb failed\n", - phylink_an_mode_str(pl->cur_link_an_mode), + phylink_an_mode_str(pl->req_link_an_mode), phy_modes(config.interface), __ETHTOOL_LINK_MODE_MASK_NBITS, support); return -EINVAL; @@ -2783,8 +2865,7 @@ int phylink_ethtool_ksettings_set(struct phylink *pl, } else { /* Validate without changing the current supported mask. */ linkmode_copy(support, pl->supported); - if (phylink_validate(pl, pl->cur_link_an_mode, support, - &config)) + if (phylink_validate(pl, support, &config)) return -EINVAL; } @@ -2797,9 +2878,8 @@ int phylink_ethtool_ksettings_set(struct phylink *pl, /* Validate the autonegotiation state. We don't have a PHY in this * situation, so the PCS is the media-facing entity. */ - if (!phylink_validate_pcs_an(pl, config.interface, - linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, - config.advertising))) + if (!phylink_validate_pcs_inband_autoneg(pl, config.interface, + config.advertising)) return -EINVAL; mutex_lock(&pl->state_mutex); @@ -2884,7 +2964,7 @@ int phylink_ethtool_set_pauseparam(struct phylink *pl, ASSERT_RTNL(); - if (phylink_mode_fixed(pl->cur_link_an_mode)) + if (phylink_mode_fixed(pl->req_link_an_mode)) return -EOPNOTSUPP; if (!phylink_test(pl->supported, Pause) && @@ -3202,7 +3282,7 @@ static int phylink_mii_read(struct phylink *pl, unsigned int phy_id, struct phylink_link_state state; int val = 0xffff; - switch (pl->cur_link_an_mode) { + switch (pl->act_link_an_mode) { case MLO_AN_FIXED: if (phy_id == 0) { phylink_get_fixed_state(pl, &state); @@ -3227,7 +3307,7 @@ static int phylink_mii_read(struct phylink *pl, unsigned int phy_id, static int phylink_mii_write(struct phylink *pl, unsigned int phy_id, unsigned int reg, unsigned int val) { - switch (pl->cur_link_an_mode) { + switch (pl->act_link_an_mode) { case MLO_AN_FIXED: break; @@ -3397,10 +3477,11 @@ static phy_interface_t phylink_choose_sfp_interface(struct phylink *pl, return interface; } -static void phylink_sfp_set_config(struct phylink *pl, u8 mode, +static void phylink_sfp_set_config(struct phylink *pl, unsigned long *supported, struct phylink_link_state *state) { + u8 mode = MLO_AN_INBAND; bool changed = false; phylink_dbg(pl, "requesting link mode %s/%s with support %*pb\n", @@ -3417,9 +3498,9 @@ static void phylink_sfp_set_config(struct phylink *pl, u8 mode, changed = true; } - if (pl->cur_link_an_mode != mode || + if (pl->req_link_an_mode != mode || pl->link_config.interface != state->interface) { - pl->cur_link_an_mode = mode; + pl->req_link_an_mode = mode; pl->link_config.interface = state->interface; changed = true; @@ -3436,10 +3517,8 @@ static void phylink_sfp_set_config(struct phylink *pl, u8 mode, static int phylink_sfp_config_phy(struct phylink *pl, struct phy_device *phy) { - __ETHTOOL_DECLARE_LINK_MODE_MASK(support1); __ETHTOOL_DECLARE_LINK_MODE_MASK(support); struct phylink_link_state config; - phy_interface_t iface; int ret; linkmode_copy(support, phy->supported); @@ -3452,7 +3531,7 @@ static int phylink_sfp_config_phy(struct phylink *pl, struct phy_device *phy) config.pause = MLO_PAUSE_AN; /* Ignore errors if we're expecting a PHY to attach later */ - ret = phylink_validate(pl, pl->sfp_link_an_mode, support, &config); + ret = phylink_validate(pl, support, &config); if (ret) { phylink_err(pl, "validation with support %*pb failed: %pe\n", __ETHTOOL_LINK_MODE_MASK_NBITS, support, @@ -3460,30 +3539,27 @@ static int phylink_sfp_config_phy(struct phylink *pl, struct phy_device *phy) return ret; } - iface = sfp_select_interface(pl->sfp_bus, config.advertising); - if (iface == PHY_INTERFACE_MODE_NA) { - phylink_err(pl, - "selection of interface failed, advertisement %*pb\n", - __ETHTOOL_LINK_MODE_MASK_NBITS, config.advertising); + config.interface = phylink_sfp_select_interface(pl, config.advertising); + if (config.interface == PHY_INTERFACE_MODE_NA) return -EINVAL; - } - config.interface = iface; - linkmode_copy(support1, support); - ret = phylink_validate(pl, pl->sfp_link_an_mode, support1, &config); - if (ret) { - phylink_err(pl, - "validation of %s/%s with support %*pb failed: %pe\n", - phylink_an_mode_str(pl->sfp_link_an_mode), - phy_modes(config.interface), - __ETHTOOL_LINK_MODE_MASK_NBITS, support, - ERR_PTR(ret)); + /* Attach the PHY so that the PHY is present when we do the major + * configuration step. + */ + ret = phylink_attach_phy(pl, phy, config.interface); + if (ret < 0) + return ret; + + /* This will validate the configuration for us. */ + ret = phylink_bringup_phy(pl, phy, config.interface); + if (ret < 0) { + phy_detach(phy); return ret; } pl->link_port = pl->sfp_port; - phylink_sfp_set_config(pl, pl->sfp_link_an_mode, support, &config); + phylink_sfp_set_config(pl, support, &config); return 0; } @@ -3522,8 +3598,8 @@ static int phylink_sfp_config_optical(struct phylink *pl) /* For all the interfaces that are supported, reduce the sfp_support * mask to only those link modes that can be supported. */ - ret = phylink_validate_mask(pl, NULL, MLO_AN_INBAND, pl->sfp_support, - &config, interfaces); + ret = phylink_validate_mask(pl, NULL, pl->sfp_support, &config, + interfaces); if (ret) { phylink_err(pl, "unsupported SFP module: validation with support %*pb failed\n", __ETHTOOL_LINK_MODE_MASK_NBITS, support); @@ -3539,9 +3615,8 @@ static int phylink_sfp_config_optical(struct phylink *pl) phylink_dbg(pl, "optical SFP: chosen %s interface\n", phy_modes(interface)); - if (!phylink_validate_pcs_an(pl, interface, - linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, - config.advertising))) { + if (!phylink_validate_pcs_inband_autoneg(pl, interface, + config.advertising)) { phylink_err(pl, "autoneg setting not compatible with PCS"); return -EINVAL; } @@ -3549,7 +3624,7 @@ static int phylink_sfp_config_optical(struct phylink *pl) config.interface = interface; /* Ignore errors if we're expecting a PHY to attach later */ - ret = phylink_validate(pl, MLO_AN_INBAND, support, &config); + ret = phylink_validate(pl, support, &config); if (ret) { phylink_err(pl, "validation with support %*pb failed: %pe\n", __ETHTOOL_LINK_MODE_MASK_NBITS, support, @@ -3559,7 +3634,7 @@ static int phylink_sfp_config_optical(struct phylink *pl) pl->link_port = pl->sfp_port; - phylink_sfp_set_config(pl, MLO_AN_INBAND, pl->sfp_support, &config); + phylink_sfp_set_config(pl, pl->sfp_support, &config); return 0; } @@ -3575,7 +3650,6 @@ static int phylink_sfp_module_insert(void *upstream, phy_interface_zero(pl->sfp_interfaces); sfp_parse_support(pl->sfp_bus, id, pl->sfp_support, pl->sfp_interfaces); pl->sfp_port = sfp_parse_port(pl->sfp_bus, id, pl->sfp_support); - pl->sfp_link_an_mode = MLO_AN_INBAND; /* If this module may have a PHY connecting later, defer until later */ pl->sfp_may_have_phy = sfp_may_have_phy(pl->sfp_bus, id); @@ -3636,7 +3710,7 @@ static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy) DECLARE_PHY_INTERFACE_MASK(interfaces); struct phylink *pl = upstream; phy_interface_t interface; - int link_inband, ret; + int ret; /* * This is the new way of dealing with flow control for PHYs, @@ -3655,64 +3729,51 @@ static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy) phylink_dbg(pl, "copper SFP: PHY provides empty supported_interfaces\n"); /* Do the initial configuration */ - ret = phylink_sfp_config_phy(pl, phy); - if (ret < 0) - return ret; - } else { - phylink_dbg(pl, "copper SFP: interfaces=[mac=%*pbl, sfp=%*pbl]\n", - (int)PHY_INTERFACE_MODE_MAX, - pl->config->supported_interfaces, - (int)PHY_INTERFACE_MODE_MAX, - phy->supported_interfaces); - - phy_interface_and(interfaces, phy->supported_interfaces, - pl->config->supported_interfaces); - interface = phylink_choose_sfp_interface(pl, interfaces); - if (interface == PHY_INTERFACE_MODE_NA) { - phylink_err(pl, - "selection of interface for PHY failed\n"); - return -EINVAL; - } - - phylink_dbg(pl, "copper SFP: chosen %s interface\n", - phy_modes(interface)); - - link_inband = phy_query_inband(phy, interface); - phylink_dbg(pl, "copper SFP: PHY link in-band modes 0x%x\n", - link_inband); - - /* If the link inband is valid but the PHY doesn't support - * inband, then we have to use PHY mode. E.g. BCM84881. - */ - if (link_inband == LINK_INBAND_VALID) - pl->sfp_link_an_mode = MLO_AN_PHY; - - if (pl->cur_link_an_mode != pl->sfp_link_an_mode || - pl->link_config.interface != interface) { - pl->link_config.interface = interface; - pl->cur_link_an_mode = pl->sfp_link_an_mode; + return phylink_sfp_config_phy(pl, phy); + } - phylink_info(pl, "switched to %s/%s link mode\n", - phylink_an_mode_str(pl->sfp_link_an_mode), - phy_modes(interface)); - } + phylink_dbg(pl, "copper SFP: interfaces=[mac=%*pbl, sfp=%*pbl]\n", + (int)PHY_INTERFACE_MODE_MAX, + pl->config->supported_interfaces, + (int)PHY_INTERFACE_MODE_MAX, + phy->supported_interfaces); - if (!test_bit(PHYLINK_DISABLE_STOPPED, - &pl->phylink_disable_state)) - phylink_mac_initial_config(pl, false); + phy_interface_and(interfaces, phy->supported_interfaces, + pl->config->supported_interfaces); + interface = phylink_choose_sfp_interface(pl, interfaces); + if (interface == PHY_INTERFACE_MODE_NA) { + phylink_err(pl, + "selection of interface for PHY failed\n"); + return -EINVAL; } - interface = pl->link_config.interface; + phylink_dbg(pl, "copper SFP: chosen %s interface\n", + phy_modes(interface)); ret = phylink_attach_phy(pl, phy, interface); if (ret < 0) return ret; ret = phylink_bringup_phy(pl, phy, interface); - if (ret) + if (ret) { phy_detach(phy); + return ret; + } - return ret; + if (pl->req_link_an_mode != MLO_AN_INBAND || + pl->link_config.interface != interface) { + pl->link_config.interface = interface; + pl->req_link_an_mode = MLO_AN_INBAND; + + phylink_info(pl, "switched to %s/%s link mode\n", + phylink_an_mode_str(pl->req_link_an_mode), + phy_modes(pl->link_config.interface)); + } + + if (!test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state)) + phylink_mac_initial_config(pl, false); + + return 0; } static void phylink_sfp_disconnect_phy(void *upstream) |