Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/net/phy/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 119 kB image not shown  

Quelle  phylink.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0
/*
 * phylink models the MAC to optional PHY connection, supporting
 * technologies such as SFP cages where the PHY is hot-pluggable.
 *
 * Copyright (C) 2015 Russell King
 */

#include <linux/acpi.h>
#include <linux/ethtool.h>
#include <linux/export.h>
#include <linux/gpio/consumer.h>
#include <linux/netdevice.h>
#include <linux/of.h>
#include <linux/of_mdio.h>
#include <linux/phy.h>
#include <linux/phy_fixed.h>
#include <linux/phylink.h>
#include <linux/rtnetlink.h>
#include <linux/spinlock.h>
#include <linux/timer.h>
#include <linux/workqueue.h>

#include "phy-caps.h"
#include "sfp.h"
#include "swphy.h"

enum {
 PHYLINK_DISABLE_STOPPED,
 PHYLINK_DISABLE_LINK,
 PHYLINK_DISABLE_MAC_WOL,

 PCS_STATE_DOWN = 0,
 PCS_STATE_STARTING,
 PCS_STATE_STARTED,
};

/**
 * struct phylink - internal data type for phylink
 */

struct phylink {
 /* private: */
 struct net_device *netdev;
 const struct phylink_mac_ops *mac_ops;
 struct phylink_config *config;
 struct phylink_pcs *pcs;
 struct device *dev;
 unsigned int old_link_state:1;

 unsigned long phylink_disable_state; /* bitmask of disables */
 struct phy_device *phydev;
 phy_interface_t link_interface; /* PHY_INTERFACE_xxx */
 u8 cfg_link_an_mode;  /* MLO_AN_xxx */
 u8 req_link_an_mode;  /* Requested MLO_AN_xxx mode */
 u8 act_link_an_mode;  /* Active MLO_AN_xxx mode */
 u8 link_port;   /* The current non-phy ethtool port */
 __ETHTOOL_DECLARE_LINK_MODE_MASK(supported);
 __ETHTOOL_DECLARE_LINK_MODE_MASK(supported_lpi);

 /* The link configuration settings */
 struct phylink_link_state link_config;

 /* The current settings */
 phy_interface_t cur_interface;

 struct gpio_desc *link_gpio;
 unsigned int link_irq;
 struct timer_list link_poll;

 struct mutex state_mutex;
 /* Serialize updates to pl->phydev with phylink_resolve() */
 struct mutex phydev_mutex;
 struct phylink_link_state phy_state;
 unsigned int phy_ib_mode;
 struct work_struct resolve;
 unsigned int pcs_neg_mode;
 unsigned int pcs_state;

 bool link_failed;
 bool suspend_link_up;
 bool major_config_failed;
 bool mac_supports_eee_ops;
 bool mac_supports_eee;
 bool phy_enable_tx_lpi;
 bool mac_enable_tx_lpi;
 bool mac_tx_clk_stop;
 u32 mac_tx_lpi_timer;
 u8 mac_rx_clk_stop_blocked;

 struct sfp_bus *sfp_bus;
 bool sfp_may_have_phy;
 DECLARE_PHY_INTERFACE_MASK(sfp_interfaces);
 __ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_support);
 u8 sfp_port;

 struct eee_config eee_cfg;
};

#define phylink_printk(level, pl, fmt, ...) \
 do { \
  if ((pl)->config->type == PHYLINK_NETDEV) \
   netdev_printk(level, (pl)->netdev, fmt, ##__VA_ARGS__); \
  else if ((pl)->config->type == PHYLINK_DEV) \
   dev_printk(level, (pl)->dev, fmt, ##__VA_ARGS__); \
 } while (0)

#define phylink_err(pl, fmt, ...) \
 phylink_printk(KERN_ERR, pl, fmt, ##__VA_ARGS__)
#define phylink_warn(pl, fmt, ...) \
 phylink_printk(KERN_WARNING, pl, fmt, ##__VA_ARGS__)
#define phylink_info(pl, fmt, ...) \
 phylink_printk(KERN_INFO, pl, fmt, ##__VA_ARGS__)
#if defined(CONFIG_DYNAMIC_DEBUG)
#define phylink_dbg(pl, fmt, ...) \
do {         \
 if ((pl)->config->type == PHYLINK_NETDEV)   \
  netdev_dbg((pl)->netdev, fmt, ##__VA_ARGS__);  \
 else if ((pl)->config->type == PHYLINK_DEV)   \
  dev_dbg((pl)->dev, fmt, ##__VA_ARGS__);   \
while (0)
#elif defined(DEBUG)
#define phylink_dbg(pl, fmt, ...)     \
 phylink_printk(KERN_DEBUG, pl, fmt, ##__VA_ARGS__)
#else
#define phylink_dbg(pl, fmt, ...)     \
({         \
 if (0)        \
  phylink_printk(KERN_DEBUG, pl, fmt, ##__VA_ARGS__); \
})
#endif

static const phy_interface_t phylink_sfp_interface_preference[] = {
 PHY_INTERFACE_MODE_100GBASEP,
 PHY_INTERFACE_MODE_50GBASER,
 PHY_INTERFACE_MODE_LAUI,
 PHY_INTERFACE_MODE_25GBASER,
 PHY_INTERFACE_MODE_USXGMII,
 PHY_INTERFACE_MODE_10GBASER,
 PHY_INTERFACE_MODE_5GBASER,
 PHY_INTERFACE_MODE_2500BASEX,
 PHY_INTERFACE_MODE_SGMII,
 PHY_INTERFACE_MODE_1000BASEX,
 PHY_INTERFACE_MODE_100BASEX,
};

static DECLARE_PHY_INTERFACE_MASK(phylink_sfp_interfaces);

/**
 * phylink_set_port_modes() - set the port type modes in the ethtool mask
 * @mask: ethtool link mode mask
 *
 * Sets all the port type modes in the ethtool mask.  MAC drivers should
 * use this in their 'validate' callback.
 */

void phylink_set_port_modes(unsigned long *mask)
{
 phylink_set(mask, TP);
 phylink_set(mask, AUI);
 phylink_set(mask, MII);
 phylink_set(mask, FIBRE);
 phylink_set(mask, BNC);
 phylink_set(mask, Backplane);
}
EXPORT_SYMBOL_GPL(phylink_set_port_modes);

static int phylink_is_empty_linkmode(const unsigned long *linkmode)
{
 __ETHTOOL_DECLARE_LINK_MODE_MASK(tmp) = { 0, };

 phylink_set_port_modes(tmp);
 phylink_set(tmp, Autoneg);
 phylink_set(tmp, Pause);
 phylink_set(tmp, Asym_Pause);

 return linkmode_subset(linkmode, tmp);
}

static const char *phylink_an_mode_str(unsigned int mode)
{
 static const char *modestr[] = {
  [MLO_AN_PHY] = "phy",
  [MLO_AN_FIXED] = "fixed",
  [MLO_AN_INBAND] = "inband",
 };

 return mode < ARRAY_SIZE(modestr) ? modestr[mode] : "unknown";
}

static const char *phylink_pcs_mode_str(unsigned int mode)
{
 if (!mode)
  return "none";

 if (mode & PHYLINK_PCS_NEG_OUTBAND)
  return "outband";

 if (mode & PHYLINK_PCS_NEG_INBAND) {
  if (mode & PHYLINK_PCS_NEG_ENABLED)
   return "inband,an-enabled";
  else
   return "inband,an-disabled";
 }

 return "unknown";
}

static unsigned int phylink_interface_signal_rate(phy_interface_t interface)
{
 switch (interface) {
 case PHY_INTERFACE_MODE_SGMII:
 case PHY_INTERFACE_MODE_1000BASEX: /* 1.25Mbd */
  return 1250;
 case PHY_INTERFACE_MODE_2500BASEX: /* 3.125Mbd */
  return 3125;
 case PHY_INTERFACE_MODE_5GBASER: /* 5.15625Mbd */
  return 5156;
 case PHY_INTERFACE_MODE_10GBASER: /* 10.3125Mbd */
  return 10313;
 default:
  return 0;
 }
}

/**
 * phylink_interface_max_speed() - get the maximum speed of a phy interface
 * @interface: phy interface mode defined by &typedef phy_interface_t
 *
 * Determine the maximum speed of a phy interface. This is intended to help
 * determine the correct speed to pass to the MAC when the phy is performing
 * rate matching.
 *
 * Return: The maximum speed of @interface
 */

static int phylink_interface_max_speed(phy_interface_t interface)
{
 switch (interface) {
 case PHY_INTERFACE_MODE_100BASEX:
 case PHY_INTERFACE_MODE_REVRMII:
 case PHY_INTERFACE_MODE_RMII:
 case PHY_INTERFACE_MODE_SMII:
 case PHY_INTERFACE_MODE_REVMII:
 case PHY_INTERFACE_MODE_MII:
 case PHY_INTERFACE_MODE_MIILITE:
  return SPEED_100;

 case PHY_INTERFACE_MODE_TBI:
 case PHY_INTERFACE_MODE_MOCA:
 case PHY_INTERFACE_MODE_RTBI:
 case PHY_INTERFACE_MODE_1000BASEX:
 case PHY_INTERFACE_MODE_1000BASEKX:
 case PHY_INTERFACE_MODE_TRGMII:
 case PHY_INTERFACE_MODE_RGMII_TXID:
 case PHY_INTERFACE_MODE_RGMII_RXID:
 case PHY_INTERFACE_MODE_RGMII_ID:
 case PHY_INTERFACE_MODE_RGMII:
 case PHY_INTERFACE_MODE_PSGMII:
 case PHY_INTERFACE_MODE_QSGMII:
 case PHY_INTERFACE_MODE_QUSGMII:
 case PHY_INTERFACE_MODE_SGMII:
 case PHY_INTERFACE_MODE_GMII:
  return SPEED_1000;

 case PHY_INTERFACE_MODE_2500BASEX:
 case PHY_INTERFACE_MODE_10G_QXGMII:
  return SPEED_2500;

 case PHY_INTERFACE_MODE_5GBASER:
  return SPEED_5000;

 case PHY_INTERFACE_MODE_XGMII:
 case PHY_INTERFACE_MODE_RXAUI:
 case PHY_INTERFACE_MODE_XAUI:
 case PHY_INTERFACE_MODE_10GBASER:
 case PHY_INTERFACE_MODE_10GKR:
 case PHY_INTERFACE_MODE_USXGMII:
  return SPEED_10000;

 case PHY_INTERFACE_MODE_25GBASER:
  return SPEED_25000;

 case PHY_INTERFACE_MODE_XLGMII:
  return SPEED_40000;

 case PHY_INTERFACE_MODE_50GBASER:
 case PHY_INTERFACE_MODE_LAUI:
  return SPEED_50000;

 case PHY_INTERFACE_MODE_100GBASEP:
  return SPEED_100000;

 case PHY_INTERFACE_MODE_INTERNAL:
 case PHY_INTERFACE_MODE_NA:
 case PHY_INTERFACE_MODE_MAX:
  /* No idea! Garbage in, unknown out */
  return SPEED_UNKNOWN;
 }

 /* If we get here, someone forgot to add an interface mode above */
 WARN_ON_ONCE(1);
 return SPEED_UNKNOWN;
}

static struct {
 unsigned long mask;
 int speed;
 unsigned int duplex;
 unsigned int caps_bit;
} phylink_caps_params[] = {
 { MAC_400000FD, SPEED_400000, DUPLEX_FULL, BIT(LINK_CAPA_400000FD) },
 { MAC_200000FD, SPEED_200000, DUPLEX_FULL, BIT(LINK_CAPA_200000FD) },
 { MAC_100000FD, SPEED_100000, DUPLEX_FULL, BIT(LINK_CAPA_100000FD) },
 { MAC_56000FD,  SPEED_56000,  DUPLEX_FULL, BIT(LINK_CAPA_56000FD) },
 { MAC_50000FD,  SPEED_50000,  DUPLEX_FULL, BIT(LINK_CAPA_50000FD) },
 { MAC_40000FD,  SPEED_40000,  DUPLEX_FULL, BIT(LINK_CAPA_40000FD) },
 { MAC_25000FD,  SPEED_25000,  DUPLEX_FULL, BIT(LINK_CAPA_25000FD) },
 { MAC_20000FD,  SPEED_20000,  DUPLEX_FULL, BIT(LINK_CAPA_20000FD) },
 { MAC_10000FD,  SPEED_10000,  DUPLEX_FULL, BIT(LINK_CAPA_10000FD) },
 { MAC_5000FD,   SPEED_5000,   DUPLEX_FULL, BIT(LINK_CAPA_5000FD) },
 { MAC_2500FD,   SPEED_2500,   DUPLEX_FULL, BIT(LINK_CAPA_2500FD) },
 { MAC_1000FD,   SPEED_1000,   DUPLEX_FULL, BIT(LINK_CAPA_1000FD) },
 { MAC_1000HD,   SPEED_1000,   DUPLEX_HALF, BIT(LINK_CAPA_1000HD) },
 { MAC_100FD,    SPEED_100,    DUPLEX_FULL, BIT(LINK_CAPA_100FD) },
 { MAC_100HD,    SPEED_100,    DUPLEX_HALF, BIT(LINK_CAPA_100HD) },
 { MAC_10FD,     SPEED_10,     DUPLEX_FULL, BIT(LINK_CAPA_10FD) },
 { MAC_10HD,     SPEED_10,     DUPLEX_HALF, BIT(LINK_CAPA_10HD) },
};

/**
 * phylink_caps_to_link_caps() - Convert a set of MAC capabilities LINK caps
 * @caps: A set of MAC capabilities
 *
 * Returns: The corresponding set of LINK_CAPA as defined in phy-caps.h
 */

static unsigned long phylink_caps_to_link_caps(unsigned long caps)
{
 unsigned long link_caps = 0;
 int i;

 for (i = 0; i <  ARRAY_SIZE(phylink_caps_params); i++)
  if (caps & phylink_caps_params[i].mask)
   link_caps |= phylink_caps_params[i].caps_bit;

 return link_caps;
}

static unsigned long phylink_link_caps_to_mac_caps(unsigned long link_caps)
{
 unsigned long caps = 0;
 int i;

 for (i = 0; i <  ARRAY_SIZE(phylink_caps_params); i++)
  if (link_caps & phylink_caps_params[i].caps_bit)
   caps |= phylink_caps_params[i].mask;

 return caps;
}

/**
 * phylink_caps_to_linkmodes() - Convert capabilities to ethtool link modes
 * @linkmodes: ethtool linkmode mask (must be already initialised)
 * @caps: bitmask of MAC capabilities
 *
 * Set all possible pause, speed and duplex linkmodes in @linkmodes that are
 * supported by the @caps. @linkmodes must have been initialised previously.
 */

static void phylink_caps_to_linkmodes(unsigned long *linkmodes,
          unsigned long caps)
{
 unsigned long link_caps = phylink_caps_to_link_caps(caps);

 if (caps & MAC_SYM_PAUSE)
  __set_bit(ETHTOOL_LINK_MODE_Pause_BIT, linkmodes);

 if (caps & MAC_ASYM_PAUSE)
  __set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, linkmodes);

 phy_caps_linkmodes(link_caps, linkmodes);
}

/**
 * phylink_limit_mac_speed - limit the phylink_config to a maximum speed
 * @config: pointer to a &struct phylink_config
 * @max_speed: maximum speed
 *
 * Mask off MAC capabilities for speeds higher than the @max_speed parameter.
 * Any further motifications of config.mac_capabilities will override this.
 */

void phylink_limit_mac_speed(struct phylink_config *config, u32 max_speed)
{
 int i;

 for (i = 0; i < ARRAY_SIZE(phylink_caps_params) &&
      phylink_caps_params[i].speed > max_speed; i++)
  config->mac_capabilities &= ~phylink_caps_params[i].mask;
}
EXPORT_SYMBOL_GPL(phylink_limit_mac_speed);

/**
 * phylink_cap_from_speed_duplex - Get mac capability from speed/duplex
 * @speed: the speed to search for
 * @duplex: the duplex to search for
 *
 * Find the mac capability for a given speed and duplex.
 *
 * Return: A mask with the mac capability patching @speed and @duplex, or 0 if
 *         there were no matches.
 */

static unsigned long phylink_cap_from_speed_duplex(int speed,
         unsigned int duplex)
{
 int i;

 for (i = 0; i < ARRAY_SIZE(phylink_caps_params); i++) {
  if (speed == phylink_caps_params[i].speed &&
      duplex == phylink_caps_params[i].duplex)
   return phylink_caps_params[i].mask;
 }

 return 0;
}

/**
 * phylink_get_capabilities() - get capabilities for a given MAC
 * @interface: phy interface mode defined by &typedef phy_interface_t
 * @mac_capabilities: bitmask of MAC capabilities
 * @rate_matching: type of rate matching being performed
 *
 * Get the MAC capabilities that are supported by the @interface mode and
 * @mac_capabilities.
 */

static unsigned long phylink_get_capabilities(phy_interface_t interface,
           unsigned long mac_capabilities,
           int rate_matching)
{
 unsigned long link_caps = phy_caps_from_interface(interface);
 int max_speed = phylink_interface_max_speed(interface);
 unsigned long caps = MAC_SYM_PAUSE | MAC_ASYM_PAUSE;
 unsigned long matched_caps = 0;

 caps |= phylink_link_caps_to_mac_caps(link_caps);

 switch (rate_matching) {
 case RATE_MATCH_OPEN_LOOP:
  /* TODO */
  fallthrough;
 case RATE_MATCH_NONE:
  matched_caps = 0;
  break;
 case RATE_MATCH_PAUSE: {
  /* The MAC must support asymmetric pause towards the local
 * device for this. We could allow just symmetric pause, but
 * then we might have to renegotiate if the link partner
 * doesn't support pause. This is because there's no way to
 * accept pause frames without transmitting them if we only
 * support symmetric pause.
 */

  if (!(mac_capabilities & MAC_SYM_PAUSE) ||
      !(mac_capabilities & MAC_ASYM_PAUSE))
   break;

  /* We can't adapt if the MAC doesn't support the interface's
 * max speed at full duplex.
 */

  if (mac_capabilities &
      phylink_cap_from_speed_duplex(max_speed, DUPLEX_FULL))
   matched_caps = GENMASK(__fls(caps), __fls(MAC_10HD));
  break;
 }
 case RATE_MATCH_CRS:
  /* The MAC must support half duplex at the interface's max
 * speed.
 */

  if (mac_capabilities &
      phylink_cap_from_speed_duplex(max_speed, DUPLEX_HALF)) {
   matched_caps = GENMASK(__fls(caps), __fls(MAC_10HD));
   matched_caps &= mac_capabilities;
  }
  break;
 }

 return (caps & mac_capabilities) | matched_caps;
}

/**
 * phylink_validate_mask_caps() - Restrict link modes based on caps
 * @supported: ethtool bitmask for supported link modes.
 * @state: pointer to a &struct phylink_link_state.
 * @mac_capabilities: bitmask of MAC capabilities
 *
 * Calculate the supported link modes based on @mac_capabilities, and restrict
 * @supported and @state based on that. Use this function if your capabiliies
 * aren't constant, such as if they vary depending on the interface.
 */

static void phylink_validate_mask_caps(unsigned long *supported,
           struct phylink_link_state *state,
           unsigned long mac_capabilities)
{
 __ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
 unsigned long caps;

 phylink_set_port_modes(mask);
 phylink_set(mask, Autoneg);
 caps = phylink_get_capabilities(state->interface, mac_capabilities,
     state->rate_matching);
 phylink_caps_to_linkmodes(mask, caps);

 linkmode_and(supported, supported, mask);
 linkmode_and(state->advertising, state->advertising, mask);
}

static int phylink_validate_mac_and_pcs(struct phylink *pl,
     unsigned long *supported,
     struct phylink_link_state *state)
{
 struct phylink_pcs *pcs = NULL;
 unsigned long capabilities;
 int ret;

 /* Get the PCS for this interface mode */
 if (pl->mac_ops->mac_select_pcs) {
  pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
  if (IS_ERR(pcs))
   return PTR_ERR(pcs);
 }

 if (pcs) {
  /* The PCS, if present, must be setup before phylink_create()
 * has been called. If the ops is not initialised, print an
 * error and backtrace rather than oopsing the kernel.
 */

  if (!pcs->ops) {
   phylink_err(pl, "interface %s: uninitialised PCS\n",
        phy_modes(state->interface));
   dump_stack();
   return -EINVAL;
  }

  /* Ensure that this PCS supports the interface which the MAC
 * returned it for. It is an error for the MAC to return a PCS
 * that does not support the interface mode.
 */

  if (!phy_interface_empty(pcs->supported_interfaces) &&
      !test_bit(state->interface, pcs->supported_interfaces)) {
   phylink_err(pl, "MAC returned PCS which does not support %s\n",
        phy_modes(state->interface));
   return -EINVAL;
  }

  /* Validate the link parameters with the PCS */
  if (pcs->ops->pcs_validate) {
   ret = pcs->ops->pcs_validate(pcs, supported, state);
   if (ret < 0 || phylink_is_empty_linkmode(supported))
    return -EINVAL;

   /* Ensure the advertising mask is a subset of the
 * supported mask.
 */

   linkmode_and(state->advertising, state->advertising,
         supported);
  }
 }

 /* Then validate the link parameters with the MAC */
 if (pl->mac_ops->mac_get_caps)
  capabilities = pl->mac_ops->mac_get_caps(pl->config,
        state->interface);
 else
  capabilities = pl->config->mac_capabilities;

 phylink_validate_mask_caps(supported, state, capabilities);

 return phylink_is_empty_linkmode(supported) ? -EINVAL : 0;
}

static void phylink_validate_one(struct phylink *pl, struct phy_device *phy,
     const unsigned long *supported,
     const struct phylink_link_state *state,
     phy_interface_t interface,
     unsigned long *accum_supported,
     unsigned long *accum_advertising)
{
 __ETHTOOL_DECLARE_LINK_MODE_MASK(tmp_supported);
 struct phylink_link_state tmp_state;

 linkmode_copy(tmp_supported, supported);

 tmp_state = *state;
 tmp_state.interface = interface;

 if (phy)
  tmp_state.rate_matching = phy_get_rate_matching(phy, interface);

 if (!phylink_validate_mac_and_pcs(pl, tmp_supported, &tmp_state)) {
  phylink_dbg(pl, " interface %u (%s) rate match %s supports %*pbl\n",
       interface, phy_modes(interface),
       phy_rate_matching_to_str(tmp_state.rate_matching),
       __ETHTOOL_LINK_MODE_MASK_NBITS, tmp_supported);

  linkmode_or(accum_supported, accum_supported, tmp_supported);
  linkmode_or(accum_advertising, accum_advertising,
       tmp_state.advertising);
 }
}

static int phylink_validate_mask(struct phylink *pl, struct phy_device *phy,
     unsigned long *supported,
     struct phylink_link_state *state,
     const unsigned long *interfaces)
{
 __ETHTOOL_DECLARE_LINK_MODE_MASK(all_adv) = { 0, };
 __ETHTOOL_DECLARE_LINK_MODE_MASK(all_s) = { 0, };
 int interface;

 for_each_set_bit(interface, interfaces, PHY_INTERFACE_MODE_MAX)
  phylink_validate_one(pl, phy, supported, state, interface,
         all_s, all_adv);

 linkmode_copy(supported, all_s);
 linkmode_copy(state->advertising, all_adv);

 return phylink_is_empty_linkmode(supported) ? -EINVAL : 0;
}

static int phylink_validate(struct phylink *pl, unsigned long *supported,
       struct phylink_link_state *state)
{
 const unsigned long *interfaces = pl->config->supported_interfaces;

 if (state->interface == PHY_INTERFACE_MODE_NA)
  return phylink_validate_mask(pl, NULL, supported, state,
          interfaces);

 if (!test_bit(state->interface, interfaces))
  return -EINVAL;

 return phylink_validate_mac_and_pcs(pl, supported, state);
}

static void phylink_fill_fixedlink_supported(unsigned long *supported)
{
 linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, supported);
 linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, supported);
 linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, supported);
 linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, supported);
 linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, supported);
 linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, supported);
 linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, supported);
 linkmode_set_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT, supported);
 linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT, supported);
}

static int phylink_parse_fixedlink(struct phylink *pl,
       const struct fwnode_handle *fwnode)
{
 __ETHTOOL_DECLARE_LINK_MODE_MASK(match) = { 0, };
 __ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
 const struct link_capabilities *c;
 struct fwnode_handle *fixed_node;
 struct gpio_desc *desc;
 u32 speed;
 int ret;

 fixed_node = fwnode_get_named_child_node(fwnode, "fixed-link");
 if (fixed_node) {
  ret = fwnode_property_read_u32(fixed_node, "speed", &speed);

  pl->link_config.speed = speed;
  pl->link_config.duplex = DUPLEX_HALF;

  if (fwnode_property_read_bool(fixed_node, "full-duplex"))
   pl->link_config.duplex = DUPLEX_FULL;

  /* We treat the "pause" and "asym-pause" terminology as
 * defining the link partner's ability.
 */

  if (fwnode_property_read_bool(fixed_node, "pause"))
   __set_bit(ETHTOOL_LINK_MODE_Pause_BIT,
      pl->link_config.lp_advertising);
  if (fwnode_property_read_bool(fixed_node, "asym-pause"))
   __set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
      pl->link_config.lp_advertising);

  if (ret == 0) {
   desc = fwnode_gpiod_get_index(fixed_node, "link", 0,
            GPIOD_IN, "?");

   if (!IS_ERR(desc))
    pl->link_gpio = desc;
   else if (desc == ERR_PTR(-EPROBE_DEFER))
    ret = -EPROBE_DEFER;
  }
  fwnode_handle_put(fixed_node);

  if (ret)
   return ret;
 } else {
  u32 prop[5];

  ret = fwnode_property_read_u32_array(fwnode, "fixed-link",
           NULL, 0);
  if (ret != ARRAY_SIZE(prop)) {
   phylink_err(pl, "broken fixed-link?\n");
   return -EINVAL;
  }

  ret = fwnode_property_read_u32_array(fwnode, "fixed-link",
           prop, ARRAY_SIZE(prop));
  if (!ret) {
   pl->link_config.duplex = prop[1] ?
      DUPLEX_FULL : DUPLEX_HALF;
   pl->link_config.speed = prop[2];
   if (prop[3])
    __set_bit(ETHTOOL_LINK_MODE_Pause_BIT,
       pl->link_config.lp_advertising);
   if (prop[4])
    __set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
       pl->link_config.lp_advertising);
  }
 }

 if (pl->link_config.speed > SPEED_1000 &&
     pl->link_config.duplex != DUPLEX_FULL)
  phylink_warn(pl, "fixed link specifies half duplex for %dMbps link?\n",
        pl->link_config.speed);

 linkmode_zero(pl->supported);
 phylink_fill_fixedlink_supported(pl->supported);

 linkmode_copy(pl->link_config.advertising, pl->supported);
 phylink_validate(pl, pl->supported, &pl->link_config);

 c = phy_caps_lookup(pl->link_config.speed, pl->link_config.duplex,
       pl->supported, true);
 if (c)
  linkmode_and(match, pl->supported, c->linkmodes);

 linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT, mask);
 linkmode_set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, mask);
 linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, mask);
 linkmode_and(pl->supported, pl->supported, mask);

 phylink_set(pl->supported, MII);

 if (c) {
  linkmode_or(pl->supported, pl->supported, match);
  linkmode_or(pl->link_config.lp_advertising,
       pl->link_config.lp_advertising, match);
 } else {
  phylink_warn(pl, "fixed link %s duplex %dMbps not recognised\n",
        pl->link_config.duplex == DUPLEX_FULL ? "full" : "half",
        pl->link_config.speed);
 }

 linkmode_and(pl->link_config.advertising, pl->link_config.advertising,
       pl->supported);

 pl->link_config.link = 1;
 pl->link_config.an_complete = 1;

 return 0;
}

static int phylink_parse_mode(struct phylink *pl,
         const struct fwnode_handle *fwnode)
{
 struct fwnode_handle *dn;
 const char *managed;
 unsigned long caps;

 if (pl->config->default_an_inband)
  pl->cfg_link_an_mode = MLO_AN_INBAND;

 dn = fwnode_get_named_child_node(fwnode, "fixed-link");
 if (dn || fwnode_property_present(fwnode, "fixed-link"))
  pl->cfg_link_an_mode = MLO_AN_FIXED;
 fwnode_handle_put(dn);

 if ((fwnode_property_read_string(fwnode, "managed", &managed) == 0 &&
      strcmp(managed, "in-band-status") == 0)) {
  if (pl->cfg_link_an_mode == MLO_AN_FIXED) {
   phylink_err(pl,
        "can't use both fixed-link and in-band-status\n");
   return -EINVAL;
  }

  pl->cfg_link_an_mode = MLO_AN_INBAND;
 }

 if (pl->cfg_link_an_mode == MLO_AN_INBAND) {
  linkmode_zero(pl->supported);
  phylink_set(pl->supported, MII);
  phylink_set(pl->supported, Autoneg);
  phylink_set(pl->supported, Asym_Pause);
  phylink_set(pl->supported, Pause);

  switch (pl->link_config.interface) {
  case PHY_INTERFACE_MODE_SGMII:
  case PHY_INTERFACE_MODE_PSGMII:
  case PHY_INTERFACE_MODE_QSGMII:
  case PHY_INTERFACE_MODE_QUSGMII:
  case PHY_INTERFACE_MODE_RGMII:
  case PHY_INTERFACE_MODE_RGMII_ID:
  case PHY_INTERFACE_MODE_RGMII_RXID:
  case PHY_INTERFACE_MODE_RGMII_TXID:
  case PHY_INTERFACE_MODE_RTBI:
  case PHY_INTERFACE_MODE_1000BASEX:
  case PHY_INTERFACE_MODE_2500BASEX:
  case PHY_INTERFACE_MODE_5GBASER:
  case PHY_INTERFACE_MODE_25GBASER:
  case PHY_INTERFACE_MODE_USXGMII:
  case PHY_INTERFACE_MODE_10G_QXGMII:
  case PHY_INTERFACE_MODE_10GKR:
  case PHY_INTERFACE_MODE_10GBASER:
  case PHY_INTERFACE_MODE_XLGMII:
  case PHY_INTERFACE_MODE_50GBASER:
  case PHY_INTERFACE_MODE_LAUI:
  case PHY_INTERFACE_MODE_100GBASEP:
   caps = ~(MAC_SYM_PAUSE | MAC_ASYM_PAUSE);
   caps = phylink_get_capabilities(pl->link_config.interface, caps,
       RATE_MATCH_NONE);
   phylink_caps_to_linkmodes(pl->supported, caps);
   break;

  default:
   phylink_err(pl,
        "incorrect link mode %s for in-band status\n",
        phy_modes(pl->link_config.interface));
   return -EINVAL;
  }

  linkmode_copy(pl->link_config.advertising, pl->supported);

  if (phylink_validate(pl, pl->supported, &pl->link_config)) {
   phylink_err(pl,
        "failed to validate link configuration for in-band status\n");
   return -EINVAL;
  }
 }

 return 0;
}

static void phylink_apply_manual_flow(struct phylink *pl,
          struct phylink_link_state *state)
{
 /* If autoneg is disabled, pause AN is also disabled */
 if (!linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
          state->advertising))
  state->pause &= ~MLO_PAUSE_AN;

 /* Manual configuration of pause modes */
 if (!(pl->link_config.pause & MLO_PAUSE_AN))
  state->pause = pl->link_config.pause;
}

static void phylink_resolve_an_pause(struct phylink_link_state *state)
{
 bool tx_pause, rx_pause;

 if (state->duplex == DUPLEX_FULL) {
  linkmode_resolve_pause(state->advertising,
           state->lp_advertising,
           &tx_pause, &rx_pause);
  if (tx_pause)
   state->pause |= MLO_PAUSE_TX;
  if (rx_pause)
   state->pause |= MLO_PAUSE_RX;
 }
}

static unsigned int phylink_pcs_inband_caps(struct phylink_pcs *pcs,
        phy_interface_t interface)
{
 if (pcs && pcs->ops->pcs_inband_caps)
  return pcs->ops->pcs_inband_caps(pcs, interface);

 return 0;
}

static void phylink_pcs_pre_config(struct phylink_pcs *pcs,
       phy_interface_t interface)
{
 if (pcs && pcs->ops->pcs_pre_config)
  pcs->ops->pcs_pre_config(pcs, interface);
}

static int phylink_pcs_post_config(struct phylink_pcs *pcs,
       phy_interface_t interface)
{
 int err = 0;

 if (pcs && pcs->ops->pcs_post_config)
  err = pcs->ops->pcs_post_config(pcs, interface);

 return err;
}

static void phylink_pcs_disable(struct phylink_pcs *pcs)
{
 if (pcs && pcs->ops->pcs_disable)
  pcs->ops->pcs_disable(pcs);
}

static int phylink_pcs_enable(struct phylink_pcs *pcs)
{
 int err = 0;

 if (pcs && pcs->ops->pcs_enable)
  err = pcs->ops->pcs_enable(pcs);

 return err;
}

static int phylink_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
         const struct phylink_link_state *state,
         bool permit_pause_to_mac)
{
 if (!pcs)
  return 0;

 return pcs->ops->pcs_config(pcs, neg_mode, state->interface,
        state->advertising, permit_pause_to_mac);
}

static void phylink_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
    phy_interface_t interface, int speed,
    int duplex)
{
 if (pcs && pcs->ops->pcs_link_up)
  pcs->ops->pcs_link_up(pcs, neg_mode, interface, speed, duplex);
}

static void phylink_pcs_disable_eee(struct phylink_pcs *pcs)
{
 if (pcs && pcs->ops->pcs_disable_eee)
  pcs->ops->pcs_disable_eee(pcs);
}

static void phylink_pcs_enable_eee(struct phylink_pcs *pcs)
{
 if (pcs && pcs->ops->pcs_enable_eee)
  pcs->ops->pcs_enable_eee(pcs);
}

/* Query inband for a specific interface mode, asking the MAC for the
 * PCS which will be used to handle the interface mode.
 */

static unsigned int phylink_inband_caps(struct phylink *pl,
      phy_interface_t interface)
{
 struct phylink_pcs *pcs;

 if (!pl->mac_ops->mac_select_pcs)
  return 0;

 pcs = pl->mac_ops->mac_select_pcs(pl->config, interface);
 if (!pcs)
  return 0;

 return phylink_pcs_inband_caps(pcs, interface);
}

static void phylink_pcs_poll_stop(struct phylink *pl)
{
 if (pl->cfg_link_an_mode == MLO_AN_INBAND)
  timer_delete(&pl->link_poll);
}

static void phylink_pcs_poll_start(struct phylink *pl)
{
 if (pl->pcs && pl->pcs->poll && pl->cfg_link_an_mode == MLO_AN_INBAND)
  mod_timer(&pl->link_poll, jiffies + HZ);
}

int phylink_pcs_pre_init(struct phylink *pl, struct phylink_pcs *pcs)
{
 int ret = 0;

 /* Signal to PCS driver that MAC requires RX clock for init */
 if (pl->config->mac_requires_rxc)
  pcs->rxc_always_on = true;

 if (pcs->ops->pcs_pre_init)
  ret = pcs->ops->pcs_pre_init(pcs);

 return ret;
}
EXPORT_SYMBOL_GPL(phylink_pcs_pre_init);

static void phylink_mac_config(struct phylink *pl,
          const struct phylink_link_state *state)
{
 struct phylink_link_state st = *state;

 /* Stop drivers incorrectly using these */
 linkmode_zero(st.lp_advertising);
 st.speed = SPEED_UNKNOWN;
 st.duplex = DUPLEX_UNKNOWN;
 st.an_complete = false;
 st.link = false;

 phylink_dbg(pl,
      "%s: mode=%s/%s/%s adv=%*pb pause=%02x\n",
      __func__, phylink_an_mode_str(pl->act_link_an_mode),
      phy_modes(st.interface),
      phy_rate_matching_to_str(st.rate_matching),
      __ETHTOOL_LINK_MODE_MASK_NBITS, st.advertising,
      st.pause);

 pl->mac_ops->mac_config(pl->config, pl->act_link_an_mode, &st);
}

static void phylink_pcs_an_restart(struct phylink *pl)
{
 if (pl->pcs && linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
      pl->link_config.advertising) &&
     phy_interface_mode_is_8023z(pl->link_config.interface) &&
     phylink_autoneg_inband(pl->act_link_an_mode))
  pl->pcs->ops->pcs_an_restart(pl->pcs);
}

enum inband_type {
 INBAND_NONE,
 INBAND_CISCO_SGMII,
 INBAND_BASEX,
};

static enum inband_type phylink_get_inband_type(phy_interface_t interface)
{
 switch (interface) {
 case PHY_INTERFACE_MODE_SGMII:
 case PHY_INTERFACE_MODE_QSGMII:
 case PHY_INTERFACE_MODE_QUSGMII:
 case PHY_INTERFACE_MODE_USXGMII:
 case PHY_INTERFACE_MODE_10G_QXGMII:
  /* These protocols are designed for use with a PHY which
 * communicates its negotiation result back to the MAC via
 * inband communication. Note: there exist PHYs that run
 * with SGMII but do not send the inband data.
 */

  return INBAND_CISCO_SGMII;

 case PHY_INTERFACE_MODE_1000BASEX:
 case PHY_INTERFACE_MODE_2500BASEX:
  /* 1000base-X is designed for use media-side for Fibre
 * connections, and thus the Autoneg bit needs to be
 * taken into account. We also do this for 2500base-X
 * as well, but drivers may not support this, so may
 * need to override this.
 */

  return INBAND_BASEX;

 default:
  return INBAND_NONE;
 }
}

/**
 * phylink_pcs_neg_mode() - helper to determine PCS inband mode
 * @pl: a pointer to a &struct phylink returned from phylink_create()
 * @pcs: a pointer to &struct phylink_pcs
 * @interface: interface mode to be used
 * @advertising: adertisement ethtool link mode mask
 *
 * Determines the negotiation mode to be used by the PCS, and returns
 * one of:
 *
 * - %PHYLINK_PCS_NEG_NONE: interface mode does not support inband
 * - %PHYLINK_PCS_NEG_OUTBAND: an out of band mode (e.g. reading the PHY)
 *   will be used.
 * - %PHYLINK_PCS_NEG_INBAND_DISABLED: inband mode selected but autoneg
 *   disabled
 * - %PHYLINK_PCS_NEG_INBAND_ENABLED: inband mode selected and autoneg enabled
 *
 * Note: this is for cases where the PCS itself is involved in negotiation
 * (e.g. Clause 37, SGMII and similar) not Clause 73.
 */

static void phylink_pcs_neg_mode(struct phylink *pl, struct phylink_pcs *pcs,
     phy_interface_t interface,
     const unsigned long *advertising)
{
 unsigned int pcs_ib_caps = 0;
 unsigned int phy_ib_caps = 0;
 unsigned int neg_mode, mode;
 enum inband_type type;

 type = phylink_get_inband_type(interface);
 if (type == INBAND_NONE) {
  pl->pcs_neg_mode = PHYLINK_PCS_NEG_NONE;
  pl->act_link_an_mode = pl->req_link_an_mode;
  return;
 }

 mode = pl->req_link_an_mode;

 pl->phy_ib_mode = 0;

 if (pcs)
  pcs_ib_caps = phylink_pcs_inband_caps(pcs, interface);

 if (pl->phydev)
  phy_ib_caps = phy_inband_caps(pl->phydev, interface);

 phylink_dbg(pl, "interface %s inband modes: pcs=%02x phy=%02x\n",
      phy_modes(interface), pcs_ib_caps, phy_ib_caps);

 if (!phylink_autoneg_inband(mode)) {
  bool pcs_ib_only = false;
  bool phy_ib_only = false;

  if (pcs_ib_caps && pcs_ib_caps != LINK_INBAND_DISABLE) {
   /* PCS supports reporting in-band capabilities, and
 * supports more than disable mode.
 */

   if (pcs_ib_caps & LINK_INBAND_DISABLE)
    neg_mode = PHYLINK_PCS_NEG_OUTBAND;
   else if (pcs_ib_caps & LINK_INBAND_ENABLE)
    pcs_ib_only = true;
  }

  if (phy_ib_caps && phy_ib_caps != LINK_INBAND_DISABLE) {
   /* PHY supports in-band capabilities, and supports
 * more than disable mode.
 */

   if (phy_ib_caps & LINK_INBAND_DISABLE)
    pl->phy_ib_mode = LINK_INBAND_DISABLE;
   else if (phy_ib_caps & LINK_INBAND_BYPASS)
    pl->phy_ib_mode = LINK_INBAND_BYPASS;
   else if (phy_ib_caps & LINK_INBAND_ENABLE)
    phy_ib_only = true;
  }

  /* If either the PCS or PHY requires inband to be enabled,
 * this is an invalid configuration. Provide a diagnostic
 * message for this case, but don't try to force the issue.
 */

  if (pcs_ib_only || phy_ib_only)
   phylink_warn(pl,
         "firmware wants %s mode, but %s%s%s requires inband\n",
         phylink_an_mode_str(mode),
         pcs_ib_only ? "PCS" : "",
         pcs_ib_only && phy_ib_only ? " and " : "",
         phy_ib_only ? "PHY" : "");

  neg_mode = PHYLINK_PCS_NEG_OUTBAND;
 } else if (type == INBAND_CISCO_SGMII || pl->phydev) {
  /* For SGMII modes which are designed to be used with PHYs, or
 * Base-X with a PHY, we try to use in-band mode where-ever
 * possible. However, there are some PHYs e.g. BCM84881 which
 * do not support in-band.
 */

  const unsigned int inband_ok = LINK_INBAND_ENABLE |
            LINK_INBAND_BYPASS;
  const unsigned int outband_ok = LINK_INBAND_DISABLE |
      LINK_INBAND_BYPASS;
  /* PCS PHY
 * D E D E
 * 0 0  0 0 no information inband enabled
 * 1 0  0 0 pcs doesn't support outband
 * 0 1  0 0 pcs required inband enabled
 * 1 1  0 0 pcs optional inband enabled
 * 0 0  1 0 phy doesn't support outband
 * 1 0  1 0 pcs+phy doesn't support outband
 * 0 1  1 0 pcs required, phy doesn't support, invalid
 * 1 1  1 0 pcs optional, phy doesn't support, outband
 * 0 0  0 1 phy required inband enabled
 * 1 0  0 1 pcs doesn't support, phy required, invalid
 * 0 1  0 1 pcs+phy required inband enabled
 * 1 1  0 1 pcs optional, phy required inband enabled
 * 0 0  1 1 phy optional inband enabled
 * 1 0  1 1 pcs doesn't support, phy optional, outband
 * 0 1  1 1 pcs required, phy optional inband enabled
 * 1 1  1 1 pcs+phy optional inband enabled
 */

  if ((!pcs_ib_caps || pcs_ib_caps & inband_ok) &&
      (!phy_ib_caps || phy_ib_caps & inband_ok)) {
   /* In-band supported or unknown at both ends. Enable
 * in-band mode with or without bypass at the PHY.
 */

   if (phy_ib_caps & LINK_INBAND_ENABLE)
    pl->phy_ib_mode = LINK_INBAND_ENABLE;
   else if (phy_ib_caps & LINK_INBAND_BYPASS)
    pl->phy_ib_mode = LINK_INBAND_BYPASS;

   neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
  } else if ((!pcs_ib_caps || pcs_ib_caps & outband_ok) &&
      (!phy_ib_caps || phy_ib_caps & outband_ok)) {
   /* Either in-band not supported at at least one end.
 * In-band bypass at the other end is possible.
 */

   if (phy_ib_caps & LINK_INBAND_DISABLE)
    pl->phy_ib_mode = LINK_INBAND_DISABLE;
   else if (phy_ib_caps & LINK_INBAND_BYPASS)
    pl->phy_ib_mode = LINK_INBAND_BYPASS;

   neg_mode = PHYLINK_PCS_NEG_OUTBAND;
   if (pl->phydev)
    mode = MLO_AN_PHY;
  } else {
   /* invalid */
   phylink_warn(pl, "%s: incompatible in-band capabilities, trying in-band",
         phy_modes(interface));
   neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
  }
 } else {
  /* For Base-X without a PHY */
  if (pcs_ib_caps == LINK_INBAND_DISABLE)
   /* If the PCS doesn't support inband, then inband must
 * be disabled.
 */

   neg_mode = PHYLINK_PCS_NEG_INBAND_DISABLED;
  else if (pcs_ib_caps == LINK_INBAND_ENABLE)
   /* If the PCS requires inband, then inband must always
 * be enabled.
 */

   neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
  else if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
        advertising))
   neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
  else
   neg_mode = PHYLINK_PCS_NEG_INBAND_DISABLED;
 }

 pl->pcs_neg_mode = neg_mode;
 pl->act_link_an_mode = mode;
}

static void phylink_major_config(struct phylink *pl, bool restart,
      const struct phylink_link_state *state)
{
 struct phylink_pcs *pcs = NULL;
 bool pcs_changed = false;
 unsigned int rate_kbd;
 int err;

 phylink_dbg(pl, "major config, requested %s/%s\n",
      phylink_an_mode_str(pl->req_link_an_mode),
      phy_modes(state->interface));

 pl->major_config_failed = false;

 if (pl->mac_ops->mac_select_pcs) {
  pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
  if (IS_ERR(pcs)) {
   phylink_err(pl,
        "mac_select_pcs unexpectedly failed: %pe\n",
        pcs);

   pl->major_config_failed = true;
   return;
  }

  pcs_changed = pl->pcs != pcs;
 }

 phylink_pcs_neg_mode(pl, pcs, state->interface, state->advertising);

 phylink_dbg(pl, "major config, active %s/%s/%s\n",
      phylink_an_mode_str(pl->act_link_an_mode),
      phylink_pcs_mode_str(pl->pcs_neg_mode),
      phy_modes(state->interface));

 phylink_pcs_poll_stop(pl);

 if (pl->mac_ops->mac_prepare) {
  err = pl->mac_ops->mac_prepare(pl->config, pl->act_link_an_mode,
            state->interface);
  if (err < 0) {
   phylink_err(pl, "mac_prepare failed: %pe\n",
        ERR_PTR(err));
   pl->major_config_failed = true;
   return;
  }
 }

 /* If we have a new PCS, switch to the new PCS after preparing the MAC
 * for the change.
 */

 if (pcs_changed) {
  phylink_pcs_disable(pl->pcs);

  if (pl->pcs)
   pl->pcs->phylink = NULL;

  pcs->phylink = pl;

  pl->pcs = pcs;
 }

 if (pl->pcs)
  phylink_pcs_pre_config(pl->pcs, state->interface);

 phylink_mac_config(pl, state);

 if (pl->pcs) {
  err = phylink_pcs_post_config(pl->pcs, state->interface);
  if (err < 0) {
   phylink_err(pl, "pcs_post_config failed: %pe\n",
        ERR_PTR(err));

   pl->major_config_failed = true;
  }
 }

 if (pl->pcs_state == PCS_STATE_STARTING || pcs_changed)
  phylink_pcs_enable(pl->pcs);

 err = phylink_pcs_config(pl->pcs, pl->pcs_neg_mode, state,
     !!(pl->link_config.pause & MLO_PAUSE_AN));
 if (err < 0) {
  phylink_err(pl, "pcs_config failed: %pe\n", ERR_PTR(err));
  pl->major_config_failed = true;
 } else if (err > 0) {
  restart = true;
 }

 if (restart)
  phylink_pcs_an_restart(pl);

 if (pl->mac_ops->mac_finish) {
  err = pl->mac_ops->mac_finish(pl->config, pl->act_link_an_mode,
           state->interface);
  if (err < 0) {
   phylink_err(pl, "mac_finish failed: %pe\n",
        ERR_PTR(err));

   pl->major_config_failed = true;
  }
 }

 if (pl->phydev && pl->phy_ib_mode) {
  err = phy_config_inband(pl->phydev, pl->phy_ib_mode);
  if (err < 0) {
   phylink_err(pl, "phy_config_inband: %pe\n",
        ERR_PTR(err));

   pl->major_config_failed = true;
  }
 }

 if (pl->sfp_bus) {
  rate_kbd = phylink_interface_signal_rate(state->interface);
  if (rate_kbd)
   sfp_upstream_set_signal_rate(pl->sfp_bus, rate_kbd);
 }

 phylink_pcs_poll_start(pl);
}

/*
 * Reconfigure for a change of inband advertisement.
 * If we have a separate PCS, we only need to call its pcs_config() method,
 * and then restart AN if it indicates something changed. Otherwise, we do
 * the full MAC reconfiguration.
 */

static int phylink_change_inband_advert(struct phylink *pl)
{
 int ret;

 if (test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state))
  return 0;

 phylink_dbg(pl, "%s: mode=%s/%s adv=%*pb pause=%02x\n", __func__,
      phylink_an_mode_str(pl->req_link_an_mode),
      phy_modes(pl->link_config.interface),
      __ETHTOOL_LINK_MODE_MASK_NBITS, pl->link_config.advertising,
      pl->link_config.pause);

 /* Recompute the PCS neg mode */
 phylink_pcs_neg_mode(pl, pl->pcs, pl->link_config.interface,
        pl->link_config.advertising);

 /* Modern PCS-based method; update the advert at the PCS, and
 * restart negotiation if the pcs_config() helper indicates that
 * the programmed advertisement has changed.
 */

 ret = phylink_pcs_config(pl->pcs, pl->pcs_neg_mode, &pl->link_config,
     !!(pl->link_config.pause & MLO_PAUSE_AN));
 if (ret < 0)
  return ret;

 if (ret > 0)
  phylink_pcs_an_restart(pl);

 return 0;
}

static void phylink_mac_pcs_get_state(struct phylink *pl,
          struct phylink_link_state *state)
{
 struct phylink_pcs *pcs;
 bool autoneg;

 linkmode_copy(state->advertising, pl->link_config.advertising);
 linkmode_zero(state->lp_advertising);
 state->interface = pl->link_config.interface;
 state->rate_matching = pl->link_config.rate_matching;
 state->an_complete = 0;
 state->link = 1;

 autoneg = pl->pcs_neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED;
 if (autoneg) {
  state->speed = SPEED_UNKNOWN;
  state->duplex = DUPLEX_UNKNOWN;
  state->pause = MLO_PAUSE_NONE;
 } else {
  state->speed =  pl->link_config.speed;
  state->duplex = pl->link_config.duplex;
  state->pause = pl->link_config.pause;
 }

 pcs = pl->pcs;
 if (pcs)
  pcs->ops->pcs_get_state(pcs, pl->pcs_neg_mode, state);
 else
  state->link = 0;
}

/* The fixed state is... fixed except for the link state,
 * which may be determined by a GPIO or a callback.
 */

static void phylink_get_fixed_state(struct phylink *pl,
        struct phylink_link_state *state)
{
 *state = pl->link_config;
 if (pl->config->get_fixed_state)
  pl->config->get_fixed_state(pl->config, state);
 else if (pl->link_gpio)
  state->link = !!gpiod_get_value_cansleep(pl->link_gpio);

 state->pause = MLO_PAUSE_NONE;
 phylink_resolve_an_pause(state);
}

static void phylink_mac_initial_config(struct phylink *pl, bool force_restart)
{
 struct phylink_link_state link_state;
 struct phy_device *phy = pl->phydev;

 switch (pl->req_link_an_mode) {
 case MLO_AN_PHY:
  link_state = pl->phy_state;
  break;

 case MLO_AN_FIXED:
  phylink_get_fixed_state(pl, &link_state);
  break;

 case MLO_AN_INBAND:
  link_state = pl->link_config;
  if (link_state.interface == PHY_INTERFACE_MODE_SGMII)
   link_state.pause = MLO_PAUSE_NONE;
  break;

 default/* can't happen */
  return;
 }

 link_state.link = false;

 phylink_apply_manual_flow(pl, &link_state);
 if (phy)
  mutex_lock(&phy->lock);
 phylink_major_config(pl, force_restart, &link_state);
 if (phy)
  mutex_unlock(&phy->lock);
}

static const char *phylink_pause_to_str(int pause)
{
 switch (pause & MLO_PAUSE_TXRX_MASK) {
 case MLO_PAUSE_TX | MLO_PAUSE_RX:
  return "rx/tx";
 case MLO_PAUSE_TX:
  return "tx";
 case MLO_PAUSE_RX:
  return "rx";
 default:
  return "off";
 }
}

static void phylink_deactivate_lpi(struct phylink *pl)
{
 if (pl->mac_enable_tx_lpi) {
  pl->mac_enable_tx_lpi = false;

  phylink_dbg(pl, "disabling LPI\n");

  pl->mac_ops->mac_disable_tx_lpi(pl->config);

  phylink_pcs_disable_eee(pl->pcs);
 }
}

static void phylink_activate_lpi(struct phylink *pl)
{
 int err;

 if (!test_bit(pl->cur_interface, pl->config->lpi_interfaces)) {
  phylink_dbg(pl, "MAC does not support LPI with %s\n",
       phy_modes(pl->cur_interface));
  return;
 }

 phylink_dbg(pl, "LPI timer %uus, tx clock stop %u\n",
      pl->mac_tx_lpi_timer, pl->mac_tx_clk_stop);

 phylink_pcs_enable_eee(pl->pcs);

 err = pl->mac_ops->mac_enable_tx_lpi(pl->config, pl->mac_tx_lpi_timer,
          pl->mac_tx_clk_stop);
 if (err) {
  phylink_pcs_disable_eee(pl->pcs);
  phylink_err(pl, "%ps() failed: %pe\n",
       pl->mac_ops->mac_enable_tx_lpi, ERR_PTR(err));
  return;
 }

 pl->mac_enable_tx_lpi = true;
}

static void phylink_link_up(struct phylink *pl,
       struct phylink_link_state link_state)
{
 struct net_device *ndev = pl->netdev;
 int speed, duplex;
 bool rx_pause;

 speed = link_state.speed;
 duplex = link_state.duplex;
 rx_pause = !!(link_state.pause & MLO_PAUSE_RX);

 switch (link_state.rate_matching) {
 case RATE_MATCH_PAUSE:
  /* The PHY is doing rate matchion from the media rate (in
 * the link_state) to the interface speed, and will send
 * pause frames to the MAC to limit its transmission speed.
 */

  speed = phylink_interface_max_speed(link_state.interface);
  duplex = DUPLEX_FULL;
  rx_pause = true;
  break;

 case RATE_MATCH_CRS:
  /* The PHY is doing rate matchion from the media rate (in
 * the link_state) to the interface speed, and will cause
 * collisions to the MAC to limit its transmission speed.
 */

  speed = phylink_interface_max_speed(link_state.interface);
  duplex = DUPLEX_HALF;
  break;
 }

 pl->cur_interface = link_state.interface;

 phylink_pcs_link_up(pl->pcs, pl->pcs_neg_mode, pl->cur_interface, speed,
       duplex);

 pl->mac_ops->mac_link_up(pl->config, pl->phydev, pl->act_link_an_mode,
     pl->cur_interface, speed, duplex,
     !!(link_state.pause & MLO_PAUSE_TX), rx_pause);

 if (pl->mac_supports_eee && pl->phy_enable_tx_lpi)
  phylink_activate_lpi(pl);

 if (ndev)
  netif_carrier_on(ndev);

 phylink_info(pl,
       "Link is Up - %s/%s - flow control %s\n",
       phy_speed_to_str(link_state.speed),
       phy_duplex_to_str(link_state.duplex),
       phylink_pause_to_str(link_state.pause));
}

static void phylink_link_down(struct phylink *pl)
{
 struct net_device *ndev = pl->netdev;

 if (ndev)
  netif_carrier_off(ndev);

 phylink_deactivate_lpi(pl);

 pl->mac_ops->mac_link_down(pl->config, pl->act_link_an_mode,
       pl->cur_interface);
 phylink_info(pl, "Link is Down\n");
}

static bool phylink_link_is_up(struct phylink *pl)
{
 return pl->netdev ? netif_carrier_ok(pl->netdev) : pl->old_link_state;
}

static void phylink_resolve(struct work_struct *w)
{
 struct phylink *pl = container_of(w, struct phylink, resolve);
 struct phylink_link_state link_state;
 bool mac_config = false;
 bool retrigger = false;
 struct phy_device *phy;
 bool cur_link_state;

 mutex_lock(&pl->phydev_mutex);
 phy = pl->phydev;
 if (phy)
  mutex_lock(&phy->lock);
 mutex_lock(&pl->state_mutex);
 cur_link_state = phylink_link_is_up(pl);

 if (pl->phylink_disable_state) {
  pl->link_failed = false;
  link_state.link = false;
 } else if (pl->link_failed) {
  link_state.link = false;
  retrigger = true;
 } else if (pl->act_link_an_mode == MLO_AN_FIXED) {
  phylink_get_fixed_state(pl, &link_state);
  mac_config = link_state.link;
 } else if (pl->act_link_an_mode == MLO_AN_PHY) {
  link_state = pl->phy_state;
  mac_config = link_state.link;
 } else {
  phylink_mac_pcs_get_state(pl, &link_state);

  /* The PCS may have a latching link-fail indicator. If the link
 * was up, bring the link down and re-trigger the resolve.
 * Otherwise, re-read the PCS state to get the current status
 * of the link.
 */

  if (!link_state.link) {
   if (cur_link_state)
    retrigger = true;
   else
    phylink_mac_pcs_get_state(pl, &link_state);
  }

  /* If we have a phy, the "up" state is the union of both the
 * PHY and the MAC
 */

  if (phy)
   link_state.link &= pl->phy_state.link;

  /* Only update if the PHY link is up */
  if (phy && pl->phy_state.link) {
   /* If the interface has changed, force a link down
 * event if the link isn't already down, and re-resolve.
 */

   if (link_state.interface != pl->phy_state.interface) {
    retrigger = true;
    link_state.link = false;
   }

   link_state.interface = pl->phy_state.interface;

   /* If we are doing rate matching, then the link
 * speed/duplex comes from the PHY
 */

   if (pl->phy_state.rate_matching) {
    link_state.rate_matching =
     pl->phy_state.rate_matching;
    link_state.speed = pl->phy_state.speed;
    link_state.duplex = pl->phy_state.duplex;
   }

   /* If we have a PHY, we need to update with the PHY
 * flow control bits.
 */

   link_state.pause = pl->phy_state.pause;
   mac_config = true;
  }
 }

 if (pl->act_link_an_mode != MLO_AN_FIXED)
  phylink_apply_manual_flow(pl, &link_state);

 if (mac_config) {
  if (link_state.interface != pl->link_config.interface) {
   /* The interface has changed, force the link down and
 * then reconfigure.
 */

   if (cur_link_state) {
    phylink_link_down(pl);
    cur_link_state = false;
   }
   phylink_major_config(pl, false, &link_state);
   pl->link_config.interface = link_state.interface;
  }
 }

 /* If configuration of the interface failed, force the link down
 * until we get a successful configuration.
 */

 if (pl->major_config_failed)
  link_state.link = false;

 if (link_state.link != cur_link_state) {
  pl->old_link_state = link_state.link;
  if (!link_state.link)
   phylink_link_down(pl);
  else
   phylink_link_up(pl, link_state);
 }
 if (!link_state.link && retrigger) {
  pl->link_failed = false;
  queue_work(system_power_efficient_wq, &pl->resolve);
 }
 mutex_unlock(&pl->state_mutex);
 if (phy)
  mutex_unlock(&phy->lock);
 mutex_unlock(&pl->phydev_mutex);
}

static void phylink_run_resolve(struct phylink *pl)
{
 if (!pl->phylink_disable_state)
  queue_work(system_power_efficient_wq, &pl->resolve);
}

static void phylink_run_resolve_and_disable(struct phylink *pl, int bit)
{
 unsigned long state = pl->phylink_disable_state;

 set_bit(bit, &pl->phylink_disable_state);
 if (state == 0) {
  queue_work(system_power_efficient_wq, &pl->resolve);
  flush_work(&pl->resolve);
 }
}

static void phylink_enable_and_run_resolve(struct phylink *pl, int bit)
{
 clear_bit(bit, &pl->phylink_disable_state);
 phylink_run_resolve(pl);
}

static void phylink_fixed_poll(struct timer_list *t)
{
 struct phylink *pl = container_of(t, struct phylink, link_poll);

 mod_timer(t, jiffies + HZ);

 phylink_run_resolve(pl);
}

static const struct sfp_upstream_ops sfp_phylink_ops;

static int phylink_register_sfp(struct phylink *pl,
    const struct fwnode_handle *fwnode)
{
 struct sfp_bus *bus;
 int ret;

 if (!fwnode)
  return 0;

 bus = sfp_bus_find_fwnode(fwnode);
 if (IS_ERR(bus)) {
  phylink_err(pl, "unable to attach SFP bus: %pe\n", bus);
  return PTR_ERR(bus);
 }

 pl->sfp_bus = bus;

 ret = sfp_bus_add_upstream(bus, pl, &sfp_phylink_ops);
 sfp_bus_put(bus);

 return ret;
}

/**
 * phylink_set_fixed_link() - set the fixed link
 * @pl: a pointer to a &struct phylink returned from phylink_create()
 * @state: a pointer to a struct phylink_link_state.
 *
 * This function is used when the link parameters are known and do not change,
 * making it suitable for certain types of network connections.
 *
 * Returns: zero on success or negative error code.
 */

int phylink_set_fixed_link(struct phylink *pl,
      const struct phylink_link_state *state)
{
 const struct link_capabilities *c;
 unsigned long *adv;

 if (pl->cfg_link_an_mode != MLO_AN_PHY || !state ||
     !test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state))
  return -EINVAL;

 c = phy_caps_lookup(state->speed, state->duplex,
       pl->supported, true);
 if (!c)
  return -EINVAL;

 adv = pl->link_config.advertising;
 linkmode_and(adv, pl->supported, c->linkmodes);
 linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, adv);

 pl->link_config.speed = state->speed;
 pl->link_config.duplex = state->duplex;
 pl->link_config.link = 1;
 pl->link_config.an_complete = 1;

 pl->cfg_link_an_mode = MLO_AN_FIXED;
 pl->req_link_an_mode = pl->cfg_link_an_mode;

 return 0;
}
EXPORT_SYMBOL_GPL(phylink_set_fixed_link);

/**
 * phylink_create() - create a phylink instance
 * @config: a pointer to the target &struct phylink_config
 * @fwnode: a pointer to a &struct fwnode_handle describing the network
 * interface
 * @iface: the desired link mode defined by &typedef phy_interface_t
 * @mac_ops: a pointer to a &struct phylink_mac_ops for the MAC.
 *
 * Create a new phylink instance, and parse the link parameters found in @np.
 * This will parse in-band modes, fixed-link or SFP configuration.
 *
 * Note: the rtnl lock must not be held when calling this function.
 *
 * Returns a pointer to a &struct phylink, or an error-pointer value. Users
 * must use IS_ERR() to check for errors from this function.
 */

struct phylink *phylink_create(struct phylink_config *config,
          const struct fwnode_handle *fwnode,
          phy_interface_t iface,
          const struct phylink_mac_ops *mac_ops)
{
 struct phylink *pl;
 int ret;

 /* Validate the supplied configuration */
 if (phy_interface_empty(config->supported_interfaces)) {
  dev_err(config->dev,
   "phylink: error: empty supported_interfaces\n");
  return ERR_PTR(-EINVAL);
 }

 pl = kzalloc(sizeof(*pl), GFP_KERNEL);
 if (!pl)
  return ERR_PTR(-ENOMEM);

 mutex_init(&pl->phydev_mutex);
 mutex_init(&pl->state_mutex);
 INIT_WORK(&pl->resolve, phylink_resolve);

 pl->config = config;
 if (config->type == PHYLINK_NETDEV) {
  pl->netdev = to_net_dev(config->dev);
  netif_carrier_off(pl->netdev);
 } else if (config->type == PHYLINK_DEV) {
  pl->dev = config->dev;
 } else {
  kfree(pl);
  return ERR_PTR(-EINVAL);
 }

 pl->mac_supports_eee_ops = phylink_mac_implements_lpi(mac_ops);
 pl->mac_supports_eee = pl->mac_supports_eee_ops &&
          pl->config->lpi_capabilities &&
          !phy_interface_empty(pl->config->lpi_interfaces);

 /* Set the default EEE configuration */
 pl->eee_cfg.eee_enabled = pl->config->eee_enabled_default;
 pl->eee_cfg.tx_lpi_enabled = pl->eee_cfg.eee_enabled;
 pl->eee_cfg.tx_lpi_timer = pl->config->lpi_timer_default;

 pl->phy_state.interface = iface;
 pl->link_interface = iface;
 if (iface == PHY_INTERFACE_MODE_MOCA)
  pl->link_port = PORT_BNC;
 else
  pl->link_port = PORT_MII;
 pl->link_config.interface = iface;
 pl->link_config.pause = MLO_PAUSE_AN;
 pl->link_config.speed = SPEED_UNKNOWN;
 pl->link_config.duplex = DUPLEX_UNKNOWN;
 pl->pcs_state = PCS_STATE_DOWN;
 pl->mac_ops = mac_ops;
 __set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
 timer_setup(&pl->link_poll, phylink_fixed_poll, 0);

 linkmode_fill(pl->supported);
 linkmode_copy(pl->link_config.advertising, pl->supported);
 phylink_validate(pl, pl->supported, &pl->link_config);

 ret = phylink_parse_mode(pl, fwnode);
 if (ret < 0) {
  kfree(pl);
  return ERR_PTR(ret);
 }

 if (pl->cfg_link_an_mode == MLO_AN_FIXED) {
  ret = phylink_parse_fixedlink(pl, fwnode);
  if (ret < 0) {
   kfree(pl);
   return ERR_PTR(ret);
  }
 }

 pl->req_link_an_mode = pl->cfg_link_an_mode;

 ret = phylink_register_sfp(pl, fwnode);
 if (ret < 0) {
  kfree(pl);
  return ERR_PTR(ret);
 }

 return pl;
}
EXPORT_SYMBOL_GPL(phylink_create);

/**
 * phylink_destroy() - cleanup and destroy the phylink instance
 * @pl: a pointer to a &struct phylink returned from phylink_create()
 *
 * Destroy a phylink instance. Any PHY that has been attached must have been
 * cleaned up via phylink_disconnect_phy() prior to calling this function.
 *
 * Note: the rtnl lock must not be held when calling this function.
 */

void phylink_destroy(struct phylink *pl)
{
 sfp_bus_del_upstream(pl->sfp_bus);
 if (pl->link_gpio)
  gpiod_put(pl->link_gpio);

 cancel_work_sync(&pl->resolve);
 kfree(pl);
}
EXPORT_SYMBOL_GPL(phylink_destroy);

/**
 * phylink_expects_phy() - Determine if phylink expects a phy to be attached
 * @pl: a pointer to a &struct phylink returned from phylink_create()
 *
 * When using fixed-link mode, or in-band mode with 1000base-X or 2500base-X,
 * no PHY is needed.
 *
 * Returns true if phylink will be expecting a PHY.
 */

bool phylink_expects_phy(struct phylink *pl)
{
 if (pl->cfg_link_an_mode == MLO_AN_FIXED ||
     (pl->cfg_link_an_mode == MLO_AN_INBAND &&
      phy_interface_mode_is_8023z(pl->link_interface)))
  return false;
 return true;
}
EXPORT_SYMBOL_GPL(phylink_expects_phy);

static void phylink_phy_change(struct phy_device *phydev, bool up)
{
 struct phylink *pl = phydev->phylink;
 bool tx_pause, rx_pause;

 phy_get_pause(phydev, &tx_pause, &rx_pause);

 mutex_lock(&pl->state_mutex);
 pl->phy_state.speed = phydev->speed;
 pl->phy_state.duplex = phydev->duplex;
 pl->phy_state.rate_matching = phydev->rate_matching;
 pl->phy_state.pause = MLO_PAUSE_NONE;
 if (tx_pause)
  pl->phy_state.pause |= MLO_PAUSE_TX;
 if (rx_pause)
  pl->phy_state.pause |= MLO_PAUSE_RX;
 pl->phy_state.interface = phydev->interface;
 pl->phy_state.link = up;
 if (!up)
  pl->link_failed = true;

 /* Get the LPI state from phylib */
 pl->phy_enable_tx_lpi = phydev->enable_tx_lpi;
 pl->mac_tx_lpi_timer = phydev->eee_cfg.tx_lpi_timer;
 mutex_unlock(&pl->state_mutex);

 phylink_run_resolve(pl);

 phylink_dbg(pl, "phy link %s %s/%s/%s/%s/%s/%slpi\n",
      up ? "up" : "down",
      phy_modes(phydev->interface),
      phy_speed_to_str(phydev->speed),
      phy_duplex_to_str(phydev->duplex),
      phy_rate_matching_to_str(phydev->rate_matching),
      phylink_pause_to_str(pl->phy_state.pause),
      phydev->enable_tx_lpi ? "" : "no");
}

static int phylink_validate_phy(struct phylink *pl, struct phy_device *phy,
    unsigned long *supported,
    struct phylink_link_state *state)
{
 DECLARE_PHY_INTERFACE_MASK(interfaces);

 /* If the PHY provides a bitmap of the interfaces it will be using
 * depending on the negotiated media speeds, use this to validate
 * which ethtool link modes can be used.
 */

 if (!phy_interface_empty(phy->possible_interfaces)) {
  /* We only care about the union of the PHY's interfaces and
 * those which the host supports.
 */

  phy_interface_and(interfaces, phy->possible_interfaces,
      pl->config->supported_interfaces);

  if (phy_interface_empty(interfaces)) {
   phylink_err(pl, "PHY has no common interfaces\n");
   return -EINVAL;
  }

  if (phy_on_sfp(phy)) {
   /* If the PHY is on a SFP, limit the interfaces to
 * those that can be used with a SFP module.
 */

   phy_interface_and(interfaces, interfaces,
       phylink_sfp_interfaces);

   if (phy_interface_empty(interfaces)) {
    phylink_err(pl, "SFP PHY's possible interfaces becomes empty\n");
    return -EINVAL;
   }
  }

  phylink_dbg(pl, "PHY %s uses interfaces %*pbl, validating %*pbl\n",
       phydev_name(phy),
       (int)PHY_INTERFACE_MODE_MAX,
       phy->possible_interfaces,
       (int)PHY_INTERFACE_MODE_MAX, interfaces);

  return phylink_validate_mask(pl, phy, supported, state,
          interfaces);
 }

 phylink_dbg(pl, "PHY %s doesn't supply possible interfaces\n",
      phydev_name(phy));

 /* Check whether we would use rate matching for the proposed interface
 * mode.
 */

 state->rate_matching = phy_get_rate_matching(phy, state->interface);

 /* Clause 45 PHYs may switch their Serdes lane between, e.g. 10GBASE-R,
 * 5GBASE-R, 2500BASE-X and SGMII if they are not using rate matching.
 * For some interface modes (e.g. RXAUI, XAUI and USXGMII) switching
 * their Serdes is either unnecessary or not reasonable.
 *
 * For these which switch interface modes, we really need to know which
 * interface modes the PHY supports to properly work out which ethtool
 * linkmodes can be supported. For now, as a work-around, we validate
 * against all interface modes, which may lead to more ethtool link
 * modes being advertised than are actually supported.
 */

 if (phy->is_c45 && state->rate_matching == RATE_MATCH_NONE &&
     state->interface != PHY_INTERFACE_MODE_RXAUI &&
     state->interface != PHY_INTERFACE_MODE_XAUI &&
     state->interface != PHY_INTERFACE_MODE_USXGMII)
  state->interface = PHY_INTERFACE_MODE_NA;

 return phylink_validate(pl, supported, state);
}

static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy,
          phy_interface_t interface)
{
 struct phylink_link_state config;
 __ETHTOOL_DECLARE_LINK_MODE_MASK(supported);
 char *irq_str;
 int ret;

 /*
 * This is the new way of dealing with flow control for PHYs,
 * as described by Timur Tabi in commit 529ed1275263 ("net: phy:
 * phy drivers should not set SUPPORTED_[Asym_]Pause") except
 * using our validate call to the MAC, we rely upon the MAC
 * clearing the bits from both supported and advertising fields.
 */

 phy_support_asym_pause(phy);

 memset(&config, 0, sizeof(config));
 linkmode_copy(supported, phy->supported);
 linkmode_copy(config.advertising, phy->advertising);
 config.interface = interface;

 ret = phylink_validate_phy(pl, phy, supported, &config);
 if (ret) {
  phylink_warn(pl, "validation of %s with support %*pb and advertisement %*pb failed: %pe\n",
        phy_modes(config.interface),
        __ETHTOOL_LINK_MODE_MASK_NBITS, phy->supported,
        __ETHTOOL_LINK_MODE_MASK_NBITS, config.advertising,
        ERR_PTR(ret));
  return ret;
 }

 phy->phylink = pl;
 phy->phy_link_change = phylink_phy_change;

 irq_str = phy_attached_info_irq(phy);
 phylink_info(pl,
       "PHY [%s] driver [%s] (irq=%s)\n",
       dev_name(&phy->mdio.dev), phy->drv->name, irq_str);
 kfree(irq_str);

 mutex_lock(&pl->phydev_mutex);
 mutex_lock(&phy->lock);
 mutex_lock(&pl->state_mutex);
 pl->phydev = phy;
 pl->phy_state.interface = interface;
 pl->phy_state.pause = MLO_PAUSE_NONE;
 pl->phy_state.speed = SPEED_UNKNOWN;
 pl->phy_state.duplex = DUPLEX_UNKNOWN;
 pl->phy_state.rate_matching = RATE_MATCH_NONE;
 linkmode_copy(pl->supported, supported);
 linkmode_copy(pl->link_config.advertising, config.advertising);

 /* Restrict the phy advertisement according to the MAC support. */
 linkmode_copy(phy->advertising, config.advertising);

 /* If the MAC supports phylink managed EEE, restrict the EEE
 * advertisement according to the MAC's LPI capabilities.
 */

 if (pl->mac_supports_eee) {
  /* If EEE is enabled, then we need to call phy_support_eee()
 * to ensure that the advertising mask is appropriately set.
 * This also enables EEE at the PHY.
 */

  if (pl->eee_cfg.eee_enabled)
   phy_support_eee(phy);

  phy->eee_cfg.tx_lpi_enabled = pl->eee_cfg.tx_lpi_enabled;
  phy->eee_cfg.tx_lpi_timer = pl->eee_cfg.tx_lpi_timer;

  /* Convert the MAC's LPI capabilities to linkmodes */
  linkmode_zero(pl->supported_lpi);
  phylink_caps_to_linkmodes(pl->supported_lpi,
       pl->config->lpi_capabilities);

  /* Restrict the PHYs EEE support/advertisement to the modes
 * that the MAC supports.
 */

  linkmode_and(phy->advertising_eee, phy->advertising_eee,
        pl->supported_lpi);
 } else if (pl->mac_supports_eee_ops) {
  /* MAC supports phylink EEE, but wants EEE always disabled. */
  phy_disable_eee(phy);
 }

 mutex_unlock(&pl->state_mutex);
 mutex_unlock(&phy->lock);
 mutex_unlock(&pl->phydev_mutex);

 phylink_dbg(pl,
      "phy: %s setting supported %*pb advertising %*pb\n",
      phy_modes(interface),
      __ETHTOOL_LINK_MODE_MASK_NBITS, pl->supported,
      __ETHTOOL_LINK_MODE_MASK_NBITS, phy->advertising);

 if (pl->config->mac_managed_pm)
  phy->mac_managed_pm = true;

 /* Allow the MAC to stop its clock if the PHY has the capability */
 pl->mac_tx_clk_stop = phy_eee_tx_clock_stop_capable(phy) > 0;

 if (pl->mac_supports_eee_ops) {
  /* Explicitly configure whether the PHY is allowed to stop it's
 * receive clock.
 */

  ret = phy_eee_rx_clock_stop(phy,
         pl->config->eee_rx_clk_stop_enable);
  if (ret == -EOPNOTSUPP)
   ret = 0;
 }

 if (ret == 0 && phy_interrupt_is_valid(phy))
  phy_request_interrupt(phy);

 return ret;
}

static int phylink_attach_phy(struct phylink *pl, struct phy_device *phy,
         phy_interface_t interface)
{
 u32 flags = 0;

 if (WARN_ON(pl->cfg_link_an_mode == MLO_AN_FIXED ||
      (pl->cfg_link_an_mode == MLO_AN_INBAND &&
       phy_interface_mode_is_8023z(interface) && !pl->sfp_bus)))
  return -EINVAL;

 if (pl->phydev)
  return -EBUSY;

 if (pl->config->mac_requires_rxc)
  flags |= PHY_F_RXC_ALWAYS_ON;

 return phy_attach_direct(pl->netdev, phy, flags, interface);
}

/**
 * phylink_connect_phy() - connect a PHY to the phylink instance
 * @pl: a pointer to a &struct phylink returned from phylink_create()
 * @phy: a pointer to a &struct phy_device.
 *
 * Connect @phy to the phylink instance specified by @pl by calling
 * phy_attach_direct(). Configure the @phy according to the MAC driver's
 * capabilities, start the PHYLIB state machine and enable any interrupts
 * that the PHY supports.
 *
 * This updates the phylink's ethtool supported and advertising link mode
 * masks.
 *
 * Returns 0 on success or a negative errno.
 */

int phylink_connect_phy(struct phylink *pl, struct phy_device *phy)
{
 int ret;

 /* Use PHY device/driver interface */
 if (pl->link_interface == PHY_INTERFACE_MODE_NA) {
  pl->link_interface = phy->interface;
  pl->link_config.interface = pl->link_interface;
 }

 ret = phylink_attach_phy(pl, phy, pl->link_interface);
 if (ret < 0)
  return ret;

 ret = phylink_bringup_phy(pl, phy, pl->link_config.interface);
 if (ret)
  phy_detach(phy);

 return ret;
}
EXPORT_SYMBOL_GPL(phylink_connect_phy);

/**
 * phylink_of_phy_connect() - connect the PHY specified in the DT mode.
 * @pl: a pointer to a &struct phylink returned from phylink_create()
 * @dn: a pointer to a &struct device_node.
 * @flags: PHY-specific flags to communicate to the PHY device driver
 *
 * Connect the phy specified in the device node @dn to the phylink instance
 * specified by @pl. Actions specified in phylink_connect_phy() will be
 * performed.
 *
 * Returns 0 on success or a negative errno.
 */

int phylink_of_phy_connect(struct phylink *pl, struct device_node *dn,
      u32 flags)
{
 return phylink_fwnode_phy_connect(pl, of_fwnode_handle(dn), flags);
}
EXPORT_SYMBOL_GPL(phylink_of_phy_connect);

/**
 * phylink_fwnode_phy_connect() - connect the PHY specified in the fwnode.
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=97 H=94 G=95

¤ Dauer der Verarbeitung: 0.17 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

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.