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.c87
1 files changed, 79 insertions, 8 deletions
diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 066d60f185d5..3f92941e227e 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -1048,6 +1048,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)
{
@@ -1148,6 +1157,8 @@ static void phylink_pcs_an_restart(struct phylink *pl)
/**
* phylink_pcs_neg_mode() - helper to determine PCS inband mode
+ * @pl:
+ * @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
@@ -1165,12 +1176,22 @@ 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;
+ if (phylink_autoneg_inband(mode)) {
+ pcs_link_mode = phylink_pcs_query_inband(pcs, interface);
+ if (pl->phydev)
+ phy_link_mode = phy_query_inband(pl->phydev, interface);
+ }
+
switch (interface) {
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_QSGMII:
@@ -1224,10 +1245,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)) {
@@ -1240,6 +1257,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) {
@@ -1330,9 +1352,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)
@@ -2596,6 +2619,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()
@@ -2733,6 +2789,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;
@@ -3470,6 +3534,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 */