[PATCH net-next v6 2/7] net: phy: phylink: add helper to modify pause
From: javen
Date: Thu Jul 09 2026 - 06:27:34 EST
From: Javen Xu <javen_xu@xxxxxxxxxxxxxx>
For Realtek nics, when we enable jumbo, pause are not supported. So we
must check the pause capabilities from ourself and lp.
Signed-off-by: Javen Xu <javen_xu@xxxxxxxxxxxxxx>
---
Changes in v5:
- no changes, new file
Changes in v6:
- rename phylink_update_mac_pause_capabilities(), this function only
changes mac pause capability
- set asym pause and pause according to config->pause tx and rx
- add phydev->lock when change pl->phydev->advertising
---
drivers/net/phy/phylink.c | 87 +++++++++++++++++++++++++++++++++++++++
include/linux/phylink.h | 2 +
2 files changed, 89 insertions(+)
diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 59dfe35afa54..9e9cd79301d6 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -1828,6 +1828,93 @@ int phylink_set_fixed_link(struct phylink *pl,
}
EXPORT_SYMBOL_GPL(phylink_set_fixed_link);
+/**
+ * phylink_update_mac_pause_capabilities() - Dynamically update MAC pause
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @mac_pause: the new MAC pause capabilities mask
+ *
+ * This function allows a MAC driver to dynamically change its pause state,
+ * such as losing/gaining Pause frame support based on MTU size.
+ * It recalculates supported link modes and triggers renegotiation if needed.
+ */
+void phylink_update_mac_pause_capabilities(struct phylink *pl, unsigned long mac_pause)
+{
+ struct phylink_link_state *config = &pl->link_config;
+ unsigned long old_pause, caps_added, caps_removed;
+ bool pause_adv, asym_adv;
+
+ ASSERT_RTNL();
+
+ if (mac_pause & ~(MAC_SYM_PAUSE | MAC_ASYM_PAUSE)) {
+ phylink_err(pl, "Attempted to dynamically change non-pause MAC capabilities\n");
+ return;
+ }
+
+ old_pause = pl->config->mac_capabilities & (MAC_SYM_PAUSE | MAC_ASYM_PAUSE);
+ caps_added = mac_pause & ~old_pause;
+ caps_removed = old_pause & ~mac_pause;
+
+ if (!caps_added && !caps_removed)
+ return;
+
+ mutex_lock(&pl->state_mutex);
+
+ pl->config->mac_capabilities &= ~(MAC_SYM_PAUSE | MAC_ASYM_PAUSE);
+ pl->config->mac_capabilities |= mac_pause;
+
+ if (caps_removed & MAC_SYM_PAUSE)
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_Pause_BIT, pl->supported);
+ if (caps_removed & MAC_ASYM_PAUSE)
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, pl->supported);
+
+ linkmode_and(config->advertising, config->advertising, pl->supported);
+
+ if (caps_added & MAC_SYM_PAUSE) {
+ linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT, pl->supported);
+ if (pl->phydev && !phylink_test(pl->phydev->supported, Pause))
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_Pause_BIT, pl->supported);
+ }
+ if (caps_added & MAC_ASYM_PAUSE) {
+ linkmode_set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, pl->supported);
+ if (pl->phydev && !phylink_test(pl->phydev->supported, Asym_Pause))
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, pl->supported);
+ }
+
+ linkmode_and(config->advertising, config->advertising, pl->supported);
+
+ if (config->pause & MLO_PAUSE_AN) {
+ if (phylink_test(pl->supported, Pause) &&
+ (config->pause & MLO_PAUSE_RX) && (config->pause & MLO_PAUSE_TX))
+ linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT, config->advertising);
+
+ if (phylink_test(pl->supported, Asym_Pause) &&
+ ((config->pause & MLO_PAUSE_RX) ^ (config->pause & MLO_PAUSE_TX)))
+ linkmode_set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, config->advertising);
+ }
+
+ if (!pl->phydev)
+ phylink_change_inband_advert(pl);
+
+ mutex_unlock(&pl->state_mutex);
+
+ if (pl->phydev) {
+ pause_adv = linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT,
+ config->advertising);
+ asym_adv = linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
+ config->advertising);
+
+ mutex_lock(&pl->phydev->lock);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_Pause_BIT,
+ pl->phydev->advertising, pause_adv);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
+ pl->phydev->advertising, asym_adv);
+ mutex_unlock(&pl->phydev->lock);
+
+ phy_start_aneg(pl->phydev);
+ }
+}
+EXPORT_SYMBOL_GPL(phylink_update_mac_pause_capabilities);
+
/**
* phylink_create() - create a phylink instance
* @config: a pointer to the target &struct phylink_config
diff --git a/include/linux/phylink.h b/include/linux/phylink.h
index 2bc0db3d52ac..e3c7822100ab 100644
--- a/include/linux/phylink.h
+++ b/include/linux/phylink.h
@@ -842,4 +842,6 @@ void phylink_replay_link_begin(struct phylink *pl);
void phylink_replay_link_end(struct phylink *pl);
+void phylink_update_mac_pause_capabilities(struct phylink *pl, unsigned long mac_pause);
+
#endif
--
2.43.0