From: Maxime Chevallier <maxime.chevallier@bootlin.com>
To: netdev-bot+sashiko@kernel.org
Cc: andrew@lunn.ch, kuba@kernel.org, davem@davemloft.net,
edumazet@google.com, pabeni@redhat.com, horms@kernel.org,
linux@armlinux.org.uk, hkallweit1@gmail.com, corbet@lwn.net,
skhan@linuxfoundation.org, o.rempel@pengutronix.de,
vladimir.oltean@nxp.com, f.fainelli@gmail.com,
thomas.petazzoni@bootlin.com, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org
Subject: Re: [PATCH net-next v2 2/3] selftests: drv-net: pause: Validate the pause autonegotiation with a partner
Date: Tue, 13 Oct 2026 11:35:17 +0200 [thread overview]
Message-ID: <40f413f0-1dfb-4968-b483-f03aa2f1bdbb@bootlin.com> (raw)
In-Reply-To: <179065240018.434549.346139764454021625@kernel.org>
On 9/29/26 05:26, netdev-bot+sashiko@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 7 potential
> issue(s) to consider.
>
> Critical: 0 · High: 0 · Medium: 4 · Low: 3
>
> If you believe none of the issues are legitimate, please reply to each
> issue of Medium severity or higher and explain why the AI is wrong.
> If any of them are legitimate and you plan to respin, please let patchwork
> know by including "pw-bot: cr" as a separate line at the end of your reply
> (one such reply per series is enough).
>
> - [Medium] In tools/testing/selftests/drivers/net/hw/pause.py, the
> docstring of pause_aneg_resolution() says the test fails if 'the PHY…
> - [Medium] controllable_lp() in pause.py runs `ip link set {cfg.ifname}
> down` (line 152), then a remote `ethtool --json` poll loop, then `ip…
> - [Medium] controllable_lp() only treats the remote as the direct link
> partner if `ip link set <local> down` makes the remote lose carrier…
> - [Medium] selftests: drv-net: pause: false test failure for unsupported
> devices on no-op changes
> - [Low] Several new peer helpers in pause.py have docstrings or messages
> copied from the local helpers that do not match the code.
> - [Low] pause_aneg_resolution() only handles EOPNOTSUPP from
> set_local_pauseparams() (lines 693-695).
> - [Low] All four advertisement checks in pause_aneg_resolution() (lines
> 715-738) only test that required bits are present (ksft_in over…
>
> --
>
> selftests: drv-net: pause: Validate the pause autonegotiation with a partner
>
> This commit adds a pause_aneg_resolution test to pause.py. The test sets
> pause parameters on both the local device and the link partner, then
> checks the resulting 802.3 pause resolution for all 16 combinations. It
> also adds helpers that detect whether the remote is the direct link
> partner, and extends pause_advertising_test to check the remote's
> lp_advertising.
>
>> diff --git a/tools/testing/selftests/drivers/net/hw/pause.py b/tools/testing/selftests/drivers/net/hw/pause.py
>> index f07db32715003..96ee730074f8c 100755
>> --- a/tools/testing/selftests/drivers/net/hw/pause.py
>> +++ b/tools/testing/selftests/drivers/net/hw/pause.py
>> @@ -124,6 +124,62 @@ def wait_for_link(cfg):
>> # the remote to report link up, let's wait a bit less
>> return wait_for_link_remote(cfg, timeout = 3)
>>
>> +def controllable_lp(cfg):
>
> [ ... ]
>
>> + # Set both ends up, wait for up
>> + ip(f"link set {cfg.ifname} up")
>> + ip(f"link set {cfg.remote_ifname} up", host=cfg.remote)
>> +
>> + # No link established
>> + if not wait_for_link(cfg):
>> + ksft_pr(f"{cfg.remote_ifname} is not the link partner: no link with both ends up")
>> + cfg.lp_controllable = False
>> + return False
>> +
>> + ip(f"link set {cfg.ifname} down")
>> +
>> + deadline = time.monotonic() + 3
>> + dropped = False
>> + while time.monotonic() < deadline and not dropped:
>> + dropped = not ethtool(f"{cfg.remote_ifname}", json=True,
>> + host=cfg.remote)[0]["link-detected"]
>> + time.sleep(0.1)
>> +
>> + ip(f"link set {cfg.ifname} up")
>
> [Severity: Medium]
> What happens to cfg.ifname if something raises between the link down and
> the link up here?
>
> ethtool() goes through tool() and cmd() with the default fail=True. So any
> of these would skip the "link set ... up" call:
>
> - a failure or timeout of the remote command
> - a KeyError on "link-detected"
> - a JSON parse error
> - a KeyboardInterrupt
>
> Nothing like try/finally or defer() protects this window.
>
> ksft_run() flushes defers when a test raises, but pause_setup() never
> defers an admin-up. In pause_aneg_resolution(), require_controllable_lp()
> also runs before pause_setup(), so that test has nothing deferred at all.
>
> lp_controllable is not cached on exception, so a later test would retry
> and bring the link back up. But if the exception happens in the last
> caller of controllable_lp() in the run (pause_autoneg_link_autoneg by the
> end of the series), or on an interrupt, is the DUT interface left
> admin-down after the selftest exits?
>
> Other drv-net tests restore the link with a defer. For example, queues.py
> and stats.py use:
>
> defer(ip, f"link set dev {cfg.dev['ifname']} up")
>
> Also, both ends are forced admin-up here, and their original admin state
> is never recorded or restored.
I'm OK with forcing link state on both ends and don't try restoring. I'll add
the missing defer.
>
>> +
>> + if not dropped:
>> + ksft_pr(f"{cfg.remote_ifname} is not the link partner: "
>> + f"it kept its link through {cfg.ifname} going down")
>> + cfg.lp_controllable = False
>> + wait_for_link(cfg)
>> + return False
>
> [Severity: Medium]
> Does taking the local interface admin-down reliably drop the physical
> link on directly cabled setups?
>
> In phylib, phy_suspend() leaves the PHY active on purpose in some cases:
>
> drivers/net/phy/phy_device.c:phy_suspend() {
> ...
> if (phydev->wol_enabled && !(phydrv->flags & PHY_ALWAYS_CALL_SUSPEND))
> return -EBUSY;
>
> if (!phydrv->suspend)
> return 0;
> ...
> }
>
> Firmware-managed NICs such as ice also keep the link on close by default.
> In drivers/net/ethernet/intel/ice/ice_main.c, the PHY is only taken down
> on close when ICE_FLAG_LINK_DOWN_ON_CLOSE_ENA is set:
>
> if (test_bit(ICE_FLAG_LINK_DOWN_ON_CLOSE_ENA, vsi->back->flags)) {
> int link_err = ice_phy_cfg(vsi, false);
>
> On this kind of hardware, a directly connected peer would be cached as
> cfg.lp_controllable = False. pause_aneg_resolution(), and the four later
> tests in the series, would then skip with "is not directly connected".
> That message points at cabling, when the real cause is that the PHY
> stayed powered.
>
> The new remote lp_advertising checks in pause_advertising_test() would
> also be skipped without any notice.
Not much we can do if the peer doesn't behave as expected here. Not sure this
requires attention.
>
> This detection is not changed later in the series.
>
> [ ... ]
>
>> @@ -170,6 +230,33 @@ def get_local_pauseparams(cfg):
>> """
>> return ethtool_ret(f"-a {cfg.ifname}", is_get=True)
>>
>> +def set_peer_pauseparams(cfg, rx, tx, aneg):
>> + """ set pauseparams : ethtool -A
>> +
>> + Raise an error if the return is not 0 or EOPNOTSUPP
>> + """
>> + rx_param = onoff(rx)
>> + tx_param = onoff(tx)
>> + aneg_param = onoff(aneg)
>> +
>> + ret, _ = ethtool_ret(f"-A {cfg.remote_ifname} rx {rx_param} tx {tx_param}"
>> + f" autoneg {aneg_param}",
>> + is_get = False, host=cfg.remote)
>> +
>> + if ret != 0:
>> + raise KsftSkipEx(f"Can't set pauseparams on peer: {errno.errorcode.get(ret, ret)}")
>> +
>> + return ret
>> +
>> +def get_peer_pauseparams(cfg):
>> + """ get pauseparams : ethtool -a
>> +
>> + Raise an error if the return is not 0 or EOPNOTSUPP
>> + """
>> +
>> + return ethtool_ret(f"-a {cfg.remote_ifname}", is_get=True,
>> + host=cfg.remote)
>
> [Severity: Low]
> This isn't a bug, but the docstring of set_peer_pauseparams() says it
> raises an error unless the return is 0 or EOPNOTSUPP. The code instead
> raises KsftSkipEx on any non-zero return, EOPNOTSUPP included. A real
> peer error such as EINVAL or EIO is reported as SKIP.
>
> Several other new peer helpers have docstrings or messages that don't
> match the code:
>
> - get_peer_pauseparams() says "Raise an error if the return is not 0 or
> EOPNOTSUPP", but it only returns the ethtool_ret() tuple.
>
> - get_peer_pause_lp_advertising() says "Return the errcode and the local
> pause lp_advertised linkmodes", but it queries cfg.remote_ifname.
>
> - require_peer_pause_supported_allof() says "Skip if local device doesn't
> support all of the passed modes", but it checks the remote.
>
> - require_peer_pause_supported_anyof() checks the remote, but its skip
> message is "Local device doesn't support any of ...". Nothing calls it,
> in this patch or by the end of the series.
OK
>
>> +
>> + # Check resolution
>> + _, local_pauseparams = get_local_pauseparams(cfg)
>> + ksft_eq(local_pauseparams["negotiated"]["rx"], expected_local_rx)
>> + ksft_eq(local_pauseparams["negotiated"]["tx"], expected_local_tx)
>> +
>> + ret, remote_pauseparams = get_peer_pauseparams(cfg)
>> + if ret == 0 and "negotiated" in remote_pauseparams:
>> + ksft_eq(remote_pauseparams["negotiated"]["rx"], expected_lp_rx)
>> + ksft_eq(remote_pauseparams["negotiated"]["tx"], expected_lp_tx)
>
> [Severity: Medium]
> Where does the "negotiated" object come from?
>
> The PAUSE netlink uAPI only has the HEADER, AUTONEG, RX, TX, STATS and
> STATS_SRC attributes. pause_fill_reply() in net/ethtool/pause.c sends no
> negotiated or resolved RX/TX result.
>
> The ethtool utility builds "negotiated" in userspace from the
> ETHTOOL_A_LINKMODES_OURS and PEER bitsets. Those are the same advertised
> and lp_advertised bits checked just above.
>
> On the kernel side, phylink_ethtool_get_pauseparam() in
> drivers/net/phy/phylink.c reports the configured intent, not the resolved
> state:
>
> pause->autoneg = !!(pl->link_config.pause & MLO_PAUSE_AN);
> pause->rx_pause = !!(pl->link_config.pause & MLO_PAUSE_RX);
> pause->tx_pause = !!(pl->link_config.pause & MLO_PAUSE_TX);
>
> So once the adv and lp_adv checks pass, these asserts are a pure function
> of those bits. Would a MAC that programs its own flow control still pass
> all 16 variants? What about a PHY driver that overwrites phydev->pause or
> asym_pause without touching lp_advertising?
>
> If so, the test can't detect the failure cases its docstring lists:
>
> - that the PHY driver manually overwrites the Pause negotiation result
> - that the MAC driver ignores the Pause resolution and sets its own
> pause parameters regardless
>
> The same applies to the docstring's "This test also verifies that the peer
> resolved the parameters correctly". It also applies to this line in the
> commit message:
>
> Introduce a test that validates we're correctly resolving pause
> parameters according to 802.3.
>
> In practice, these checks test ethtool's userspace 802.3 resolution table.
>
> This is not addressed later in the series.
>
> [ ... ]
>
>> @@ -200,6 +336,32 @@ def require_pause_supported_allof(cfg, linkmodes):
>> if lm not in pause_support:
>> raise KsftSkipEx(f"Local device doesn't support {lm}")
>>
>> +def require_peer_pause_supported_anyof(cfg, linkmodes):
>> + """ Skip if remote device doesn't support any of the passed modes """
>> +
>> + ret, _ = get_peer_pauseparams(cfg)
>> + if ret != 0:
>> + raise KsftSkipEx("Remote device doesn't allow getting pauseparams")
>> +
>> + _, pause_support = get_peer_pause_supported(cfg)
>> + for lm in linkmodes:
>> + if lm in pause_support:
>> + return
>> +
>> + raise KsftSkipEx(f"Local device doesn't support any of {linkmodes}")
>> +
>> +def require_peer_pause_supported_allof(cfg, linkmodes):
>> + """ Skip if local device doesn't support all of the passed modes """
>> +
>> + ret, _ = get_peer_pauseparams(cfg)
>> + if ret != 0:
>> + raise KsftSkipEx("Remote device doesn't allow getting pauseparams")
>> +
>> + _, pause_support = get_peer_pause_supported(cfg)
>> + for lm in linkmodes:
>> + if lm not in pause_support:
>> + raise KsftSkipEx(f"Remote device doesn't support {lm}")
>> +
>> def expect_pauseparams_set(ret, linkmodes, supported, note):
>> """ Whether ethtool -A had to work or to be refused, given what the local
>> device supports
>
> [Severity: Medium]
> expect_pauseparams_set() comes from the previous patch, but it shows
> up in the context here. Can the empty supported branch fail on a
> driver that behaves correctly?
>
> ethnl_set_pause_validate() in net/ethtool/pause.c only returns
> -EOPNOTSUPP when get_pauseparam or set_pauseparam is missing. After
> that, ethnl_set_pause() reads the current state with get_pauseparam()
> and merges the requested attributes with ethnl_update_bool32(). If
> nothing changed, it returns without calling the driver:
>
> if (!mod)
> return 0;
>
> Take a phylink MAC with neither Pause nor Asym_Pause in pl->supported.
> phylink_ethtool_set_pauseparam() would reject a real change with
> -EOPNOTSUPP. It is never called, though, when the requested rx, tx
> and autoneg values match what get_pauseparam() already reports.
>
> For such a request ethtool -A succeeds and ret is 0. The
> ksft_eq(ret, errno.EOPNOTSUPP, note) check then reports a failure,
> even though the device has no pause support and the kernel did
> nothing wrong. On that hardware pause_test_support() would fail in
> CI instead of passing or skipping.
>
> Should expect_pauseparams_set() get the pauseparams before the set
> and accept 0 when the request is a no-op? Or should the no-op
> variants be skipped on devices whose supported list is empty?
Correct, will fix.
Maxime
>
next prev parent reply other threads:[~2026-10-13 9:35 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-25 18:26 [PATCH net-next v2 0/3] selftests: drv-net: Introduce flow-control selftest Maxime Chevallier (Netdev Foundation)
2026-09-25 18:26 ` [PATCH net-next v2 1/3] selftests: drv-net: Introduce a selftest for ethtool flow control Maxime Chevallier (Netdev Foundation)
2026-09-29 3:26 ` netdev-bot+sashiko
2026-10-13 9:04 ` Maxime Chevallier
2026-10-13 12:29 ` Andrew Lunn
2026-09-25 18:26 ` [PATCH net-next v2 2/3] selftests: drv-net: pause: Validate the pause autonegotiation with a partner Maxime Chevallier (Netdev Foundation)
2026-09-29 3:26 ` netdev-bot+sashiko
2026-10-13 9:35 ` Maxime Chevallier [this message]
2026-10-13 12:37 ` Andrew Lunn
2026-10-13 14:02 ` Maxime Chevallier
2026-10-13 14:49 ` Andrew Lunn
2026-09-25 18:26 ` [PATCH net-next v2 3/3] selftests: drv-net: pause: Validate pause autoneg interactions with link autoneg Maxime Chevallier (Netdev Foundation)
2026-09-29 3:26 ` netdev-bot+sashiko
2026-09-30 23:07 ` [PATCH net-next v2 0/3] selftests: drv-net: Introduce flow-control selftest Jakub Kicinski
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=40f413f0-1dfb-4968-b483-f03aa2f1bdbb@bootlin.com \
--to=maxime.chevallier@bootlin.com \
--cc=andrew@lunn.ch \
--cc=corbet@lwn.net \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=f.fainelli@gmail.com \
--cc=hkallweit1@gmail.com \
--cc=horms@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux@armlinux.org.uk \
--cc=netdev-bot+sashiko@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=o.rempel@pengutronix.de \
--cc=pabeni@redhat.com \
--cc=skhan@linuxfoundation.org \
--cc=thomas.petazzoni@bootlin.com \
--cc=vladimir.oltean@nxp.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
all inboxes | Powered by JetHome®