diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 68a58ab6a8ed..da0623d94a64 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -155,6 +155,23 @@ static const char *phylink_an_mode_str(unsigned int mode)
 	return mode < ARRAY_SIZE(modestr) ? modestr[mode] : "unknown";
 }
 
+/**
+ * phylink_state_fill_speed_duplex() - Update a state's interface speed and duplex
+ * @state: A link state
+ *
+ * Update the .speed and .duplex members of @state. We can determine them based
+ * on the .link_speed and .link_duplex. This function should be called whenever
+ * .link_speed and .link_duplex are updated.  For example, userspace deals with
+ * link speed and duplex, and not the interface speed and duplex. Similarly,
+ * phys deal with link speed and duplex and only implicitly the interface speed
+ * and duplex.
+ */
+static void phylink_state_fill_speed_duplex(struct phylink_link_state *state)
+{
+	state->speed = state->link_speed;
+	state->duplex = state->link_duplex;
+}
+
 /**
  * phylink_caps_to_linkmodes() - Convert capabilities to ethtool link modes
  * @linkmodes: ethtool linkmode mask (must be already initialised)
@@ -524,11 +541,11 @@ static int phylink_parse_fixedlink(struct phylink *pl,
 	if (fixed_node) {
 		ret = fwnode_property_read_u32(fixed_node, "speed", &speed);
 
-		pl->link_config.speed = speed;
-		pl->link_config.duplex = DUPLEX_HALF;
+		pl->link_config.link_speed = speed;
+		pl->link_config.link_duplex = DUPLEX_HALF;
 
 		if (fwnode_property_read_bool(fixed_node, "full-duplex"))
-			pl->link_config.duplex = DUPLEX_FULL;
+			pl->link_config.link_duplex = DUPLEX_FULL;
 
 		/* We treat the "pause" and "asym-pause" terminology as
 		 * defining the link partner's ability.
@@ -566,9 +583,9 @@ static int phylink_parse_fixedlink(struct phylink *pl,
 		ret = fwnode_property_read_u32_array(fwnode, "fixed-link",
 						     prop, ARRAY_SIZE(prop));
 		if (!ret) {
-			pl->link_config.duplex = prop[1] ?
+			pl->link_config.link_duplex = prop[1] ?
 						DUPLEX_FULL : DUPLEX_HALF;
-			pl->link_config.speed = prop[2];
+			pl->link_config.link_speed = prop[2];
 			if (prop[3])
 				__set_bit(ETHTOOL_LINK_MODE_Pause_BIT,
 					  pl->link_config.lp_advertising);
@@ -578,16 +595,18 @@ static int phylink_parse_fixedlink(struct phylink *pl,
 		}
 	}
 
-	if (pl->link_config.speed > SPEED_1000 &&
-	    pl->link_config.duplex != DUPLEX_FULL)
+	if (pl->link_config.link_speed > SPEED_1000 &&
+	    pl->link_config.link_duplex != DUPLEX_FULL)
 		phylink_warn(pl, "fixed link specifies half duplex for %dMbps link?\n",
-			     pl->link_config.speed);
+			     pl->link_config.link_speed);
 
+	phylink_state_fill_speed_duplex(&pl->link_config);
 	bitmap_fill(pl->supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
 	linkmode_copy(pl->link_config.advertising, pl->supported);
 	phylink_validate(pl, pl->supported, &pl->link_config);
 
-	s = phy_lookup_setting(pl->link_config.speed, pl->link_config.duplex,
+	s = phy_lookup_setting(pl->link_config.link_speed,
+			       pl->link_config.link_duplex,
 			       pl->supported, true);
 	linkmode_zero(pl->supported);
 	phylink_set(pl->supported, MII);
@@ -599,8 +618,8 @@ static int phylink_parse_fixedlink(struct phylink *pl,
 		__set_bit(s->bit, pl->link_config.lp_advertising);
 	} else {
 		phylink_warn(pl, "fixed link %s duplex %dMbps not recognised\n",
-			     pl->link_config.duplex == DUPLEX_FULL ? "full" : "half",
-			     pl->link_config.speed);
+			     pl->link_config.link_duplex == DUPLEX_FULL ? "full" : "half",
+			     pl->link_config.link_speed);
 	}
 
 	linkmode_and(pl->link_config.advertising, pl->link_config.advertising,
@@ -757,7 +776,7 @@ static void phylink_resolve_flow(struct phylink_link_state *state)
 	bool tx_pause, rx_pause;
 
 	state->pause = MLO_PAUSE_NONE;
-	if (state->duplex == DUPLEX_FULL) {
+	if (state->link_duplex == DUPLEX_FULL) {
 		linkmode_resolve_pause(state->advertising,
 				       state->lp_advertising,
 				       &tx_pause, &rx_pause);
@@ -925,12 +944,16 @@ static void phylink_mac_pcs_get_state(struct phylink *pl,
 	linkmode_zero(state->lp_advertising);
 	state->interface = pl->link_config.interface;
 	state->an_enabled = pl->link_config.an_enabled;
-	if  (state->an_enabled) {
+	if (state->an_enabled) {
+		state->link_speed = SPEED_UNKNOWN;
+		state->link_duplex = DUPLEX_UNKNOWN;
 		state->speed = SPEED_UNKNOWN;
 		state->duplex = DUPLEX_UNKNOWN;
 		state->pause = MLO_PAUSE_NONE;
 	} else {
-		state->speed =  pl->link_config.speed;
+		state->link_speed = pl->link_config.link_speed;
+		state->link_duplex = pl->link_config.link_duplex;
+		state->speed = pl->link_config.speed;
 		state->duplex = pl->link_config.duplex;
 		state->pause = pl->link_config.pause;
 	}
@@ -944,6 +967,9 @@ static void phylink_mac_pcs_get_state(struct phylink *pl,
 		pl->mac_ops->mac_pcs_get_state(pl->config, state);
 	else
 		state->link = 0;
+
+	state->link_speed = state->speed;
+	state->link_duplex = state->duplex;
 }
 
 /* The fixed state is... fixed except for the link state,
@@ -953,10 +979,17 @@ static void phylink_get_fixed_state(struct phylink *pl,
 				    struct phylink_link_state *state)
 {
 	*state = pl->link_config;
-	if (pl->config->get_fixed_state)
+	if (pl->config->get_fixed_state) {
 		pl->config->get_fixed_state(pl->config, state);
-	else if (pl->link_gpio)
+		/* FIXME: these should not be updated, but
+		 * bcm_sf2_sw_fixed_state does it anyway
+		 */
+		state->link_speed = state->speed;
+		state->link_duplex = state->duplex;
+		phylink_state_fill_speed_duplex(state);
+	} else if (pl->link_gpio) {
 		state->link = !!gpiod_get_value_cansleep(pl->link_gpio);
+	}
 
 	phylink_resolve_flow(state);
 }
@@ -1027,8 +1060,8 @@ static void phylink_link_up(struct phylink *pl,
 
 	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),
+		     phy_speed_to_str(link_state.link_speed),
+		     phy_duplex_to_str(link_state.link_duplex),
 		     phylink_pause_to_str(link_state.pause));
 }
 
@@ -1279,8 +1312,9 @@ struct phylink *phylink_create(struct phylink_config *config,
 		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->link_config.link_speed = SPEED_UNKNOWN;
+	pl->link_config.link_duplex = DUPLEX_UNKNOWN;
+	phylink_state_fill_speed_duplex(&pl->link_config);
 	pl->link_config.an_enabled = true;
 	pl->mac_ops = mac_ops;
 	__set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
@@ -1344,8 +1378,8 @@ static void phylink_phy_change(struct phy_device *phydev, bool up)
 	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.link_speed = phydev->speed;
+	pl->phy_state.link_duplex = phydev->duplex;
 	pl->phy_state.pause = MLO_PAUSE_NONE;
 	if (tx_pause)
 		pl->phy_state.pause |= MLO_PAUSE_TX;
@@ -1353,6 +1387,7 @@ static void phylink_phy_change(struct phy_device *phydev, bool up)
 		pl->phy_state.pause |= MLO_PAUSE_RX;
 	pl->phy_state.interface = phydev->interface;
 	pl->phy_state.link = up;
+	phylink_state_fill_speed_duplex(&pl->phy_state);
 	mutex_unlock(&pl->state_mutex);
 
 	phylink_run_resolve(pl);
@@ -1422,8 +1457,9 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy,
 	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.link_speed = SPEED_UNKNOWN;
+	pl->phy_state.link_duplex = DUPLEX_UNKNOWN;
+	phylink_state_fill_speed_duplex(&pl->phy_state);
 	linkmode_copy(pl->supported, supported);
 	linkmode_copy(pl->link_config.advertising, config.advertising);
 
@@ -1866,8 +1902,8 @@ static void phylink_get_ksettings(const struct phylink_link_state *state,
 {
 	phylink_merge_link_mode(kset->link_modes.advertising, state->advertising);
 	linkmode_copy(kset->link_modes.lp_advertising, state->lp_advertising);
-	kset->base.speed = state->speed;
-	kset->base.duplex = state->duplex;
+	kset->base.speed = state->link_speed;
+	kset->base.duplex = state->link_duplex;
 	kset->base.autoneg = state->an_enabled ? AUTONEG_ENABLE :
 				AUTONEG_DISABLE;
 }
@@ -1983,14 +2019,14 @@ int phylink_ethtool_ksettings_set(struct phylink *pl,
 		 * If the link parameters match, accept them but do nothing.
 		 */
 		if (pl->cur_link_an_mode == MLO_AN_FIXED) {
-			if (s->speed != pl->link_config.speed ||
-			    s->duplex != pl->link_config.duplex)
+			if (s->speed != pl->link_config.link_speed ||
+			    s->duplex != pl->link_config.link_duplex)
 				return -EINVAL;
 			return 0;
 		}
 
-		config.speed = s->speed;
-		config.duplex = s->duplex;
+		config.link_speed = s->speed;
+		config.link_duplex = s->duplex;
 		break;
 
 	case AUTONEG_ENABLE:
@@ -2005,8 +2041,8 @@ int phylink_ethtool_ksettings_set(struct phylink *pl,
 			return 0;
 		}
 
-		config.speed = SPEED_UNKNOWN;
-		config.duplex = DUPLEX_UNKNOWN;
+		config.link_speed = SPEED_UNKNOWN;
+		config.link_duplex = DUPLEX_UNKNOWN;
 		break;
 
 	default:
@@ -2036,6 +2072,7 @@ int phylink_ethtool_ksettings_set(struct phylink *pl,
 		}
 
 		/* Revalidate with the selected interface */
+		phylink_state_fill_speed_duplex(&config);
 		linkmode_copy(support, pl->supported);
 		if (phylink_validate(pl, support, &config)) {
 			phylink_err(pl, "validation of %s/%s with support %*pb failed\n",
@@ -2046,6 +2083,7 @@ int phylink_ethtool_ksettings_set(struct phylink *pl,
 		}
 	} else {
 		/* Validate without changing the current supported mask. */
+		phylink_state_fill_speed_duplex(&config);
 		linkmode_copy(support, pl->supported);
 		if (phylink_validate(pl, support, &config))
 			return -EINVAL;
@@ -2056,9 +2094,10 @@ int phylink_ethtool_ksettings_set(struct phylink *pl,
 		return -EINVAL;
 
 	mutex_lock(&pl->state_mutex);
-	pl->link_config.speed = config.speed;
-	pl->link_config.duplex = config.duplex;
+	pl->link_config.link_speed = config.link_speed;
+	pl->link_config.link_duplex = config.link_duplex;
 	pl->link_config.an_enabled = config.an_enabled;
+	phylink_state_fill_speed_duplex(&pl->link_config);
 
 	if (pl->link_config.interface != config.interface) {
 		/* The interface changed, e.g. 1000base-X <-> 2500base-X */
@@ -2597,10 +2636,11 @@ static int phylink_sfp_config(struct phylink *pl, u8 mode,
 	memset(&config, 0, sizeof(config));
 	linkmode_copy(config.advertising, advertising);
 	config.interface = PHY_INTERFACE_MODE_NA;
-	config.speed = SPEED_UNKNOWN;
-	config.duplex = DUPLEX_UNKNOWN;
+	config.link_speed = SPEED_UNKNOWN;
+	config.link_duplex = DUPLEX_UNKNOWN;
 	config.pause = MLO_PAUSE_AN;
 	config.an_enabled = pl->link_config.an_enabled;
+	phylink_state_fill_speed_duplex(&config);
 
 	/* Ignore errors if we're expecting a PHY to attach later */
 	ret = phylink_validate(pl, support, &config);
diff --git a/include/linux/phylink.h b/include/linux/phylink.h
index 5008ec3dcade..ab5edc1e5330 100644
--- a/include/linux/phylink.h
+++ b/include/linux/phylink.h
@@ -56,8 +56,16 @@ static inline bool phylink_autoneg_inband(unsigned int mode)
  * @lp_advertising: ethtool bitmask containing link partner advertised link
  *   modes
  * @interface: link &typedef phy_interface_t mode
- * @speed: link speed, one of the SPEED_* constants.
- * @duplex: link duplex mode, one of DUPLEX_* constants.
+ * @speed: interface speed, one of the SPEED_* constants. This is the speed of
+ *   the host-side interface to the phy. If @rate_adaptation is being
+ *   performed, this will be different from @link_speed.
+ * @link_speed: link speed, one of the SPEED_* constants. This is the speed of
+ *   the line-side interface to the link partner.
+ * @duplex: interface duplex mode, one of DUPLEX_* constants. This is the
+ *   duplex of then host-side interface to the phy. If @rate_adaptation is
+ *   being performed, this may be different from @link_duplex.
+ * @link_duplex: link duplex, one of the DUPLEX_* constants. This is the duplex
+ *   of the line-side interface to the link partner.
  * @pause: link pause state, described by MLO_PAUSE_* constants.
  * @link: true if the link is up.
  * @an_enabled: true if autonegotiation is enabled/desired.
@@ -68,7 +76,9 @@ struct phylink_link_state {
 	__ETHTOOL_DECLARE_LINK_MODE_MASK(lp_advertising);
 	phy_interface_t interface;
 	int speed;
+	int link_speed;
 	int duplex;
+	int link_duplex;
 	int pause;
 	unsigned int link:1;
 	unsigned int an_enabled:1;
