diff options
Diffstat (limited to 'drivers/net/phy/phylink.c')
-rw-r--r-- | drivers/net/phy/phylink.c | 136 |
1 files changed, 134 insertions, 2 deletions
diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 5f416314a76c..417e4608b4a2 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -132,6 +132,9 @@ static int phylink_parse_fixedlink(struct phylink *pl, struct device_node *np) if (of_property_read_bool(fixed_node, "full-duplex")) pl->link_config.duplex = DUPLEX_FULL; + + /* We treat the "pause" and "asym-pause" terminology as + * defining the link partner's ability. */ if (of_property_read_bool(fixed_node, "pause")) pl->link_config.pause |= MLO_PAUSE_SYM; if (of_property_read_bool(fixed_node, "asym-pause")) @@ -277,6 +280,56 @@ static void phylink_get_fixed_state(struct phylink *pl, struct phylink_link_stat state->link = !!gpiod_get_value(pl->link_gpio); } +/* Flow control is resolved according to our and the link partners + * advertisments using the following drawn from the 802.3 specs: + * Local device Link partner + * Pause AsymDir Pause AsymDir Result + * 1 X 1 X TX+RX + * 0 1 1 1 RX + * 1 1 0 1 TX + */ +static void phylink_resolve_flow(struct phylink *pl, + struct phylink_link_state *state) +{ + int new_pause = 0; + + if (pl->link_config.pause & MLO_PAUSE_AN) { + int pause = 0; + + if (phylink_test(pl->link_config.advertising, Pause)) + pause |= MLO_PAUSE_SYM; + if (phylink_test(pl->link_config.advertising, Asym_Pause)) + pause |= MLO_PAUSE_ASYM; + + pause &= state->pause; + + if (pause & MLO_PAUSE_SYM) + new_pause = MLO_PAUSE_TX | MLO_PAUSE_RX; + else if (pause & MLO_PAUSE_ASYM) + new_pause = state->pause & MLO_PAUSE_SYM ? + MLO_PAUSE_RX : MLO_PAUSE_TX; + } else { + new_pause = pl->link_config.pause & MLO_PAUSE_TXRX_MASK; + } + + state->pause &= ~MLO_PAUSE_TXRX_MASK; + state->pause |= new_pause; +} + +static const char *phylink_pause_to_str(int pause) +{ + switch (pause & MLO_PAUSE_TXRX_MASK) { + case MLO_PAUSE_TX | MLO_PAUSE_RX: + return "rx/tx"; + case MLO_PAUSE_TX: + return "tx"; + case MLO_PAUSE_RX: + return "rx"; + default: + return "off"; + } +} + static void phylink_resolve(struct work_struct *w) { struct phylink *pl = container_of(w, struct phylink, resolve); @@ -290,6 +343,7 @@ static void phylink_resolve(struct work_struct *w) switch (pl->link_an_mode) { case MLO_AN_PHY: link_state = pl->phy_state; + phylink_resolve_flow(pl, &link_state); break; case MLO_AN_FIXED: @@ -298,9 +352,12 @@ static void phylink_resolve(struct work_struct *w) case MLO_AN_SGMII: phylink_get_mac_state(pl, &link_state); - if (pl->phydev) + if (pl->phydev) { link_state.link = link_state.link && pl->phy_state.link; + link_state.pause |= pl->phy_state.pause; + phylink_resolve_flow(pl, &link_state); + } break; case MLO_AN_8023Z: @@ -330,7 +387,7 @@ static void phylink_resolve(struct work_struct *w) "Link is Up - %s/%s - flow control %s\n", phy_speed_to_str(link_state.speed), phy_duplex_to_str(link_state.duplex), - link_state.pause ? "rx/tx" : "off"); + phylink_pause_to_str(link_state.pause)); } } mutex_unlock(&pl->state_mutex); @@ -358,6 +415,7 @@ struct phylink *phylink_create(struct net_device *ndev, struct device_node *np, pl->netdev = ndev; pl->link_interface = iface; pl->link_port = PORT_MII; + pl->link_config.pause = MLO_PAUSE_AN; pl->link_config.speed = SPEED_UNKNOWN; pl->link_config.duplex = DUPLEX_UNKNOWN; pl->ops = ops; @@ -580,6 +638,7 @@ void phylink_start(struct phylink *pl) * a fixed-link to start with the correct parameters, and also * ensures that we set the appropriate advertisment for Serdes links. */ + phylink_resolve_flow(pl, &pl->link_config); phylink_mac_config(pl, &pl->link_config); clear_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state); @@ -797,6 +856,79 @@ int phylink_ethtool_nway_reset(struct phylink *pl) } EXPORT_SYMBOL_GPL(phylink_ethtool_nway_reset); +void phylink_ethtool_get_pauseparam(struct phylink *pl, + struct ethtool_pauseparam *pause) +{ + mutex_lock(&pl->config_mutex); + + pause->autoneg = !!(pl->link_config.pause & MLO_PAUSE_AN); + pause->rx_pause = !!(pl->link_config.pause & MLO_PAUSE_RX); + pause->tx_pause = !!(pl->link_config.pause & MLO_PAUSE_TX); + + mutex_unlock(&pl->config_mutex); +} +EXPORT_SYMBOL_GPL(phylink_ethtool_get_pauseparam); + +static int __phylink_ethtool_set_pauseparam(struct phylink *pl, + struct ethtool_pauseparam *pause) +{ + struct phylink_link_state *config = &pl->link_config; + + if (!phylink_test(pl->supported, Pause) && + !phylink_test(pl->supported, Asym_Pause)) + return -EOPNOTSUPP; + + if (!phylink_test(pl->supported, Asym_Pause) && + !pause->autoneg && pause->rx_pause != pause->tx_pause) + return -EINVAL; + + config->pause &= ~(MLO_PAUSE_AN | MLO_PAUSE_TXRX_MASK); + + if (pause->autoneg) + config->pause |= MLO_PAUSE_AN; + if (pause->rx_pause) + config->pause |= MLO_PAUSE_RX; + if (pause->tx_pause) + config->pause |= MLO_PAUSE_TX; + + if (!test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state)) { + switch (pl->link_an_mode) { + case MLO_AN_PHY: + /* Silently mark the carrier down, and then trigger a resolve */ + netif_carrier_off(pl->netdev); + phylink_run_resolve(pl); + break; + + case MLO_AN_FIXED: + /* Should we allow fixed links to change against the config? */ + phylink_resolve_flow(pl, config); + phylink_mac_config(pl, config); + break; + + case MLO_AN_SGMII: + case MLO_AN_8023Z: + phylink_mac_config(pl, config); + phylink_mac_an_restart(pl); + break; + } + } + + return 0; +} + +int phylink_ethtool_set_pauseparam(struct phylink *pl, + struct ethtool_pauseparam *pause) +{ + int ret; + + mutex_lock(&pl->config_mutex); + ret = __phylink_ethtool_set_pauseparam(pl, pause); + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_ethtool_set_pauseparam); + /* This emulates MII registers for a fixed-mode phy operating as per the * passed in state. "aneg" defines if we report negotiation is possible. * |