summaryrefslogtreecommitdiff
path: root/drivers/net/phy/phy.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/phy/phy.c')
-rw-r--r--drivers/net/phy/phy.c1111
1 files changed, 828 insertions, 283 deletions
diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index e5b6cb1a77f9..13dd1691886d 100644
--- a/drivers/net/phy/phy.c
+++ b/drivers/net/phy/phy.c
@@ -36,6 +36,9 @@
#include <net/genetlink.h>
#include <net/sock.h>
+#include "phylib-internal.h"
+#include "phy-caps.h"
+
#define PHY_STATE_TIME HZ
#define PHY_STATE_STR(_state) \
@@ -52,11 +55,24 @@ static const char *phy_state_to_str(enum phy_state st)
PHY_STATE_STR(NOLINK)
PHY_STATE_STR(CABLETEST)
PHY_STATE_STR(HALTED)
+ PHY_STATE_STR(ERROR)
}
return NULL;
}
+static void phy_process_state_change(struct phy_device *phydev,
+ enum phy_state old_state)
+{
+ if (old_state != phydev->state) {
+ phydev_dbg(phydev, "PHY state change %s -> %s\n",
+ phy_state_to_str(old_state),
+ phy_state_to_str(phydev->state));
+ if (phydev->drv && phydev->drv->link_change_notify)
+ phydev->drv->link_change_notify(phydev);
+ }
+}
+
static void phy_link_up(struct phy_device *phydev)
{
phydev->phy_link_change(phydev, true);
@@ -198,25 +214,6 @@ int phy_aneg_done(struct phy_device *phydev)
EXPORT_SYMBOL(phy_aneg_done);
/**
- * phy_find_valid - find a PHY setting that matches the requested parameters
- * @speed: desired speed
- * @duplex: desired duplex
- * @supported: mask of supported link modes
- *
- * Locate a supported phy setting that is, in priority order:
- * - an exact match for the specified speed and duplex mode
- * - a match for the specified speed, or slower speed
- * - the slowest supported speed
- * Returns the matched phy_setting entry, or %NULL if no supported phy
- * settings were found.
- */
-static const struct phy_setting *
-phy_find_valid(int speed, int duplex, unsigned long *supported)
-{
- return phy_lookup_setting(speed, duplex, supported, false);
-}
-
-/**
* phy_supported_speeds - return all speeds currently supported by a phy device
* @phy: The phy device to return supported speeds of.
* @speeds: buffer to store supported speeds in.
@@ -230,7 +227,7 @@ unsigned int phy_supported_speeds(struct phy_device *phy,
unsigned int *speeds,
unsigned int size)
{
- return phy_speeds(speeds, size, phy->supported);
+ return phy_caps_speeds(speeds, size, phy->supported);
}
/**
@@ -242,11 +239,11 @@ unsigned int phy_supported_speeds(struct phy_device *phy,
*
* Description: Returns true if there is a valid setting, false otherwise.
*/
-static inline bool phy_check_valid(int speed, int duplex,
- unsigned long *features)
+bool phy_check_valid(int speed, int duplex, unsigned long *features)
{
- return !!phy_lookup_setting(speed, duplex, features, true);
+ return phy_caps_valid(speed, duplex, features);
}
+EXPORT_SYMBOL(phy_check_valid);
/**
* phy_sanitize_settings - make sure the PHY is set to supported speed and duplex
@@ -258,13 +255,14 @@ static inline bool phy_check_valid(int speed, int duplex,
*/
static void phy_sanitize_settings(struct phy_device *phydev)
{
- const struct phy_setting *setting;
+ const struct link_capabilities *c;
- setting = phy_find_valid(phydev->speed, phydev->duplex,
- phydev->supported);
- if (setting) {
- phydev->speed = setting->speed;
- phydev->duplex = setting->duplex;
+ c = phy_caps_lookup(phydev->speed, phydev->duplex, phydev->supported,
+ false);
+
+ if (c) {
+ phydev->speed = c->speed;
+ phydev->duplex = c->duplex;
} else {
/* We failed to find anything (no supported speeds?) */
phydev->speed = SPEED_UNKNOWN;
@@ -289,7 +287,7 @@ void phy_ethtool_ksettings_get(struct phy_device *phydev,
cmd->base.port = PORT_BNC;
else
cmd->base.port = phydev->port;
- cmd->base.transceiver = phy_is_internal(phydev) ?
+ cmd->base.transceiver = phydev->is_internal ?
XCVR_INTERNAL : XCVR_EXTERNAL;
cmd->base.phy_address = phydev->mdio.addr;
cmd->base.autoneg = phydev->autoneg;
@@ -312,9 +310,13 @@ EXPORT_SYMBOL(phy_ethtool_ksettings_get);
int phy_mii_ioctl(struct phy_device *phydev, struct ifreq *ifr, int cmd)
{
struct mii_ioctl_data *mii_data = if_mii(ifr);
+ struct kernel_hwtstamp_config kernel_cfg;
+ struct netlink_ext_ack extack = {};
u16 val = mii_data->val_in;
bool change_autoneg = false;
+ struct hwtstamp_config cfg;
int prtad, devad;
+ int ret;
switch (cmd) {
case SIOCGMIIPHY:
@@ -325,14 +327,19 @@ int phy_mii_ioctl(struct phy_device *phydev, struct ifreq *ifr, int cmd)
if (mdio_phy_id_is_c45(mii_data->phy_id)) {
prtad = mdio_phy_id_prtad(mii_data->phy_id);
devad = mdio_phy_id_devad(mii_data->phy_id);
- mii_data->val_out = mdiobus_c45_read(
- phydev->mdio.bus, prtad, devad,
- mii_data->reg_num);
+ ret = mdiobus_c45_read(phydev->mdio.bus, prtad, devad,
+ mii_data->reg_num);
+
} else {
- mii_data->val_out = mdiobus_read(
- phydev->mdio.bus, mii_data->phy_id,
- mii_data->reg_num);
+ ret = mdiobus_read(phydev->mdio.bus, mii_data->phy_id,
+ mii_data->reg_num);
}
+
+ if (ret < 0)
+ return ret;
+
+ mii_data->val_out = ret;
+
return 0;
case SIOCSMIIREG:
@@ -398,8 +405,23 @@ int phy_mii_ioctl(struct phy_device *phydev, struct ifreq *ifr, int cmd)
return 0;
case SIOCSHWTSTAMP:
- if (phydev->mii_ts && phydev->mii_ts->hwtstamp)
- return phydev->mii_ts->hwtstamp(phydev->mii_ts, ifr);
+ if (phydev->mii_ts && phydev->mii_ts->hwtstamp_set) {
+ if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg)))
+ return -EFAULT;
+
+ hwtstamp_config_to_kernel(&kernel_cfg, &cfg);
+ ret = phydev->mii_ts->hwtstamp_set(phydev->mii_ts,
+ &kernel_cfg,
+ &extack);
+ if (ret)
+ return ret;
+
+ hwtstamp_config_from_kernel(&cfg, &kernel_cfg);
+ if (copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)))
+ return -EFAULT;
+
+ return 0;
+ }
fallthrough;
default:
@@ -443,17 +465,58 @@ int phy_do_ioctl_running(struct net_device *dev, struct ifreq *ifr, int cmd)
EXPORT_SYMBOL(phy_do_ioctl_running);
/**
+ * __phy_hwtstamp_get - Get hardware timestamping configuration from PHY
+ *
+ * @phydev: the PHY device structure
+ * @config: structure holding the timestamping configuration
+ *
+ * Query the PHY device for its current hardware timestamping configuration.
+ */
+int __phy_hwtstamp_get(struct phy_device *phydev,
+ struct kernel_hwtstamp_config *config)
+{
+ if (!phydev)
+ return -ENODEV;
+
+ if (phydev->mii_ts && phydev->mii_ts->hwtstamp_get)
+ return phydev->mii_ts->hwtstamp_get(phydev->mii_ts, config);
+
+ return -EOPNOTSUPP;
+}
+
+/**
+ * __phy_hwtstamp_set - Modify PHY hardware timestamping configuration
+ *
+ * @phydev: the PHY device structure
+ * @config: structure holding the timestamping configuration
+ * @extack: netlink extended ack structure, for error reporting
+ */
+int __phy_hwtstamp_set(struct phy_device *phydev,
+ struct kernel_hwtstamp_config *config,
+ struct netlink_ext_ack *extack)
+{
+ if (!phydev)
+ return -ENODEV;
+
+ if (phydev->mii_ts && phydev->mii_ts->hwtstamp_set)
+ return phydev->mii_ts->hwtstamp_set(phydev->mii_ts, config,
+ extack);
+
+ return -EOPNOTSUPP;
+}
+
+/**
* phy_queue_state_machine - Trigger the state machine to run soon
*
* @phydev: the phy_device struct
* @jiffies: Run the state machine after these jiffies
*/
-void phy_queue_state_machine(struct phy_device *phydev, unsigned long jiffies)
+static void phy_queue_state_machine(struct phy_device *phydev,
+ unsigned long jiffies)
{
mod_delayed_work(system_power_efficient_wq, &phydev->state_queue,
jiffies);
}
-EXPORT_SYMBOL(phy_queue_state_machine);
/**
* phy_trigger_machine - Trigger the state machine to run now
@@ -544,6 +607,241 @@ int phy_ethtool_get_stats(struct phy_device *phydev,
EXPORT_SYMBOL(phy_ethtool_get_stats);
/**
+ * __phy_ethtool_get_phy_stats - Retrieve standardized PHY statistics
+ * @phydev: Pointer to the PHY device
+ * @phy_stats: Pointer to ethtool_eth_phy_stats structure
+ * @phydev_stats: Pointer to ethtool_phy_stats structure
+ *
+ * Fetches PHY statistics using a kernel-defined interface for consistent
+ * diagnostics. Unlike phy_ethtool_get_stats(), which allows custom stats,
+ * this function enforces a standardized format for better interoperability.
+ */
+void __phy_ethtool_get_phy_stats(struct phy_device *phydev,
+ struct ethtool_eth_phy_stats *phy_stats,
+ struct ethtool_phy_stats *phydev_stats)
+{
+ if (!phydev->drv || !phydev->drv->get_phy_stats)
+ return;
+
+ mutex_lock(&phydev->lock);
+ phydev->drv->get_phy_stats(phydev, phy_stats, phydev_stats);
+ mutex_unlock(&phydev->lock);
+}
+
+/**
+ * __phy_ethtool_get_link_ext_stats - Retrieve extended link statistics for a PHY
+ * @phydev: Pointer to the PHY device
+ * @link_stats: Pointer to the structure to store extended link statistics
+ *
+ * Populates the ethtool_link_ext_stats structure with link down event counts
+ * and additional driver-specific link statistics, if available.
+ */
+void __phy_ethtool_get_link_ext_stats(struct phy_device *phydev,
+ struct ethtool_link_ext_stats *link_stats)
+{
+ link_stats->link_down_events = READ_ONCE(phydev->link_down_events);
+
+ if (!phydev->drv || !phydev->drv->get_link_stats)
+ return;
+
+ mutex_lock(&phydev->lock);
+ phydev->drv->get_link_stats(phydev, link_stats);
+ mutex_unlock(&phydev->lock);
+}
+
+/**
+ * phy_ethtool_get_plca_cfg - Get PLCA RS configuration
+ * @phydev: the phy_device struct
+ * @plca_cfg: where to store the retrieved configuration
+ *
+ * Retrieve the PLCA configuration from the PHY. Return 0 on success or a
+ * negative value if an error occurred.
+ */
+int phy_ethtool_get_plca_cfg(struct phy_device *phydev,
+ struct phy_plca_cfg *plca_cfg)
+{
+ int ret;
+
+ if (!phydev->drv) {
+ ret = -EIO;
+ goto out;
+ }
+
+ if (!phydev->drv->get_plca_cfg) {
+ ret = -EOPNOTSUPP;
+ goto out;
+ }
+
+ mutex_lock(&phydev->lock);
+ ret = phydev->drv->get_plca_cfg(phydev, plca_cfg);
+
+ mutex_unlock(&phydev->lock);
+out:
+ return ret;
+}
+
+/**
+ * plca_check_valid - Check PLCA configuration before enabling
+ * @phydev: the phy_device struct
+ * @plca_cfg: current PLCA configuration
+ * @extack: extack for reporting useful error messages
+ *
+ * Checks whether the PLCA and PHY configuration are consistent and it is safe
+ * to enable PLCA. Returns 0 on success or a negative value if the PLCA or PHY
+ * configuration is not consistent.
+ */
+static int plca_check_valid(struct phy_device *phydev,
+ const struct phy_plca_cfg *plca_cfg,
+ struct netlink_ext_ack *extack)
+{
+ int ret = 0;
+
+ if (!linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT1S_P2MP_Half_BIT,
+ phydev->advertising)) {
+ ret = -EOPNOTSUPP;
+ NL_SET_ERR_MSG(extack,
+ "Point to Multi-Point mode is not enabled");
+ } else if (plca_cfg->node_id >= 255) {
+ NL_SET_ERR_MSG(extack, "PLCA node ID is not set");
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+/**
+ * phy_ethtool_set_plca_cfg - Set PLCA RS configuration
+ * @phydev: the phy_device struct
+ * @plca_cfg: new PLCA configuration to apply
+ * @extack: extack for reporting useful error messages
+ *
+ * Sets the PLCA configuration in the PHY. Return 0 on success or a
+ * negative value if an error occurred.
+ */
+int phy_ethtool_set_plca_cfg(struct phy_device *phydev,
+ const struct phy_plca_cfg *plca_cfg,
+ struct netlink_ext_ack *extack)
+{
+ struct phy_plca_cfg *curr_plca_cfg;
+ int ret;
+
+ if (!phydev->drv) {
+ ret = -EIO;
+ goto out;
+ }
+
+ if (!phydev->drv->set_plca_cfg ||
+ !phydev->drv->get_plca_cfg) {
+ ret = -EOPNOTSUPP;
+ goto out;
+ }
+
+ curr_plca_cfg = kmalloc(sizeof(*curr_plca_cfg), GFP_KERNEL);
+ if (!curr_plca_cfg) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ mutex_lock(&phydev->lock);
+
+ ret = phydev->drv->get_plca_cfg(phydev, curr_plca_cfg);
+ if (ret)
+ goto out_drv;
+
+ if (curr_plca_cfg->enabled < 0 && plca_cfg->enabled >= 0) {
+ NL_SET_ERR_MSG(extack,
+ "PHY does not support changing the PLCA 'enable' attribute");
+ ret = -EINVAL;
+ goto out_drv;
+ }
+
+ if (curr_plca_cfg->node_id < 0 && plca_cfg->node_id >= 0) {
+ NL_SET_ERR_MSG(extack,
+ "PHY does not support changing the PLCA 'local node ID' attribute");
+ ret = -EINVAL;
+ goto out_drv;
+ }
+
+ if (curr_plca_cfg->node_cnt < 0 && plca_cfg->node_cnt >= 0) {
+ NL_SET_ERR_MSG(extack,
+ "PHY does not support changing the PLCA 'node count' attribute");
+ ret = -EINVAL;
+ goto out_drv;
+ }
+
+ if (curr_plca_cfg->to_tmr < 0 && plca_cfg->to_tmr >= 0) {
+ NL_SET_ERR_MSG(extack,
+ "PHY does not support changing the PLCA 'TO timer' attribute");
+ ret = -EINVAL;
+ goto out_drv;
+ }
+
+ if (curr_plca_cfg->burst_cnt < 0 && plca_cfg->burst_cnt >= 0) {
+ NL_SET_ERR_MSG(extack,
+ "PHY does not support changing the PLCA 'burst count' attribute");
+ ret = -EINVAL;
+ goto out_drv;
+ }
+
+ if (curr_plca_cfg->burst_tmr < 0 && plca_cfg->burst_tmr >= 0) {
+ NL_SET_ERR_MSG(extack,
+ "PHY does not support changing the PLCA 'burst timer' attribute");
+ ret = -EINVAL;
+ goto out_drv;
+ }
+
+ // if enabling PLCA, perform a few sanity checks
+ if (plca_cfg->enabled > 0) {
+ // allow setting node_id concurrently with enabled
+ if (plca_cfg->node_id >= 0)
+ curr_plca_cfg->node_id = plca_cfg->node_id;
+
+ ret = plca_check_valid(phydev, curr_plca_cfg, extack);
+ if (ret)
+ goto out_drv;
+ }
+
+ ret = phydev->drv->set_plca_cfg(phydev, plca_cfg);
+
+out_drv:
+ kfree(curr_plca_cfg);
+ mutex_unlock(&phydev->lock);
+out:
+ return ret;
+}
+
+/**
+ * phy_ethtool_get_plca_status - Get PLCA RS status information
+ * @phydev: the phy_device struct
+ * @plca_st: where to store the retrieved status information
+ *
+ * Retrieve the PLCA status information from the PHY. Return 0 on success or a
+ * negative value if an error occurred.
+ */
+int phy_ethtool_get_plca_status(struct phy_device *phydev,
+ struct phy_plca_status *plca_st)
+{
+ int ret;
+
+ if (!phydev->drv) {
+ ret = -EIO;
+ goto out;
+ }
+
+ if (!phydev->drv->get_plca_status) {
+ ret = -EOPNOTSUPP;
+ goto out;
+ }
+
+ mutex_lock(&phydev->lock);
+ ret = phydev->drv->get_plca_status(phydev, plca_st);
+
+ mutex_unlock(&phydev->lock);
+out:
+ return ret;
+}
+
+/**
* phy_start_cable_test - Start a cable test
*
* @phydev: the phy_device struct
@@ -724,9 +1022,16 @@ static int phy_check_link_status(struct phy_device *phydev)
if (phydev->link && phydev->state != PHY_RUNNING) {
phy_check_downshift(phydev);
phydev->state = PHY_RUNNING;
+ err = genphy_c45_eee_is_active(phydev, NULL);
+ phydev->eee_active = err > 0;
+ phydev->enable_tx_lpi = phydev->eee_cfg.tx_lpi_enabled &&
+ phydev->eee_active;
+
phy_link_up(phydev);
} else if (!phydev->link && phydev->state != PHY_NOLINK) {
phydev->state = PHY_NOLINK;
+ phydev->eee_active = false;
+ phydev->enable_tx_lpi = false;
phy_link_down(phydev);
}
@@ -734,6 +1039,55 @@ static int phy_check_link_status(struct phy_device *phydev)
}
/**
+ * 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)
+{
+ lockdep_assert_held(&phydev->lock);
+
+ if (!!(modes & LINK_INBAND_DISABLE) +
+ !!(modes & LINK_INBAND_ENABLE) +
+ !!(modes & LINK_INBAND_BYPASS) != 1)
+ return -EINVAL;
+
+ if (!phydev->drv)
+ return -EIO;
+ else if (!phydev->drv->config_inband)
+ return -EOPNOTSUPP;
+
+ return phydev->drv->config_inband(phydev, modes);
+}
+EXPORT_SYMBOL(phy_config_inband);
+
+/**
* _phy_start_aneg - start auto-negotiation for this PHY device
* @phydev: the phy_device struct
*
@@ -742,7 +1096,7 @@ static int phy_check_link_status(struct phy_device *phydev)
* If the PHYCONTROL Layer is operating, we change the state to
* reflect the beginning of Auto-negotiation or forcing.
*/
-static int _phy_start_aneg(struct phy_device *phydev)
+int _phy_start_aneg(struct phy_device *phydev)
{
int err;
@@ -763,6 +1117,7 @@ static int _phy_start_aneg(struct phy_device *phydev)
return err;
}
+EXPORT_SYMBOL(_phy_start_aneg);
/**
* phy_start_aneg - start auto-negotiation for this PHY device
@@ -821,7 +1176,10 @@ int phy_ethtool_ksettings_set(struct phy_device *phydev,
if (autoneg != AUTONEG_ENABLE && autoneg != AUTONEG_DISABLE)
return -EINVAL;
- if (autoneg == AUTONEG_ENABLE && linkmode_empty(advertising))
+ if (autoneg == AUTONEG_ENABLE &&
+ (linkmode_empty(advertising) ||
+ !linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+ phydev->supported)))
return -EINVAL;
if (autoneg == AUTONEG_DISABLE &&
@@ -877,27 +1235,35 @@ EXPORT_SYMBOL(phy_ethtool_ksettings_set);
int phy_speed_down(struct phy_device *phydev, bool sync)
{
__ETHTOOL_DECLARE_LINK_MODE_MASK(adv_tmp);
- int ret;
+ int ret = 0;
+
+ mutex_lock(&phydev->lock);
if (phydev->autoneg != AUTONEG_ENABLE)
- return 0;
+ goto out;
linkmode_copy(adv_tmp, phydev->advertising);
ret = phy_speed_down_core(phydev);
if (ret)
- return ret;
+ goto out;
linkmode_copy(phydev->adv_old, adv_tmp);
- if (linkmode_equal(phydev->advertising, adv_tmp))
- return 0;
+ if (linkmode_equal(phydev->advertising, adv_tmp)) {
+ ret = 0;
+ goto out;
+ }
ret = phy_config_aneg(phydev);
if (ret)
- return ret;
+ goto out;
+
+ ret = sync ? phy_poll_aneg_done(phydev) : 0;
+out:
+ mutex_unlock(&phydev->lock);
- return sync ? phy_poll_aneg_done(phydev) : 0;
+ return ret;
}
EXPORT_SYMBOL_GPL(phy_speed_down);
@@ -910,21 +1276,28 @@ EXPORT_SYMBOL_GPL(phy_speed_down);
int phy_speed_up(struct phy_device *phydev)
{
__ETHTOOL_DECLARE_LINK_MODE_MASK(adv_tmp);
+ int ret = 0;
+
+ mutex_lock(&phydev->lock);
if (phydev->autoneg != AUTONEG_ENABLE)
- return 0;
+ goto out;
if (linkmode_empty(phydev->adv_old))
- return 0;
+ goto out;
linkmode_copy(adv_tmp, phydev->advertising);
linkmode_copy(phydev->advertising, phydev->adv_old);
linkmode_zero(phydev->adv_old);
if (linkmode_equal(phydev->advertising, adv_tmp))
- return 0;
+ goto out;
+
+ ret = phy_config_aneg(phydev);
+out:
+ mutex_unlock(&phydev->lock);
- return phy_config_aneg(phydev);
+ return ret;
}
EXPORT_SYMBOL_GPL(phy_speed_up);
@@ -962,24 +1335,36 @@ void phy_stop_machine(struct phy_device *phydev)
mutex_unlock(&phydev->lock);
}
+static void phy_process_error(struct phy_device *phydev)
+{
+ /* phydev->lock must be held for the state change to be safe */
+ if (!mutex_is_locked(&phydev->lock))
+ phydev_err(phydev, "PHY-device data unsafe context\n");
+
+ phydev->state = PHY_ERROR;
+
+ phy_trigger_machine(phydev);
+}
+
+static void phy_error_precise(struct phy_device *phydev,
+ const void *func, int err)
+{
+ WARN(1, "%pS: returned: %d\n", func, err);
+ phy_process_error(phydev);
+}
+
/**
- * phy_error - enter HALTED state for this PHY device
+ * phy_error - enter ERROR state for this PHY device
* @phydev: target phy_device struct
*
- * Moves the PHY to the HALTED state in response to a read
+ * Moves the PHY to the ERROR state in response to a read
* or write error, and tells the controller the link is down.
- * Must not be called from interrupt context, or while the
- * phydev->lock is held.
+ * Must be called with phydev->lock held.
*/
void phy_error(struct phy_device *phydev)
{
WARN_ON(1);
-
- mutex_lock(&phydev->lock);
- phydev->state = PHY_HALTED;
- mutex_unlock(&phydev->lock);
-
- phy_trigger_machine(phydev);
+ phy_process_error(phydev);
}
EXPORT_SYMBOL(phy_error);
@@ -1003,7 +1388,6 @@ int phy_disable_interrupts(struct phy_device *phydev)
static irqreturn_t phy_interrupt(int irq, void *phy_dat)
{
struct phy_device *phydev = phy_dat;
- struct phy_driver *drv = phydev->drv;
irqreturn_t ret;
/* Wakeup interrupts may occur during a system sleep transition.
@@ -1015,7 +1399,7 @@ static irqreturn_t phy_interrupt(int irq, void *phy_dat)
if (netdev) {
struct device *parent = netdev->dev.parent;
- if (netdev->wol_enabled)
+ if (netdev->ethtool->wol_enabled)
pm_system_wakeup();
else if (device_may_wakeup(&netdev->dev))
pm_wakeup_dev_event(&netdev->dev, 0, true);
@@ -1029,7 +1413,7 @@ static irqreturn_t phy_interrupt(int irq, void *phy_dat)
}
mutex_lock(&phydev->lock);
- ret = drv->handle_interrupt(phydev);
+ ret = phydev->drv->handle_interrupt(phydev);
mutex_unlock(&phydev->lock);
return ret;
@@ -1045,6 +1429,23 @@ static int phy_enable_interrupts(struct phy_device *phydev)
}
/**
+ * phy_update_stats - Update PHY device statistics if supported.
+ * @phydev: Pointer to the PHY device structure.
+ *
+ * If the PHY driver provides an update_stats callback, this function
+ * invokes it to update the PHY statistics. If not, it returns 0.
+ *
+ * Return: 0 on success, or a negative error code if the callback fails.
+ */
+static int phy_update_stats(struct phy_device *phydev)
+{
+ if (!phydev->drv->update_stats)
+ return 0;
+
+ return phydev->drv->update_stats(phydev);
+}
+
+/**
* phy_request_interrupt - request and enable interrupt for a PHY device
* @phydev: target phy_device struct
*
@@ -1088,20 +1489,168 @@ void phy_free_interrupt(struct phy_device *phydev)
EXPORT_SYMBOL(phy_free_interrupt);
/**
+ * phy_get_next_update_time - Determine the next PHY update time
+ * @phydev: Pointer to the phy_device structure
+ *
+ * This function queries the PHY driver to get the time for the next polling
+ * event. If the driver does not implement the callback, a default value is
+ * used.
+ *
+ * Return: The time for the next polling event in jiffies
+ */
+static unsigned int phy_get_next_update_time(struct phy_device *phydev)
+{
+ if (phydev->drv && phydev->drv->get_next_update_time)
+ return phydev->drv->get_next_update_time(phydev);
+
+ return PHY_STATE_TIME;
+}
+
+enum phy_state_work {
+ PHY_STATE_WORK_NONE,
+ PHY_STATE_WORK_ANEG,
+ PHY_STATE_WORK_SUSPEND,
+};
+
+static enum phy_state_work _phy_state_machine(struct phy_device *phydev)
+{
+ enum phy_state_work state_work = PHY_STATE_WORK_NONE;
+ struct net_device *dev = phydev->attached_dev;
+ enum phy_state old_state = phydev->state;
+ const void *func = NULL;
+ bool finished = false;
+ int err = 0;
+
+ switch (phydev->state) {
+ case PHY_DOWN:
+ case PHY_READY:
+ break;
+ case PHY_UP:
+ state_work = PHY_STATE_WORK_ANEG;
+ break;
+ case PHY_NOLINK:
+ case PHY_RUNNING:
+ err = phy_check_link_status(phydev);
+ func = &phy_check_link_status;
+
+ if (!err)
+ err = phy_update_stats(phydev);
+ break;
+ case PHY_CABLETEST:
+ err = phydev->drv->cable_test_get_status(phydev, &finished);
+ if (err) {
+ phy_abort_cable_test(phydev);
+ netif_testing_off(dev);
+ state_work = PHY_STATE_WORK_ANEG;
+ phydev->state = PHY_UP;
+ break;
+ }
+
+ if (finished) {
+ ethnl_cable_test_finished(phydev);
+ netif_testing_off(dev);
+ state_work = PHY_STATE_WORK_ANEG;
+ phydev->state = PHY_UP;
+ }
+ break;
+ case PHY_HALTED:
+ if (phydev->link) {
+ if (phydev->autoneg == AUTONEG_ENABLE) {
+ phydev->speed = SPEED_UNKNOWN;
+ phydev->duplex = DUPLEX_UNKNOWN;
+ }
+ if (phydev->master_slave_state !=
+ MASTER_SLAVE_STATE_UNSUPPORTED)
+ phydev->master_slave_state =
+ MASTER_SLAVE_STATE_UNKNOWN;
+ phydev->mdix = ETH_TP_MDI_INVALID;
+ linkmode_zero(phydev->lp_advertising);
+ }
+ fallthrough;
+ case PHY_ERROR:
+ if (phydev->link) {
+ phydev->link = 0;
+ phydev->eee_active = false;
+ phydev->enable_tx_lpi = false;
+ phy_link_down(phydev);
+ }
+ state_work = PHY_STATE_WORK_SUSPEND;
+ break;
+ }
+
+ if (state_work == PHY_STATE_WORK_ANEG) {
+ err = _phy_start_aneg(phydev);
+ func = &_phy_start_aneg;
+ }
+
+ if (err == -ENODEV)
+ return state_work;
+
+ if (err < 0)
+ phy_error_precise(phydev, func, err);
+
+ phy_process_state_change(phydev, old_state);
+
+ /* Only re-schedule a PHY state machine change if we are polling the
+ * PHY, if PHY_MAC_INTERRUPT is set, then we will be moving
+ * between states from phy_mac_interrupt().
+ *
+ * In state PHY_HALTED the PHY gets suspended, so rescheduling the
+ * state machine would be pointless and possibly error prone when
+ * called from phy_disconnect() synchronously.
+ */
+ if (phy_polling_mode(phydev) && phy_is_started(phydev))
+ phy_queue_state_machine(phydev,
+ phy_get_next_update_time(phydev));
+
+ return state_work;
+}
+
+/* unlocked part of the PHY state machine */
+static void _phy_state_machine_post_work(struct phy_device *phydev,
+ enum phy_state_work state_work)
+{
+ if (state_work == PHY_STATE_WORK_SUSPEND)
+ phy_suspend(phydev);
+}
+
+/**
+ * phy_state_machine - Handle the state machine
+ * @work: work_struct that describes the work to be done
+ */
+void phy_state_machine(struct work_struct *work)
+{
+ struct delayed_work *dwork = to_delayed_work(work);
+ struct phy_device *phydev =
+ container_of(dwork, struct phy_device, state_queue);
+ enum phy_state_work state_work;
+
+ mutex_lock(&phydev->lock);
+ state_work = _phy_state_machine(phydev);
+ mutex_unlock(&phydev->lock);
+
+ _phy_state_machine_post_work(phydev, state_work);
+}
+
+/**
* phy_stop - Bring down the PHY link, and stop checking the status
* @phydev: target phy_device struct
*/
void phy_stop(struct phy_device *phydev)
{
struct net_device *dev = phydev->attached_dev;
+ enum phy_state_work state_work;
+ enum phy_state old_state;
- if (!phy_is_started(phydev) && phydev->state != PHY_DOWN) {
+ if (!phy_is_started(phydev) && phydev->state != PHY_DOWN &&
+ phydev->state != PHY_ERROR) {
WARN(1, "called from state %s\n",
phy_state_to_str(phydev->state));
return;
}
mutex_lock(&phydev->lock);
+ old_state = phydev->state;
if (phydev->state == PHY_CABLETEST) {
phy_abort_cable_test(phydev);
@@ -1112,10 +1661,12 @@ void phy_stop(struct phy_device *phydev)
sfp_upstream_stop(phydev->sfp_bus);
phydev->state = PHY_HALTED;
+ phy_process_state_change(phydev, old_state);
+ state_work = _phy_state_machine(phydev);
mutex_unlock(&phydev->lock);
- phy_state_machine(&phydev->state_queue.work);
+ _phy_state_machine_post_work(phydev, state_work);
phy_stop_machine(phydev);
/* Cannot call flush_scheduled_work() here as desired because
@@ -1160,134 +1711,146 @@ out:
EXPORT_SYMBOL(phy_start);
/**
- * phy_state_machine - Handle the state machine
- * @work: work_struct that describes the work to be done
+ * phy_mac_interrupt - MAC says the link has changed
+ * @phydev: phy_device struct with changed link
+ *
+ * The MAC layer is able to indicate there has been a change in the PHY link
+ * status. Trigger the state machine and work a work queue.
*/
-void phy_state_machine(struct work_struct *work)
+void phy_mac_interrupt(struct phy_device *phydev)
{
- struct delayed_work *dwork = to_delayed_work(work);
- struct phy_device *phydev =
- container_of(dwork, struct phy_device, state_queue);
- struct net_device *dev = phydev->attached_dev;
- bool needs_aneg = false, do_suspend = false;
- enum phy_state old_state;
- bool finished = false;
- int err = 0;
-
- mutex_lock(&phydev->lock);
+ /* Trigger a state machine change */
+ phy_trigger_machine(phydev);
+}
+EXPORT_SYMBOL(phy_mac_interrupt);
- old_state = phydev->state;
+/**
+ * phy_loopback - Configure loopback mode of PHY
+ * @phydev: target phy_device struct
+ * @enable: enable or disable loopback mode
+ * @speed: enable loopback mode with speed
+ *
+ * Configure loopback mode of PHY and signal link down and link up if speed is
+ * changing.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int phy_loopback(struct phy_device *phydev, bool enable, int speed)
+{
+ bool link_up = false;
+ int ret = 0;
- switch (phydev->state) {
- case PHY_DOWN:
- case PHY_READY:
- break;
- case PHY_UP:
- needs_aneg = true;
+ if (!phydev->drv)
+ return -EIO;
- break;
- case PHY_NOLINK:
- case PHY_RUNNING:
- err = phy_check_link_status(phydev);
- break;
- case PHY_CABLETEST:
- err = phydev->drv->cable_test_get_status(phydev, &finished);
- if (err) {
- phy_abort_cable_test(phydev);
- netif_testing_off(dev);
- needs_aneg = true;
- phydev->state = PHY_UP;
- break;
- }
+ mutex_lock(&phydev->lock);
- if (finished) {
- ethnl_cable_test_finished(phydev);
- netif_testing_off(dev);
- needs_aneg = true;
- phydev->state = PHY_UP;
- }
- break;
- case PHY_HALTED:
- if (phydev->link) {
- phydev->link = 0;
- phy_link_down(phydev);
- }
- do_suspend = true;
- break;
+ if (enable && phydev->loopback_enabled) {
+ ret = -EBUSY;
+ goto out;
}
- mutex_unlock(&phydev->lock);
+ if (!enable && !phydev->loopback_enabled) {
+ ret = -EINVAL;
+ goto out;
+ }
- if (needs_aneg)
- err = phy_start_aneg(phydev);
- else if (do_suspend)
- phy_suspend(phydev);
+ if (enable) {
+ /*
+ * Link up is signaled with a defined speed. If speed changes,
+ * then first link down and after that link up needs to be
+ * signaled.
+ */
+ if (phydev->link && phydev->state == PHY_RUNNING) {
+ /* link is up and signaled */
+ if (speed && phydev->speed != speed) {
+ /* signal link down and up for new speed */
+ phydev->link = false;
+ phydev->state = PHY_NOLINK;
+ phy_link_down(phydev);
+
+ link_up = true;
+ }
+ } else {
+ /* link is not signaled */
+ if (speed) {
+ /* signal link up for new speed */
+ link_up = true;
+ }
+ }
+ }
- if (err == -ENODEV)
- return;
+ if (phydev->drv->set_loopback)
+ ret = phydev->drv->set_loopback(phydev, enable, speed);
+ else
+ ret = genphy_loopback(phydev, enable, speed);
+
+ if (ret) {
+ if (enable) {
+ /* try to restore link if enabling loopback fails */
+ if (phydev->drv->set_loopback)
+ phydev->drv->set_loopback(phydev, false, 0);
+ else
+ genphy_loopback(phydev, false, 0);
+ }
- if (err < 0)
- phy_error(phydev);
+ goto out;
+ }
- if (old_state != phydev->state) {
- phydev_dbg(phydev, "PHY state change %s -> %s\n",
- phy_state_to_str(old_state),
- phy_state_to_str(phydev->state));
- if (phydev->drv && phydev->drv->link_change_notify)
- phydev->drv->link_change_notify(phydev);
+ if (link_up) {
+ phydev->link = true;
+ phydev->state = PHY_RUNNING;
+ phy_link_up(phydev);
}
- /* Only re-schedule a PHY state machine change if we are polling the
- * PHY, if PHY_MAC_INTERRUPT is set, then we will be moving
- * between states from phy_mac_interrupt().
- *
- * In state PHY_HALTED the PHY gets suspended, so rescheduling the
- * state machine would be pointless and possibly error prone when
- * called from phy_disconnect() synchronously.
- */
- mutex_lock(&phydev->lock);
- if (phy_polling_mode(phydev) && phy_is_started(phydev))
- phy_queue_state_machine(phydev, PHY_STATE_TIME);
+ phydev->loopback_enabled = enable;
+
+out:
mutex_unlock(&phydev->lock);
+ return ret;
}
+EXPORT_SYMBOL(phy_loopback);
/**
- * phy_mac_interrupt - MAC says the link has changed
- * @phydev: phy_device struct with changed link
+ * phy_eee_tx_clock_stop_capable() - indicate whether the MAC can stop tx clock
+ * @phydev: target phy_device struct
*
- * The MAC layer is able to indicate there has been a change in the PHY link
- * status. Trigger the state machine and work a work queue.
+ * Indicate whether the MAC can disable the transmit xMII clock while in LPI
+ * state. Returns 1 if the MAC may stop the transmit clock, 0 if the MAC must
+ * not stop the transmit clock, or negative error.
*/
-void phy_mac_interrupt(struct phy_device *phydev)
+int phy_eee_tx_clock_stop_capable(struct phy_device *phydev)
{
- /* Trigger a state machine change */
- phy_trigger_machine(phydev);
+ int stat1;
+
+ stat1 = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_STAT1);
+ if (stat1 < 0)
+ return stat1;
+
+ return !!(stat1 & MDIO_PCS_STAT1_CLKSTOP_CAP);
}
-EXPORT_SYMBOL(phy_mac_interrupt);
+EXPORT_SYMBOL_GPL(phy_eee_tx_clock_stop_capable);
-static void mmd_eee_adv_to_linkmode(unsigned long *advertising, u16 eee_adv)
-{
- linkmode_zero(advertising);
-
- if (eee_adv & MDIO_EEE_100TX)
- linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT,
- advertising);
- if (eee_adv & MDIO_EEE_1000T)
- linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
- advertising);
- if (eee_adv & MDIO_EEE_10GT)
- linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
- advertising);
- if (eee_adv & MDIO_EEE_1000KX)
- linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseKX_Full_BIT,
- advertising);
- if (eee_adv & MDIO_EEE_10GKX4)
- linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT,
- advertising);
- if (eee_adv & MDIO_EEE_10GKR)
- linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseKR_Full_BIT,
- advertising);
+/**
+ * phy_eee_rx_clock_stop() - configure PHY receive clock in LPI
+ * @phydev: target phy_device struct
+ * @clk_stop_enable: flag to indicate whether the clock can be stopped
+ *
+ * Configure whether the PHY can disable its receive clock during LPI mode,
+ * See IEEE 802.3 sections 22.2.2.2, 35.2.2.10, and 45.2.3.1.4.
+ *
+ * Returns: 0 or negative error.
+ */
+int phy_eee_rx_clock_stop(struct phy_device *phydev, bool clk_stop_enable)
+{
+ /* Configure the PHY to stop receiving xMII
+ * clock while it is signaling LPI.
+ */
+ return phy_modify_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1,
+ MDIO_PCS_CTRL1_CLKSTOP_EN,
+ clk_stop_enable ? MDIO_PCS_CTRL1_CLKSTOP_EN : 0);
}
+EXPORT_SYMBOL_GPL(phy_eee_rx_clock_stop);
/**
* phy_init_eee - init and check the EEE feature
@@ -1301,62 +1864,21 @@ static void mmd_eee_adv_to_linkmode(unsigned long *advertising, u16 eee_adv)
*/
int phy_init_eee(struct phy_device *phydev, bool clk_stop_enable)
{
+ int ret;
+
if (!phydev->drv)
return -EIO;
- /* According to 802.3az,the EEE is supported only in full duplex-mode.
- */
- if (phydev->duplex == DUPLEX_FULL) {
- __ETHTOOL_DECLARE_LINK_MODE_MASK(common);
- __ETHTOOL_DECLARE_LINK_MODE_MASK(lp);
- __ETHTOOL_DECLARE_LINK_MODE_MASK(adv);
- int eee_lp, eee_cap, eee_adv;
- int status;
- u32 cap;
-
- /* Read phy status to properly get the right settings */
- status = phy_read_status(phydev);
- if (status)
- return status;
-
- /* First check if the EEE ability is supported */
- eee_cap = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_EEE_ABLE);
- if (eee_cap <= 0)
- goto eee_exit_err;
-
- cap = mmd_eee_cap_to_ethtool_sup_t(eee_cap);
- if (!cap)
- goto eee_exit_err;
-
- /* Check which link settings negotiated and verify it in
- * the EEE advertising registers.
- */
- eee_lp = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_LPABLE);
- if (eee_lp <= 0)
- goto eee_exit_err;
-
- eee_adv = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV);
- if (eee_adv <= 0)
- goto eee_exit_err;
-
- mmd_eee_adv_to_linkmode(adv, eee_adv);
- mmd_eee_adv_to_linkmode(lp, eee_lp);
- linkmode_and(common, adv, lp);
-
- if (!phy_check_valid(phydev->speed, phydev->duplex, common))
- goto eee_exit_err;
+ ret = genphy_c45_eee_is_active(phydev, NULL);
+ if (ret < 0)
+ return ret;
+ if (!ret)
+ return -EPROTONOSUPPORT;
- if (clk_stop_enable)
- /* Configure the PHY to stop receiving xMII
- * clock while it is signaling LPI.
- */
- phy_set_bits_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1,
- MDIO_PCS_CTRL1_CLKSTOP_EN);
+ if (clk_stop_enable)
+ ret = phy_eee_rx_clock_stop(phydev, true);
- return 0; /* EEE supported */
- }
-eee_exit_err:
- return -EPROTONOSUPPORT;
+ return ret < 0 ? ret : 0;
}
EXPORT_SYMBOL(phy_init_eee);
@@ -1369,99 +1891,107 @@ EXPORT_SYMBOL(phy_init_eee);
*/
int phy_get_eee_err(struct phy_device *phydev)
{
+ int ret;
+
if (!phydev->drv)
return -EIO;
- return phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_EEE_WK_ERR);
+ mutex_lock(&phydev->lock);
+ ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_EEE_WK_ERR);
+ mutex_unlock(&phydev->lock);
+
+ return ret;
}
EXPORT_SYMBOL(phy_get_eee_err);
/**
* phy_ethtool_get_eee - get EEE supported and status
* @phydev: target phy_device struct
- * @data: ethtool_eee data
+ * @data: ethtool_keee data
*
- * Description: it reportes the Supported/Advertisement/LP Advertisement
- * capabilities.
+ * Description: get the current EEE settings, filling in all members of
+ * @data.
*/
-int phy_ethtool_get_eee(struct phy_device *phydev, struct ethtool_eee *data)
+int phy_ethtool_get_eee(struct phy_device *phydev, struct ethtool_keee *data)
{
- int val;
+ int ret;
if (!phydev->drv)
return -EIO;
- /* Get Supported EEE */
- val = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_EEE_ABLE);
- if (val < 0)
- return val;
- data->supported = mmd_eee_cap_to_ethtool_sup_t(val);
+ mutex_lock(&phydev->lock);
+ ret = genphy_c45_ethtool_get_eee(phydev, data);
+ eeecfg_to_eee(data, &phydev->eee_cfg);
+ mutex_unlock(&phydev->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(phy_ethtool_get_eee);
- /* Get advertisement EEE */
- val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV);
- if (val < 0)
- return val;
- data->advertised = mmd_eee_adv_to_ethtool_adv_t(val);
- data->eee_enabled = !!data->advertised;
+/**
+ * phy_ethtool_set_eee_noneg - Adjusts MAC LPI configuration without PHY
+ * renegotiation
+ * @phydev: pointer to the target PHY device structure
+ * @old_cfg: pointer to the eee_config structure containing the old EEE settings
+ *
+ * This function updates the Energy Efficient Ethernet (EEE) configuration
+ * for cases where only the MAC's Low Power Idle (LPI) configuration changes,
+ * without triggering PHY renegotiation. It ensures that the MAC is properly
+ * informed of the new LPI settings by cycling the link down and up, which
+ * is necessary for the MAC to adopt the new configuration. This adjustment
+ * is done only if there is a change in the tx_lpi_enabled or tx_lpi_timer
+ * configuration.
+ */
+static void phy_ethtool_set_eee_noneg(struct phy_device *phydev,
+ const struct eee_config *old_cfg)
+{
+ bool enable_tx_lpi;
- /* Get LP advertisement EEE */
- val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_LPABLE);
- if (val < 0)
- return val;
- data->lp_advertised = mmd_eee_adv_to_ethtool_adv_t(val);
+ if (!phydev->link)
+ return;
- data->eee_active = !!(data->advertised & data->lp_advertised);
+ enable_tx_lpi = phydev->eee_cfg.tx_lpi_enabled && phydev->eee_active;
- return 0;
+ if (phydev->enable_tx_lpi != enable_tx_lpi ||
+ phydev->eee_cfg.tx_lpi_timer != old_cfg->tx_lpi_timer) {
+ phydev->enable_tx_lpi = false;
+ phydev->link = false;
+ phy_link_down(phydev);
+ phydev->enable_tx_lpi = enable_tx_lpi;
+ phydev->link = true;
+ phy_link_up(phydev);
+ }
}
-EXPORT_SYMBOL(phy_ethtool_get_eee);
/**
* phy_ethtool_set_eee - set EEE supported and status
* @phydev: target phy_device struct
- * @data: ethtool_eee data
+ * @data: ethtool_keee data
*
* Description: it is to program the Advertisement EEE register.
*/
-int phy_ethtool_set_eee(struct phy_device *phydev, struct ethtool_eee *data)
+int phy_ethtool_set_eee(struct phy_device *phydev, struct ethtool_keee *data)
{
- int cap, old_adv, adv = 0, ret;
+ struct eee_config old_cfg;
+ int ret;
if (!phydev->drv)
return -EIO;
- /* Get Supported EEE */
- cap = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_EEE_ABLE);
- if (cap < 0)
- return cap;
-
- old_adv = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV);
- if (old_adv < 0)
- return old_adv;
+ mutex_lock(&phydev->lock);
- if (data->eee_enabled) {
- adv = !data->advertised ? cap :
- ethtool_adv_to_mmd_eee_adv_t(data->advertised) & cap;
- /* Mask prohibited EEE modes */
- adv &= ~phydev->eee_broken_modes;
- }
+ old_cfg = phydev->eee_cfg;
+ eee_to_eeecfg(&phydev->eee_cfg, data);
- if (old_adv != adv) {
- ret = phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, adv);
- if (ret < 0)
- return ret;
+ 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;
- /* Restart autonegotiation so the new modes get sent to the
- * link partner.
- */
- if (phydev->autoneg == AUTONEG_ENABLE) {
- ret = phy_restart_aneg(phydev);
- if (ret < 0)
- return ret;
- }
- }
+ mutex_unlock(&phydev->lock);
- return 0;
+ return ret < 0 ? ret : 0;
}
EXPORT_SYMBOL(phy_ethtool_set_eee);
@@ -1473,8 +2003,15 @@ EXPORT_SYMBOL(phy_ethtool_set_eee);
*/
int phy_ethtool_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol)
{
- if (phydev->drv && phydev->drv->set_wol)
- return phydev->drv->set_wol(phydev, wol);
+ int ret;
+
+ if (phydev->drv && phydev->drv->set_wol) {
+ mutex_lock(&phydev->lock);
+ ret = phydev->drv->set_wol(phydev, wol);
+ mutex_unlock(&phydev->lock);
+
+ return ret;
+ }
return -EOPNOTSUPP;
}
@@ -1488,8 +2025,11 @@ EXPORT_SYMBOL(phy_ethtool_set_wol);
*/
void phy_ethtool_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol)
{
- if (phydev->drv && phydev->drv->get_wol)
+ if (phydev->drv && phydev->drv->get_wol) {
+ mutex_lock(&phydev->lock);
phydev->drv->get_wol(phydev, wol);
+ mutex_unlock(&phydev->lock);
+ }
}
EXPORT_SYMBOL(phy_ethtool_get_wol);
@@ -1526,6 +2066,7 @@ EXPORT_SYMBOL(phy_ethtool_set_link_ksettings);
int phy_ethtool_nway_reset(struct net_device *ndev)
{
struct phy_device *phydev = ndev->phydev;
+ int ret;
if (!phydev)
return -ENODEV;
@@ -1533,6 +2074,10 @@ int phy_ethtool_nway_reset(struct net_device *ndev)
if (!phydev->drv)
return -EIO;
- return phy_restart_aneg(phydev);
+ mutex_lock(&phydev->lock);
+ ret = phy_restart_aneg(phydev);
+ mutex_unlock(&phydev->lock);
+
+ return ret;
}
EXPORT_SYMBOL(phy_ethtool_nway_reset);