summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRussell King (Oracle) <rmk+kernel@armlinux.org.uk>2024-02-08 16:12:43 +0000
committerRussell King (Oracle) <rmk+kernel@armlinux.org.uk>2024-04-16 16:51:04 +0100
commit7cb3771a782bb7db06169b98d15f4f20bb40ecbc (patch)
treebbc6e431d0cb60b55953998a8358bbdb3fea450f
parent2c621b33b510f6b9adbadcf1eec01cdf62fa9bd6 (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>
-rw-r--r--drivers/net/phy/phylink.c180
-rw-r--r--include/linux/phylink.h17
2 files changed, 176 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 */
diff --git a/include/linux/phylink.h b/include/linux/phylink.h
index e1fa495c6f70..924cfdcc73f2 100644
--- a/include/linux/phylink.h
+++ b/include/linux/phylink.h
@@ -445,6 +445,7 @@ struct phylink_pcs {
/**
* struct phylink_pcs_ops - MAC PCS operations structure.
* @pcs_validate: validate the link configuration.
+ * @pcs_query_inband: query inband support for interface mode.
* @pcs_enable: enable the PCS.
* @pcs_disable: disable the PCS.
* @pcs_pre_config: pre-mac_config method (for errata)
@@ -458,6 +459,8 @@ struct phylink_pcs {
struct phylink_pcs_ops {
int (*pcs_validate)(struct phylink_pcs *pcs, unsigned long *supported,
const struct phylink_link_state *state);
+ unsigned int (*pcs_query_inband)(struct phylink_pcs *pcs,
+ phy_interface_t interface);
int (*pcs_enable)(struct phylink_pcs *pcs);
void (*pcs_disable)(struct phylink_pcs *pcs);
void (*pcs_pre_config)(struct phylink_pcs *pcs,
@@ -494,6 +497,20 @@ int pcs_validate(struct phylink_pcs *pcs, unsigned long *supported,
const struct phylink_link_state *state);
/**
+ * pcs_query_inband - query inband support for interface mode.
+ * @pcs: a pointer to a &struct phylink_pcs.
+ * @interface: interface mode to be queried
+ *
+ * Returns zero if it is unknown what in-band signalling is supported by the
+ * PHY (e.g. because the PHY driver doesn't implement the method.) Otherwise,
+ * returns a bit mask of the LINK_INBAND_* values from
+ * &enum link_inband_signalling to describe which inband modes are supported
+ * for this interface mode.
+ */
+unsigned int pcs_query_inband(struct phylink_pcs *pcs,
+ phy_interface_t interface);
+
+/**
* pcs_enable() - enable the PCS.
* @pcs: a pointer to a &struct phylink_pcs.
*/