summaryrefslogtreecommitdiff
path: root/drivers/net
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-05-07 10:49:42 +0100
commitfbc05acc0d2e1797904ffcdc2f9b753daf0ae4e6 (patch)
treed86704d8552eb8e956bb852434d9db72095824f5 /drivers/net
parent14cafd1f0a2742967ac25286444451c3c57b56e7 (diff)
net: phylink: add pcs_query_inband() *WIP*
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. This revised version uses enable/disable/bypass flags to indicate the capabilities. Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
Diffstat (limited to 'drivers/net')
-rw-r--r--drivers/net/phy/phylink.c141
1 files changed, 123 insertions, 18 deletions
diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index c764a7a8ac66..b9857f4d7f99 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -1125,6 +1125,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
@@ -1142,11 +1144,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:
@@ -1158,10 +1168,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:
@@ -1172,20 +1179,116 @@ 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;
}
@@ -1223,7 +1326,8 @@ 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->req_link_an_mode,
+ pl->pcs_neg_mode = phylink_pcs_neg_mode(pl, pcs,
+ pl->req_link_an_mode,
state->interface,
state->advertising);
@@ -1327,9 +1431,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->req_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.