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-03-06 10:43:36 +0000
commit8c0eeb4eab5526050b5df39a77878e71e4f47f5b (patch)
tree1039239f20acb439e183f4fad12538863fee621e
parent716dadc3d52bae265c020ee6ebc9fb059caab725 (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. Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
-rw-r--r--drivers/net/phy/phylink.c87
-rw-r--r--include/linux/phylink.h17
2 files changed, 96 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 */
diff --git a/include/linux/phylink.h b/include/linux/phylink.h
index 57d867290662..8664b3442a3a 100644
--- a/include/linux/phylink.h
+++ b/include/linux/phylink.h
@@ -460,6 +460,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)
@@ -474,6 +475,8 @@ struct phylink_pcs_ops {
int (*pcs_validate)(struct phylink_pcs *pcs, unsigned int mode,
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,
@@ -511,6 +514,20 @@ int pcs_validate(struct phylink_pcs *pcs, unsigned int mode,
unsigned long *supported, 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.
*/