The ethtool Tx LPI fields currently update phylib software state and notify an attached MAC, but they do not configure PHYs which generate LPI autonomously. This can make a successful ethtool request leave the PHY hardware unchanged. Add a PHY driver callback for applying the Tx LPI enable state and timer. Call it before updating EEE advertisement, and attempt to restore the old hardware state if either the callback itself or the advertisement update fails. Use the callback to disable autonomous LPI when phy_support_eee() hands LPI control to the MAC, and reapply that state after PHY reset. Drivers without the new callback retain the existing behavior in which the Tx LPI fields configure phylib and the attached MAC. Existing drivers which only implement disable_autonomous_eee() therefore need no changes. Signed-off-by: James Hilliard --- drivers/net/phy/phy.c | 35 +++++++++++++++++++++++++++++++++-- drivers/net/phy/phy_device.c | 25 ++++++++++++++++++++----- include/linux/phy.h | 18 ++++++++++++++++++ 3 files changed, 71 insertions(+), 7 deletions(-) diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index fce9bc7be330..c76d5baa1f84 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -1996,6 +1996,8 @@ static void phy_ethtool_set_eee_noneg(struct phy_device *phydev, int phy_ethtool_set_eee(struct phy_device *phydev, struct ethtool_keee *data) { struct eee_config old_cfg; + bool tx_lpi_cfg_attempted = false; + bool tx_lpi_cfg_changed; int ret; if (!phydev->drv) @@ -2005,16 +2007,45 @@ int phy_ethtool_set_eee(struct phy_device *phydev, struct ethtool_keee *data) old_cfg = phydev->eee_cfg; eee_to_eeecfg(&phydev->eee_cfg, data); + tx_lpi_cfg_changed = phydev->eee_cfg.tx_lpi_enabled != + old_cfg.tx_lpi_enabled || + phydev->eee_cfg.tx_lpi_timer != + old_cfg.tx_lpi_timer; + + if (tx_lpi_cfg_changed && !phydev->autonomous_eee_disabled && + phydev->drv->set_tx_lpi) { + ret = phydev->drv->set_tx_lpi(phydev, &phydev->eee_cfg); + tx_lpi_cfg_attempted = true; + if (ret) + goto restore_tx_lpi; + } ret = genphy_c45_ethtool_set_eee(phydev, data); if (ret == 0) phy_ethtool_set_eee_noneg(phydev, &old_cfg); else if (ret < 0) - phydev->eee_cfg = old_cfg; + goto restore_tx_lpi; mutex_unlock(&phydev->lock); - return ret < 0 ? ret : 0; + return 0; + +restore_tx_lpi: + if (tx_lpi_cfg_attempted) { + int rollback_ret; + + rollback_ret = phydev->drv->set_tx_lpi(phydev, &old_cfg); + if (rollback_ret) + phydev_warn(phydev, + "Failed to restore autonomous Tx LPI: %pe\n", + ERR_PTR(rollback_ret)); + } + + phydev->eee_cfg = old_cfg; + + mutex_unlock(&phydev->lock); + + return ret; } EXPORT_SYMBOL(phy_ethtool_set_eee); diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index f7472a743185..30929b0354b6 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -1357,6 +1357,21 @@ static int phy_poll_reset(struct phy_device *phydev) return 0; } +static int phy_disable_autonomous_tx_lpi(struct phy_device *phydev) +{ + struct eee_config config = phydev->eee_cfg; + + if (phydev->drv->set_tx_lpi) { + config.tx_lpi_enabled = false; + return phydev->drv->set_tx_lpi(phydev, &config); + } + + if (phydev->drv->disable_autonomous_eee) + return phydev->drv->disable_autonomous_eee(phydev); + + return 0; +} + int phy_init_hw(struct phy_device *phydev) { int ret = 0; @@ -1395,9 +1410,8 @@ int phy_init_hw(struct phy_device *phydev) } /* Re-apply autonomous EEE disable after soft reset */ - if (phydev->autonomous_eee_disabled && - phydev->drv->disable_autonomous_eee) { - ret = phydev->drv->disable_autonomous_eee(phydev); + if (phydev->autonomous_eee_disabled) { + ret = phy_disable_autonomous_tx_lpi(phydev); if (ret) return ret; } @@ -2973,8 +2987,9 @@ void phy_support_eee(struct phy_device *phydev) * manage LPI signaling instead. The flag is stored so it can be * re-applied after a PHY soft reset (e.g. suspend/resume). */ - if (phydev->drv && phydev->drv->disable_autonomous_eee) { - int ret = phydev->drv->disable_autonomous_eee(phydev); + if (phydev->drv && (phydev->drv->set_tx_lpi || + phydev->drv->disable_autonomous_eee)) { + int ret = phy_disable_autonomous_tx_lpi(phydev); if (ret) phydev_warn(phydev, "Failed to disable autonomous EEE: %pe\n", diff --git a/include/linux/phy.h b/include/linux/phy.h index 2eba54e068f6..55053b914144 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -1391,6 +1391,24 @@ struct phy_driver { */ int (*disable_autonomous_eee)(struct phy_device *dev); + /** + * @set_tx_lpi: Configure PHY-autonomous Tx LPI + * @dev: PHY device to configure + * @config: Requested Tx LPI enable state and idle timer + * + * The presence of this callback advertises that the driver supports + * configuring PHY-autonomous LPI through the ethtool EEE Tx LPI fields. + * It is not used for userspace configuration after phylib has handed LPI + * control to the MAC. Drivers without this callback retain the legacy + * behavior in which those fields only configure phylib and the attached + * MAC. A request with Tx LPI disabled must be accepted regardless of the + * timer, since the timer has no meaning while the transmitter is disabled. + * + * Return: 0 on success, negative errno on failure. + */ + int (*set_tx_lpi)(struct phy_device *dev, + const struct eee_config *config); + /* Get and Set PHY tunables */ /** @get_tunable: Return the value of a tunable */ int (*get_tunable)(struct phy_device *dev, -- 2.53.0