2106 lines · 52.3 KB · C
RawDownload
// 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.