// SPDX-License-Identifier: GPL-2.0+ /* Framework for configuring and reading PHY devices * Based on code in sungem_phy.c and gianfar_phy.c * * Author: Andy Fleming * * Copyright (c) 2004 Freescale Semiconductor, Inc. * Copyright (c) 2006, 2007 Maciej W. Rozycki
*/
if (local_asym_pause && phydev->asym_pause) { if (local_pause) return"rx"; if (phydev->pause) return"tx";
}
no_pause: return"off";
}
/** * phy_print_status - Convenience function to print out the current phy status * @phydev: the phy_device struct
*/ void phy_print_status(struct phy_device *phydev)
{ if (phydev->link) {
netdev_info(phydev->attached_dev, "Link is Up - %s/%s %s- flow control %s\n",
phy_speed_to_str(phydev->speed),
phy_duplex_to_str(phydev->duplex),
phydev->downshifted_rate ? "(downshifted) " : "",
phy_pause_str(phydev));
} else {
netdev_info(phydev->attached_dev, "Link is Down\n");
}
}
EXPORT_SYMBOL(phy_print_status);
/** * phy_get_rate_matching - determine if rate matching is supported * @phydev: The phy device to return rate matching for * @iface: The interface mode to use * * This determines the type of rate matching (if any) that @phy supports * using @iface. @iface may be %PHY_INTERFACE_MODE_NA to determine if any * interface supports rate matching. * * Return: The type of rate matching @phy supports for @iface, or * %RATE_MATCH_NONE.
*/ int phy_get_rate_matching(struct phy_device *phydev,
phy_interface_t iface)
{ int ret = RATE_MATCH_NONE;
if (phydev->drv->get_rate_matching) {
mutex_lock(&phydev->lock);
ret = phydev->drv->get_rate_matching(phydev, iface);
mutex_unlock(&phydev->lock);
}
/** * phy_aneg_done - return auto-negotiation status * @phydev: target phy_device struct * * Description: Return the auto-negotiation status from this @phydev * Returns > 0 on success or < 0 on error. 0 means that auto-negotiation * is still pending.
*/ int phy_aneg_done(struct phy_device *phydev)
{ if (phydev->drv && phydev->drv->aneg_done) return phydev->drv->aneg_done(phydev); elseif (phydev->is_c45) return genphy_c45_aneg_done(phydev); else return genphy_aneg_done(phydev);
}
EXPORT_SYMBOL(phy_aneg_done);
/** * 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. * @size: size of speeds buffer. * * Description: Returns the number of supported speeds, and fills the speeds * buffer with the supported speeds. If speeds buffer is too small to contain * all currently supported speeds, will return as many speeds as can fit.
*/ unsignedint phy_supported_speeds(struct phy_device *phy, unsignedint *speeds, unsignedint size)
{ return phy_caps_speeds(speeds, size, phy->supported);
}
/** * phy_check_valid - check if there is a valid PHY setting which matches * speed, duplex, and feature mask * @speed: speed to match * @duplex: duplex to match * @features: A mask of the valid settings * * Description: Returns true if there is a valid setting, false otherwise.
*/ bool phy_check_valid(int speed, int duplex, unsignedlong *features)
{ 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 * @phydev: the target phy_device struct * * Description: Make sure the PHY is set to supported speeds and * duplexes. Drop down by one in this order: 1000/FULL, * 1000/HALF, 100/FULL, 100/HALF, 10/FULL, 10/HALF.
*/ staticvoid phy_sanitize_settings(struct phy_device *phydev)
{ conststruct link_capabilities *c;
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;
phydev->duplex = DUPLEX_UNKNOWN;
}
}
/** * phy_mii_ioctl - generic PHY MII ioctl interface * @phydev: the phy_device struct * @ifr: &struct ifreq for socket ioctl's * @cmd: ioctl cmd to execute * * Note that this function is currently incompatible with the * PHYCONTROL layer. It changes registers without regard to * current state. Use at own risk.
*/ 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:
mii_data->phy_id = phydev->mdio.addr;
fallthrough;
case SIOCGMIIREG: 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);
ret = mdiobus_c45_read(phydev->mdio.bus, prtad, devad,
mii_data->reg_num);
} else {
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: 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);
} else {
prtad = mii_data->phy_id;
devad = mii_data->reg_num;
} if (prtad == phydev->mdio.addr) { switch (devad) { case MII_BMCR: if ((val & (BMCR_RESET | BMCR_ANENABLE)) == 0) { if (phydev->autoneg == AUTONEG_ENABLE)
change_autoneg = true;
phydev->autoneg = AUTONEG_DISABLE; if (val & BMCR_FULLDPLX)
phydev->duplex = DUPLEX_FULL; else
phydev->duplex = DUPLEX_HALF; if (val & BMCR_SPEED1000)
phydev->speed = SPEED_1000; elseif (val & BMCR_SPEED100)
phydev->speed = SPEED_100; else phydev->speed = SPEED_10;
} else { if (phydev->autoneg == AUTONEG_DISABLE)
change_autoneg = true;
phydev->autoneg = AUTONEG_ENABLE;
} break; case MII_ADVERTISE:
mii_adv_mod_linkmode_adv_t(phydev->advertising,
val);
change_autoneg = true; break; case MII_CTRL1000:
mii_ctrl1000_mod_linkmode_adv_t(phydev->advertising,
val);
change_autoneg = true; break; default: /* do nothing */ break;
}
}
/** * phy_do_ioctl_running - generic ndo_eth_ioctl implementation but test first * * @dev: the net_device struct * @ifr: &struct ifreq for socket ioctl's * @cmd: ioctl cmd to execute * * Same as phy_do_ioctl, but ensures that net_device is running before * handling the ioctl.
*/ int phy_do_ioctl_running(struct net_device *dev, struct ifreq *ifr, int cmd)
{ if (!netif_running(dev)) return -ENODEV;
/** * __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;
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) return phydev->mii_ts->hwtstamp(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
*/ staticvoid phy_queue_state_machine(struct phy_device *phydev, unsignedlong jiffies)
{
mod_delayed_work(system_power_efficient_wq, &phydev->state_queue,
jiffies);
}
/** * phy_trigger_machine - Trigger the state machine to run now * * @phydev: the phy_device struct
*/ void phy_trigger_machine(struct phy_device *phydev)
{
phy_queue_state_machine(phydev, 0);
}
EXPORT_SYMBOL(phy_trigger_machine);
staticvoid phy_abort_cable_test(struct phy_device *phydev)
{ int err;
ethnl_cable_test_finished(phydev);
err = phy_init_hw(phydev); if (err)
phydev_err(phydev, "Error while aborting cable test");
}
/** * phy_ethtool_get_strings - Get the statistic counter names * * @phydev: the phy_device struct * @data: Where to put the strings
*/ int phy_ethtool_get_strings(struct phy_device *phydev, u8 *data)
{ if (!phydev->drv) return -EIO;
/** * phy_ethtool_get_sset_count - Get the number of statistic counters * * @phydev: the phy_device struct
*/ int phy_ethtool_get_sset_count(struct phy_device *phydev)
{ int ret;
if (!phydev->drv) return -EIO;
if (phydev->drv->get_sset_count &&
phydev->drv->get_strings &&
phydev->drv->get_stats) {
mutex_lock(&phydev->lock);
ret = phydev->drv->get_sset_count(phydev);
mutex_unlock(&phydev->lock);
/** * phy_ethtool_get_stats - Get the statistic counters * * @phydev: the phy_device struct * @stats: What counters to get * @data: Where to store the counters
*/ int phy_ethtool_get_stats(struct phy_device *phydev, struct ethtool_stats *stats, u64 *data)
{ if (!phydev->drv) return -EIO;
/** * __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;
/** * 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.
*/ staticint plca_check_valid(struct phy_device *phydev, conststruct 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");
} elseif (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, conststruct 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);
/** * 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 * @extack: extack for reporting useful error messages
*/ int phy_start_cable_test(struct phy_device *phydev, struct netlink_ext_ack *extack)
{ struct net_device *dev = phydev->attached_dev; int err = -ENOMEM;
if (!(phydev->drv &&
phydev->drv->cable_test_start &&
phydev->drv->cable_test_get_status)) {
NL_SET_ERR_MSG(extack, "PHY driver does not support cable testing"); return -EOPNOTSUPP;
}
mutex_lock(&phydev->lock); if (phydev->state == PHY_CABLETEST) {
NL_SET_ERR_MSG(extack, "PHY already performing a test");
err = -EBUSY; goto out;
}
/** * phy_start_cable_test_tdr - Start a raw TDR cable test * * @phydev: the phy_device struct * @extack: extack for reporting useful error messages * @config: Configuration of the test to run
*/ int phy_start_cable_test_tdr(struct phy_device *phydev, struct netlink_ext_ack *extack, conststruct phy_tdr_config *config)
{ struct net_device *dev = phydev->attached_dev; int err = -ENOMEM;
if (!(phydev->drv &&
phydev->drv->cable_test_tdr_start &&
phydev->drv->cable_test_get_status)) {
NL_SET_ERR_MSG(extack, "PHY driver does not support cable test TDR"); return -EOPNOTSUPP;
}
mutex_lock(&phydev->lock); if (phydev->state == PHY_CABLETEST) {
NL_SET_ERR_MSG(extack, "PHY already performing a test");
err = -EBUSY; goto out;
}
/** * phy_check_link_status - check link status and set state accordingly * @phydev: the phy_device struct * * Description: Check for link and whether autoneg was triggered / is running * and set state accordingly
*/ staticint phy_check_link_status(struct phy_device *phydev)
{ int err;
lockdep_assert_held(&phydev->lock);
/* Keep previous state if loopback is enabled because some PHYs * report that Link is Down when loopback is enabled.
*/ if (phydev->loopback_enabled) return 0;
err = phy_read_status(phydev); if (err) return err;
/** * 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.
*/ unsignedint 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, unsignedint modes)
{
lockdep_assert_held(&phydev->lock);
/** * _phy_start_aneg - start auto-negotiation for this PHY device * @phydev: the phy_device struct * * Description: Sanitizes the settings (if we're not autonegotiating * them), and then calls the driver's config_aneg function. * If the PHYCONTROL Layer is operating, we change the state to * reflect the beginning of Auto-negotiation or forcing.
*/ int _phy_start_aneg(struct phy_device *phydev)
{ int err;
lockdep_assert_held(&phydev->lock);
if (!phydev->drv) return -EIO;
if (AUTONEG_DISABLE == phydev->autoneg)
phy_sanitize_settings(phydev);
err = phy_config_aneg(phydev); if (err < 0) return err;
if (phy_is_started(phydev))
err = phy_check_link_status(phydev);
return err;
}
EXPORT_SYMBOL(_phy_start_aneg);
/** * phy_start_aneg - start auto-negotiation for this PHY device * @phydev: the phy_device struct * * Description: Sanitizes the settings (if we're not autonegotiating * them), and then calls the driver's config_aneg function. * If the PHYCONTROL Layer is operating, we change the state to * reflect the beginning of Auto-negotiation or forcing.
*/ int phy_start_aneg(struct phy_device *phydev)
{ int err;
/** * phy_speed_down - set speed to lowest speed supported by both link partners * @phydev: the phy_device struct * @sync: perform action synchronously * * Description: Typically used to save energy when waiting for a WoL packet * * WARNING: Setting sync to false may cause the system being unable to suspend * in case the PHY generates an interrupt when finishing the autonegotiation. * This interrupt may wake up the system immediately after suspend. * Therefore use sync = false only if you're sure it's safe with the respective * network chip.
*/ int phy_speed_down(struct phy_device *phydev, bool sync)
{
__ETHTOOL_DECLARE_LINK_MODE_MASK(adv_tmp); int ret = 0;
mutex_lock(&phydev->lock);
if (phydev->autoneg != AUTONEG_ENABLE) goto out;
linkmode_copy(adv_tmp, phydev->advertising);
ret = phy_speed_down_core(phydev); if (ret) goto out;
linkmode_copy(phydev->adv_old, adv_tmp);
if (linkmode_equal(phydev->advertising, adv_tmp)) {
ret = 0; goto out;
}
ret = phy_config_aneg(phydev); if (ret) goto out;
ret = sync ? phy_poll_aneg_done(phydev) : 0;
out:
mutex_unlock(&phydev->lock);
return ret;
}
EXPORT_SYMBOL_GPL(phy_speed_down);
/** * phy_speed_up - (re)set advertised speeds to all supported speeds * @phydev: the phy_device struct * * Description: Used to revert the effect of phy_speed_down
*/ int phy_speed_up(struct phy_device *phydev)
{
__ETHTOOL_DECLARE_LINK_MODE_MASK(adv_tmp); int ret = 0;
if (linkmode_equal(phydev->advertising, adv_tmp)) goto out;
ret = phy_config_aneg(phydev);
out:
mutex_unlock(&phydev->lock);
return ret;
}
EXPORT_SYMBOL_GPL(phy_speed_up);
/** * phy_start_machine - start PHY state machine tracking * @phydev: the phy_device struct * * Description: The PHY infrastructure can run a state machine * which tracks whether the PHY is starting up, negotiating, * etc. This function starts the delayed workqueue which tracks * the state of the PHY. If you want to maintain your own state machine, * do not call this function.
*/ void phy_start_machine(struct phy_device *phydev)
{
phy_trigger_machine(phydev);
}
EXPORT_SYMBOL_GPL(phy_start_machine);
/** * phy_stop_machine - stop the PHY state machine tracking * @phydev: target phy_device struct * * Description: Stops the state machine delayed workqueue, sets the * state to UP (unless it wasn't up yet). This function must be * called BEFORE phy_detach.
*/ void phy_stop_machine(struct phy_device *phydev)
{
cancel_delayed_work_sync(&phydev->state_queue);
mutex_lock(&phydev->lock); if (phy_is_started(phydev))
phydev->state = PHY_UP;
mutex_unlock(&phydev->lock);
}
staticvoid 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");
/** * phy_error - enter ERROR state for this PHY device * @phydev: target phy_device struct * * Moves the PHY to the ERROR state in response to a read * or write error, and tells the controller the link is down. * Must be called with phydev->lock held.
*/ void phy_error(struct phy_device *phydev)
{
WARN_ON(1);
phy_process_error(phydev);
}
EXPORT_SYMBOL(phy_error);
/** * phy_disable_interrupts - Disable the PHY interrupts from the PHY side * @phydev: target phy_device struct
*/ int phy_disable_interrupts(struct phy_device *phydev)
{ /* Disable PHY interrupts */ return phy_config_interrupt(phydev, PHY_INTERRUPT_DISABLED);
}
/* Wakeup interrupts may occur during a system sleep transition. * Postpone handling until the PHY has resumed.
*/ if (IS_ENABLED(CONFIG_PM_SLEEP) && phydev->irq_suspended) { struct net_device *netdev = phydev->attached_dev;
if (netdev) { struct device *parent = netdev->dev.parent;
mutex_lock(&phydev->lock);
ret = phydev->drv->handle_interrupt(phydev);
mutex_unlock(&phydev->lock);
return ret;
}
/** * phy_enable_interrupts - Enable the interrupts from the PHY side * @phydev: target phy_device struct
*/ staticint phy_enable_interrupts(struct phy_device *phydev)
{ return phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED);
}
/** * 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.
*/ staticint 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 * * Description: Request and enable the interrupt for the given PHY. * If this fails, then we set irq to PHY_POLL. * This should only be called with a valid IRQ number.
*/ void phy_request_interrupt(struct phy_device *phydev)
{ int err;
err = request_threaded_irq(phydev->irq, NULL, phy_interrupt,
IRQF_ONESHOT | IRQF_SHARED,
phydev_name(phydev), phydev); if (err) {
phydev_warn(phydev, "Error %d requesting IRQ %d, falling back to polling\n",
err, phydev->irq);
phydev->irq = PHY_POLL;
} else { if (phy_enable_interrupts(phydev)) {
phydev_warn(phydev, "Can't enable interrupt, falling back to polling\n");
phy_free_interrupt(phydev);
phydev->irq = PHY_POLL;
}
}
}
EXPORT_SYMBOL(phy_request_interrupt);
/** * phy_free_interrupt - disable and free interrupt for a PHY device * @phydev: target phy_device struct * * Description: Disable and free the interrupt for the given PHY. * This should only be called with a valid IRQ number.
*/ void phy_free_interrupt(struct phy_device *phydev)
{
phy_disable_interrupts(phydev);
free_irq(phydev->irq, 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
*/ staticunsignedint 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);
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 (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 */ staticvoid _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;
/* Cannot call flush_scheduled_work() here as desired because * of rtnl_lock(), but PHY_HALTED shall guarantee irq handler * will not reenable interrupts.
*/
}
EXPORT_SYMBOL(phy_stop);
/** * phy_start - start or restart a PHY device * @phydev: target phy_device struct * * Description: Indicates the attached device's readiness to * handle PHY-related work. Used during startup to start the * PHY, and after a call to phy_stop() to resume operation. * Also used to indicate the MDIO bus has cleared an error * condition.
*/ void phy_start(struct phy_device *phydev)
{
mutex_lock(&phydev->lock);
if (phydev->state != PHY_READY && phydev->state != PHY_HALTED) {
WARN(1, "called from state %s\n",
phy_state_to_str(phydev->state)); goto out;
}
if (phydev->sfp_bus)
sfp_upstream_start(phydev->sfp_bus);
/* if phy was suspended, bring the physical link up again */
__phy_resume(phydev);
/** * 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_mac_interrupt(struct phy_device *phydev)
{ /* Trigger a state machine change */
phy_trigger_machine(phydev);
}
EXPORT_SYMBOL(phy_mac_interrupt);
/** * 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;
if (!phydev->drv) return -EIO;
mutex_lock(&phydev->lock);
if (enable && phydev->loopback_enabled) {
ret = -EBUSY; goto out;
}
if (!enable && !phydev->loopback_enabled) {
ret = -EINVAL; goto out;
}
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 (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);
}
/** * phy_eee_tx_clock_stop_capable() - indicate whether the MAC can stop tx clock * @phydev: target phy_device struct * * 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.
*/ int phy_eee_tx_clock_stop_capable(struct phy_device *phydev)
{ int stat1;
/** * 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 * @phydev: target phy_device struct * @clk_stop_enable: PHY may stop the clock during LPI * * Description: it checks if the Energy-Efficient Ethernet (EEE) * is supported by looking at the MMD registers 3.20 and 7.60/61 * and it programs the MMD register 3.0 setting the "Clock stop enable" * bit if required.
*/ int phy_init_eee(struct phy_device *phydev, bool clk_stop_enable)
{ int ret;
if (!phydev->drv) return -EIO;
ret = genphy_c45_eee_is_active(phydev, NULL); if (ret < 0) return ret; if (!ret) return -EPROTONOSUPPORT;
if (clk_stop_enable)
ret = phy_eee_rx_clock_stop(phydev, true);
return ret < 0 ? ret : 0;
}
EXPORT_SYMBOL(phy_init_eee);
/** * phy_get_eee_err - report the EEE wake error count * @phydev: target phy_device struct * * Description: it is to report the number of time where the PHY * failed to complete its normal wake sequence.
*/ int phy_get_eee_err(struct phy_device *phydev)
{ int ret;
if (!phydev->drv) return -EIO;
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_keee data * * Description: get the current EEE settings, filling in all members of * @data.
*/ int phy_ethtool_get_eee(struct phy_device *phydev, struct ethtool_keee *data)
{ int ret;
if (!phydev->drv) return -EIO;
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);
/** * 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.
*/ staticvoid phy_ethtool_set_eee_noneg(struct phy_device *phydev, conststruct eee_config *old_cfg)
{ bool enable_tx_lpi;
/** * phy_ethtool_set_eee - set EEE supported and status * @phydev: target phy_device struct * @data: ethtool_keee data * * Description: it is to program the Advertisement EEE register.
*/ int phy_ethtool_set_eee(struct phy_device *phydev, struct ethtool_keee *data)
{ struct eee_config old_cfg; int ret;
/** * phy_ethtool_get_wol - Get the current Wake On LAN configuration * * @phydev: target phy_device struct * @wol: Store the current configuration here
*/ void phy_ethtool_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *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);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.