summaryrefslogtreecommitdiff
path: root/drivers/net/phy/phylink.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/phy/phylink.c')
-rw-r--r--drivers/net/phy/phylink.c745
1 files changed, 571 insertions, 174 deletions
diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index ed0b4ccaa6a6..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);
@@ -86,6 +87,9 @@ struct phylink {
DECLARE_PHY_INTERFACE_MASK(sfp_interfaces);
__ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_support);
u8 sfp_port;
+
+ struct eee_config eee_cfg;
+ bool eee_active;
};
#define phylink_printk(level, pl, fmt, ...) \
@@ -175,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) {
@@ -893,7 +915,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;
@@ -977,6 +999,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)
{
@@ -1030,15 +1061,34 @@ 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 (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);
}
@@ -1056,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)
@@ -1070,12 +1120,14 @@ 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);
}
/**
* 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 +1145,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 +1169,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,23 +1180,128 @@ 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);
+ 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 (s1 != empty || s3 != empty) {
+ 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. However, there are
+ * some PHYs e.g. BCM84881 which do not support in-band.
+ */
+ if (type == INBAND_CISCO_SGMII) {
+ /* 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)
+ 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 */
+ }
+ }
+
+ /* 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_DISABLE)
+ return PHYLINK_PCS_NEG_INBAND_DISABLED;
+
+ /* If the PCS requires inband, then inband must always be
+ * enabled.
+ */
+ if (pcs_link_mode == LINK_INBAND_ENABLE)
+ 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;
}
+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)
{
@@ -1149,11 +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));
-
- pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->cur_link_an_mode,
- state->interface,
- state->advertising);
+ 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);
@@ -1167,10 +1327,25 @@ 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->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",
@@ -1204,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;
@@ -1220,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",
@@ -1251,17 +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->cur_link_an_mode,
- pl->link_config.interface,
- pl->link_config.advertising);
+ pl->pcs_neg_mode = phylink_pcs_neg_mode(pl, pl->pcs,
+ pl->req_link_an_mode,
+ pl->link_config.interface,
+ pl->link_config.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);
- neg_mode = pl->cur_link_an_mode;
+ neg_mode = pl->act_link_an_mode;
if (pl->pcs->neg_mode)
neg_mode = pl->pcs_neg_mode;
@@ -1326,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;
@@ -1365,6 +1546,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)
{
@@ -1400,17 +1650,20 @@ 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);
+ if (eeecfg_mac_can_tx_lpi(&pl->eee_cfg))
+ phylink_activate_eee(pl);
+
if (ndev)
netif_carrier_on(ndev);
@@ -1427,25 +1680,29 @@ static void phylink_link_down(struct phylink *pl)
if (ndev)
netif_carrier_off(ndev);
- pl->mac_ops->mac_link_down(pl->config, pl->cur_link_an_mode,
+
+ phylink_deactivate_eee(pl);
+
+ pl->mac_ops->mac_link_down(pl->config, pl->act_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;
@@ -1453,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
@@ -1683,13 +1937,16 @@ struct phylink *phylink_create(struct phylink_config *config,
linkmode_copy(pl->link_config.advertising, pl->supported);
phylink_validate(pl, 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) {
kfree(pl);
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);
@@ -1697,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) {
@@ -1823,6 +2081,9 @@ static int phylink_validate_phy(struct phylink *pl, struct phy_device *phy,
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.
*/
@@ -1902,6 +2163,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 +2191,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 +2278,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;
}
@@ -2141,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 */
@@ -2164,7 +2432,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) {
@@ -2352,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);
@@ -2400,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
@@ -2431,6 +2725,26 @@ int phylink_ethtool_ksettings_get(struct phylink *pl,
}
EXPORT_SYMBOL_GPL(phylink_ethtool_ksettings_get);
+static bool phylink_validate_pcs_inband_autoneg(struct phylink *pl,
+ phy_interface_t interface,
+ unsigned long *adv)
+{
+ unsigned int inband = phylink_query_inband(pl, interface);
+ unsigned int mask;
+
+ /* If the PCS doesn't implement inband support, be permissive. */
+ if (!inband)
+ return true;
+
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, adv))
+ mask = LINK_INBAND_ENABLE;
+ else
+ mask = LINK_INBAND_DISABLE;
+
+ /* Check whether the PCS implements the required mode */
+ return !!(inband & mask);
+}
+
/**
* phylink_ethtool_ksettings_set() - set the link settings
* @pl: a pointer to a &struct phylink returned from phylink_create()
@@ -2492,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 (pl->cur_link_an_mode == MLO_AN_FIXED) {
+ if (phylink_mode_fixed(pl->req_link_an_mode)) {
if (s->speed != pl->link_config.speed ||
s->duplex != pl->link_config.duplex)
return -EINVAL;
@@ -2508,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 (pl->cur_link_an_mode == MLO_AN_FIXED) {
+ if (phylink_mode_fixed(pl->req_link_an_mode)) {
if (!linkmode_equal(config.advertising,
pl->link_config.advertising))
return -EINVAL;
@@ -2534,21 +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, 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;
@@ -2566,6 +2875,13 @@ 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_inband_autoneg(pl, config.interface,
+ config.advertising))
+ return -EINVAL;
+
mutex_lock(&pl->state_mutex);
pl->link_config.speed = config.speed;
pl->link_config.duplex = config.duplex;
@@ -2648,7 +2964,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->req_link_an_mode))
return -EOPNOTSUPP;
if (!phylink_test(pl->supported, Pause) &&
@@ -2756,6 +3072,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 +3090,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 +3116,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;
}
@@ -2912,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);
@@ -2937,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;
@@ -3107,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",
@@ -3127,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;
@@ -3144,13 +3515,10 @@ 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);
struct phylink_link_state config;
- phy_interface_t iface;
int ret;
linkmode_copy(support, phy->supported);
@@ -3171,30 +3539,27 @@ static int phylink_sfp_config_phy(struct phylink *pl, u8 mode,
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, support1, &config);
- if (ret) {
- phylink_err(pl,
- "validation of %s/%s with support %*pb failed: %pe\n",
- phylink_an_mode_str(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, mode, support, &config);
+ phylink_sfp_set_config(pl, support, &config);
return 0;
}
@@ -3250,6 +3615,12 @@ 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_inband_autoneg(pl, interface,
+ 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 */
@@ -3263,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;
}
@@ -3334,20 +3705,11 @@ 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;
/*
@@ -3359,30 +3721,59 @@ 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 */
+ return phylink_sfp_config_phy(pl, phy);
+ }
+
+ 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));
- interface = pl->link_config.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)
@@ -3501,12 +3892,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;