From 8c0eeb4eab5526050b5df39a77878e71e4f47f5b Mon Sep 17 00:00:00 2001 From: "Russell King (Oracle)" Date: Thu, 8 Feb 2024 16:12:43 +0000 Subject: 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) --- drivers/net/phy/phylink.c | 87 ++++++++++++++++++++++++++++++++++++++++++----- include/linux/phylink.h | 17 +++++++++ 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, @@ -510,6 +513,20 @@ struct phylink_pcs_ops { 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. -- cgit