// 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 */ #include <linux/kernel.h>#include <linux/string.h>#include <linux/errno.h>#include <linux/unistd.h>#include <linux/interrupt.h>#include <linux/delay.h>#include <linux/netdevice.h>#include <linux/netlink.h>#include <linux/etherdevice.h>#include <linux/skbuff.h>#include <linux/mm.h>#include <linux/module.h>#include <linux/mii.h>#include <linux/ethtool.h>#include <linux/ethtool_netlink.h>#include <linux/phy.h>#include <linux/phy_led_triggers.h>#include <linux/sfp.h>#include <linux/workqueue.h>#include <linux/mdio.h>#include <linux/io.h>#include <linux/uaccess.h>#include <linux/atomic.h>#include <linux/suspend.h>#include <net/netlink.h>#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) \ case PHY_##_state: \ return __stringify(_state); \ static const char *phy_state_to_str(enum phy_state st){ switch (st) { PHY_STATE_STR(DOWN) PHY_STATE_STR(READY) PHY_STATE_STR(UP) PHY_STATE_STR(RUNNING) 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); phy_led_trigger_change_speed(phydev);} static void phy_link_down(struct phy_device *phydev){ phydev->phy_link_change(phydev, false); phy_led_trigger_change_speed(phydev); WRITE_ONCE(phydev->link_down_events, phydev->link_down_events + 1);} static const char *phy_pause_str(struct phy_device *phydev){ bool local_pause, local_asym_pause; if (phydev->autoneg == AUTONEG_DISABLE) goto no_pause; local_pause = linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT, phydev->advertising); local_asym_pause = linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, phydev->advertising); if (local_pause && phydev->pause) return "rx/tx"; 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); } return ret;}EXPORT_SYMBOL_GPL(phy_get_rate_matching); /** * phy_config_interrupt - configure the PHY device for the requested interrupts * @phydev: the phy_device struct * @interrupts: interrupt flags to configure for this @phydev * * Returns 0 on success or < 0 on error. */static int phy_config_interrupt(struct phy_device *phydev, bool interrupts){ phydev->interrupts = interrupts ? 1 : 0; if (phydev->drv->config_intr) return phydev->drv->config_intr(phydev); return 0;} /** * phy_restart_aneg - restart auto-negotiation * @phydev: target phy_device struct * * Restart the autonegotiation on @phydev. Returns >= 0 on success or * negative errno on error. */int phy_restart_aneg(struct phy_device *phydev){ int ret; if (phydev->is_c45 && !(phydev->c45_ids.devices_in_package & BIT(0))) ret = genphy_c45_restart_aneg(phydev); else ret = genphy_restart_aneg(phydev); return ret;}EXPORT_SYMBOL_GPL(phy_restart_aneg); /** * 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); else if (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. */unsigned int phy_supported_speeds(struct phy_device *phy, unsigned int *speeds, unsigned int 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, unsigned long *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. */static void phy_sanitize_settings(struct phy_device *phydev){ const struct 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; }} void phy_ethtool_ksettings_get(struct phy_device *phydev, struct ethtool_link_ksettings *cmd){ mutex_lock(&phydev->lock); linkmode_copy(cmd->link_modes.supported, phydev->supported); linkmode_copy(cmd->link_modes.advertising, phydev->advertising); linkmode_copy(cmd->link_modes.lp_advertising, phydev->lp_advertising); cmd->base.speed = phydev->speed; cmd->base.duplex = phydev->duplex; cmd->base.master_slave_cfg = phydev->master_slave_get; cmd->base.master_slave_state = phydev->master_slave_state; cmd->base.rate_matching = phydev->rate_matching; if (phydev->interface == PHY_INTERFACE_MODE_MOCA) cmd->base.port = PORT_BNC; else cmd->base.port = phydev->port; cmd->base.transceiver = phydev->is_internal ? XCVR_INTERNAL : XCVR_EXTERNAL; cmd->base.phy_address = phydev->mdio.addr; cmd->base.autoneg = phydev->autoneg; cmd->base.eth_tp_mdix_ctrl = phydev->mdix_ctrl; cmd->base.eth_tp_mdix = phydev->mdix; mutex_unlock(&phydev->lock);}EXPORT_SYMBOL(phy_ethtool_ksettings_get); /** * 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; else if (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; } } if (mdio_phy_id_is_c45(mii_data->phy_id)) mdiobus_c45_write(phydev->mdio.bus, prtad, devad, mii_data->reg_num, val); else mdiobus_write(phydev->mdio.bus, prtad, devad, val); if (prtad == phydev->mdio.addr && devad == MII_BMCR && val & BMCR_RESET) return phy_init_hw(phydev); if (change_autoneg) return phy_start_aneg(phydev); return 0; case SIOCSHWTSTAMP: 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: return -EOPNOTSUPP; }}EXPORT_SYMBOL(phy_mii_ioctl); /** * phy_do_ioctl - generic ndo_eth_ioctl implementation * @dev: the net_device struct * @ifr: &struct ifreq for socket ioctl's * @cmd: ioctl cmd to execute */int phy_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd){ if (!dev->phydev) return -ENODEV; return phy_mii_ioctl(dev->phydev, ifr, cmd);}EXPORT_SYMBOL(phy_do_ioctl); /** * 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; return phy_do_ioctl(dev, ifr, 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; Lines 1–500 of 2106. Load more
// 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 */ #include <linux/kernel.h> #include <linux/string.h> #include <linux/errno.h> #include <linux/unistd.h> #include <linux/interrupt.h> #include <linux/delay.h> #include <linux/netdevice.h> #include <linux/netlink.h> #include <linux/etherdevice.h> #include <linux/skbuff.h> #include <linux/mm.h> #include <linux/module.h> #include <linux/mii.h> #include <linux/ethtool.h> #include <linux/ethtool_netlink.h> #include <linux/phy.h> #include <linux/phy_led_triggers.h> #include <linux/sfp.h> #include <linux/workqueue.h> #include <linux/mdio.h> #include <linux/io.h> #include <linux/uaccess.h> #include <linux/atomic.h> #include <linux/suspend.h> #include <net/netlink.h> #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) \ case PHY_##_state: \ return __stringify(_state); \ static const char *phy_state_to_str(enum phy_state st) { switch (st) { PHY_STATE_STR(DOWN) PHY_STATE_STR(READY) PHY_STATE_STR(UP) PHY_STATE_STR(RUNNING) 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); phy_led_trigger_change_speed(phydev); } static void phy_link_down(struct phy_device *phydev) { phydev->phy_link_change(phydev, false); phy_led_trigger_change_speed(phydev); WRITE_ONCE(phydev->link_down_events, phydev->link_down_events + 1); } static const char *phy_pause_str(struct phy_device *phydev) { bool local_pause, local_asym_pause; if (phydev->autoneg == AUTONEG_DISABLE) goto no_pause; local_pause = linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT, phydev->advertising); local_asym_pause = linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, phydev->advertising); if (local_pause && phydev->pause) return "rx/tx"; 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); } return ret; } EXPORT_SYMBOL_GPL(phy_get_rate_matching); /** * phy_config_interrupt - configure the PHY device for the requested interrupts * @phydev: the phy_device struct * @interrupts: interrupt flags to configure for this @phydev * * Returns 0 on success or < 0 on error. */ static int phy_config_interrupt(struct phy_device *phydev, bool interrupts) { phydev->interrupts = interrupts ? 1 : 0; if (phydev->drv->config_intr) return phydev->drv->config_intr(phydev); return 0; } /** * phy_restart_aneg - restart auto-negotiation * @phydev: target phy_device struct * * Restart the autonegotiation on @phydev. Returns >= 0 on success or * negative errno on error. */ int phy_restart_aneg(struct phy_device *phydev) { int ret; if (phydev->is_c45 && !(phydev->c45_ids.devices_in_package & BIT(0))) ret = genphy_c45_restart_aneg(phydev); else ret = genphy_restart_aneg(phydev); return ret; } EXPORT_SYMBOL_GPL(phy_restart_aneg); /** * 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); else if (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. */ unsigned int phy_supported_speeds(struct phy_device *phy, unsigned int *speeds, unsigned int 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, unsigned long *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. */ static void phy_sanitize_settings(struct phy_device *phydev) { const struct 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; } } void phy_ethtool_ksettings_get(struct phy_device *phydev, struct ethtool_link_ksettings *cmd) { mutex_lock(&phydev->lock); linkmode_copy(cmd->link_modes.supported, phydev->supported); linkmode_copy(cmd->link_modes.advertising, phydev->advertising); linkmode_copy(cmd->link_modes.lp_advertising, phydev->lp_advertising); cmd->base.speed = phydev->speed; cmd->base.duplex = phydev->duplex; cmd->base.master_slave_cfg = phydev->master_slave_get; cmd->base.master_slave_state = phydev->master_slave_state; cmd->base.rate_matching = phydev->rate_matching; if (phydev->interface == PHY_INTERFACE_MODE_MOCA) cmd->base.port = PORT_BNC; else cmd->base.port = phydev->port; cmd->base.transceiver = phydev->is_internal ? XCVR_INTERNAL : XCVR_EXTERNAL; cmd->base.phy_address = phydev->mdio.addr; cmd->base.autoneg = phydev->autoneg; cmd->base.eth_tp_mdix_ctrl = phydev->mdix_ctrl; cmd->base.eth_tp_mdix = phydev->mdix; mutex_unlock(&phydev->lock); } EXPORT_SYMBOL(phy_ethtool_ksettings_get); /** * 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; else if (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; } } if (mdio_phy_id_is_c45(mii_data->phy_id)) mdiobus_c45_write(phydev->mdio.bus, prtad, devad, mii_data->reg_num, val); else mdiobus_write(phydev->mdio.bus, prtad, devad, val); if (prtad == phydev->mdio.addr && devad == MII_BMCR && val & BMCR_RESET) return phy_init_hw(phydev); if (change_autoneg) return phy_start_aneg(phydev); return 0; case SIOCSHWTSTAMP: 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: return -EOPNOTSUPP; } } EXPORT_SYMBOL(phy_mii_ioctl); /** * phy_do_ioctl - generic ndo_eth_ioctl implementation * @dev: the net_device struct * @ifr: &struct ifreq for socket ioctl's * @cmd: ioctl cmd to execute */ int phy_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) { if (!dev->phydev) return -ENODEV; return phy_mii_ioctl(dev->phydev, ifr, cmd); } EXPORT_SYMBOL(phy_do_ioctl); /** * 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; return phy_do_ioctl(dev, ifr, 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;