diff options
Diffstat (limited to 'drivers/net/phy/phylink.c')
-rw-r--r-- | drivers/net/phy/phylink.c | 506 |
1 files changed, 421 insertions, 85 deletions
diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index ed0b4ccaa6a6..8a3728d423e7 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -85,7 +85,11 @@ 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; + bool eee_active; }; #define phylink_printk(level, pl, fmt, ...) \ @@ -647,7 +651,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, +static int phylink_validate_mac_and_pcs(struct phylink *pl, unsigned int mode, unsigned long *supported, struct phylink_link_state *state) { @@ -678,7 +682,8 @@ static int phylink_validate_mac_and_pcs(struct phylink *pl, /* Validate the link parameters with the PCS */ if (pcs->ops->pcs_validate) { - ret = pcs->ops->pcs_validate(pcs, supported, state); + ret = pcs->ops->pcs_validate(pcs, mode, supported, + state); if (ret < 0 || phylink_is_empty_linkmode(supported)) return -EINVAL; @@ -703,6 +708,7 @@ static int phylink_validate_mac_and_pcs(struct phylink *pl, } 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, @@ -720,7 +726,8 @@ 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, tmp_supported, &tmp_state)) { + if (!phylink_validate_mac_and_pcs(pl, mode, 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), @@ -733,6 +740,7 @@ 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) @@ -742,7 +750,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, supported, state, interface, + phylink_validate_one(pl, phy, mode, supported, state, interface, all_s, all_adv); linkmode_copy(supported, all_s); @@ -751,19 +759,20 @@ 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 long *supported, +static int phylink_validate(struct phylink *pl, unsigned int mode, + 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, supported, state, + return phylink_validate_mask(pl, NULL, mode, supported, state, interfaces); if (!test_bit(state->interface, interfaces)) return -EINVAL; - return phylink_validate_mac_and_pcs(pl, supported, state); + return phylink_validate_mac_and_pcs(pl, mode, supported, state); } static int phylink_parse_fixedlink(struct phylink *pl, @@ -841,7 +850,7 @@ static int phylink_parse_fixedlink(struct phylink *pl, linkmode_fill(pl->supported); linkmode_copy(pl->link_config.advertising, pl->supported); - phylink_validate(pl, pl->supported, &pl->link_config); + phylink_validate(pl, MLO_AN_FIXED, pl->supported, &pl->link_config); pause = phylink_test(pl->supported, Pause); asym_pause = phylink_test(pl->supported, Asym_Pause); @@ -893,7 +902,7 @@ static int phylink_parse_mode(struct phylink *pl, if ((fwnode_property_read_string(fwnode, "managed", &managed) == 0 && strcmp(managed, "in-band-status") == 0) || pl->config->ovr_an_inband) { - if (pl->cfg_link_an_mode == MLO_AN_FIXED) { + if (phylink_mode_fixed(pl->cfg_link_an_mode)) { phylink_err(pl, "can't use both fixed-link and in-band-status\n"); return -EINVAL; @@ -939,7 +948,8 @@ static int phylink_parse_mode(struct phylink *pl, linkmode_copy(pl->link_config.advertising, pl->supported); - if (phylink_validate(pl, pl->supported, &pl->link_config)) { + if (phylink_validate(pl, pl->cfg_link_an_mode, pl->supported, + &pl->link_config)) { phylink_err(pl, "failed to validate link configuration for in-band status\n"); return -EINVAL; @@ -977,6 +987,15 @@ static void phylink_resolve_an_pause(struct phylink_link_state *state) } } +static unsigned int phylink_pcs_query_inband(struct phylink_pcs *pcs, + phy_interface_t interface) +{ + if (pcs && pcs->ops->pcs_query_inband) + return pcs->ops->pcs_query_inband(pcs, interface); + + return 0; +} + static void phylink_pcs_pre_config(struct phylink_pcs *pcs, phy_interface_t interface) { @@ -1032,13 +1051,14 @@ static void phylink_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode, static void phylink_pcs_poll_stop(struct phylink *pl) { - if (pl->cfg_link_an_mode == MLO_AN_INBAND) + if (phylink_mode_inband(pl->cfg_link_an_mode)) del_timer(&pl->link_poll); } static void phylink_pcs_poll_start(struct phylink *pl) { - if (pl->pcs && pl->pcs->poll && pl->cfg_link_an_mode == MLO_AN_INBAND) + if (pl->pcs && pl->pcs->poll && + phylink_mode_inband(pl->cfg_link_an_mode)) mod_timer(&pl->link_poll, jiffies + HZ); } @@ -1076,6 +1096,8 @@ static void phylink_pcs_an_restart(struct phylink *pl) /** * phylink_pcs_neg_mode() - helper to determine PCS inband mode + * @pl: a pointer to a &struct phylink returned from phylink_create() + * @pcs: a pointer to &struct phylink_pcs * @mode: one of %MLO_AN_FIXED, %MLO_AN_PHY, %MLO_AN_INBAND. * @interface: interface mode to be used * @advertising: adertisement ethtool link mode mask @@ -1093,11 +1115,19 @@ static void phylink_pcs_an_restart(struct phylink *pl) * Note: this is for cases where the PCS itself is involved in negotiation * (e.g. Clause 37, SGMII and similar) not Clause 73. */ -static unsigned int phylink_pcs_neg_mode(unsigned int mode, +static unsigned int phylink_pcs_neg_mode(struct phylink *pl, + struct phylink_pcs *pcs, + unsigned int mode, phy_interface_t interface, const unsigned long *advertising) { + unsigned int phy_link_mode = 0; + unsigned int pcs_link_mode; unsigned int neg_mode; + enum { + INBAND_CISCO_SGMII, + INBAND_8023Z, + } type; switch (interface) { case PHY_INTERFACE_MODE_SGMII: @@ -1109,10 +1139,7 @@ static unsigned int phylink_pcs_neg_mode(unsigned int mode, * inband communication. Note: there exist PHYs that run * with SGMII but do not send the inband data. */ - if (!phylink_autoneg_inband(mode)) - neg_mode = PHYLINK_PCS_NEG_OUTBAND; - else - neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED; + type = INBAND_CISCO_SGMII; break; case PHY_INTERFACE_MODE_1000BASEX: @@ -1123,20 +1150,92 @@ static unsigned int phylink_pcs_neg_mode(unsigned int mode, * as well, but drivers may not support this, so may * need to override this. */ - if (!phylink_autoneg_inband(mode)) - neg_mode = PHYLINK_PCS_NEG_OUTBAND; - else if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, - advertising)) - neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED; - else - neg_mode = PHYLINK_PCS_NEG_INBAND_DISABLED; + type = INBAND_8023Z; break; default: - neg_mode = PHYLINK_PCS_NEG_NONE; - break; + return PHYLINK_PCS_NEG_NONE; + } + + pcs_link_mode = phylink_pcs_query_inband(pcs, interface); + if (pl->phydev) + phy_link_mode = phy_query_inband(pl->phydev, interface); + + if (!phylink_autoneg_inband(mode)) { + /* 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) + s2 = " and "; + + phylink_warn(pl, + "firmware wants %s mode, but %s%s%s requires inband\n", + phylink_an_mode_str(mode), + s1, s2, s3); + } + return PHYLINK_PCS_NEG_OUTBAND; } + /* For SGMII modes, which are designed to be used with PHYs, we + * try to use inband mode where-ever possible. + */ + if (type == INBAND_CISCO_SGMII) { + /* If the PCS or PHY can not provide inband, then use + * out of band. + */ + if (pcs_link_mode == LINK_INBAND_VALID || + phy_link_mode == LINK_INBAND_VALID) { + /* If either require inband, then we have a conflict. */ + return PHYLINK_PCS_NEG_OUTBAND; + } + + /* 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 */ + if (!pl->phydev) { + /* PHY absent: always use inband but inband may be disabled */ + /* If the PCS doesn't support inband, then inband must be + * disabled. + */ + if (pcs_link_mode == LINK_INBAND_VALID) + return PHYLINK_PCS_NEG_INBAND_DISABLED; + + /* If the PCS requires inband, then inband must always be + * enabled. + */ + if (pcs_link_mode & LINK_INBAND_REQUIRED) + return PHYLINK_PCS_NEG_INBAND_ENABLED; + + /* For the possible case, fall through to the "legacy" code. */ + } else { + /* PHY present, inband mode depends on the capabilities + * of both. + */ + } + + /* Legacy, so determine inband depending on the advertising bit */ + if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, advertising)) + neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED; + else + neg_mode = PHYLINK_PCS_NEG_INBAND_DISABLED; + return neg_mode; } @@ -1151,10 +1250,6 @@ static void phylink_major_config(struct phylink *pl, bool restart, phylink_dbg(pl, "major config %s\n", phy_modes(state->interface)); - pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->cur_link_an_mode, - state->interface, - state->advertising); - if (pl->using_mac_select_pcs) { pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface); if (IS_ERR(pcs)) { @@ -1167,6 +1262,11 @@ static void phylink_major_config(struct phylink *pl, bool restart, pcs_changed = pcs && pl->pcs != pcs; } + pl->pcs_neg_mode = phylink_pcs_neg_mode(pl, pcs, + pl->cur_link_an_mode, + state->interface, + state->advertising); + phylink_pcs_poll_stop(pl); if (pl->mac_ops->mac_prepare) { @@ -1257,9 +1357,10 @@ static int phylink_change_inband_advert(struct phylink *pl) pl->link_config.pause); /* Recompute the PCS neg mode */ - pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->cur_link_an_mode, - pl->link_config.interface, - pl->link_config.advertising); + pl->pcs_neg_mode = phylink_pcs_neg_mode(pl, pl->pcs, + pl->cur_link_an_mode, + pl->link_config.interface, + pl->link_config.advertising); neg_mode = pl->cur_link_an_mode; if (pl->pcs->neg_mode) @@ -1365,6 +1466,75 @@ static const char *phylink_pause_to_str(int pause) } } +static void phylink_disable_tx_lpi(struct phylink *pl) +{ + phylink_dbg(pl, "disabling tx_lpi\n"); + + if (pl->mac_ops->mac_disable_tx_lpi) + pl->mac_ops->mac_disable_tx_lpi(pl->config); +} + +static void phylink_enable_tx_lpi(struct phylink *pl) +{ + phylink_dbg(pl, "enabling tx_lpi, timer %uus\n", + pl->eee_cfg.tx_lpi_timer); + + if (pl->mac_ops->mac_enable_tx_lpi) + pl->mac_ops->mac_enable_tx_lpi(pl->config, + pl->eee_cfg.tx_lpi_timer); +} + +static bool phylink_eee_is_active(struct phylink *pl) +{ + return phylink_init_eee(pl, pl->config->eee_clk_stop_enable) >= 0; +} + +static void phylink_deactivate_eee(struct phylink *pl) +{ + phylink_dbg(pl, "deactivating EEE, was %sactive\n", + pl->eee_active ? "" : "in"); + + if (pl->eee_active) { + pl->eee_active = false; + phylink_disable_tx_lpi(pl); + } +} + +static void phylink_activate_eee(struct phylink *pl) +{ + pl->eee_active = phylink_eee_is_active(pl); + + phylink_dbg(pl, "can LPI, EEE enabled, %sactive\n", + pl->eee_active ? "" : "in"); + + if (pl->eee_active) + phylink_enable_tx_lpi(pl); +} + +/* Determine whether the MAC has new EEE support. We detect this by checking + * for the two new methods being present, but for DSA it will populate these + * anyway, so also check that lpi_capabilities is non-zero. + */ +static bool phylink_mac_supports_eee(struct phylink *pl) +{ + return pl->mac_ops->mac_disable_tx_lpi && + pl->mac_ops->mac_enable_tx_lpi && + pl->config->lpi_capabilities; +} + +static void phylink_phy_restrict_eee(struct phylink *pl, struct phy_device *phy) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(eee_supported); + + /* Convert the MAC's LPI capabilities to linkmodes */ + linkmode_zero(eee_supported); + phylink_caps_to_linkmodes(eee_supported, pl->config->lpi_capabilities); + + /* Mask out EEE modes that are not supported */ + linkmode_and(phy->supported_eee, phy->supported_eee, eee_supported); + linkmode_and(phy->advertising_eee, phy->advertising_eee, eee_supported); +} + static void phylink_link_up(struct phylink *pl, struct phylink_link_state link_state) { @@ -1411,6 +1581,9 @@ static void phylink_link_up(struct phylink *pl, pl->cur_interface, speed, duplex, !!(link_state.pause & MLO_PAUSE_TX), rx_pause); + if (eeecfg_mac_can_tx_lpi(&pl->eee_cfg)) + phylink_activate_eee(pl); + if (ndev) netif_carrier_on(ndev); @@ -1427,25 +1600,29 @@ static void phylink_link_down(struct phylink *pl) if (ndev) netif_carrier_off(ndev); + + phylink_deactivate_eee(pl); + pl->mac_ops->mac_link_down(pl->config, pl->cur_link_an_mode, pl->cur_interface); phylink_info(pl, "Link is Down\n"); } +static bool phylink_link_is_up(struct phylink *pl) +{ + return pl->netdev ? netif_carrier_ok(pl->netdev) : pl->old_link_state; +} + static void phylink_resolve(struct work_struct *w) { struct phylink *pl = container_of(w, struct phylink, resolve); struct phylink_link_state link_state; - struct net_device *ndev = pl->netdev; bool mac_config = false; bool retrigger = false; bool cur_link_state; mutex_lock(&pl->state_mutex); - if (pl->netdev) - cur_link_state = netif_carrier_ok(ndev); - else - cur_link_state = pl->old_link_state; + cur_link_state = phylink_link_is_up(pl); if (pl->phylink_disable_state) { pl->mac_link_dropped = false; @@ -1681,7 +1858,10 @@ struct phylink *phylink_create(struct phylink_config *config, linkmode_fill(pl->supported); linkmode_copy(pl->link_config.advertising, pl->supported); - phylink_validate(pl, pl->supported, &pl->link_config); + phylink_validate(pl, MLO_AN_FIXED, pl->supported, &pl->link_config); + + /* Set the default EEE configuration */ + pl->eee_cfg = pl->config->eee; ret = phylink_parse_mode(pl, fwnode); if (ret < 0) { @@ -1689,7 +1869,7 @@ struct phylink *phylink_create(struct phylink_config *config, return ERR_PTR(ret); } - if (pl->cfg_link_an_mode == MLO_AN_FIXED) { + if (phylink_mode_fixed(pl->cfg_link_an_mode)) { ret = phylink_parse_fixedlink(pl, fwnode); if (ret < 0) { kfree(pl); @@ -1783,6 +1963,9 @@ 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 @@ -1819,10 +2002,13 @@ 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, supported, state, + return phylink_validate_mask(pl, phy, mode, supported, state, interfaces); } + phylink_dbg(pl, "PHY %s doesn't supply possible interfaces\n", + phydev_name(phy)); + /* Check whether we would use rate matching for the proposed interface * mode. */ @@ -1845,7 +2031,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, supported, state); + return phylink_validate(pl, mode, supported, state); } static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy, @@ -1902,6 +2088,13 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy, /* Restrict the phy advertisement according to the MAC support. */ linkmode_copy(phy->advertising, config.advertising); + + /* If the MAC supports phylink managed EEE, restrict the EEE + * advertisement according to the MAC's LPI capabilities. + */ + if (phylink_mac_supports_eee(pl)) + phylink_phy_restrict_eee(pl, phy); + mutex_unlock(&pl->state_mutex); mutex_unlock(&phy->lock); @@ -1923,8 +2116,8 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy, static int phylink_attach_phy(struct phylink *pl, struct phy_device *phy, phy_interface_t interface) { - if (WARN_ON(pl->cfg_link_an_mode == MLO_AN_FIXED || - (pl->cfg_link_an_mode == MLO_AN_INBAND && + if (WARN_ON(phylink_mode_fixed(pl->cfg_link_an_mode) || + (phylink_mode_inband(pl->cfg_link_an_mode) && phy_interface_mode_is_8023z(interface) && !pl->sfp_bus))) return -EINVAL; @@ -2010,14 +2203,14 @@ int phylink_fwnode_phy_connect(struct phylink *pl, int ret; /* Fixed links and 802.3z are handled without needing a PHY */ - if (pl->cfg_link_an_mode == MLO_AN_FIXED || - (pl->cfg_link_an_mode == MLO_AN_INBAND && + if (phylink_mode_fixed(pl->cfg_link_an_mode) || + (phylink_mode_inband(pl->cfg_link_an_mode) && phy_interface_mode_is_8023z(pl->link_interface))) return 0; phy_fwnode = fwnode_get_phy_node(fwnode); if (IS_ERR(phy_fwnode)) { - if (pl->cfg_link_an_mode == MLO_AN_PHY) + if (phylink_mode_phy(pl->cfg_link_an_mode)) return -ENODEV; return 0; } @@ -2164,7 +2357,7 @@ void phylink_start(struct phylink *pl) phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_STOPPED); - if (pl->cfg_link_an_mode == MLO_AN_FIXED && pl->link_gpio) { + if (phylink_mode_fixed(pl->cfg_link_an_mode) && pl->link_gpio) { int irq = gpiod_to_irq(pl->link_gpio); if (irq > 0) { @@ -2431,6 +2624,39 @@ 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) +{ + 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); + + /* If the PCS doesn't implement inband support, be permissive. */ + if (!(link_inband & LINK_INBAND_VALID)) + return true; + + /* If the PCS can configure the use of inband, allow either state */ + if (link_inband & LINK_INBAND_POSSIBLE) + return true; + + /* Otherwise, require the AN state to be fixed according to the + * required flag. + */ + return an_state == !!(link_inband & LINK_INBAND_REQUIRED); +} + /** * phylink_ethtool_ksettings_set() - set the link settings * @pl: a pointer to a &struct phylink returned from phylink_create() @@ -2492,7 +2718,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 (pl->cur_link_an_mode == MLO_AN_FIXED) { + if (phylink_mode_fixed(pl->cur_link_an_mode)) { if (s->speed != pl->link_config.speed || s->duplex != pl->link_config.duplex) return -EINVAL; @@ -2508,7 +2734,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 (pl->cur_link_an_mode == MLO_AN_FIXED) { + if (phylink_mode_fixed(pl->cur_link_an_mode)) { if (!linkmode_equal(config.advertising, pl->link_config.advertising)) return -EINVAL; @@ -2546,7 +2772,8 @@ int phylink_ethtool_ksettings_set(struct phylink *pl, /* Revalidate with the selected interface */ linkmode_copy(support, pl->supported); - if (phylink_validate(pl, support, &config)) { + if (phylink_validate(pl, pl->cur_link_an_mode, support, + &config)) { phylink_err(pl, "validation of %s/%s with support %*pb failed\n", phylink_an_mode_str(pl->cur_link_an_mode), phy_modes(config.interface), @@ -2556,7 +2783,8 @@ 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, support, &config)) + if (phylink_validate(pl, pl->cur_link_an_mode, support, + &config)) return -EINVAL; } @@ -2566,6 +2794,14 @@ int phylink_ethtool_ksettings_set(struct phylink *pl, phylink_is_empty_linkmode(config.advertising)) return -EINVAL; + /* 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))) + return -EINVAL; + mutex_lock(&pl->state_mutex); pl->link_config.speed = config.speed; pl->link_config.duplex = config.duplex; @@ -2648,7 +2884,7 @@ int phylink_ethtool_set_pauseparam(struct phylink *pl, ASSERT_RTNL(); - if (pl->cur_link_an_mode == MLO_AN_FIXED) + if (phylink_mode_fixed(pl->cur_link_an_mode)) return -EOPNOTSUPP; if (!phylink_test(pl->supported, Pause) && @@ -2756,6 +2992,8 @@ int phylink_init_eee(struct phylink *pl, bool clk_stop_enable) if (pl->phydev) ret = phy_init_eee(pl->phydev, clk_stop_enable); + else if (pl->sfp_bus && phylink_mac_supports_eee(pl)) + ret = 0; return ret; } @@ -2772,8 +3010,19 @@ int phylink_ethtool_get_eee(struct phylink *pl, struct ethtool_eee *eee) ASSERT_RTNL(); - if (pl->phydev) + if (pl->phydev) { ret = phy_ethtool_get_eee(pl->phydev, eee); + } else if (pl->sfp_bus && phylink_mac_supports_eee(pl)) { + /* For optical SFPs, ensure that eee->supported is nonzero. */ + eee->supported = SUPPORTED_FIBRE; + ret = 0; + } + + if (!ret && phylink_mac_supports_eee(pl)) { + /* Overwrite phylib's interpretation of configuration */ + eeecfg_to_eee(&pl->eee_cfg, eee); + eee->eee_active = pl->eee_active; + } return ret; } @@ -2787,11 +3036,52 @@ EXPORT_SYMBOL_GPL(phylink_ethtool_get_eee); int phylink_ethtool_set_eee(struct phylink *pl, struct ethtool_eee *eee) { int ret = -EOPNOTSUPP; + bool mac_eee; ASSERT_RTNL(); + mac_eee = phylink_mac_supports_eee(pl); + + phylink_dbg(pl, "mac %s phylink EEE%s, adv 0x%08x, LPI%s timer %uus\n", + mac_eee ? "supports" : "does not support", + eee->eee_enabled ? ", enabled" : "", eee->advertised, + eee->tx_lpi_enabled ? " enabled" : "", eee->tx_lpi_timer); + + /* Clamp the LPI timer maximum value */ + if (mac_eee && eee->tx_lpi_timer > pl->config->lpi_timer_limit_us) { + eee->tx_lpi_timer = pl->config->lpi_timer_limit_us; + phylink_dbg(pl, "LPI timer limited to %uus\n", + eee->tx_lpi_timer); + } + if (pl->phydev) ret = phy_ethtool_set_eee(pl->phydev, eee); + else if (pl->sfp_bus && phylink_mac_supports_eee(pl)) + ret = 0; + + if (!ret && mac_eee) { + bool can_lpi, old_can_lpi; + + mutex_lock(&pl->state_mutex); + old_can_lpi = eeecfg_mac_can_tx_lpi(&pl->eee_cfg); + eee_to_eeecfg(eee, &pl->eee_cfg); + can_lpi = eeecfg_mac_can_tx_lpi(&pl->eee_cfg); + + phylink_dbg(pl, "can_lpi %u -> %u\n", old_can_lpi, can_lpi); + + /* If the link is up, and the configuration changes the + * LPI permissive state, deal with the change at the MAC. + */ + if (phylink_link_is_up(pl) && old_can_lpi != can_lpi) { + phylink_dbg(pl, "link is up, lpi changed\n"); + if (can_lpi) + phylink_activate_eee(pl); + else + phylink_deactivate_eee(pl); + } + + mutex_unlock(&pl->state_mutex); + } return ret; } @@ -3144,8 +3434,7 @@ static void phylink_sfp_set_config(struct phylink *pl, u8 mode, phylink_mac_initial_config(pl, false); } -static int phylink_sfp_config_phy(struct phylink *pl, u8 mode, - struct phy_device *phy) +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); @@ -3163,7 +3452,7 @@ static int phylink_sfp_config_phy(struct phylink *pl, u8 mode, config.pause = MLO_PAUSE_AN; /* Ignore errors if we're expecting a PHY to attach later */ - ret = phylink_validate(pl, support, &config); + ret = phylink_validate(pl, pl->sfp_link_an_mode, support, &config); if (ret) { phylink_err(pl, "validation with support %*pb failed: %pe\n", __ETHTOOL_LINK_MODE_MASK_NBITS, support, @@ -3181,11 +3470,11 @@ static int phylink_sfp_config_phy(struct phylink *pl, u8 mode, config.interface = iface; linkmode_copy(support1, support); - ret = phylink_validate(pl, support1, &config); + 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(mode), + phylink_an_mode_str(pl->sfp_link_an_mode), phy_modes(config.interface), __ETHTOOL_LINK_MODE_MASK_NBITS, support, ERR_PTR(ret)); @@ -3194,7 +3483,7 @@ static int phylink_sfp_config_phy(struct phylink *pl, u8 mode, pl->link_port = pl->sfp_port; - phylink_sfp_set_config(pl, mode, support, &config); + phylink_sfp_set_config(pl, pl->sfp_link_an_mode, support, &config); return 0; } @@ -3233,8 +3522,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, pl->sfp_support, &config, - interfaces); + ret = phylink_validate_mask(pl, NULL, MLO_AN_INBAND, 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); @@ -3250,10 +3539,17 @@ 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))) { + phylink_err(pl, "autoneg setting not compatible with PCS"); + return -EINVAL; + } + config.interface = interface; /* Ignore errors if we're expecting a PHY to attach later */ - ret = phylink_validate(pl, support, &config); + ret = phylink_validate(pl, MLO_AN_INBAND, support, &config); if (ret) { phylink_err(pl, "validation with support %*pb failed: %pe\n", __ETHTOOL_LINK_MODE_MASK_NBITS, support, @@ -3279,6 +3575,7 @@ 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); @@ -3334,21 +3631,12 @@ static void phylink_sfp_link_up(void *upstream) phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_LINK); } -/* The Broadcom BCM84881 in the Methode DM7052 is unable to provide a SGMII - * or 802.3z control word, so inband will not work. - */ -static bool phylink_phy_no_inband(struct phy_device *phy) -{ - return phy->is_c45 && phy_id_compare(phy->c45_ids.device_ids[1], - 0xae025150, 0xfffffff0); -} - 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; - u8 mode; - int ret; + int link_inband, ret; /* * This is the new way of dealing with flow control for PHYs, @@ -3359,21 +3647,63 @@ static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy) */ phy_support_asym_pause(phy); - if (phylink_phy_no_inband(phy)) - mode = MLO_AN_PHY; - else - mode = MLO_AN_INBAND; - /* Set the PHY's host supported interfaces */ phy_interface_and(phy->host_interfaces, phylink_sfp_interfaces, pl->config->supported_interfaces); - /* Do the initial configuration */ - ret = phylink_sfp_config_phy(pl, mode, phy); - if (ret < 0) - return ret; + if (phy_interface_empty(phy->supported_interfaces)) { + 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; + + phylink_info(pl, "switched to %s/%s link mode\n", + phylink_an_mode_str(pl->sfp_link_an_mode), + phy_modes(interface)); + } + + if (!test_bit(PHYLINK_DISABLE_STOPPED, + &pl->phylink_disable_state)) + phylink_mac_initial_config(pl, false); + } interface = pl->link_config.interface; + ret = phylink_attach_phy(pl, phy, interface); if (ret < 0) return ret; @@ -3501,12 +3831,18 @@ static void phylink_decode_sgmii_word(struct phylink_link_state *state, * @lpa: a 16 bit value which stores the USXGMII auto-negotiation word * * Helper for MAC PCS supporting the USXGMII protocol and the auto-negotiation - * code word. Decode the USXGMII code word and populate the corresponding fields - * (speed, duplex) into the phylink_link_state structure. + * code word. Decode the USXGMII code word and populate the corresponding fields + * (speed, duplex) into the phylink_link_state structure. If the code word + * indicates link is down, or we are unable to decode it, set the link down. */ void phylink_decode_usxgmii_word(struct phylink_link_state *state, uint16_t lpa) { + if (!(lpa & MDIO_USXGMII_LINK)) { + state->link = false; + return; + } + switch (lpa & MDIO_USXGMII_SPD_MASK) { case MDIO_USXGMII_10: state->speed = SPEED_10; |