Netdev List
 help / color / mirror / Atom feed
From: netdev-bot+sashiko@kernel.org
To: javen_xu@realsil.com.cn
Cc: hkallweit1@gmail.com, nic_swsd@realtek.com,
	andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
	kuba@kernel.org, pabeni@redhat.com,
	maxime.chevallier@bootlin.com, horms@kernel.org,
	netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	daniel@makrotopia.org, linux@armlinux.org.uk,
	enelsonmoore@gmail.com, daniel@thingy.jp
Subject: Re: [PATCH net-next v9 2/7] net: phy: phylink: add helper to modify pause
Date: Fri, 04 Sep 2026 22:25:16 +0000	[thread overview]
Message-ID: <178856071681.219967.2291837962290089662@kernel.org> (raw)
In-Reply-To: <20260831053745.1197-3-javen_xu@realsil.com.cn>

Thank you for your contribution! Sashiko AI review found 9 potential
issue(s) to consider:

Critical: 0 · High: 1 · Medium: 6 · Low: 2

- [High] phylink_update_mac_pause_capabilities() destroys, rather than
  masks, the requested pause configuration, so the "pause capability…
- [Medium] phylink_update_mac_pause_capabilities() mutates the live
  pl->supported mask in place: it unconditionally sets Pause and…
- [Medium] phylink_update_mac_pause_capabilities() has no guaranteed way
  to re-program the MAC after a pause-capability change: it relies on…
- [Medium] phylink_update_mac_pause_capabilities() conveys the capability
  change only by writing pl->config->mac_capabilities (lines 1923-1924)…
- [Medium] In phylink_update_mac_pause_capabilities(), the asymmetric-
  request test is written as '(pause_state & MLO_PAUSE_RX) ^…
- [Medium] phylink_update_mac_pause_capabilities() treats an asymmetric-
  only MAC capability as if pause were entirely unsupported: the first…
- [Medium] phylink_update_mac_pause_capabilities() omits the MLO_AN_FIXED
  guard that phylink_ethtool_set_pauseparam() applies before reaching…
- [Low] phylink_update_mac_pause_capabilities() updates
  pl->config->mac_capabilities and pl->supported inside one…
- [Low] The commit message describes only the Realtek motivation ("when
  we enable jumbo, pause are not supported.

--

net: phy: phylink: add helper to modify pause

This patch factors the body of phylink_ethtool_set_pauseparam() out into a
new static helper phylink_update_pause_state(), and adds a new exported
core API, phylink_update_mac_pause_capabilities(), which lets a MAC driver
withdraw or restore MAC_SYM_PAUSE/MAC_ASYM_PAUSE at runtime.  The stated
motivation is Realtek hardware that cannot do flow control while jumbo
frames are enabled.

No Link: tags are present in the commit header.

>     For Realtek nics, when we enable jumbo, pause are not supported. So we
>     must check the pause capabilities from ourself and lp.

[Severity: Low]

Could the commit message describe what the patch does, in addition to why?

Two things happen here that a reader of the log would want to know about:
phylink_update_pause_state() is split out of the existing ethtool path as a
behaviour-preserving refactor, and a new exported core API,
phylink_update_mac_pause_capabilities(), is added which mutates the
driver-owned pl->config->mac_capabilities, the phylink supported mask and
pl->link_config.

The only caller is added later in the same series, in rtl8169_change_mtu(),
which is the usual API-then-user layout, so that part is fine.

> diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
> index 3ec3bb439109..646b8e1e727b 100644
> --- a/drivers/net/phy/phylink.c
> +++ b/drivers/net/phy/phylink.c
> @@ -1829,6 +1829,126 @@ int phylink_set_fixed_link(struct phylink *pl,
>  }
>  EXPORT_SYMBOL_GPL(phylink_set_fixed_link);
>  
> +/**
> + * phylink_update_pause_state() - Update the phylink pause frame configuration
> + * @pl: a pointer to a &struct phylink instance
> + * @pause_state: bitmask indicating the new pause state
> + *
> + * Update the MAC pause frame (flow control) state for the phylink instance.
> + */
> +static void phylink_update_pause_state(struct phylink *pl, int pause_state)
> +{
> +	struct phylink_link_state *config = &pl->link_config;
> +	bool tx_pause = !!(pause_state & MLO_PAUSE_TX);
> +	bool rx_pause = !!(pause_state & MLO_PAUSE_RX);
> +	bool manual_changed;
> +
> +	mutex_lock(&pl->state_mutex);
> +
> +	/*
> +	 * See the comments for linkmode_set_pause(), wrt the deficiencies
> +	 * with the current implementation.  A solution to this issue would
> +	 * be:
> +	 * ethtool  Local device
> +	 *  rx  tx  Pause AsymDir
> +	 *  0   0   0     0
> +	 *  1   0   1     1
> +	 *  0   1   0     1
> +	 *  1   1   1     1
> +	 * and then use the ethtool rx/tx enablement status to mask the
> +	 * rx/tx pause resolution.
> +	 */
> +	linkmode_set_pause(config->advertising, tx_pause,
> +			   rx_pause);
> +
> +	manual_changed = (config->pause ^ pause_state) & MLO_PAUSE_AN ||
> +			 (!(pause_state & MLO_PAUSE_AN) &&
> +			   (config->pause ^ pause_state) & MLO_PAUSE_TXRX_MASK);

[Severity: Medium]

This manual_changed test is the only thing that forces pl->link_failed and
phylink_run_resolve(), and it was written for the ethtool path where
MLO_PAUSE_AN can toggle.

When phylink_update_mac_pause_capabilities() calls in, only MLO_PAUSE_RX
and MLO_PAUSE_TX are ever cleared and MLO_PAUSE_AN never changes, so with
pause autoneg enabled (the phylink default, link_config.pause =
MLO_PAUSE_AN) manual_changed is false and no resolve is queued.

For an MLO_AN_INBAND SGMII setup with no phydev, is there anything left to
re-run mac_link_up()?  phylink_change_inband_advert() ends up in
phylink_mii_c22_pcs_encode_advertisement(), where the SGMII family has no
pause bits at all:

	case PHY_INTERFACE_MODE_SGMII:
	case PHY_INTERFACE_MODE_PSGMII:
	case PHY_INTERFACE_MODE_QSGMII:
		return 0x0001;

so pcs_config() reports no change, no AN restart happens and the link does
not cycle.  Does the MAC then keep the tx_pause/rx_pause programming from
the previous mac_link_up() call while phylink believes pause is
unsupported, which is the jumbo-plus-pause combination this series is
trying to avoid?  The capability-regained direction has the same gap, and
so does the PHY case when phydev->autoneg is clear, since
phy_set_asym_pause() only renegotiates when autoneg is set.

> +
> +	config->pause = pause_state;
> +
> +	/* Update our in-band advertisement, triggering a renegotiation if
> +	 * the advertisement changed.
> +	 */
> +	if (!pl->phydev)
> +		phylink_change_inband_advert(pl);
> +
> +	mutex_unlock(&pl->state_mutex);
> +
> +	/* If we have a PHY, a change of the pause frame advertisement will
> +	 * cause phylib to renegotiate (if AN is enabled) which will in turn
> +	 * call our phylink_phy_change() and trigger a resolve.  Note that
> +	 * we can't hold our state mutex while calling phy_set_asym_pause().
> +	 */
> +	if (pl->phydev)
> +		phy_set_asym_pause(pl->phydev, rx_pause, tx_pause);
> +
> +	/* If the manual pause settings changed, make sure we trigger a
> +	 * resolve to update their state; we can not guarantee that the
> +	 * link will cycle.
> +	 */
> +	if (manual_changed) {
> +		pl->link_failed = true;
> +		phylink_run_resolve(pl);
> +	}
> +}
> +
> +/**
> + * 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;
> +	int pause_state;
> +
> +	ASSERT_RTNL();
> +
> +	if (mac_pause & ~(MAC_SYM_PAUSE | MAC_ASYM_PAUSE)) {
> +		phylink_err(pl, "Attempted to dynamically change non-pause MAC capabilities\n");
> +		return;
> +	}

[Severity: Medium]

The sibling entry point into the same (now shared) code guards it with:

phylink_ethtool_set_pauseparam()
	if (pl->req_link_an_mode == MLO_AN_FIXED)
		return -EOPNOTSUPP;

	if (!phylink_test(pl->supported, Pause) &&
	    !phylink_test(pl->supported, Asym_Pause))
		return -EOPNOTSUPP;

Should the new exported helper carry the MLO_AN_FIXED check too?  An MTU
change is independent of the AN mode, so a fixed-link MAC can reach here,
and phylink_update_pause_state() will then run
linkmode_set_pause(config->advertising, tx, rx) with tx/rx derived from
config->pause, which defaults to MLO_PAUSE_AN, i.e. false/false, clearing
ETHTOOL_LINK_MODE_Pause_BIT and Asym_Pause_BIT from
pl->link_config.advertising.

That mask is what fixed-link pause resolution consumes:

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

with lp_advertising seeded from the DT properties in
phylink_parse_fixedlink():

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

Can this silently disable the flow control described in firmware, with no
error returned to the caller since the helper is void?

> +
> +	old_pause = pl->config->mac_capabilities & (MAC_SYM_PAUSE | MAC_ASYM_PAUSE);
> +	if (old_pause == mac_pause)
> +		return;
> +
> +	mutex_lock(&pl->state_mutex);
> +
> +	pl->config->mac_capabilities &= ~(MAC_SYM_PAUSE | MAC_ASYM_PAUSE);
> +	pl->config->mac_capabilities |= mac_pause;

[Severity: Medium]

Does writing config->mac_capabilities have any effect for a MAC that
implements mac_get_caps()?  phylink_validate_mac_and_pcs() prefers the
callback:

	if (pl->mac_ops->mac_get_caps)
		capabilities = pl->mac_ops->mac_get_caps(pl->config,
							 state->interface);
	else
		capabilities = pl->config->mac_capabilities;

and the mac_get_caps kernel-doc says config->mac_capabilities is only used
when the callback is absent.  stmmac_mac_get_caps() goes further and
overwrites the field the helper just wrote:

	config->mac_capabilities = priv->hw->link.caps;
	return priv->hw->link.caps;

For such a driver, would the phylink_test() checks below both still pass,
leaving pause_state unmasked while the advertisement is nevertheless
rewritten by phylink_update_pause_state()?  The helper is void and prints
no warning, so the driver cannot tell the update was a no-op.

Also, is the core expected to write into the driver-owned struct
phylink_config after phylink_create()?

> +
> +	phylink_set(pl->supported, Pause);
> +	phylink_set(pl->supported, Asym_Pause);
> +
> +	if (pl->phydev)
> +		linkmode_and(pl->supported, pl->supported, pl->phydev->supported);
> +	else if (pl->sfp_bus)
> +		linkmode_and(pl->supported, pl->supported, pl->sfp_support);
> +
> +	phylink_validate(pl, pl->supported, config);

[Severity: Medium]

Is it safe to force Pause and Asym_Pause into the live pl->supported mask
and then ignore the phylink_validate() return value?  The bits are only
removed again inside phylink_validate_mask_caps(), that is, on the success
path.

phylink_validate() has early returns that leave the caller's mask
untouched:

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

as do the mac_select_pcs() ERR_PTR, PCS interface mismatch and
pcs_validate() failures.  On those paths pl->supported keeps the
force-set Pause|Asym_Pause bits, so the phylink_test() checks just below
conclude pause is supported, the requested restriction is not applied and
ethtool keeps reporting pause the MAC has just declared unsupported.

Such a failure is reachable in exactly the case this API is for, since
phylink_get_capabilities() drops RATE_MATCH_PAUSE support when either
pause capability is missing:

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

On the state->interface == PHY_INTERFACE_MODE_NA path,
phylink_validate_mask() copies a possibly empty accumulator into supported
before returning -EINVAL, so can pl->supported end up empty and
pl->link_config.advertising rewritten, with no rollback and no way for the
void caller to notice?

Related question: every other phylink_validate*() call site validates into
a local mask, for example phylink_bringup_phy():

	ret = phylink_validate_phy(pl, phy, supported, &config);
	if (ret) { ... return ret; }

Validating here against the single pl->link_config.interface rather than
over phy->possible_interfaces or pl->sfp_interfaces looks like it would
permanently prune modes only reachable via other interface modes on c45
PHYs and multi-interface SFPs.  Is that intended?

> +
> +	pause_state = config->pause;

[Severity: High]

Does this destroy the requested pause configuration rather than mask it,
so that the capability-regained direction can never restore pause?

pause_state is read from config->pause, masked here, and then written back
into the same field by phylink_update_pause_state():

	linkmode_set_pause(config->advertising, tx_pause,
			   rx_pause);
	...
	config->pause = pause_state;
	...
	if (pl->phydev)
		phy_set_asym_pause(pl->phydev, rx_pause, tx_pause);

There is no saved copy of the original request, and the advertisement is
re-programmed from the masked value.

In the default configuration phylink_create() sets

	pl->link_config.pause = MLO_PAUSE_AN;

with no MLO_PAUSE_RX/MLO_PAUSE_TX, while phylink_bringup_phy() advertises
pause via phy_support_asym_pause() and copies those bits into
pl->link_config.advertising.  So even a call that grants
MAC_SYM_PAUSE|MAC_ASYM_PAUSE ends up running

	linkmode_set_pause(adv, false, false);
	phy_set_asym_pause(phydev, false, false);

and linkmode_set_pause() clears both bits for false/false:

	linkmode_mod_bit(ETHTOOL_LINK_MODE_Pause_BIT, advertisement, rx);
	linkmode_mod_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, advertisement,
			 rx ^ tx);

With the r8169 caller added later in the series that gives:

	ip link set eth0 mtu 9000
	  rtl8169_change_mtu()
	    phylink_update_mac_pause_capabilities(pl, 0)
	      -> pause bits cleared in phydev->advertising
	ip link set eth0 mtu 1500
	  rtl8169_change_mtu()
	    phylink_update_mac_pause_capabilities(pl, MAC_SYM_PAUSE|MAC_ASYM_PAUSE)
	      -> pause_state is still MLO_PAUSE_AN, bits cleared again

Nothing re-runs phy_support_asym_pause() or otherwise re-seeds the
advertisement, so is flow control lost for good after the first
transition?  Would keeping the user request separately and masking it only
at resolve time avoid this?

> +
> +	if (!phylink_test(pl->supported, Pause)) {
> +		pause_state &= ~(MLO_PAUSE_RX | MLO_PAUSE_TX);

[Severity: Medium]

Should this branch also consider Asym_Pause?  MAC_ASYM_PAUSE is accepted
independently of MAC_SYM_PAUSE, and phylink_caps_to_linkmodes() sets only
ETHTOOL_LINK_MODE_Asym_Pause_BIT for it, so pl->supported can legitimately
hold Asym_Pause without Pause.

The capability table in include/linux/phylink.h documents tx-only pause as
valid in that state, and phylink_ethtool_set_pauseparam() allows it:

	if (!phylink_test(pl->supported, Pause) &&
	    !phylink_test(pl->supported, Asym_Pause))
		return -EOPNOTSUPP;

After phylink_update_mac_pause_capabilities(pl, MAC_ASYM_PAUSE), does an
existing tx-only setting get cleared here and then un-advertised by
phylink_update_pause_state(), even though the MAC still supports it?

> +	} else if (!phylink_test(pl->supported, Asym_Pause)) {
> +		if ((pause_state & MLO_PAUSE_RX) ^ (pause_state & MLO_PAUSE_TX))
> +			pause_state &= ~(MLO_PAUSE_RX | MLO_PAUSE_TX);

[Severity: Medium]

Does this XOR detect an asymmetric request?  MLO_PAUSE_RX and MLO_PAUSE_TX
are different bit positions:

	MLO_PAUSE_RX = BIT(0),
	MLO_PAUSE_TX = BIT(1),

so 0x1 ^ 0x2 == 0x3 and the condition is non-zero whenever either bit is
set, including the symmetric rx=1/tx=1 case.  It looks equivalent to
pause_state & MLO_PAUSE_TXRX_MASK, so after "ethtool -A eth0 rx on tx on"
a symmetric-pause-capable MAC would have flow control cleared entirely.

The peer check in phylink_ethtool_set_pauseparam() expresses the intended
rule as a plain comparison:

	if (!phylink_test(pl->supported, Asym_Pause) &&
	    pause->rx_pause != pause->tx_pause)
		return -EINVAL;

Would !!(pause_state & MLO_PAUSE_RX) != !!(pause_state & MLO_PAUSE_TX) be
what is wanted here?

> +	}
> +
> +	mutex_unlock(&pl->state_mutex);
> +
> +	phylink_update_pause_state(pl, pause_state);

[Severity: Low]

The capability update and the dependent pause state are committed in two
separate pl->state_mutex sections: mac_capabilities and pl->supported are
written above, the mutex is dropped here, and phylink_update_pause_state()
re-acquires it to publish link_config.pause and link_config.advertising.

phylink_resolve() runs from system_power_efficient_wq without RTNL and
takes pl->state_mutex, so it can run in that window and see pl->supported
already saying pause is unsupported while link_config.pause still has
MLO_PAUSE_RX|MLO_PAUSE_TX, then call mac_ops->mac_link_up() with pause
enabled.  The observed combination equals the pre-call state, so this is a
transient window rather than a lasting one, but would doing both updates
under a single acquisition be simpler to reason about?

> +}
> +EXPORT_SYMBOL_GPL(phylink_update_mac_pause_capabilities);
> +

[ ... ]

> diff --git a/include/linux/phylink.h b/include/linux/phylink.h
> index 1dda5c7ed5f1..3a88a69882a6 100644
> --- a/include/linux/phylink.h
> +++ b/include/linux/phylink.h
> @@ -843,4 +843,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

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260831053745.1197-1-javen_xu%40realsil.com.cn

  reply	other threads:[~2026-09-04 22:25 UTC|newest]

Thread overview: 22+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-31  5:37 [PATCH net-next v9 0/7] r8169: add support for phylink javen
2026-08-31  5:37 ` [PATCH net-next v9 1/7] r8169: add speed in private struct javen
2026-09-04 22:25   ` netdev-bot+sashiko
2026-08-31  5:37 ` [PATCH net-next v9 2/7] net: phy: phylink: add helper to modify pause javen
2026-09-04 22:25   ` netdev-bot+sashiko [this message]
2026-08-31  5:37 ` [PATCH net-next v9 3/7] r8169: add support for phylink javen
2026-09-02 14:31   ` Andrew Lunn
2026-09-04 22:25   ` netdev-bot+sashiko
2026-08-31  5:37 ` [PATCH net-next v9 4/7] r8169: add support for RTL8116af javen
2026-09-02 14:38   ` Andrew Lunn
2026-09-04 22:25   ` netdev-bot+sashiko
2026-08-31  5:37 ` [PATCH net-next v9 5/7] r8169: add support for RTL8127atf javen
2026-09-02 14:41   ` Andrew Lunn
2026-09-04 22:25   ` netdev-bot+sashiko
2026-08-31  5:37 ` [PATCH net-next v9 6/7] r8169: add ltr support for RTL8117 series javen
2026-09-02 14:42   ` Andrew Lunn
2026-09-04 22:25   ` netdev-bot+sashiko
2026-08-31  5:37 ` [PATCH net-next v9 7/7] r8169: fix RTL8116af can not enter s0idle and c10 javen
2026-09-02 14:42   ` Andrew Lunn
2026-09-04 22:25   ` netdev-bot+sashiko
2026-09-02 14:28 ` [PATCH net-next v9 0/7] r8169: add support for phylink Andrew Lunn
2026-09-04 21:50 ` patchwork-bot+netdevbpf

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=178856071681.219967.2291837962290089662@kernel.org \
    --to=netdev-bot+sashiko@kernel.org \
    --cc=andrew+netdev@lunn.ch \
    --cc=daniel@makrotopia.org \
    --cc=daniel@thingy.jp \
    --cc=davem@davemloft.net \
    --cc=edumazet@google.com \
    --cc=enelsonmoore@gmail.com \
    --cc=hkallweit1@gmail.com \
    --cc=horms@kernel.org \
    --cc=javen_xu@realsil.com.cn \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux@armlinux.org.uk \
    --cc=maxime.chevallier@bootlin.com \
    --cc=netdev@vger.kernel.org \
    --cc=nic_swsd@realtek.com \
    --cc=pabeni@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox