Merge branch 'net-add-negotiation-of-in-band-capabilities'

Russell King says:

====================
net: add negotiation of in-band capabilities

This is a repost without RFC for this series, shrunk down to 13 patches
by removing the non-Marvell PCS.

Phylink's handling of in-band has been deficient for a long time, and
people keep hitting problems with it. Notably, situations with the way-
to-late standardized 2500Base-X and whether that should or should not
have in-band enabled. We have also been carrying a hack in the form of
phylink_phy_no_inband() for a PHY that has been used on a SFP module,
but has no in-band capabilities, not even for SGMII.

When phylink is trying to operate in in-band mode, this series will look
at the capabilities of the MAC-side PCS and PHY, and work out whether
in-band can or should be used, programming the PHY as appropriate. This
includes in-band bypass mode at the PHY.

We don't... yet... support bypass on the MAC side PCS, because that
requires yet more complexity.

Patch 1 passes struct phylink and struct phylink_pcs into
phylink_pcs_neg_mode() so we can look at more state in this function in
a future patch.

Patch 2 splits "cur_link_an_mode" (the MLO_AN_* mode) into two separate
purposes - a requested and an active mode. The active mode is the one
we will be using for the MAC, which becomes dependent on the result of
in-band negotiation.

Patch 3 adds debug to phylink_major_config() so we can see what is going
on with the requested and active AN modes.

Patch 4 adds to phylib a method to get the in-band capabilities of the
PHY from phylib. Patches 5 and 6 add implementations for BCM84881 and
some Marvell PHYs found on SFPs.

Patch 7 adds to phylib a method to configure the PHY in-band signalling,
and patch 8 implements it for those Marvell PHYs that support the method
in patch 4.

Patch 9 does the same as patch 4 but for the MAC-side PCS, with patches
10 and 11 adding support to Marvell NETA and PP2.

Patch 12 adds the code to phylink_pcs_neg_mode() which looks at the
capabilities, and works out whether to use in-band or out-band mode for
driving the link between the MAC PCS and PHY.

Patch 13 removes the phylink_phy_no_inband() hack now that we are
publishing the in-band capabilities from the BCM84881 PHY driver.

Three more PCS, omitted from this series due to the limit of 15 patches,
will be sent once this has been merged.
====================

Link: https://patch.msgid.link/Z08kCwxdkU4n2V6x@shell.armlinux.org.uk
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski
2024-12-04 19:19:09 -08:00
8 changed files with 474 additions and 92 deletions

View File

@@ -3960,20 +3960,27 @@ static struct mvneta_port *mvneta_pcs_to_port(struct phylink_pcs *pcs)
return container_of(pcs, struct mvneta_port, phylink_pcs);
}
static int mvneta_pcs_validate(struct phylink_pcs *pcs,
unsigned long *supported,
const struct phylink_link_state *state)
static unsigned int mvneta_pcs_inband_caps(struct phylink_pcs *pcs,
phy_interface_t interface)
{
/* We only support QSGMII, SGMII, 802.3z and RGMII modes.
* When in 802.3z mode, we must have AN enabled:
/* When operating in an 802.3z mode, we must have AN enabled:
* "Bit 2 Field InBandAnEn In-band Auto-Negotiation enable. ...
* When <PortType> = 1 (1000BASE-X) this field must be set to 1."
* Therefore, inband is "required".
*/
if (phy_interface_mode_is_8023z(state->interface) &&
!phylink_test(state->advertising, Autoneg))
return -EINVAL;
if (phy_interface_mode_is_8023z(interface))
return LINK_INBAND_ENABLE;
return 0;
/* QSGMII, SGMII and RGMII can be configured to use inband
* signalling of the AN result. Indicate these as "possible".
*/
if (interface == PHY_INTERFACE_MODE_SGMII ||
interface == PHY_INTERFACE_MODE_QSGMII ||
phy_interface_mode_is_rgmii(interface))
return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
/* For any other modes, indicate that inband is not supported. */
return LINK_INBAND_DISABLE;
}
static void mvneta_pcs_get_state(struct phylink_pcs *pcs,
@@ -4071,7 +4078,7 @@ static void mvneta_pcs_an_restart(struct phylink_pcs *pcs)
}
static const struct phylink_pcs_ops mvneta_phylink_pcs_ops = {
.pcs_validate = mvneta_pcs_validate,
.pcs_inband_caps = mvneta_pcs_inband_caps,
.pcs_get_state = mvneta_pcs_get_state,
.pcs_config = mvneta_pcs_config,
.pcs_an_restart = mvneta_pcs_an_restart,

View File

@@ -6224,19 +6224,26 @@ static const struct phylink_pcs_ops mvpp2_phylink_xlg_pcs_ops = {
.pcs_config = mvpp2_xlg_pcs_config,
};
static int mvpp2_gmac_pcs_validate(struct phylink_pcs *pcs,
unsigned long *supported,
const struct phylink_link_state *state)
static unsigned int mvpp2_gmac_pcs_inband_caps(struct phylink_pcs *pcs,
phy_interface_t interface)
{
/* When in 802.3z mode, we must have AN enabled:
/* When operating in an 802.3z mode, we must have AN enabled:
* Bit 2 Field InBandAnEn In-band Auto-Negotiation enable. ...
* When <PortType> = 1 (1000BASE-X) this field must be set to 1.
* Therefore, inband is "required".
*/
if (phy_interface_mode_is_8023z(state->interface) &&
!phylink_test(state->advertising, Autoneg))
return -EINVAL;
if (phy_interface_mode_is_8023z(interface))
return LINK_INBAND_ENABLE;
return 0;
/* SGMII and RGMII can be configured to use inband signalling of the
* AN result. Indicate these as "possible".
*/
if (interface == PHY_INTERFACE_MODE_SGMII ||
phy_interface_mode_is_rgmii(interface))
return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
/* For any other modes, indicate that inband is not supported. */
return LINK_INBAND_DISABLE;
}
static void mvpp2_gmac_pcs_get_state(struct phylink_pcs *pcs,
@@ -6343,7 +6350,7 @@ static void mvpp2_gmac_pcs_an_restart(struct phylink_pcs *pcs)
}
static const struct phylink_pcs_ops mvpp2_phylink_gmac_pcs_ops = {
.pcs_validate = mvpp2_gmac_pcs_validate,
.pcs_inband_caps = mvpp2_gmac_pcs_inband_caps,
.pcs_get_state = mvpp2_gmac_pcs_get_state,
.pcs_config = mvpp2_gmac_pcs_config,
.pcs_an_restart = mvpp2_gmac_pcs_an_restart,

View File

@@ -235,11 +235,21 @@ static int bcm84881_read_status(struct phy_device *phydev)
return genphy_c45_read_mdix(phydev);
}
/* The Broadcom BCM84881 in the Methode DM7052 is unable to provide a SGMII
* or 802.3z control word, so inband will not work.
*/
static unsigned int bcm84881_inband_caps(struct phy_device *phydev,
phy_interface_t interface)
{
return LINK_INBAND_DISABLE;
}
static struct phy_driver bcm84881_drivers[] = {
{
.phy_id = 0xae025150,
.phy_id_mask = 0xfffffff0,
.name = "Broadcom BCM84881",
.inband_caps = bcm84881_inband_caps,
.config_init = bcm84881_config_init,
.probe = bcm84881_probe,
.get_features = bcm84881_get_features,

View File

@@ -717,6 +717,48 @@ static int marvell_config_aneg_fiber(struct phy_device *phydev)
return genphy_check_and_restart_aneg(phydev, changed);
}
static unsigned int m88e1111_inband_caps(struct phy_device *phydev,
phy_interface_t interface)
{
/* In 1000base-X and SGMII modes, the inband mode can be changed
* through the Fibre page BMCR ANENABLE bit.
*/
if (interface == PHY_INTERFACE_MODE_1000BASEX ||
interface == PHY_INTERFACE_MODE_SGMII)
return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE |
LINK_INBAND_BYPASS;
return 0;
}
static int m88e1111_config_inband(struct phy_device *phydev, unsigned int modes)
{
u16 extsr, bmcr;
int err;
if (phydev->interface != PHY_INTERFACE_MODE_1000BASEX &&
phydev->interface != PHY_INTERFACE_MODE_SGMII)
return -EINVAL;
if (modes == LINK_INBAND_BYPASS)
extsr = MII_M1111_HWCFG_SERIAL_AN_BYPASS;
else
extsr = 0;
if (modes == LINK_INBAND_DISABLE)
bmcr = 0;
else
bmcr = BMCR_ANENABLE;
err = phy_modify(phydev, MII_M1111_PHY_EXT_SR,
MII_M1111_HWCFG_SERIAL_AN_BYPASS, extsr);
if (err < 0)
return extsr;
return phy_modify_paged(phydev, MII_MARVELL_FIBER_PAGE, MII_BMCR,
BMCR_ANENABLE, bmcr);
}
static int m88e1111_config_aneg(struct phy_device *phydev)
{
int extsr = phy_read(phydev, MII_M1111_PHY_EXT_SR);
@@ -3677,6 +3719,8 @@ static struct phy_driver marvell_drivers[] = {
.name = "Marvell 88E1112",
/* PHY_GBIT_FEATURES */
.probe = marvell_probe,
.inband_caps = m88e1111_inband_caps,
.config_inband = m88e1111_config_inband,
.config_init = m88e1112_config_init,
.config_aneg = marvell_config_aneg,
.config_intr = marvell_config_intr,
@@ -3698,6 +3742,8 @@ static struct phy_driver marvell_drivers[] = {
/* PHY_GBIT_FEATURES */
.flags = PHY_POLL_CABLE_TEST,
.probe = marvell_probe,
.inband_caps = m88e1111_inband_caps,
.config_inband = m88e1111_config_inband,
.config_init = m88e1111gbe_config_init,
.config_aneg = m88e1111_config_aneg,
.read_status = marvell_read_status,
@@ -3721,6 +3767,8 @@ static struct phy_driver marvell_drivers[] = {
.name = "Marvell 88E1111 (Finisar)",
/* PHY_GBIT_FEATURES */
.probe = marvell_probe,
.inband_caps = m88e1111_inband_caps,
.config_inband = m88e1111_config_inband,
.config_init = m88e1111gbe_config_init,
.config_aneg = m88e1111_config_aneg,
.read_status = marvell_read_status,

View File

@@ -1005,6 +1005,59 @@ static int phy_check_link_status(struct phy_device *phydev)
return 0;
}
/**
* phy_inband_caps - query which in-band signalling modes are supported
* @phydev: a pointer to a &struct phy_device
* @interface: the interface mode for the PHY
*
* 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
* by the PHY for this interface mode.
*/
unsigned int phy_inband_caps(struct phy_device *phydev,
phy_interface_t interface)
{
if (phydev->drv && phydev->drv->inband_caps)
return phydev->drv->inband_caps(phydev, interface);
return 0;
}
EXPORT_SYMBOL_GPL(phy_inband_caps);
/**
* phy_config_inband - configure the desired PHY in-band mode
* @phydev: the phy_device struct
* @modes: in-band modes to configure
*
* Description: disables, enables or enables-with-bypass in-band signalling
* between the PHY and host system.
*
* Returns: zero on success, or negative errno value.
*/
int phy_config_inband(struct phy_device *phydev, unsigned int modes)
{
int err;
if (!!(modes & LINK_INBAND_DISABLE) +
!!(modes & LINK_INBAND_ENABLE) +
!!(modes & LINK_INBAND_BYPASS) != 1)
return -EINVAL;
mutex_lock(&phydev->lock);
if (!phydev->drv)
err = -EIO;
else if (!phydev->drv->config_inband)
err = -EOPNOTSUPP;
else
err = phydev->drv->config_inband(phydev, modes);
mutex_unlock(&phydev->lock);
return err;
}
EXPORT_SYMBOL(phy_config_inband);
/**
* _phy_start_aneg - start auto-negotiation for this PHY device
* @phydev: the phy_device struct

View File

@@ -56,7 +56,8 @@ struct phylink {
struct phy_device *phydev;
phy_interface_t link_interface; /* PHY_INTERFACE_xxx */
u8 cfg_link_an_mode; /* MLO_AN_xxx */
u8 cur_link_an_mode;
u8 req_link_an_mode; /* Requested MLO_AN_xxx mode */
u8 act_link_an_mode; /* Active MLO_AN_xxx mode */
u8 link_port; /* The current non-phy ethtool port */
__ETHTOOL_DECLARE_LINK_MODE_MASK(supported);
@@ -74,6 +75,7 @@ struct phylink {
struct mutex state_mutex;
struct phylink_link_state phy_state;
unsigned int phy_ib_mode;
struct work_struct resolve;
unsigned int pcs_neg_mode;
unsigned int pcs_state;
@@ -174,6 +176,24 @@ static const char *phylink_an_mode_str(unsigned int mode)
return mode < ARRAY_SIZE(modestr) ? modestr[mode] : "unknown";
}
static const char *phylink_pcs_mode_str(unsigned int mode)
{
if (!mode)
return "none";
if (mode & PHYLINK_PCS_NEG_OUTBAND)
return "outband";
if (mode & PHYLINK_PCS_NEG_INBAND) {
if (mode & PHYLINK_PCS_NEG_ENABLED)
return "inband,an-enabled";
else
return "inband,an-disabled";
}
return "unknown";
}
static unsigned int phylink_interface_signal_rate(phy_interface_t interface)
{
switch (interface) {
@@ -971,6 +991,15 @@ static void phylink_resolve_an_pause(struct phylink_link_state *state)
}
}
static unsigned int phylink_pcs_inband_caps(struct phylink_pcs *pcs,
phy_interface_t interface)
{
if (pcs && pcs->ops->pcs_inband_caps)
return pcs->ops->pcs_inband_caps(pcs, interface);
return 0;
}
static void phylink_pcs_pre_config(struct phylink_pcs *pcs,
phy_interface_t interface)
{
@@ -1024,6 +1053,24 @@ static void phylink_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
pcs->ops->pcs_link_up(pcs, neg_mode, interface, speed, duplex);
}
/* Query inband for a specific interface mode, asking the MAC for the
* PCS which will be used to handle the interface mode.
*/
static unsigned int phylink_inband_caps(struct phylink *pl,
phy_interface_t interface)
{
struct phylink_pcs *pcs;
if (!pl->mac_ops->mac_select_pcs)
return 0;
pcs = pl->mac_ops->mac_select_pcs(pl->config, interface);
if (!pcs)
return 0;
return phylink_pcs_inband_caps(pcs, interface);
}
static void phylink_pcs_poll_stop(struct phylink *pl)
{
if (pl->cfg_link_an_mode == MLO_AN_INBAND)
@@ -1065,13 +1112,13 @@ static void phylink_mac_config(struct phylink *pl,
phylink_dbg(pl,
"%s: mode=%s/%s/%s adv=%*pb pause=%02x\n",
__func__, phylink_an_mode_str(pl->cur_link_an_mode),
__func__, phylink_an_mode_str(pl->act_link_an_mode),
phy_modes(st.interface),
phy_rate_matching_to_str(st.rate_matching),
__ETHTOOL_LINK_MODE_MASK_NBITS, st.advertising,
st.pause);
pl->mac_ops->mac_config(pl->config, pl->cur_link_an_mode, &st);
pl->mac_ops->mac_config(pl->config, pl->act_link_an_mode, &st);
}
static void phylink_pcs_an_restart(struct phylink *pl)
@@ -1079,13 +1126,14 @@ static void phylink_pcs_an_restart(struct phylink *pl)
if (pl->pcs && linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
pl->link_config.advertising) &&
phy_interface_mode_is_8023z(pl->link_config.interface) &&
phylink_autoneg_inband(pl->cur_link_an_mode))
phylink_autoneg_inband(pl->act_link_an_mode))
pl->pcs->ops->pcs_an_restart(pl->pcs);
}
/**
* phylink_pcs_neg_mode() - helper to determine PCS inband mode
* @mode: one of %MLO_AN_FIXED, %MLO_AN_PHY, %MLO_AN_INBAND.
* @pl: a pointer to a &struct phylink returned from phylink_create()
* @pcs: a pointer to &struct phylink_pcs
* @interface: interface mode to be used
* @advertising: adertisement ethtool link mode mask
*
@@ -1102,11 +1150,21 @@ 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,
phy_interface_t interface,
const unsigned long *advertising)
static void phylink_pcs_neg_mode(struct phylink *pl, struct phylink_pcs *pcs,
phy_interface_t interface,
const unsigned long *advertising)
{
unsigned int neg_mode;
unsigned int pcs_ib_caps = 0;
unsigned int phy_ib_caps = 0;
unsigned int neg_mode, mode;
enum {
INBAND_CISCO_SGMII,
INBAND_BASEX,
} type;
mode = pl->req_link_an_mode;
pl->phy_ib_mode = 0;
switch (interface) {
case PHY_INTERFACE_MODE_SGMII:
@@ -1119,10 +1177,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:
@@ -1133,21 +1188,143 @@ 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))
type = INBAND_BASEX;
break;
default:
pl->pcs_neg_mode = PHYLINK_PCS_NEG_NONE;
pl->act_link_an_mode = mode;
return;
}
if (pcs)
pcs_ib_caps = phylink_pcs_inband_caps(pcs, interface);
if (pl->phydev)
phy_ib_caps = phy_inband_caps(pl->phydev, interface);
phylink_dbg(pl, "interface %s inband modes: pcs=%02x phy=%02x\n",
phy_modes(interface), pcs_ib_caps, phy_ib_caps);
if (!phylink_autoneg_inband(mode)) {
bool pcs_ib_only = false;
bool phy_ib_only = false;
if (pcs_ib_caps && pcs_ib_caps != LINK_INBAND_DISABLE) {
/* PCS supports reporting in-band capabilities, and
* supports more than disable mode.
*/
if (pcs_ib_caps & LINK_INBAND_DISABLE)
neg_mode = PHYLINK_PCS_NEG_OUTBAND;
else if (pcs_ib_caps & LINK_INBAND_ENABLE)
pcs_ib_only = true;
}
if (phy_ib_caps && phy_ib_caps != LINK_INBAND_DISABLE) {
/* PHY supports in-band capabilities, and supports
* more than disable mode.
*/
if (phy_ib_caps & LINK_INBAND_DISABLE)
pl->phy_ib_mode = LINK_INBAND_DISABLE;
else if (phy_ib_caps & LINK_INBAND_BYPASS)
pl->phy_ib_mode = LINK_INBAND_BYPASS;
else if (phy_ib_caps & LINK_INBAND_ENABLE)
phy_ib_only = true;
}
/* 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_ib_only || phy_ib_only)
phylink_warn(pl,
"firmware wants %s mode, but %s%s%s requires inband\n",
phylink_an_mode_str(mode),
pcs_ib_only ? "PCS" : "",
pcs_ib_only && phy_ib_only ? " and " : "",
phy_ib_only ? "PHY" : "");
neg_mode = PHYLINK_PCS_NEG_OUTBAND;
} else if (type == INBAND_CISCO_SGMII || pl->phydev) {
/* For SGMII modes which are designed to be used with PHYs, or
* Base-X with a PHY, we try to use in-band mode where-ever
* possible. However, there are some PHYs e.g. BCM84881 which
* do not support in-band.
*/
const unsigned int inband_ok = LINK_INBAND_ENABLE |
LINK_INBAND_BYPASS;
const unsigned int outband_ok = LINK_INBAND_DISABLE |
LINK_INBAND_BYPASS;
/* 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_ib_caps || pcs_ib_caps & inband_ok) &&
(!phy_ib_caps || phy_ib_caps & inband_ok)) {
/* In-band supported or unknown at both ends. Enable
* in-band mode with or without bypass at the PHY.
*/
if (phy_ib_caps & LINK_INBAND_ENABLE)
pl->phy_ib_mode = LINK_INBAND_ENABLE;
else if (phy_ib_caps & LINK_INBAND_BYPASS)
pl->phy_ib_mode = LINK_INBAND_BYPASS;
neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
} else if ((!pcs_ib_caps || pcs_ib_caps & outband_ok) &&
(!phy_ib_caps || phy_ib_caps & outband_ok)) {
/* Either in-band not supported at at least one end.
* In-band bypass at the other end is possible.
*/
if (phy_ib_caps & LINK_INBAND_DISABLE)
pl->phy_ib_mode = LINK_INBAND_DISABLE;
else if (phy_ib_caps & LINK_INBAND_BYPASS)
pl->phy_ib_mode = LINK_INBAND_BYPASS;
neg_mode = PHYLINK_PCS_NEG_OUTBAND;
if (pl->phydev)
mode = MLO_AN_PHY;
} else {
/* invalid */
phylink_warn(pl, "%s: incompatible in-band capabilities, trying in-band",
phy_modes(interface));
neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
}
} else {
/* For Base-X without a PHY */
if (pcs_ib_caps == LINK_INBAND_DISABLE)
/* If the PCS doesn't support inband, then inband must
* be disabled.
*/
neg_mode = PHYLINK_PCS_NEG_INBAND_DISABLED;
else if (pcs_ib_caps == LINK_INBAND_ENABLE)
/* If the PCS requires inband, then inband must always
* be enabled.
*/
neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
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;
break;
default:
neg_mode = PHYLINK_PCS_NEG_NONE;
break;
}
return neg_mode;
pl->pcs_neg_mode = neg_mode;
pl->act_link_an_mode = mode;
}
static void phylink_major_config(struct phylink *pl, bool restart,
@@ -1159,11 +1336,9 @@ static void phylink_major_config(struct phylink *pl, bool restart,
unsigned int neg_mode;
int err;
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);
phylink_dbg(pl, "major config, requested %s/%s\n",
phylink_an_mode_str(pl->req_link_an_mode),
phy_modes(state->interface));
if (pl->mac_ops->mac_select_pcs) {
pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
@@ -1177,10 +1352,17 @@ static void phylink_major_config(struct phylink *pl, bool restart,
pcs_changed = pl->pcs != pcs;
}
phylink_pcs_neg_mode(pl, pcs, state->interface, state->advertising);
phylink_dbg(pl, "major config, active %s/%s/%s\n",
phylink_an_mode_str(pl->act_link_an_mode),
phylink_pcs_mode_str(pl->pcs_neg_mode),
phy_modes(state->interface));
phylink_pcs_poll_stop(pl);
if (pl->mac_ops->mac_prepare) {
err = pl->mac_ops->mac_prepare(pl->config, pl->cur_link_an_mode,
err = pl->mac_ops->mac_prepare(pl->config, pl->act_link_an_mode,
state->interface);
if (err < 0) {
phylink_err(pl, "mac_prepare failed: %pe\n",
@@ -1214,7 +1396,7 @@ static void phylink_major_config(struct phylink *pl, bool restart,
if (pl->pcs_state == PCS_STATE_STARTING || pcs_changed)
phylink_pcs_enable(pl->pcs);
neg_mode = pl->cur_link_an_mode;
neg_mode = pl->act_link_an_mode;
if (pl->pcs && pl->pcs->neg_mode)
neg_mode = pl->pcs_neg_mode;
@@ -1230,13 +1412,20 @@ static void phylink_major_config(struct phylink *pl, bool restart,
phylink_pcs_an_restart(pl);
if (pl->mac_ops->mac_finish) {
err = pl->mac_ops->mac_finish(pl->config, pl->cur_link_an_mode,
err = pl->mac_ops->mac_finish(pl->config, pl->act_link_an_mode,
state->interface);
if (err < 0)
phylink_err(pl, "mac_finish failed: %pe\n",
ERR_PTR(err));
}
if (pl->phydev && pl->phy_ib_mode) {
err = phy_config_inband(pl->phydev, pl->phy_ib_mode);
if (err < 0)
phylink_err(pl, "phy_config_inband: %pe\n",
ERR_PTR(err));
}
if (pl->sfp_bus) {
rate_kbd = phylink_interface_signal_rate(state->interface);
if (rate_kbd)
@@ -1261,17 +1450,16 @@ static int phylink_change_inband_advert(struct phylink *pl)
return 0;
phylink_dbg(pl, "%s: mode=%s/%s adv=%*pb pause=%02x\n", __func__,
phylink_an_mode_str(pl->cur_link_an_mode),
phylink_an_mode_str(pl->req_link_an_mode),
phy_modes(pl->link_config.interface),
__ETHTOOL_LINK_MODE_MASK_NBITS, pl->link_config.advertising,
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);
phylink_pcs_neg_mode(pl, pl->pcs, pl->link_config.interface,
pl->link_config.advertising);
neg_mode = pl->cur_link_an_mode;
neg_mode = pl->act_link_an_mode;
if (pl->pcs->neg_mode)
neg_mode = pl->pcs_neg_mode;
@@ -1336,7 +1524,7 @@ static void phylink_mac_initial_config(struct phylink *pl, bool force_restart)
{
struct phylink_link_state link_state;
switch (pl->cur_link_an_mode) {
switch (pl->req_link_an_mode) {
case MLO_AN_PHY:
link_state = pl->phy_state;
break;
@@ -1410,14 +1598,14 @@ static void phylink_link_up(struct phylink *pl,
pl->cur_interface = link_state.interface;
neg_mode = pl->cur_link_an_mode;
neg_mode = pl->act_link_an_mode;
if (pl->pcs && pl->pcs->neg_mode)
neg_mode = pl->pcs_neg_mode;
phylink_pcs_link_up(pl->pcs, neg_mode, pl->cur_interface, speed,
duplex);
pl->mac_ops->mac_link_up(pl->config, pl->phydev, pl->cur_link_an_mode,
pl->mac_ops->mac_link_up(pl->config, pl->phydev, pl->act_link_an_mode,
pl->cur_interface, speed, duplex,
!!(link_state.pause & MLO_PAUSE_TX), rx_pause);
@@ -1437,7 +1625,7 @@ static void phylink_link_down(struct phylink *pl)
if (ndev)
netif_carrier_off(ndev);
pl->mac_ops->mac_link_down(pl->config, pl->cur_link_an_mode,
pl->mac_ops->mac_link_down(pl->config, pl->act_link_an_mode,
pl->cur_interface);
phylink_info(pl, "Link is Down\n");
}
@@ -1463,10 +1651,10 @@ static void phylink_resolve(struct work_struct *w)
} else if (pl->link_failed) {
link_state.link = false;
retrigger = true;
} else if (pl->cur_link_an_mode == MLO_AN_FIXED) {
} else if (pl->act_link_an_mode == MLO_AN_FIXED) {
phylink_get_fixed_state(pl, &link_state);
mac_config = link_state.link;
} else if (pl->cur_link_an_mode == MLO_AN_PHY) {
} else if (pl->act_link_an_mode == MLO_AN_PHY) {
link_state = pl->phy_state;
mac_config = link_state.link;
} else {
@@ -1520,7 +1708,7 @@ static void phylink_resolve(struct work_struct *w)
}
}
if (pl->cur_link_an_mode != MLO_AN_FIXED)
if (pl->act_link_an_mode != MLO_AN_FIXED)
phylink_apply_manual_flow(pl, &link_state);
if (mac_config) {
@@ -1644,7 +1832,7 @@ int phylink_set_fixed_link(struct phylink *pl,
pl->link_config.an_complete = 1;
pl->cfg_link_an_mode = MLO_AN_FIXED;
pl->cur_link_an_mode = pl->cfg_link_an_mode;
pl->req_link_an_mode = pl->cfg_link_an_mode;
return 0;
}
@@ -1732,7 +1920,7 @@ struct phylink *phylink_create(struct phylink_config *config,
}
}
pl->cur_link_an_mode = pl->cfg_link_an_mode;
pl->req_link_an_mode = pl->cfg_link_an_mode;
ret = phylink_register_sfp(pl, fwnode);
if (ret < 0) {
@@ -2189,7 +2377,7 @@ void phylink_start(struct phylink *pl)
ASSERT_RTNL();
phylink_info(pl, "configuring for %s/%s link mode\n",
phylink_an_mode_str(pl->cur_link_an_mode),
phylink_an_mode_str(pl->req_link_an_mode),
phy_modes(pl->link_config.interface));
/* Always set the carrier off */
@@ -2474,7 +2662,7 @@ int phylink_ethtool_ksettings_get(struct phylink *pl,
linkmode_copy(kset->link_modes.supported, pl->supported);
switch (pl->cur_link_an_mode) {
switch (pl->act_link_an_mode) {
case MLO_AN_FIXED:
/* We are using fixed settings. Report these as the
* current link settings - and note that these also
@@ -2505,6 +2693,26 @@ int phylink_ethtool_ksettings_get(struct phylink *pl,
}
EXPORT_SYMBOL_GPL(phylink_ethtool_ksettings_get);
static bool phylink_validate_pcs_inband_autoneg(struct phylink *pl,
phy_interface_t interface,
unsigned long *adv)
{
unsigned int inband = phylink_inband_caps(pl, interface);
unsigned int mask;
/* If the PCS doesn't implement inband support, be permissive. */
if (!inband)
return true;
if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, adv))
mask = LINK_INBAND_ENABLE;
else
mask = LINK_INBAND_DISABLE;
/* Check whether the PCS implements the required mode */
return !!(inband & mask);
}
/**
* phylink_ethtool_ksettings_set() - set the link settings
* @pl: a pointer to a &struct phylink returned from phylink_create()
@@ -2566,7 +2774,7 @@ int phylink_ethtool_ksettings_set(struct phylink *pl,
/* If we have a fixed link, refuse to change link parameters.
* If the link parameters match, accept them but do nothing.
*/
if (pl->cur_link_an_mode == MLO_AN_FIXED) {
if (pl->req_link_an_mode == MLO_AN_FIXED) {
if (s->speed != pl->link_config.speed ||
s->duplex != pl->link_config.duplex)
return -EINVAL;
@@ -2582,7 +2790,7 @@ int phylink_ethtool_ksettings_set(struct phylink *pl,
* is our default case) but do not allow the advertisement to
* be changed. If the advertisement matches, simply return.
*/
if (pl->cur_link_an_mode == MLO_AN_FIXED) {
if (pl->req_link_an_mode == MLO_AN_FIXED) {
if (!linkmode_equal(config.advertising,
pl->link_config.advertising))
return -EINVAL;
@@ -2617,7 +2825,7 @@ int phylink_ethtool_ksettings_set(struct phylink *pl,
linkmode_copy(support, pl->supported);
if (phylink_validate(pl, support, &config)) {
phylink_err(pl, "validation of %s/%s with support %*pb failed\n",
phylink_an_mode_str(pl->cur_link_an_mode),
phylink_an_mode_str(pl->req_link_an_mode),
phy_modes(config.interface),
__ETHTOOL_LINK_MODE_MASK_NBITS, support);
return -EINVAL;
@@ -2635,6 +2843,13 @@ 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_inband_autoneg(pl, config.interface,
config.advertising))
return -EINVAL;
mutex_lock(&pl->state_mutex);
pl->link_config.speed = config.speed;
pl->link_config.duplex = config.duplex;
@@ -2717,7 +2932,7 @@ int phylink_ethtool_set_pauseparam(struct phylink *pl,
ASSERT_RTNL();
if (pl->cur_link_an_mode == MLO_AN_FIXED)
if (pl->req_link_an_mode == MLO_AN_FIXED)
return -EOPNOTSUPP;
if (!phylink_test(pl->supported, Pause) &&
@@ -2981,7 +3196,7 @@ static int phylink_mii_read(struct phylink *pl, unsigned int phy_id,
struct phylink_link_state state;
int val = 0xffff;
switch (pl->cur_link_an_mode) {
switch (pl->act_link_an_mode) {
case MLO_AN_FIXED:
if (phy_id == 0) {
phylink_get_fixed_state(pl, &state);
@@ -3006,7 +3221,7 @@ static int phylink_mii_read(struct phylink *pl, unsigned int phy_id,
static int phylink_mii_write(struct phylink *pl, unsigned int phy_id,
unsigned int reg, unsigned int val)
{
switch (pl->cur_link_an_mode) {
switch (pl->act_link_an_mode) {
case MLO_AN_FIXED:
break;
@@ -3176,10 +3391,11 @@ static phy_interface_t phylink_choose_sfp_interface(struct phylink *pl,
return interface;
}
static void phylink_sfp_set_config(struct phylink *pl, u8 mode,
static void phylink_sfp_set_config(struct phylink *pl,
unsigned long *supported,
struct phylink_link_state *state)
{
u8 mode = MLO_AN_INBAND;
bool changed = false;
phylink_dbg(pl, "requesting link mode %s/%s with support %*pb\n",
@@ -3196,9 +3412,9 @@ static void phylink_sfp_set_config(struct phylink *pl, u8 mode,
changed = true;
}
if (pl->cur_link_an_mode != mode ||
if (pl->req_link_an_mode != mode ||
pl->link_config.interface != state->interface) {
pl->cur_link_an_mode = mode;
pl->req_link_an_mode = mode;
pl->link_config.interface = state->interface;
changed = true;
@@ -3213,8 +3429,7 @@ static void phylink_sfp_set_config(struct phylink *pl, u8 mode,
phylink_mac_initial_config(pl, false);
}
static int phylink_sfp_config_phy(struct phylink *pl, u8 mode,
struct phy_device *phy)
static int phylink_sfp_config_phy(struct phylink *pl, struct phy_device *phy)
{
__ETHTOOL_DECLARE_LINK_MODE_MASK(support);
struct phylink_link_state config;
@@ -3258,7 +3473,7 @@ static int phylink_sfp_config_phy(struct phylink *pl, u8 mode,
pl->link_port = pl->sfp_port;
phylink_sfp_set_config(pl, mode, support, &config);
phylink_sfp_set_config(pl, support, &config);
return 0;
}
@@ -3314,6 +3529,12 @@ 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_inband_autoneg(pl, interface,
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 */
@@ -3327,7 +3548,7 @@ static int phylink_sfp_config_optical(struct phylink *pl)
pl->link_port = pl->sfp_port;
phylink_sfp_set_config(pl, MLO_AN_INBAND, pl->sfp_support, &config);
phylink_sfp_set_config(pl, pl->sfp_support, &config);
return 0;
}
@@ -3398,19 +3619,9 @@ static void phylink_sfp_link_up(void *upstream)
phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_LINK);
}
/* The Broadcom BCM84881 in the Methode DM7052 is unable to provide a SGMII
* or 802.3z control word, so inband will not work.
*/
static bool phylink_phy_no_inband(struct phy_device *phy)
{
return phy->is_c45 && phy_id_compare(phy->c45_ids.device_ids[1],
0xae025150, 0xfffffff0);
}
static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy)
{
struct phylink *pl = upstream;
u8 mode;
/*
* This is the new way of dealing with flow control for PHYs,
@@ -3421,17 +3632,12 @@ static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy)
*/
phy_support_asym_pause(phy);
if (phylink_phy_no_inband(phy))
mode = MLO_AN_PHY;
else
mode = MLO_AN_INBAND;
/* Set the PHY's host supported interfaces */
phy_interface_and(phy->host_interfaces, phylink_sfp_interfaces,
pl->config->supported_interfaces);
/* Do the initial configuration */
return phylink_sfp_config_phy(pl, mode, phy);
return phylink_sfp_config_phy(pl, phy);
}
static void phylink_sfp_disconnect_phy(void *upstream,

View File

@@ -817,6 +817,24 @@ struct phy_tdr_config {
};
#define PHY_PAIR_ALL -1
/**
* enum link_inband_signalling - in-band signalling modes that are supported
*
* @LINK_INBAND_DISABLE: in-band signalling can be disabled
* @LINK_INBAND_ENABLE: in-band signalling can be enabled without bypass
* @LINK_INBAND_BYPASS: in-band signalling can be enabled with bypass
*
* The possible and required bits can only be used if the valid bit is set.
* If possible is clear, that means inband signalling can not be used.
* Required is only valid when possible is set, and means that inband
* signalling must be used.
*/
enum link_inband_signalling {
LINK_INBAND_DISABLE = BIT(0),
LINK_INBAND_ENABLE = BIT(1),
LINK_INBAND_BYPASS = BIT(2),
};
/**
* struct phy_plca_cfg - Configuration of the PLCA (Physical Layer Collision
* Avoidance) Reconciliation Sublayer.
@@ -956,6 +974,19 @@ struct phy_driver {
*/
int (*get_features)(struct phy_device *phydev);
/**
* @inband_caps: query whether in-band is supported for the given PHY
* interface mode. Returns a bitmask of bits defined by enum
* link_inband_signalling.
*/
unsigned int (*inband_caps)(struct phy_device *phydev,
phy_interface_t interface);
/**
* @config_inband: configure in-band mode for the PHY
*/
int (*config_inband)(struct phy_device *phydev, unsigned int modes);
/**
* @get_rate_matching: Get the supported type of rate matching for a
* particular phy interface. This is used by phy consumers to determine
@@ -1818,6 +1849,9 @@ int phy_config_aneg(struct phy_device *phydev);
int _phy_start_aneg(struct phy_device *phydev);
int phy_start_aneg(struct phy_device *phydev);
int phy_aneg_done(struct phy_device *phydev);
unsigned int phy_inband_caps(struct phy_device *phydev,
phy_interface_t interface);
int phy_config_inband(struct phy_device *phydev, unsigned int modes);
int phy_speed_down(struct phy_device *phydev, bool sync);
int phy_speed_up(struct phy_device *phydev);
bool phy_check_valid(int speed, int duplex, unsigned long *features);

View File

@@ -419,6 +419,7 @@ struct phylink_pcs {
/**
* struct phylink_pcs_ops - MAC PCS operations structure.
* @pcs_validate: validate the link configuration.
* @pcs_inband_caps: 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)
@@ -434,6 +435,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_inband_caps)(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,
@@ -470,6 +473,20 @@ struct phylink_pcs_ops {
int pcs_validate(struct phylink_pcs *pcs, unsigned long *supported,
const struct phylink_link_state *state);
/**
* pcs_inband_caps - query PCS in-band capabilities 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_inband_caps(struct phylink_pcs *pcs,
phy_interface_t interface);
/**
* pcs_enable() - enable the PCS.
* @pcs: a pointer to a &struct phylink_pcs.