int phylink_ethtool_set_pauseparam(struct phylink *pl, struct ethtool_pauseparam *pause) { struct phylink_link_state *config = &pl->link_config; WARN_ON(!lockdep_rtnl_is_held()); 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; }
/** * phylink_start() - start a phylink instance * @pl: a pointer to a &struct phylink returned from phylink_create() * * Start the phylink instance specified by @pl, configuring the MAC for the * desired link mode(s) and negotiation style. This should be called from the * network device driver's &struct net_device_ops ndo_open() method. */ void phylink_start(struct phylink *pl) { ASSERT_RTNL(); netdev_info(pl->netdev, "configuring for %s/%s link mode\n", phylink_an_mode_str(pl->link_an_mode), phy_modes(pl->link_config.interface)); /* Always set the carrier off */ netif_carrier_off(pl->netdev); /* Apply the link configuration to the MAC when starting. This allows * a fixed-link to start with the correct parameters, and also * ensures that we set the appropriate advertisement for Serdes links. */ phylink_resolve_flow(pl, &pl->link_config); phylink_mac_config(pl, &pl->link_config); /* Restart autonegotiation if using 802.3z to ensure that the link * parameters are properly negotiated. This is necessary for DSA * switches using 802.3z negotiation to ensure they see our modes. */ phylink_mac_an_restart(pl); clear_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state); phylink_run_resolve(pl); if (pl->link_an_mode == MLO_AN_FIXED && !IS_ERR(pl->link_gpio)) mod_timer(&pl->link_poll, jiffies + HZ); if (pl->sfp_bus) sfp_upstream_start(pl->sfp_bus); if (pl->phydev) phy_start(pl->phydev); }
void phylink_start(struct phylink *pl) { WARN_ON(!lockdep_rtnl_is_held()); netdev_info(pl->netdev, "configuring for %s/%s link mode\n", phylink_an_mode_str(pl->link_an_mode), phy_modes(pl->link_config.interface)); /* Apply the link configuration to the MAC when starting. This allows * 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); phylink_run_resolve(pl); if (pl->sfp_bus) sfp_upstream_start(pl->sfp_bus); if (pl->phydev) phy_start(pl->phydev); }
int phylink_ethtool_ksettings_set(struct phylink *pl, const struct ethtool_link_ksettings *kset) { struct ethtool_link_ksettings our_kset; struct phylink_link_state config; int ret; WARN_ON(!lockdep_rtnl_is_held()); if (kset->base.autoneg != AUTONEG_DISABLE && kset->base.autoneg != AUTONEG_ENABLE) return -EINVAL; config = pl->link_config; /* Mask out unsupported advertisments */ linkmode_and(config.advertising, kset->link_modes.advertising, pl->supported); /* FIXME: should we reject autoneg if phy/mac does not support it? */ if (kset->base.autoneg == AUTONEG_DISABLE) { const struct phy_setting *s; /* Autonegotiation disabled, select a suitable speed and * duplex. */ s = phy_lookup_setting(kset->base.speed, kset->base.duplex, pl->supported, __ETHTOOL_LINK_MODE_MASK_NBITS, false); if (!s) return -EINVAL; /* If we have a fixed link (as specified by firmware), refuse * to change link parameters. */ if (pl->link_an_mode == MLO_AN_FIXED && (s->speed != pl->link_config.speed || s->duplex != pl->link_config.duplex)) return -EINVAL; config.speed = s->speed; config.duplex = s->duplex; config.an_enabled = false; __clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, config.advertising); } else { /* If we have a fixed link, refuse to enable autonegotiation */ if (pl->link_an_mode == MLO_AN_FIXED) return -EINVAL; config.speed = SPEED_UNKNOWN; config.duplex = DUPLEX_UNKNOWN; config.an_enabled = true; __set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, config.advertising); } if (phylink_validate(pl, pl->supported, &config)) return -EINVAL; /* If autonegotiation is enabled, we must have an advertisment */ if (config.an_enabled && phylink_is_empty_linkmode(config.advertising)) return -EINVAL; our_kset = *kset; linkmode_copy(our_kset.link_modes.advertising, config.advertising); our_kset.base.speed = config.speed; our_kset.base.duplex = config.duplex; /* If we have a PHY, configure the phy */ if (pl->phydev) { ret = phy_ethtool_ksettings_set(pl->phydev, &our_kset); if (ret) return ret; } mutex_lock(&pl->state_mutex); /* Configure the MAC to match the new settings */ linkmode_copy(pl->link_config.advertising, our_kset.link_modes.advertising); pl->link_config.speed = our_kset.base.speed; pl->link_config.duplex = our_kset.base.duplex; pl->link_config.an_enabled = our_kset.base.autoneg != AUTONEG_DISABLE; if (!test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state)) { phylink_mac_config(pl, &pl->link_config); phylink_mac_an_restart(pl); } mutex_unlock(&pl->state_mutex); return 0; }
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; mutex_lock(&pl->state_mutex); if (pl->phylink_disable_state) { pl->mac_link_dropped = false; link_state.link = false; } else if (pl->mac_link_dropped) { link_state.link = false; } else { switch (pl->link_an_mode) { case MLO_AN_PHY: link_state = pl->phy_state; phylink_resolve_flow(pl, &link_state); phylink_mac_config(pl, &link_state); break; case MLO_AN_FIXED: phylink_get_fixed_state(pl, &link_state); phylink_mac_config(pl, &link_state); break; case MLO_AN_SGMII: phylink_get_mac_state(pl, &link_state); if (pl->phydev) { bool changed = false; link_state.link = link_state.link && pl->phy_state.link; if (pl->phy_state.interface != link_state.interface) { link_state.interface = pl->phy_state.interface; changed = true; } /* Propagate the flow control from the PHY * to the MAC. Also propagate the interface * if changed. */ if (pl->phy_state.link || changed) { link_state.pause |= pl->phy_state.pause; phylink_resolve_flow(pl, &link_state); phylink_mac_config(pl, &link_state); } } break; case MLO_AN_8023Z: phylink_get_mac_state(pl, &link_state); break; } } if (link_state.link != netif_carrier_ok(ndev)) { if (!link_state.link) { netif_carrier_off(ndev); pl->ops->mac_link_down(ndev, pl->link_an_mode); netdev_info(ndev, "Link is Down\n"); } else { pl->ops->mac_link_up(ndev, pl->link_an_mode, pl->phydev); netif_carrier_on(ndev); netdev_info(ndev, "Link is Up - %s/%s - flow control %s\n", phy_speed_to_str(link_state.speed), phy_duplex_to_str(link_state.duplex), phylink_pause_to_str(link_state.pause)); } } if (!link_state.link && pl->mac_link_dropped) { pl->mac_link_dropped = false; queue_work(system_power_efficient_wq, &pl->resolve); } mutex_unlock(&pl->state_mutex); }
static int phylink_sfp_module_insert(void *upstream, const struct sfp_eeprom_id *id) { struct phylink *pl = upstream; __ETHTOOL_DECLARE_LINK_MODE_MASK(support) = { 0, }; struct phylink_link_state config; phy_interface_t iface; int mode, ret = 0; bool changed; u8 port; sfp_parse_support(pl->sfp_bus, id, support); port = sfp_parse_port(pl->sfp_bus, id, support); iface = sfp_parse_interface(pl->sfp_bus, id); WARN_ON(!lockdep_rtnl_is_held()); switch (iface) { case PHY_INTERFACE_MODE_SGMII: mode = MLO_AN_SGMII; break; case PHY_INTERFACE_MODE_1000BASEX: mode = MLO_AN_8023Z; break; default: return -EINVAL; } memset(&config, 0, sizeof(config)); linkmode_copy(config.advertising, support); config.interface = iface; config.speed = SPEED_UNKNOWN; config.duplex = DUPLEX_UNKNOWN; config.pause = MLO_PAUSE_AN; config.an_enabled = pl->link_config.an_enabled; /* Ignore errors if we're expecting a PHY to attach later */ ret = phylink_validate(pl, support, &config); if (ret) { netdev_err(pl->netdev, "validation of %s/%s with support %*pb failed: %d\n", phylink_an_mode_str(mode), phy_modes(config.interface), __ETHTOOL_LINK_MODE_MASK_NBITS, support, ret); return ret; } netdev_dbg(pl->netdev, "requesting link mode %s/%s with support %*pb\n", phylink_an_mode_str(mode), phy_modes(config.interface), __ETHTOOL_LINK_MODE_MASK_NBITS, support); if (mode == MLO_AN_8023Z && pl->phydev) return -EINVAL; changed = !bitmap_equal(pl->supported, support, __ETHTOOL_LINK_MODE_MASK_NBITS); if (changed) { linkmode_copy(pl->supported, support); linkmode_copy(pl->link_config.advertising, config.advertising); } if (pl->link_an_mode != mode || pl->link_config.interface != config.interface) { pl->link_config.interface = config.interface; pl->link_an_mode = mode; changed = true; netdev_info(pl->netdev, "switched to %s/%s link mode\n", phylink_an_mode_str(mode), phy_modes(config.interface)); } pl->link_port = port; if (changed && !test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state)) phylink_mac_config(pl, &pl->link_config); return ret; }
static int phylink_sfp_module_insert(void *upstream, const struct sfp_eeprom_id *id) { struct phylink *pl = upstream; __ETHTOOL_DECLARE_LINK_MODE_MASK(support) = { 0, }; struct phylink_link_state config; phy_interface_t iface; int ret = 0; bool changed; u8 port; ASSERT_RTNL(); sfp_parse_support(pl->sfp_bus, id, support); port = sfp_parse_port(pl->sfp_bus, id, support); memset(&config, 0, sizeof(config)); linkmode_copy(config.advertising, support); config.interface = PHY_INTERFACE_MODE_NA; config.speed = SPEED_UNKNOWN; config.duplex = DUPLEX_UNKNOWN; config.pause = MLO_PAUSE_AN; config.an_enabled = pl->link_config.an_enabled; /* Ignore errors if we're expecting a PHY to attach later */ ret = phylink_validate(pl, support, &config); if (ret) { netdev_err(pl->netdev, "validation with support %*pb failed: %d\n", __ETHTOOL_LINK_MODE_MASK_NBITS, support, ret); return ret; } iface = sfp_select_interface(pl->sfp_bus, id, config.advertising); if (iface == PHY_INTERFACE_MODE_NA) { netdev_err(pl->netdev, "selection of interface failed, advertisement %*pb\n", __ETHTOOL_LINK_MODE_MASK_NBITS, config.advertising); return -EINVAL; } config.interface = iface; ret = phylink_validate(pl, support, &config); if (ret) { netdev_err(pl->netdev, "validation of %s/%s with support %*pb failed: %d\n", phylink_an_mode_str(MLO_AN_INBAND), phy_modes(config.interface), __ETHTOOL_LINK_MODE_MASK_NBITS, support, ret); return ret; } netdev_dbg(pl->netdev, "requesting link mode %s/%s with support %*pb\n", phylink_an_mode_str(MLO_AN_INBAND), phy_modes(config.interface), __ETHTOOL_LINK_MODE_MASK_NBITS, support); if (phy_interface_mode_is_8023z(iface) && pl->phydev) return -EINVAL; changed = !bitmap_equal(pl->supported, support, __ETHTOOL_LINK_MODE_MASK_NBITS); if (changed) { linkmode_copy(pl->supported, support); linkmode_copy(pl->link_config.advertising, config.advertising); } if (pl->link_an_mode != MLO_AN_INBAND || pl->link_config.interface != config.interface) { pl->link_config.interface = config.interface; pl->link_an_mode = MLO_AN_INBAND; changed = true; netdev_info(pl->netdev, "switched to %s/%s link mode\n", phylink_an_mode_str(MLO_AN_INBAND), phy_modes(config.interface)); } pl->link_port = port; if (changed && !test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state)) phylink_mac_config(pl, &pl->link_config); return ret; }
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; mutex_lock(&pl->state_mutex); if (pl->phylink_disable_state) { pl->mac_link_dropped = false; link_state.link = false; } else if (pl->mac_link_dropped) { link_state.link = false; } else { switch (pl->link_an_mode) { case MLO_AN_PHY: link_state = pl->phy_state; phylink_resolve_flow(pl, &link_state); phylink_mac_config_up(pl, &link_state); break; case MLO_AN_FIXED: phylink_get_fixed_state(pl, &link_state); phylink_mac_config_up(pl, &link_state); break; case MLO_AN_INBAND: phylink_get_mac_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; /* Only update if the PHY link is up */ if (pl->phydev && pl->phy_state.link) { link_state.interface = pl->phy_state.interface; /* If we have a PHY, we need to update with * the pause mode bits. */ link_state.pause |= pl->phy_state.pause; phylink_resolve_flow(pl, &link_state); phylink_mac_config(pl, &link_state); } break; } } if (link_state.link != netif_carrier_ok(ndev)) { if (!link_state.link) { netif_carrier_off(ndev); pl->ops->mac_link_down(ndev, pl->link_an_mode, pl->phy_state.interface); netdev_info(ndev, "Link is Down\n"); } else { pl->ops->mac_link_up(ndev, pl->link_an_mode, pl->phy_state.interface, pl->phydev); netif_carrier_on(ndev); netdev_info(ndev, "Link is Up - %s/%s - flow control %s\n", phy_speed_to_str(link_state.speed), phy_duplex_to_str(link_state.duplex), phylink_pause_to_str(link_state.pause)); } } if (!link_state.link && pl->mac_link_dropped) { pl->mac_link_dropped = false; queue_work(system_power_efficient_wq, &pl->resolve); } mutex_unlock(&pl->state_mutex); }
static void phylink_mac_config_up(struct phylink *pl, const struct phylink_link_state *state) { if (state->link) phylink_mac_config(pl, state); }