diff options
author | Russell King (Oracle) <rmk+kernel@armlinux.org.uk> | 2024-02-08 16:12:43 +0000 |
---|---|---|
committer | Russell King (Oracle) <rmk+kernel@armlinux.org.uk> | 2024-04-16 16:51:04 +0100 |
commit | 7cb3771a782bb7db06169b98d15f4f20bb40ecbc (patch) | |
tree | bbc6e431d0cb60b55953998a8358bbdb3fea450f /drivers | |
parent | 2c621b33b510f6b9adbadcf1eec01cdf62fa9bd6 (diff) |
net: phylink: add pcs_query_inband()
Add a pcs_query_inband() interface which reflects phy_query_inband()
for PHYs. This can be used to determine for the specified interface
mode whether in-band signalling is supported by the PCS, and whether
the PCS requires in-band signalling.
This is used to determine whether we should use inband autonegotiation
in inband mode, which may be required or may be unsupported in various
interface modes.
Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/net/phy/phylink.c | 180 |
1 files changed, 159 insertions, 21 deletions
diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index e96eded31e4c..b526488a84cc 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -980,6 +980,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) { @@ -1079,6 +1088,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 @@ -1096,11 +1107,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: @@ -1112,10 +1131,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: @@ -1126,20 +1142,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; } @@ -1154,10 +1242,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)) { @@ -1170,6 +1254,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) { @@ -1260,9 +1349,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) @@ -2520,6 +2610,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() @@ -2655,6 +2778,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; @@ -3393,6 +3524,13 @@ 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 */ |