AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/09/27 14:08 flow patch-triage
0m Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "KMSANReasoning": "",
  "NeedsKMSAN": false,
  "Reasoning": "The patch only modifies user-space selftests under tools/testing/selftests/drivers/net/hw/ and does not modify the kernel image or modules.",
  "WorthFuzzing": false
}

1/1 2026/09/27 14:08 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 265b0f5aac8645578ecdea4e2af902c2b2dc1f65\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Sun Sep 27 14:08:26 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile\nindex bd3b8d2fa47e0..f0aeb70c20fdf 100644\n--- a/tools/testing/selftests/drivers/net/hw/Makefile\n+++ b/tools/testing/selftests/drivers/net/hw/Makefile\n@@ -44,6 +44,7 @@ TEST_PROGS = \\\n \tnk_netns.py \\\n \tnk_qlease.py \\\n \tntuple.py \\\n+\tpause.py \\\n \tpp_alloc_fail.py \\\n \trss_api.py \\\n \trss_ctx.py \\\ndiff --git a/tools/testing/selftests/drivers/net/hw/pause.py b/tools/testing/selftests/drivers/net/hw/pause.py\nnew file mode 100755\nindex 0000000000000..74bc21c606958\n--- /dev/null\n+++ b/tools/testing/selftests/drivers/net/hw/pause.py\n@@ -0,0 +1,1035 @@\n+#!/usr/bin/env python3\n+# SPDX-License-Identifier: GPL-2.0\n+\n+\"\"\"\n+Driver-related behavior tests for Pause-based Flow Control.\n+\"\"\"\n+\n+import errno\n+import json\n+import os\n+import time\n+\n+from lib.py import KsftFailEx, KsftNamedVariant, KsftSkipEx\n+from lib.py import NetDrvEpEnv, EthtoolFamily\n+from lib.py import cmd, defer, ethtool, ip\n+from lib.py import ksft_disruptive, ksft_variants, ksft_run\n+from lib.py import ksft_eq, ksft_exit, ksft_in, ksft_not_in, ksft_pr\n+\n+# Linkmodes to Pause params :\n+# Pause bit is set if rx == 1\n+# Asym_Pause bit is set if rx != tx\n+pauseparams_to_linkmodes = {\n+    0: {0: {\"rx\": 0, \"tx\": 0, \"linkmodes\": []},\n+        1: {\"rx\": 0, \"tx\": 1, \"linkmodes\": [\"Asym_Pause\"]}},\n+    1: {0: {\"rx\": 1, \"tx\": 0, \"linkmodes\": [\"Pause\", \"Asym_Pause\"]},\n+        1: {\"rx\": 1, \"tx\": 1, \"linkmodes\": [\"Pause\"]}},\n+}\n+\n+def onoff(val):\n+    \"\"\" Convert a bool to on/off \"\"\"\n+    return \"on\" if val else \"off\"\n+\n+pauseparams_variants = [\n+    KsftNamedVariant(f\"RX {onoff(p['rx'])} TX {onoff(p['tx'])}\", p)\n+    for by_tx in pauseparams_to_linkmodes.values() for p in by_tx.values()\n+]\n+\n+_strerrors = {os.strerror(e): e for e in errno.errorcode}\n+\n+def ethtool_ret(command, is_get=True, host=None):\n+    \"\"\" Execute an ethtool command, returns the return code and JSON content\n+\n+    :param command: the ethtool arguments\n+    :param is_get: Is the command a get or a set. Get commands return the loaded\n+                   JSON attributes\n+    :param host: The host on which to run the command on. None means local host.\n+    \"\"\"\n+    json_flag = \"--json\" if is_get else \"\"\n+    cmd_res = cmd(f\"ethtool {json_flag} {command}\", host=host, fail=False)\n+\n+    if cmd_res.ret != 0:\n+        # ethtool returns 1 upon error, not the netlink errcode. Try to get it\n+        # by parsing the stderr output, which looks like :\n+        # \"netlink error: Operation not supported\"\n+        for line in cmd_res.stderr.splitlines():\n+            err = _strerrors.get(line.rsplit(\": \", 1)[-1].strip())\n+            if err:\n+                return err, None\n+        return cmd_res.ret, None\n+\n+    # Not a get operation, we don't have any JSON output to parse\n+    if not is_get:\n+        return 0, None\n+\n+    return 0, json.loads(cmd_res.stdout)[0]\n+\n+def wait_for_aneg(cfg, link_drop=False, timeout=15):\n+    \"\"\" Wait for a renegotiation to complete.\n+\n+    :param link_drop: Set to true if the link HAS to flap.\n+    :returns: True if link is UP, False if timeout\n+    \"\"\"\n+    deadline = time.monotonic() + timeout\n+\n+    # Link has 2 seconds to come back up\n+    restart_by = time.monotonic() + 2\n+\n+    # The link may still be up for a short while when we trigger an autoneg\n+    # restart, we need to wait for it to drop, then come back up again\n+    while time.monotonic() \u003c deadline:\n+        if not ethtool(f\"{cfg.ifname}\", json=True)[0][\"link-detected\"]:\n+            return wait_for_link(cfg)\n+\n+        if not link_drop and time.monotonic() \u003e restart_by:\n+            return wait_for_link(cfg)\n+\n+        time.sleep(0.1)\n+\n+    return False\n+\n+def wait_for_link_local(cfg, timeout=15):\n+    \"\"\" Wait for the local link to be up \"\"\"\n+    deadline = time.monotonic() + timeout\n+\n+    while time.monotonic() \u003c deadline:\n+        link = ethtool(f\"{cfg.ifname}\", json=True)[0][\"link-detected\"]\n+        if link:\n+            return True\n+\n+        time.sleep(0.1)\n+\n+    return False\n+\n+def wait_for_link_remote(cfg, timeout=15):\n+    \"\"\" Wait for the far end of the link to be up \"\"\"\n+    deadline = time.monotonic() + timeout\n+\n+    while time.monotonic() \u003c deadline:\n+        link = ethtool(f\"{cfg.remote_ifname}\", json=True,\n+                       host=cfg.remote)[0][\"link-detected\"]\n+        if link:\n+            return True\n+\n+        time.sleep(0.1)\n+\n+    return False\n+\n+def wait_for_link(cfg):\n+    \"\"\" Wait for both ends of the link to be up \"\"\"\n+    if not wait_for_link_local(cfg):\n+        return False\n+\n+    # Local link is UP, we shouldn't have to wait for a whole 8 seconds for\n+    # the remote to report link up, let's wait a bit less\n+    return wait_for_link_remote(cfg, timeout = 3)\n+\n+def controllable_lp(cfg):\n+    \"\"\" Whether the remote interface is the link partner of the local one.\n+\n+    For low level ethtool tests, we need to have the remote directly connected\n+    to the local host (i.e. not through a switch).\n+\n+    This is tested by taking the local link down and checking that the\n+    remote's link drops.\n+\n+    :returns: True if the remote is our link partner\n+    \"\"\"\n+    known = getattr(cfg, \"lp_controllable\", None)\n+    if known is not None:\n+        return known\n+\n+    # Set both ends up, wait for up\n+    ip(f\"link set {cfg.ifname} up\")\n+    ip(f\"link set {cfg.remote_ifname} up\", host=cfg.remote)\n+\n+    # No link established\n+    if not wait_for_link(cfg):\n+        ksft_pr(f\"{cfg.remote_ifname} is not the link partner: no link with both ends up\")\n+        cfg.lp_controllable = False\n+        return False\n+\n+    ip(f\"link set {cfg.ifname} down\")\n+\n+    deadline = time.monotonic() + 3\n+    dropped = False\n+    while time.monotonic() \u003c deadline and not dropped:\n+        dropped = not ethtool(f\"{cfg.remote_ifname}\", json=True,\n+                              host=cfg.remote)[0][\"link-detected\"]\n+        time.sleep(0.1)\n+\n+    ip(f\"link set {cfg.ifname} up\")\n+\n+    if not dropped:\n+        ksft_pr(f\"{cfg.remote_ifname} is not the link partner: \"\n+                f\"it kept its link through {cfg.ifname} going down\")\n+        cfg.lp_controllable = False\n+        wait_for_link(cfg)\n+        return False\n+\n+    cfg.lp_controllable = wait_for_link(cfg)\n+    if not cfg.lp_controllable:\n+        ksft_pr(f\"{cfg.remote_ifname} is not the link partner: \"\n+                f\"no link back after {cfg.ifname} came up\")\n+    return cfg.lp_controllable\n+\n+def require_controllable_lp(cfg):\n+    \"\"\" Skip if the remote isn't directly controllable, e.g. accessed through\n+        a switch\n+    \"\"\"\n+    if not controllable_lp(cfg):\n+        raise KsftSkipEx(f\"{cfg.remote_ifname} is not directly connected to {cfg.ifname}\")\n+\n+def require_link_autoneg(cfg):\n+    \"\"\" Skip if local or remote device don't support link aneg \"\"\"\n+    # Does local device support link aneg\n+    if not ethtool(f\"{cfg.ifname}\", json=True)[0][\"supports-auto-negotiation\"]:\n+        raise KsftSkipEx(f\"{cfg.ifname} doesn't support link autoneg\")\n+\n+    # Does remote device support link aneg\n+    if not ethtool(f\"{cfg.remote_ifname}\",\n+                   json=True, host=cfg.remote)[0][\"supports-auto-negotiation\"]:\n+        raise KsftSkipEx(f\"Remote {cfg.remote_ifname} doesn't support link autoneg\")\n+\n+def forced_link_settings(cfg):\n+    \"\"\" Returns a string to pass to ethtool -s with speed/duplex corresponding\n+        to the current settings.\n+\n+        Note that some devices don't return duplex info, so assume full duplex\n+        in that case.\n+    \"\"\"\n+    link = ethtool(f\"{cfg.ifname}\", json=True)[0]\n+    if \"speed\" not in link:\n+        return \"\"\n+\n+    return f\"speed {link['speed']} duplex {link.get('duplex', 'Full').lower()}\"\n+\n+def _ethtool_pause_use_to_linkmodes(use):\n+    \"\"\" Convert the ethtool output for pause modes into pause linkmodes \"\"\"\n+    if use == \"Symmetric\":\n+        return [\"Pause\"]\n+    elif use == \"Symmetric Receive-only\":\n+        return [\"Pause\", \"Asym_Pause\"]\n+    elif use == \"Transmit-only\":\n+        return [\"Asym_Pause\"]\n+    else:\n+        return []\n+\n+def pause_to_linkmodes(rx, tx):\n+    \"\"\" Convert the bool rx/tx pause into Pause/Asym \"\"\"\n+    return pauseparams_to_linkmodes[rx][tx][\"linkmodes\"]\n+\n+def set_local_pauseparams(cfg, rx, tx, aneg):\n+    \"\"\" set pauseparams : ethtool -A\n+\n+    Raise an error if the return is not 0 or EOPNOTSUPP\n+    \"\"\"\n+    rx_param = onoff(rx)\n+    tx_param = onoff(tx)\n+    aneg_param = onoff(aneg)\n+\n+    ret, _ = ethtool_ret(f\"-A {cfg.ifname} rx {rx_param} tx {tx_param}\"\n+                         f\" autoneg {aneg_param}\",\n+                         is_get = False)\n+\n+    return ret\n+\n+def get_local_pauseparams(cfg):\n+    \"\"\" get pauseparams : ethtool -a\n+\n+    Raise an error if the return is not 0 or EOPNOTSUPP\n+    \"\"\"\n+    return ethtool_ret(f\"-a {cfg.ifname}\", is_get=True)\n+\n+def set_peer_pauseparams(cfg, rx, tx, aneg):\n+    \"\"\" set pauseparams : ethtool -A\n+\n+    Raise an error if the return is not 0 or EOPNOTSUPP\n+    \"\"\"\n+    rx_param = onoff(rx)\n+    tx_param = onoff(tx)\n+    aneg_param = onoff(aneg)\n+\n+    ret, _ = ethtool_ret(f\"-A {cfg.remote_ifname} rx {rx_param} tx {tx_param}\"\n+                         f\" autoneg {aneg_param}\",\n+                         is_get = False, host=cfg.remote)\n+\n+    if ret != 0:\n+        raise KsftSkipEx(f\"Can't set pauseparams on peer: {errno.errorcode.get(ret, ret)}\")\n+\n+    return ret\n+\n+def get_peer_pauseparams(cfg):\n+    \"\"\" get pauseparams : ethtool -a\n+\n+    Raise an error if the return is not 0 or EOPNOTSUPP\n+    \"\"\"\n+\n+    return ethtool_ret(f\"-a {cfg.remote_ifname}\", is_get=True,\n+                       host=cfg.remote)\n+\n+def get_local_pause_supported(cfg):\n+    \"\"\" Return the errcode and the local pause supported linkmodes \"\"\"\n+\n+    ret, data = ethtool_ret(f\"{cfg.ifname}\")\n+    if ret != 0:\n+        raise KsftFailEx(f\"ethtool {cfg.ifname} failed: {ret}\")\n+\n+    return ret, _ethtool_pause_use_to_linkmodes(data[\"supported-pause-frame-use\"])\n+\n+def get_local_pause_advertising(cfg):\n+    \"\"\" Return the errcode and the local pause advertised linkmodes \"\"\"\n+\n+    ret, data = ethtool_ret(f\"{cfg.ifname}\")\n+    if ret != 0:\n+        raise KsftFailEx(f\"ethtool {cfg.ifname} failed: {ret}\")\n+\n+    return ret, _ethtool_pause_use_to_linkmodes(data[\"advertised-pause-frame-use\"])\n+\n+def get_local_pause_lp_advertising(cfg):\n+    \"\"\" Return the errcode and the local pause lp_advertised linkmodes,\n+        if any.\n+    \"\"\"\n+\n+    ret, data = ethtool_ret(f\"{cfg.ifname}\")\n+    if ret != 0:\n+        raise KsftFailEx(f\"ethtool {cfg.ifname} failed: {errno.errorcode.get(ret, ret)}\")\n+\n+    if \"link-partner-advertised-pause-frame-use\" in data:\n+        return ret, _ethtool_pause_use_to_linkmodes(data[\"link-partner-advertised-pause-frame-use\"])\n+    else:\n+        ksft_pr(f\"Warning: {cfg.ifname} does not report the LP's advertising\")\n+        return errno.EOPNOTSUPP, None\n+\n+def get_peer_pause_supported(cfg):\n+    \"\"\" Return the errcode and the remote pause supported linkmodes \"\"\"\n+\n+    ret, data = ethtool_ret(f\"{cfg.remote_ifname}\", host = cfg.remote)\n+    if ret != 0:\n+        raise KsftFailEx(f\"ethtool {cfg.remote_ifname} failed: {errno.errorcode.get(ret, ret)}\")\n+\n+    return ret, _ethtool_pause_use_to_linkmodes(data[\"supported-pause-frame-use\"])\n+\n+\n+def get_peer_pause_advertising(cfg):\n+    \"\"\" Return the errcode and the remote pause advertised linkmodes \"\"\"\n+\n+    ret, data = ethtool_ret(f\"{cfg.remote_ifname}\", host = cfg.remote)\n+    if ret != 0:\n+        raise KsftFailEx(f\"ethtool {cfg.remote_ifname} failed: {errno.errorcode.get(ret, ret)}\")\n+\n+    return ret, _ethtool_pause_use_to_linkmodes(data[\"advertised-pause-frame-use\"])\n+\n+def get_peer_pause_lp_advertising(cfg):\n+    \"\"\" Return the errcode and the local pause lp_advertised linkmodes,\n+        if any.\n+    \"\"\"\n+\n+    ret, data = ethtool_ret(f\"{cfg.remote_ifname}\", host = cfg.remote)\n+    if ret != 0:\n+        raise KsftFailEx(f\"ethtool {cfg.remote_ifname} failed: {errno.errorcode.get(ret, ret)}\")\n+\n+    if \"link-partner-advertised-pause-frame-use\" in data:\n+        return ret, _ethtool_pause_use_to_linkmodes(data[\"link-partner-advertised-pause-frame-use\"])\n+    else:\n+        ksft_pr(f\"Warning: {cfg.remote_ifname} does not report the LP's advertising\")\n+        return errno.EOPNOTSUPP, None\n+\n+def require_pause_supported_anyof(cfg, linkmodes):\n+    \"\"\" Skip if local device doesn't support any of the passed modes \"\"\"\n+\n+    ret, _ = get_local_pauseparams(cfg)\n+    if ret != 0:\n+        raise KsftSkipEx(\"device doesn't allow getting pauseparams\")\n+\n+    _, pause_support = get_local_pause_supported(cfg)\n+    for lm in linkmodes:\n+        if lm in pause_support:\n+            return\n+\n+    raise KsftSkipEx(f\"Local device doesn't support any of {linkmodes}\")\n+\n+def require_pause_supported_allof(cfg, linkmodes):\n+    \"\"\" Skip if local device doesn't support all of the passed modes \"\"\"\n+\n+    ret, _ = get_local_pauseparams(cfg)\n+    if ret != 0:\n+        raise KsftSkipEx(\"device doesn't allow getting pauseparams\")\n+\n+    _, pause_support = get_local_pause_supported(cfg)\n+    for lm in linkmodes:\n+        if lm not in pause_support:\n+            raise KsftSkipEx(f\"Local device doesn't support {lm}\")\n+\n+def require_peer_pause_supported_anyof(cfg, linkmodes):\n+    \"\"\" Skip if remote device doesn't support any of the passed modes \"\"\"\n+\n+    ret, _ = get_peer_pauseparams(cfg)\n+    if ret != 0:\n+        raise KsftSkipEx(\"Remote device doesn't allow getting pauseparams\")\n+\n+    _, pause_support = get_peer_pause_supported(cfg)\n+    for lm in linkmodes:\n+        if lm in pause_support:\n+            return\n+\n+    raise KsftSkipEx(f\"Local device doesn't support any of {linkmodes}\")\n+\n+def require_peer_pause_supported_allof(cfg, linkmodes):\n+    \"\"\" Skip if local device doesn't support all of the passed modes \"\"\"\n+\n+    ret, _ = get_peer_pauseparams(cfg)\n+    if ret != 0:\n+        raise KsftSkipEx(\"Remote device doesn't allow getting pauseparams\")\n+\n+    _, pause_support = get_peer_pause_supported(cfg)\n+    for lm in linkmodes:\n+        if lm not in pause_support:\n+            raise KsftSkipEx(f\"Remote device doesn't support {lm}\")\n+\n+def supported_pauseparams(cfg):\n+    \"\"\" The rx/tx params covering every mode the local device supports \"\"\"\n+\n+    _, pause_support = get_local_pause_supported(cfg)\n+    if \"Pause\" in pause_support:\n+        return 1, 1\n+    if \"Asym_Pause\" in pause_support:\n+        return 0, 1\n+\n+    raise KsftSkipEx(\"Local device doesn't support pause\")\n+\n+def set_local_pause_autoneg(cfg, aneg):\n+    \"\"\" Enable/Disable local pause autoneg \"\"\"\n+\n+    ret, _ = ethtool_ret(f\"-A {cfg.ifname} autoneg {onoff(aneg)}\",\n+                         is_get=False)\n+    return ret\n+\n+def check_local_pauseparams(cfg, aneg, rx, tx):\n+    \"\"\" Check that the local pauseparams are the passed parameters \"\"\"\n+\n+    ret, params = get_local_pauseparams(cfg)\n+    ksft_eq(ret, 0)\n+    if ret != 0:\n+        return\n+\n+    ksft_eq(params[\"autonegotiate\"], bool(aneg), \"pause autoneg\")\n+    ksft_eq(params[\"rx\"], bool(rx), \"rx pause\")\n+    ksft_eq(params[\"tx\"], bool(tx), \"tx pause\")\n+\n+def check_local_advertising(cfg, linkmodes):\n+    \"\"\" Check that the local advertised modes are the passed parameters \"\"\"\n+\n+    _, adv = get_local_pause_advertising(cfg)\n+    ksft_eq(adv, linkmodes, \"advertised pause modes\")\n+\n+def check_local_lp_advertising(cfg, linkmodes):\n+    \"\"\" Check that the local lp_advertised modes are the passed parameters \"\"\"\n+\n+    ret, adv = get_local_pause_lp_advertising(cfg)\n+    if ret == 0:\n+        ksft_eq(adv, linkmodes, \"link partner advertised pause modes\")\n+\n+def expect_pauseparams_set(ret, linkmodes, supported, note):\n+    \"\"\" Whether ethtool -A had to work or to be refused, given what the local\n+        device supports\n+    \"\"\"\n+\n+    # If supported is empty, ethtool -A must return -EOPNOTSUPP\n+    if not supported :\n+        ksft_eq(ret, errno.EOPNOTSUPP, note)\n+    elif set(linkmodes).issubset(set(supported)) :\n+        # The configured pauseparams are supposed to be supported,\n+        #ethtool -A must have worked.\n+        ksft_eq(ret, 0, note)\n+    else :\n+        # We tried to configure parameters that aren't supporteed,\n+        # ethtool -A must have failed.\n+        ksft_in(ret, (errno.EOPNOTSUPP, errno.EINVAL), note)\n+\n+def pause_setup(cfg):\n+    \"\"\" The starting conditions every test counts on, restored on exit:\n+        - both ports admin up\n+        - link autoneg on on both sides ifsupported\n+        - link actually up (carrier on)\n+    \"\"\"\n+\n+    # Get init pause parameters\n+    ret, params = ethtool_ret(f\"-a {cfg.ifname}\")\n+    if ret == 0:\n+        defer(cmd, f\"ethtool -A {cfg.ifname} rx {onoff(params['rx'])} \"\n+                   f\"tx {onoff(params['tx'])} \"\n+                   f\"autoneg {onoff(params['autonegotiate'])}\")\n+\n+    # Get init link parameters\n+    link = ethtool(f\"{cfg.ifname}\", json=True)[0]\n+    if link[\"auto-negotiation\"]:\n+        defer(cmd, f\"ethtool -s {cfg.ifname} autoneg on\")\n+    elif \"speed\" in link and \"duplex\" in link:\n+        defer(cmd, f\"ethtool -s {cfg.ifname} autoneg off speed {link['speed']} \"\n+                   f\"duplex {link['duplex'].lower()}\")\n+\n+    # Local interface admin up, link aneg on\n+    ip(f\"link set {cfg.ifname} up\")\n+    if link[\"supports-auto-negotiation\"] and not link[\"auto-negotiation\"]:\n+        ethtool(f\"-s {cfg.ifname} autoneg on\")\n+\n+    # Get remote pause params\n+    ret, params = ethtool_ret(f\"-a {cfg.remote_ifname}\", host=cfg.remote)\n+    if ret == 0:\n+        defer(cmd, f\"ethtool -A {cfg.remote_ifname} rx {onoff(params['rx'])} \"\n+                   f\"tx {onoff(params['tx'])} \"\n+                   f\"autoneg {onoff(params['autonegotiate'])}\",\n+                   host=cfg.remote)\n+\n+    # Get remote link params\n+    link = ethtool(f\"{cfg.remote_ifname}\", json=True, host=cfg.remote)[0]\n+    if link[\"auto-negotiation\"]:\n+        defer(cmd, f\"ethtool -s {cfg.remote_ifname} autoneg on\",\n+              host=cfg.remote)\n+    elif \"speed\" in link and \"duplex\" in link:\n+        defer(cmd, f\"ethtool -s {cfg.remote_ifname} autoneg off \"\n+                   f\"speed {link['speed']} duplex {link['duplex'].lower()}\",\n+                   host=cfg.remote)\n+\n+    # Remote interface admin up, link aneg on\n+    ip(f\"link set {cfg.remote_ifname} up\", host=cfg.remote)\n+    if link[\"supports-auto-negotiation\"] and not link[\"auto-negotiation\"]:\n+        ethtool(f\"-s {cfg.remote_ifname} autoneg on\", host=cfg.remote)\n+\n+    # Wait for link to become up on both ends\n+    if not wait_for_link(cfg):\n+        raise KsftFailEx(\"No link before the test\")\n+\n+# Pause support : Supported linkmodes vs ability to set/get pauseparams\n+@ksft_variants(pauseparams_variants)\n+@ksft_disruptive\n+def pause_test_support(cfg, pauseparams):\n+    \"\"\" Verify that the supported linkmodes Pause and Asym_Pause match the\n+        ability to configure the rx and tx pauseparams.\n+\n+    Drivers are expected to reject pauseparams they don't support, and\n+    accept the ones they support. The supported modes are exposed by\n+    the MAC to the PHY layer through phylink mac_capabilities MAC_SYM_PAUSE\n+    and MAC_ASYM_PAUSE, or through phylib directly with the\n+    phy_support_sym_pause() and phy_support_asym_pause() helpers.\n+\n+    The expectation is for drivers to refuse setting pauseparams that don't\n+    match the Pause and Asym_Pause bits in the supported linkmodes with a\n+    -EOPNOTSUPP return value. Unsupported pause params must be rejected.\n+\n+    Failing this test likely means the MAC driver doesn't implement the\n+    set/get_pauseparam, but still sets flow control as supported through\n+    phylink mac_capabilities or phylib's pause API. Conversely, the MAC driver\n+    may have omitted to indicate its supported Pause modes. Finally, the PHY\n+    driver may incorrectly override the Pause and Asym_Pause bits in its\n+    supported fields.\n+\n+    The sequence runs with link autoneg on, then with the link forced\n+    (ethtool -s ethX autoneg off): the pause params are accepted or rejected\n+    the same way in both cases, and both with pause autoneg off and on.\n+    \"\"\"\n+\n+    rx = onoff(pauseparams[\"rx\"])\n+    tx = onoff(pauseparams[\"tx\"])\n+    linkmodes = pauseparams[\"linkmodes\"]\n+\n+    pause_setup(cfg)\n+\n+    forced = forced_link_settings(cfg)\n+    _, supported = get_local_pause_supported(cfg)\n+\n+    # We check that what we can configure in the pause params matches what we\n+    # support under various contditions : Link aneg on/off, pause aneg on/off\n+    ret, _ = ethtool_ret(f\"-s {cfg.ifname} autoneg on\", is_get = False)\n+    if ret != 0:\n+        ksft_pr(\"link autoneg on refused, not tested\")\n+    else:\n+        ret, _ = ethtool_ret(f\"-A {cfg.ifname} rx {rx} tx {tx} autoneg off\",\n+                             is_get = False)\n+        expect_pauseparams_set(ret, linkmodes, supported, \"link autoneg on\")\n+\n+        ret, _ = ethtool_ret(f\"-A {cfg.ifname} rx {rx} tx {tx} autoneg on\",\n+                             is_get = False)\n+        expect_pauseparams_set(ret, linkmodes, supported, \"link autoneg on\")\n+\n+    if not forced:\n+        ksft_pr(\"link speed unknown, the forced link is not tested\")\n+        return\n+\n+    ret, _ = ethtool_ret(f\"-s {cfg.ifname} autoneg off {forced}\", is_get = False)\n+    if ret != 0:\n+        ksft_pr(f\"link autoneg off {forced} refused, not tested\")\n+        return\n+\n+    ret, _ = ethtool_ret(f\"-A {cfg.ifname} rx {rx} tx {tx} autoneg off\",\n+                         is_get = False)\n+    expect_pauseparams_set(ret, linkmodes, supported, \"link autoneg off\")\n+\n+    ret, _ = ethtool_ret(f\"-A {cfg.ifname} rx {rx} tx {tx} autoneg on\",\n+                         is_get = False)\n+    expect_pauseparams_set(ret, linkmodes, supported, \"link autoneg off\")\n+\n+@ksft_variants(pauseparams_variants)\n+@ksft_disruptive\n+def pause_advertising_test(cfg, pauseparams):\n+    \"\"\"Pause advertisement\n+\n+    Validate that changing pause params through the ETHTOOL_MSG_PAUSE command\n+    translates to a change in the advertised pause params, and that these\n+    parameters are correct w.r.t the supported pause params and requested pause\n+    params.\n+\n+    This exercises the .set_pauseparam() ethtool ops for MAC configuration,\n+    as well as the reconfiguration of the PHY's advertising and negotiation.\n+\n+    On non-phylink MACs, the MAC should call phy_set_sym_pause() to update the\n+    PHY's advertising, and restart a negotiation with phy_start_aneg() if\n+    need be. Failure to do so will result in the wrong advertising parameters.\n+\n+    On phylink-enabled MACs, phylink deals with the PHY reconfiguration provided\n+    the MAC driver calls phylink_ethtool_set_pauseparam().\n+\n+    Failing this test likely means that the PHY driver is not correctly\n+    advertising pause settings, either due to the MAC not triggering a PHY\n+    reconfiguration, a misconfiguration of the advertising registers by the PHY,\n+    or by mis-handling the phydev-\u003eadvertising bitmap in the PHY driver directly.\n+\n+    The validation is made by looking at the advertised modes locally, as well\n+    as what the peer's 'lp_advertising' values report.\n+    \"\"\"\n+\n+    require_pause_supported_allof(cfg, pauseparams[\"linkmodes\"])\n+    pause_setup(cfg)\n+    lp = controllable_lp(cfg)\n+\n+    tx = pauseparams[\"tx\"]\n+    rx = pauseparams[\"rx\"]\n+    adv = pauseparams[\"linkmodes\"]\n+    not_adv = [ l for l in [\"Pause\", \"Asym_Pause\"] if l not in adv]\n+\n+    # It's OK to skip here, we're already validating the EOPNOTSUPP behaviour\n+    # the pause_test_support test.\n+    ret = set_local_pauseparams(cfg, rx, tx, True)\n+    if ret == errno.EOPNOTSUPP:\n+        raise KsftSkipEx(f\"RX {rx} TX {tx} not supported\")\n+\n+    # Wait for link parameters to re-negotiate and link to come back up. It must\n+    # come back up, otherwise that means changing pauseparams can bring the\n+    # link down.\n+    ret = wait_for_aneg(cfg)\n+    ksft_eq(ret, True)\n+\n+    _, linkmodes = get_local_pause_advertising(cfg)\n+    for mode in adv:\n+        ksft_in(mode, linkmodes,\n+                f\"rx {rx} tx {tx} aneg on must advertise {adv}\")\n+\n+    for mode in not_adv:\n+        ksft_not_in(mode, linkmodes,\n+                    f\"rx {rx} tx {tx} aneg on must not advertise {not_adv}\")\n+\n+    if not lp:\n+        return\n+\n+    returncode, remote_linkmodes = get_peer_pause_lp_advertising(cfg)\n+    if returncode == errno.EOPNOTSUPP:\n+        return\n+\n+    for mode in adv:\n+        ksft_in(mode, remote_linkmodes, f\"PHY does not advertise {adv}\")\n+\n+    for mode in not_adv:\n+        ksft_not_in(mode, remote_linkmodes,\n+                    f\"PHY incorrectly advertises {not_adv}\")\n+\n+\n+# Pause autonegotiation resolution : Resolved pause settings vs configured\n+# pauseparams on local device and link partner\n+@ksft_variants([\n+    # We advertise nothing, all off\n+    KsftNamedVariant(\"local rx off tx off, remote rx off tx off\",\n+        {\"rx\": 0, \"tx\": 0, \"lp_rx\": 0, \"lp_tx\": 0, \"neg_rx\": 0, \"neg_tx\": 0}),\n+\n+    # We advertise nothing, all off\n+    KsftNamedVariant(\"local rx off tx off, remote rx off tx on\",\n+        {\"rx\": 0, \"tx\": 0, \"lp_rx\": 0, \"lp_tx\": 1, \"neg_rx\": 0, \"neg_tx\": 0}),\n+\n+    # We advertise nothing, all off\n+    KsftNamedVariant(\"local rx off tx off, remote rx on tx off\",\n+        {\"rx\": 0, \"tx\": 0, \"lp_rx\": 1, \"lp_tx\": 0, \"neg_rx\": 0, \"neg_tx\": 0}),\n+\n+    # We advertise nothing, all off\n+    KsftNamedVariant(\"local rx off tx off, remote rx on tx on\",\n+        {\"rx\": 0, \"tx\": 0, \"lp_rx\": 1, \"lp_tx\": 1, \"neg_rx\": 0, \"neg_tx\": 0}),\n+\n+    # LP advertises nothing, all off\n+    KsftNamedVariant(\"local rx off tx on, remote rx off tx off\",\n+        {\"rx\": 0, \"tx\": 1, \"lp_rx\": 0, \"lp_tx\": 0, \"neg_rx\": 0, \"neg_tx\": 0}),\n+\n+    # We advertise Asym, LP advertises Asym, all off\n+    KsftNamedVariant(\"local rx off tx on, remote rx off tx on\",\n+        {\"rx\": 0, \"tx\": 1, \"lp_rx\": 0, \"lp_tx\": 1, \"neg_rx\": 0, \"neg_tx\": 0}),\n+\n+    # We advertise Asym, LP advertises Pause + Asym, tx on\n+    KsftNamedVariant(\"local rx off tx on, remote rx on tx off\",\n+        {\"rx\": 0, \"tx\": 1, \"lp_rx\": 1, \"lp_tx\": 0, \"neg_rx\": 0, \"neg_tx\": 1}),\n+\n+    # Tricky case :\n+    # We advertise Asym, LP advertises Pause, resolves to all off\n+    KsftNamedVariant(\"local rx off tx on, remote rx on tx on\",\n+        {\"rx\": 0, \"tx\": 1, \"lp_rx\": 1, \"lp_tx\": 1, \"neg_rx\": 0, \"neg_tx\": 0}),\n+\n+    # LP advertises nothing, all off\n+    KsftNamedVariant(\"local rx on tx off, remote rx off tx off\",\n+        {\"rx\": 1, \"tx\": 0, \"lp_rx\": 0, \"lp_tx\": 0, \"neg_rx\": 0, \"neg_tx\": 0}),\n+\n+    # We advertise Pause + Asym , LP advertises Asym, rx on\n+    KsftNamedVariant(\"local rx on tx off, remote rx off tx on\",\n+        {\"rx\": 1, \"tx\": 0, \"lp_rx\": 0, \"lp_tx\": 1, \"neg_rx\": 1, \"neg_tx\": 0}),\n+\n+    # Also tricky: Only rx enabled on both ends, but we negotiate rx/tx\n+    # We advertise Pause + Asym, LP advertises Pause + Asym, all on\n+    KsftNamedVariant(\"local rx on tx off, remote rx on tx off\",\n+        {\"rx\": 1, \"tx\": 0, \"lp_rx\": 1, \"lp_tx\": 0, \"neg_rx\": 1, \"neg_tx\": 1}),\n+\n+    # We advertise Pause + Asym, LP advertises Pause, all on\n+    KsftNamedVariant(\"local rx on tx off, remote rx on tx on\",\n+        {\"rx\": 1, \"tx\": 0, \"lp_rx\": 1, \"lp_tx\": 1, \"neg_rx\": 1, \"neg_tx\": 1}),\n+\n+    # LP advertises nothing, all off\n+    KsftNamedVariant(\"local rx on tx on, remote rx off tx off\",\n+        {\"rx\": 1, \"tx\": 1, \"lp_rx\": 0, \"lp_tx\": 0, \"neg_rx\": 0, \"neg_tx\": 0}),\n+\n+    # Tricky case :\n+    # We advertise Pause, LP advertises Asym, resolves to all off\n+    KsftNamedVariant(\"local rx on tx on, remote rx off tx on\",\n+        {\"rx\": 1, \"tx\": 1, \"lp_rx\": 0, \"lp_tx\": 1, \"neg_rx\": 0, \"neg_tx\": 0}),\n+\n+    # We advertise Pause, LP advertises Pause + Asym, all on\n+    KsftNamedVariant(\"local rx on tx on, remote rx on tx off\",\n+        {\"rx\": 1, \"tx\": 1, \"lp_rx\": 1, \"lp_tx\": 0, \"neg_rx\": 1, \"neg_tx\": 1}),\n+\n+    # We advertise Pause, LP advertises Pause, all on\n+    KsftNamedVariant(\"local rx on tx on, remote rx on tx on\",\n+        {\"rx\": 1, \"tx\": 1, \"lp_rx\": 1, \"lp_tx\": 1, \"neg_rx\": 1, \"neg_tx\": 1}),\n+])\n+@ksft_disruptive\n+def pause_aneg_resolution(cfg, settings):\n+    \"\"\" Verify that rx and tx pause parameters are negotiated according to 802.3\n+\n+    802.3 dictates the rules for pause negotiation, all 16 cases are tested, one\n+    for each combination of Pause and Asym_Pause advertising on the local device\n+    and the link-partner.\n+\n+    This test also verifies that the peer resolved the parameters correctly,\n+    to ensure the negotiation is triggered correctly.\n+\n+    Failing this test can happen if :\n+     - The MAC accepts the pause parameters but doesn't trigger a link\n+       renegotiation\n+     - that the PHY driver manually overwrites the Pause negotiation result\n+     - that the MAC driver ignores the Pause resolution and sets its own\n+       pause parameters regardless\n+    \"\"\"\n+\n+    expected_local_rx = settings[\"neg_rx\"]\n+    expected_local_tx = settings[\"neg_tx\"]\n+\n+    required_local_linkmodes = pause_to_linkmodes(settings[\"rx\"],\n+                                                  settings[\"tx\"])\n+    required_remote_linkmodes = pause_to_linkmodes(settings[\"lp_rx\"],\n+                                                   settings[\"lp_tx\"])\n+\n+    require_pause_supported_allof(cfg, required_local_linkmodes)\n+    require_peer_pause_supported_allof(cfg, required_remote_linkmodes)\n+    require_controllable_lp(cfg)\n+    pause_setup(cfg)\n+\n+    # There's symmetry between local device and LP on pause negotiation:\n+    # - if local resolves all off or all on, LP must resolve the same\n+    # - if local resolves RX only, remote must resolve to TX only\n+    # - if local resolves TX only, remote must resolve to RX only\n+    if expected_local_rx == expected_local_tx:\n+        expected_lp_rx = expected_local_rx\n+        expected_lp_tx = expected_local_tx\n+    else:\n+        expected_lp_rx = expected_local_tx\n+        expected_lp_tx = expected_local_rx\n+\n+    # Set pauseparams\n+    ret = set_local_pauseparams(cfg, settings[\"rx\"], settings[\"tx\"], True)\n+    if ret == errno.EOPNOTSUPP:\n+        raise KsftSkipEx(f\"RX {settings['rx']} TX {settings['tx']} not supported\")\n+\n+    ksft_eq(wait_for_aneg(cfg), True)\n+\n+    set_peer_pauseparams(cfg, settings[\"lp_rx\"], settings[\"lp_tx\"], True)\n+\n+    # Wait for link to re-negotiate\n+    ret = wait_for_aneg(cfg)\n+\n+    # Fail if it doesn't\n+    ksft_eq(ret, True)\n+\n+    if get_local_pause_lp_advertising(cfg)[0] != 0:\n+        raise KsftSkipEx(\"Local device doesn't report the LP's advertising\")\n+\n+    ret, local_pauseparams = get_local_pauseparams(cfg)\n+    if ret != 0 or \"negotiated\" not in local_pauseparams:\n+        raise KsftSkipEx(\"Local device doesn't report the negotiated pause params\")\n+\n+    # check adv\n+    _, linkmodes = get_local_pause_advertising(cfg)\n+    for mode in required_local_linkmodes:\n+        ksft_in(mode, linkmodes,\n+                f\"local rx {settings['rx']} tx {settings['tx']} must advertise \"\n+                f\"{required_local_linkmodes}\")\n+\n+    _, linkmodes = get_peer_pause_advertising(cfg)\n+    for mode in required_remote_linkmodes:\n+        ksft_in(mode, linkmodes,\n+                f\"remote rx {settings['lp_rx']} tx {settings['lp_tx']} must advertise \"\n+                f\"{required_remote_linkmodes}\")\n+\n+    # check lp_adv if available\n+    _, linkmodes = get_local_pause_lp_advertising(cfg)\n+    for mode in required_remote_linkmodes:\n+        ksft_in(mode, linkmodes,\n+                f\"local lp_adv must show the remote's {required_remote_linkmodes}\")\n+\n+    # check lp_adv on remote\n+    ret, linkmodes = get_peer_pause_lp_advertising(cfg)\n+    if ret == 0:\n+        for mode in required_local_linkmodes:\n+            ksft_in(mode, linkmodes,\n+                    f\"remote lp_adv must show our {required_local_linkmodes}\")\n+\n+    # Check resolution\n+    _, local_pauseparams = get_local_pauseparams(cfg)\n+    ksft_eq(local_pauseparams[\"negotiated\"][\"rx\"], expected_local_rx)\n+    ksft_eq(local_pauseparams[\"negotiated\"][\"tx\"], expected_local_tx)\n+\n+    ret, remote_pauseparams = get_peer_pauseparams(cfg)\n+    if ret == 0 and \"negotiated\" in remote_pauseparams:\n+        ksft_eq(remote_pauseparams[\"negotiated\"][\"rx\"], expected_lp_rx)\n+        ksft_eq(remote_pauseparams[\"negotiated\"][\"tx\"], expected_lp_tx)\n+\n+@ksft_disruptive\n+def pause_autoneg_state_adv(cfg):\n+    \"\"\"Validate that toggling pause advertising changes the advertised linkmodes\n+\n+    When disabling pause autoneg, we enforce the pause params based on what user\n+    asks, instead of relying on the negociation process (which may not be what\n+    the user asked for). In forced pause settings, we don't advertise pause and\n+    asym_pause bits.\n+\n+    Failing this test means that .set_pauseparam in the MAC driver doesn't\n+    forward to the PHY (in charge of advertising these bits) that we are in\n+    fixed pause mode.\n+    \"\"\"\n+\n+    require_pause_supported_anyof(cfg, [\"Pause\", \"Asym_Pause\"])\n+    require_controllable_lp(cfg)\n+    pause_setup(cfg)\n+\n+    set_peer_pauseparams(cfg, True, True, True)\n+    ksft_eq(wait_for_aneg(cfg), True)\n+\n+    rx, tx = supported_pauseparams(cfg)\n+\n+    # Enable all possible pauseparams with pause autoneg\n+    ret = set_local_pauseparams(cfg, rx, tx, True)\n+    ksft_eq(ret, 0)\n+    ksft_eq(wait_for_aneg(cfg), True)\n+\n+    # Make sure we advertise them\n+    ret, adv = get_local_pause_advertising(cfg)\n+    ksft_eq(ret, 0)\n+    ksft_eq(adv, pause_to_linkmodes(rx, tx))\n+\n+    # Disable pause autoneg\n+    ret = set_local_pauseparams(cfg, rx, tx, False)\n+    ksft_eq(ret, 0)\n+\n+    # This may trigger a link renegociation\n+    ksft_eq(wait_for_aneg(cfg), True)\n+\n+    # We shouldn't be advertising anything anymore\n+    ret, adv = get_local_pause_advertising(cfg)\n+    ksft_eq(ret, 0)\n+    ksft_eq(adv, [])\n+\n+    # Validate on the LP that we aren't advertising anything\n+    ret, adv = get_peer_pause_lp_advertising(cfg)\n+    if ret == errno.EOPNOTSUPP:\n+        return\n+\n+    ksft_eq(ret, 0)\n+    ksft_eq(adv, [])\n+\n+@ksft_disruptive\n+def pause_autoneg_state_params(cfg):\n+    \"\"\"Validate the pause params when transitioning between fixed pause\n+       params and negotiated ones. The goal is to make sure that user\n+       intent on the RX and TX pause params are stored when user decides\n+       to use negotiated parameters instead. The main gotcha lies on the\n+       fact that when pause autoneg is used, the autoneg result may differ\n+       from the user intent.\n+\n+    Failing this test means the MAC driver is overwriting the user intent\n+    when switching to forced pause.\n+    \"\"\"\n+\n+    require_pause_supported_allof(cfg, [\"Pause\", \"Asym_Pause\"])\n+    require_controllable_lp(cfg)\n+    require_peer_pause_supported_allof(cfg, [\"Pause\"])\n+    require_link_autoneg(cfg)\n+    pause_setup(cfg)\n+\n+    # Set peer user intent to RX on TX on, with Pause autoneg on\n+    set_peer_pauseparams(cfg, 1, 1, True)\n+    ksft_eq(wait_for_aneg(cfg), True)\n+\n+    # Set the local intent to RX on TX off with pause autoneg\n+    ksft_eq(set_local_pauseparams(cfg, 1, 0, True), 0)\n+    ksft_eq(wait_for_aneg(cfg), True)\n+\n+    check_local_pauseparams(cfg, True, 1, 0)\n+    # Peer advertisiong Pause + Asym and us advertising Pause means we are\n+    # actually using RX on TX on here, which is different than the intent.\n+    check_local_advertising(cfg, [\"Pause\", \"Asym_Pause\"])\n+    check_local_lp_advertising(cfg, [\"Pause\"])\n+\n+    # Disable pause autoneg\n+    ksft_eq(set_local_pause_autoneg(cfg, False), 0)\n+    ksft_eq(wait_for_aneg(cfg), True)\n+\n+    # The pauseparams must still be what we configured before, and not the\n+    # previously negotiated ones\n+    check_local_pauseparams(cfg, False, 1, 0)\n+\n+    # Re-enable autoneg\n+    ksft_eq(set_local_pause_autoneg(cfg, True), 0)\n+    ksft_eq(wait_for_aneg(cfg), True)\n+\n+    check_local_pauseparams(cfg, True, 1, 0)\n+    # We must be advertising our intent again, and not RX on TX on, which would\n+    # be \"Pause\" only.\n+    check_local_advertising(cfg, [\"Pause\", \"Asym_Pause\"])\n+    check_local_lp_advertising(cfg, [\"Pause\"])\n+\n+@ksft_disruptive\n+def pause_autoneg_off_while_link_autoneg_on(cfg):\n+    \"\"\" Validate that when link autoneg is on but pause autoneg is off, we do\n+        not use negotiated pause parameters.\n+\n+        Failing this test means the MAC driver incorrectly accounts for the\n+        negotiated pause parameters even with pause aneg off, likely due to\n+        confusion between link autoneg and pause autoneg.\n+    \"\"\"\n+\n+    require_pause_supported_anyof(cfg, [\"Pause\", \"Asym_Pause\"])\n+    require_controllable_lp(cfg)\n+    require_peer_pause_supported_allof(cfg, [\"Pause\"])\n+    require_link_autoneg(cfg)\n+    pause_setup(cfg)\n+\n+    rx, tx = supported_pauseparams(cfg)\n+\n+    # Enable pause autoneg with all the locally supported modes enabled\n+    set_peer_pauseparams(cfg, 1, 1, True)\n+    ksft_eq(set_local_pauseparams(cfg, rx, tx, True), 0)\n+    ksft_eq(wait_for_aneg(cfg), True)\n+\n+    # Disable Pause autoneg\n+    ksft_eq(set_local_pauseparams(cfg, rx, tx, False), 0)\n+    ksft_eq(wait_for_aneg(cfg), True)\n+    # Pause autoneg must read \"disabled\"\n+    check_local_pauseparams(cfg, False, rx, tx)\n+\n+    set_peer_pauseparams(cfg, 0, 0, True)\n+    ksft_eq(wait_for_aneg(cfg, link_drop=True), True)\n+\n+    ip(f\"link set {cfg.remote_ifname} down\", host=cfg.remote)\n+    ip(f\"link set {cfg.remote_ifname} up\", host=cfg.remote)\n+    ksft_eq(wait_for_aneg(cfg, link_drop=True), True)\n+\n+    # Pause autoneg must still be off even after a link renegotiation\n+    check_local_pauseparams(cfg, False, rx, tx)\n+\n+@ksft_disruptive\n+def pause_autoneg_link_autoneg(cfg):\n+    \"\"\"Validate pause autoneg and link autoneg interactions. The link autoneg's\n+       admin status (i.e. do we autoneg link parameters or force them) must not\n+       impact the pause autoneg status. While link autoneg is disabled, we don't\n+       negotiate the pause params, however we must keep pause autoneg on as this\n+       is the user intent. When link autoneg is re-enabled, pause params must be\n+       derived from the negotiation.\n+    \"\"\"\n+\n+    require_pause_supported_anyof(cfg, [\"Pause\", \"Asym_Pause\"])\n+    require_controllable_lp(cfg)\n+    require_peer_pause_supported_allof(cfg, [\"Pause\"])\n+    require_link_autoneg(cfg)\n+    pause_setup(cfg)\n+\n+    rx, tx = supported_pauseparams(cfg)\n+    adv = pause_to_linkmodes(rx, tx)\n+\n+    # Enable all possible pause modes and autoneg\n+    set_peer_pauseparams(cfg, 1, 1, True)\n+    ksft_eq(set_local_pauseparams(cfg, rx, tx, True), 0)\n+    ksft_eq(wait_for_aneg(cfg), True)\n+\n+    check_local_pauseparams(cfg, True, rx, tx)\n+    check_local_advertising(cfg, adv)\n+    check_local_lp_advertising(cfg, [\"Pause\"])\n+\n+    # Disable link autoneg, at the speed and duplex the link runs at\n+    forced = forced_link_settings(cfg)\n+    if not forced:\n+        raise KsftSkipEx(\"Can't tell what to force the link at\")\n+\n+    ret, _ = ethtool_ret(f\"-s {cfg.remote_ifname} autoneg off {forced}\",\n+                         is_get=False, host=cfg.remote)\n+    if ret != 0:\n+        raise KsftSkipEx(f\"Can't force the peer's link at {forced}\")\n+\n+    ret, _ = ethtool_ret(f\"-s {cfg.ifname} autoneg off {forced}\",\n+                         is_get=False)\n+    if ret != 0:\n+        raise KsftSkipEx(f\"Can't force the link at {forced}\")\n+\n+    if not wait_for_aneg(cfg):\n+        raise KsftSkipEx(f\"No link when forced at {forced}\")\n+\n+    # We must have pause autoneg still enabled, even if we don't negotiate pause\n+    check_local_pauseparams(cfg, True, rx, tx)\n+\n+    # Re-enable autoneg\n+    ethtool(f\"-s {cfg.remote_ifname} autoneg on\", host=cfg.remote)\n+    ethtool(f\"-s {cfg.ifname} autoneg on\")\n+    ksft_eq(wait_for_aneg(cfg), True)\n+\n+    # Pause autoneg must still be on\n+    check_local_pauseparams(cfg, True, rx, tx)\n+    check_local_advertising(cfg, adv)\n+    check_local_lp_advertising(cfg, [\"Pause\"])\n+\n+def main():\n+    with NetDrvEpEnv(__file__, nsim_test=False) as cfg:\n+        cfg.ethnl = EthtoolFamily()\n+        ksft_run([pause_test_support,\n+                  pause_advertising_test,\n+                  pause_aneg_resolution,\n+                  pause_autoneg_state_adv,\n+                  pause_autoneg_state_params,\n+                  pause_autoneg_off_while_link_autoneg_on,\n+                  pause_autoneg_link_autoneg,\n+                  ],\n+                 args=(cfg, ))\n+    ksft_exit()\n+\n+if __name__ == \"__main__\":\n+    main()\n"
}

2/1 2026/09/27 14:08 agent patch-evaluator
0m Model:
core

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch only modifies user-space selftests under tools/testing/selftests/drivers/net/hw/ and does not modify the kernel image or modules.",
  "WorthFuzzing": false
}

Instruction:
You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.

================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.

- REACHABILITY IS THE PRIMARY GATE:
  Fuzzing can only discover bugs in code that can actually execute in standard virtualized
  environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
  If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
  regardless of whether it adds assertions or complex logic.

- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
  Patch authors routinely label changes as "cleanups", "refactorings", or state
  "No functional change intended". Do NOT take these claims at face value.
  Code refactorings that rearrange logic, introduce helper functions, or alter state management
  in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
  If reachable executable code is modified or refactored, it MUST be fuzzed.

- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
  When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
  BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
  Even if the author believes the invariant always holds, fuzzing is essential to verify whether
  an unusual sequence of operations can violate it.

================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:

- Non-kernel and non-executable changes:
  * Modifications to Documentation/, comments, or spelling fixes.
  * User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
    that do not affect the compiled kernel image (vmlinux) or kernel modules.
  * Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
    that do not alter control flow or data structures.
  * Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
  * Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
    ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
  * Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
    physical PCI hot-unplug or manual sysfs driver unbinding.
  * Code paths exclusive to architectures other than the target architecture.

================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
  * Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
    BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
  * Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
  * Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
  * Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).

================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:

- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
  Do NOT list generic, ubiquitous functions called by almost every program in the corpus
  (including, but not limited to: general memory allocators and deallocators, page fault
  and trap handlers, or core synchronization primitives; this is not an exhaustive list).
  Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
  which severely dilutes fuzzing effort away from the actual changes.

- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
  List functions that specifically implement the logic being added or altered, or direct API entrypoints
  for the subsystem feature under review.

- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
  Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
  distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
  If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
  functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).

================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 265b0f5aac8645578ecdea4e2af902c2b2dc1f65
Author: syz-cluster <triage@syzkaller.com>
Date:   Sun Sep 27 14:08:26 2026 +0000

    syz-cluster: applied patch under review

diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile
index bd3b8d2fa47e0..f0aeb70c20fdf 100644
--- a/tools/testing/selftests/drivers/net/hw/Makefile
+++ b/tools/testing/selftests/drivers/net/hw/Makefile
@@ -44,6 +44,7 @@ TEST_PROGS = \
 	nk_netns.py \
 	nk_qlease.py \
 	ntuple.py \
+	pause.py \
 	pp_alloc_fail.py \
 	rss_api.py \
 	rss_ctx.py \
diff --git a/tools/testing/selftests/drivers/net/hw/pause.py b/tools/testing/selftests/drivers/net/hw/pause.py
new file mode 100755
index 0000000000000..74bc21c606958
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/pause.py
@@ -0,0 +1,1035 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+"""
+Driver-related behavior tests for Pause-based Flow Control.
+"""
+
+import errno
+import json
+import os
+import time
+
+from lib.py import KsftFailEx, KsftNamedVariant, KsftSkipEx
+from lib.py import NetDrvEpEnv, EthtoolFamily
+from lib.py import cmd, defer, ethtool, ip
+from lib.py import ksft_disruptive, ksft_variants, ksft_run
+from lib.py import ksft_eq, ksft_exit, ksft_in, ksft_not_in, ksft_pr
+
+# Linkmodes to Pause params :
+# Pause bit is set if rx == 1
+# Asym_Pause bit is set if rx != tx
+pauseparams_to_linkmodes = {
+    0: {0: {"rx": 0, "tx": 0, "linkmodes": []},
+        1: {"rx": 0, "tx": 1, "linkmodes": ["Asym_Pause"]}},
+    1: {0: {"rx": 1, "tx": 0, "linkmodes": ["Pause", "Asym_Pause"]},
+        1: {"rx": 1, "tx": 1, "linkmodes": ["Pause"]}},
+}
+
+def onoff(val):
+    """ Convert a bool to on/off """
+    return "on" if val else "off"
+
+pauseparams_variants = [
+    KsftNamedVariant(f"RX {onoff(p['rx'])} TX {onoff(p['tx'])}", p)
+    for by_tx in pauseparams_to_linkmodes.values() for p in by_tx.values()
+]
+
+_strerrors = {os.strerror(e): e for e in errno.errorcode}
+
+def ethtool_ret(command, is_get=True, host=None):
+    """ Execute an ethtool command, returns the return code and JSON content
+
+    :param command: the ethtool arguments
+    :param is_get: Is the command a get or a set. Get commands return the loaded
+                   JSON attributes
+    :param host: The host on which to run the command on. None means local host.
+    """
+    json_flag = "--json" if is_get else ""
+    cmd_res = cmd(f"ethtool {json_flag} {command}", host=host, fail=False)
+
+    if cmd_res.ret != 0:
+        # ethtool returns 1 upon error, not the netlink errcode. Try to get it
+        # by parsing the stderr output, which looks like :
+        # "netlink error: Operation not supported"
+        for line in cmd_res.stderr.splitlines():
+            err = _strerrors.get(line.rsplit(": ", 1)[-1].strip())
+            if err:
+                return err, None
+        return cmd_res.ret, None
+
+    # Not a get operation, we don't have any JSON output to parse
+    if not is_get:
+        return 0, None
+
+    return 0, json.loads(cmd_res.stdout)[0]
+
+def wait_for_aneg(cfg, link_drop=False, timeout=15):
+    """ Wait for a renegotiation to complete.
+
+    :param link_drop: Set to true if the link HAS to flap.
+    :returns: True if link is UP, False if timeout
+    """
+    deadline = time.monotonic() + timeout
+
+    # Link has 2 seconds to come back up
+    restart_by = time.monotonic() + 2
+
+    # The link may still be up for a short while when we trigger an autoneg
+    # restart, we need to wait for it to drop, then come back up again
+    while time.monotonic() < deadline:
+        if not ethtool(f"{cfg.ifname}", json=True)[0]["link-detected"]:
+            return wait_for_link(cfg)
+
+        if not link_drop and time.monotonic() > restart_by:
+            return wait_for_link(cfg)
+
+        time.sleep(0.1)
+
+    return False
+
+def wait_for_link_local(cfg, timeout=15):
+    """ Wait for the local link to be up """
+    deadline = time.monotonic() + timeout
+
+    while time.monotonic() < deadline:
+        link = ethtool(f"{cfg.ifname}", json=True)[0]["link-detected"]
+        if link:
+            return True
+
+        time.sleep(0.1)
+
+    return False
+
+def wait_for_link_remote(cfg, timeout=15):
+    """ Wait for the far end of the link to be up """
+    deadline = time.monotonic() + timeout
+
+    while time.monotonic() < deadline:
+        link = ethtool(f"{cfg.remote_ifname}", json=True,
+                       host=cfg.remote)[0]["link-detected"]
+        if link:
+            return True
+
+        time.sleep(0.1)
+
+    return False
+
+def wait_for_link(cfg):
+    """ Wait for both ends of the link to be up """
+    if not wait_for_link_local(cfg):
+        return False
+
+    # Local link is UP, we shouldn't have to wait for a whole 8 seconds for
+    # the remote to report link up, let's wait a bit less
+    return wait_for_link_remote(cfg, timeout = 3)
+
+def controllable_lp(cfg):
+    """ Whether the remote interface is the link partner of the local one.
+
+    For low level ethtool tests, we need to have the remote directly connected
+    to the local host (i.e. not through a switch).
+
+    This is tested by taking the local link down and checking that the
+    remote's link drops.
+
+    :returns: True if the remote is our link partner
+    """
+    known = getattr(cfg, "lp_controllable", None)
+    if known is not None:
+        return known
+
+    # 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")
+
+    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
+
+    cfg.lp_controllable = wait_for_link(cfg)
+    if not cfg.lp_controllable:
+        ksft_pr(f"{cfg.remote_ifname} is not the link partner: "
+                f"no link back after {cfg.ifname} came up")
+    return cfg.lp_controllable
+
+def require_controllable_lp(cfg):
+    """ Skip if the remote isn't directly controllable, e.g. accessed through
+        a switch
+    """
+    if not controllable_lp(cfg):
+        raise KsftSkipEx(f"{cfg.remote_ifname} is not directly connected to {cfg.ifname}")
+
+def require_link_autoneg(cfg):
+    """ Skip if local or remote device don't support link aneg """
+    # Does local device support link aneg
+    if not ethtool(f"{cfg.ifname}", json=True)[0]["supports-auto-negotiation"]:
+        raise KsftSkipEx(f"{cfg.ifname} doesn't support link autoneg")
+
+    # Does remote device support link aneg
+    if not ethtool(f"{cfg.remote_ifname}",
+                   json=True, host=cfg.remote)[0]["supports-auto-negotiation"]:
+        raise KsftSkipEx(f"Remote {cfg.remote_ifname} doesn't support link autoneg")
+
+def forced_link_settings(cfg):
+    """ Returns a string to pass to ethtool -s with speed/duplex corresponding
+        to the current settings.
+
+        Note that some devices don't return duplex info, so assume full duplex
+        in that case.
+    """
+    link = ethtool(f"{cfg.ifname}", json=True)[0]
+    if "speed" not in link:
+        return ""
+
+    return f"speed {link['speed']} duplex {link.get('duplex', 'Full').lower()}"
+
+def _ethtool_pause_use_to_linkmodes(use):
+    """ Convert the ethtool output for pause modes into pause linkmodes """
+    if use == "Symmetric":
+        return ["Pause"]
+    elif use == "Symmetric Receive-only":
+        return ["Pause", "Asym_Pause"]
+    elif use == "Transmit-only":
+        return ["Asym_Pause"]
+    else:
+        return []
+
+def pause_to_linkmodes(rx, tx):
+    """ Convert the bool rx/tx pause into Pause/Asym """
+    return pauseparams_to_linkmodes[rx][tx]["linkmodes"]
+
+def set_local_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.ifname} rx {rx_param} tx {tx_param}"
+                         f" autoneg {aneg_param}",
+                         is_get = False)
+
+    return ret
+
+def get_local_pauseparams(cfg):
+    """ get pauseparams : ethtool -a
+
+    Raise an error if the return is not 0 or EOPNOTSUPP
+    """
+    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)
+
+def get_local_pause_supported(cfg):
+    """ Return the errcode and the local pause supported linkmodes """
+
+    ret, data = ethtool_ret(f"{cfg.ifname}")
+    if ret != 0:
+        raise KsftFailEx(f"ethtool {cfg.ifname} failed: {ret}")
+
+    return ret, _ethtool_pause_use_to_linkmodes(data["supported-pause-frame-use"])
+
+def get_local_pause_advertising(cfg):
+    """ Return the errcode and the local pause advertised linkmodes """
+
+    ret, data = ethtool_ret(f"{cfg.ifname}")
+    if ret != 0:
+        raise KsftFailEx(f"ethtool {cfg.ifname} failed: {ret}")
+
+    return ret, _ethtool_pause_use_to_linkmodes(data["advertised-pause-frame-use"])
+
+def get_local_pause_lp_advertising(cfg):
+    """ Return the errcode and the local pause lp_advertised linkmodes,
+        if any.
+    """
+
+    ret, data = ethtool_ret(f"{cfg.ifname}")
+    if ret != 0:
+        raise KsftFailEx(f"ethtool {cfg.ifname} failed: {errno.errorcode.get(ret, ret)}")
+
+    if "link-partner-advertised-pause-frame-use" in data:
+        return ret, _ethtool_pause_use_to_linkmodes(data["link-partner-advertised-pause-frame-use"])
+    else:
+        ksft_pr(f"Warning: {cfg.ifname} does not report the LP's advertising")
+        return errno.EOPNOTSUPP, None
+
+def get_peer_pause_supported(cfg):
+    """ Return the errcode and the remote pause supported linkmodes """
+
+    ret, data = ethtool_ret(f"{cfg.remote_ifname}", host = cfg.remote)
+    if ret != 0:
+        raise KsftFailEx(f"ethtool {cfg.remote_ifname} failed: {errno.errorcode.get(ret, ret)}")
+
+    return ret, _ethtool_pause_use_to_linkmodes(data["supported-pause-frame-use"])
+
+
+def get_peer_pause_advertising(cfg):
+    """ Return the errcode and the remote pause advertised linkmodes """
+
+    ret, data = ethtool_ret(f"{cfg.remote_ifname}", host = cfg.remote)
+    if ret != 0:
+        raise KsftFailEx(f"ethtool {cfg.remote_ifname} failed: {errno.errorcode.get(ret, ret)}")
+
+    return ret, _ethtool_pause_use_to_linkmodes(data["advertised-pause-frame-use"])
+
+def get_peer_pause_lp_advertising(cfg):
+    """ Return the errcode and the local pause lp_advertised linkmodes,
+        if any.
+    """
+
+    ret, data = ethtool_ret(f"{cfg.remote_ifname}", host = cfg.remote)
+    if ret != 0:
+        raise KsftFailEx(f"ethtool {cfg.remote_ifname} failed: {errno.errorcode.get(ret, ret)}")
+
+    if "link-partner-advertised-pause-frame-use" in data:
+        return ret, _ethtool_pause_use_to_linkmodes(data["link-partner-advertised-pause-frame-use"])
+    else:
+        ksft_pr(f"Warning: {cfg.remote_ifname} does not report the LP's advertising")
+        return errno.EOPNOTSUPP, None
+
+def require_pause_supported_anyof(cfg, linkmodes):
+    """ Skip if local device doesn't support any of the passed modes """
+
+    ret, _ = get_local_pauseparams(cfg)
+    if ret != 0:
+        raise KsftSkipEx("device doesn't allow getting pauseparams")
+
+    _, pause_support = get_local_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_pause_supported_allof(cfg, linkmodes):
+    """ Skip if local device doesn't support all of the passed modes """
+
+    ret, _ = get_local_pauseparams(cfg)
+    if ret != 0:
+        raise KsftSkipEx("device doesn't allow getting pauseparams")
+
+    _, pause_support = get_local_pause_supported(cfg)
+    for lm in 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 supported_pauseparams(cfg):
+    """ The rx/tx params covering every mode the local device supports """
+
+    _, pause_support = get_local_pause_supported(cfg)
+    if "Pause" in pause_support:
+        return 1, 1
+    if "Asym_Pause" in pause_support:
+        return 0, 1
+
+    raise KsftSkipEx("Local device doesn't support pause")
+
+def set_local_pause_autoneg(cfg, aneg):
+    """ Enable/Disable local pause autoneg """
+
+    ret, _ = ethtool_ret(f"-A {cfg.ifname} autoneg {onoff(aneg)}",
+                         is_get=False)
+    return ret
+
+def check_local_pauseparams(cfg, aneg, rx, tx):
+    """ Check that the local pauseparams are the passed parameters """
+
+    ret, params = get_local_pauseparams(cfg)
+    ksft_eq(ret, 0)
+    if ret != 0:
+        return
+
+    ksft_eq(params["autonegotiate"], bool(aneg), "pause autoneg")
+    ksft_eq(params["rx"], bool(rx), "rx pause")
+    ksft_eq(params["tx"], bool(tx), "tx pause")
+
+def check_local_advertising(cfg, linkmodes):
+    """ Check that the local advertised modes are the passed parameters """
+
+    _, adv = get_local_pause_advertising(cfg)
+    ksft_eq(adv, linkmodes, "advertised pause modes")
+
+def check_local_lp_advertising(cfg, linkmodes):
+    """ Check that the local lp_advertised modes are the passed parameters """
+
+    ret, adv = get_local_pause_lp_advertising(cfg)
+    if ret == 0:
+        ksft_eq(adv, linkmodes, "link partner advertised pause modes")
+
+def expect_pauseparams_set(ret, linkmodes, supported, note):
+    """ Whether ethtool -A had to work or to be refused, given what the local
+        device supports
+    """
+
+    # If supported is empty, ethtool -A must return -EOPNOTSUPP
+    if not supported :
+        ksft_eq(ret, errno.EOPNOTSUPP, note)
+    elif set(linkmodes).issubset(set(supported)) :
+        # The configured pauseparams are supposed to be supported,
+        #ethtool -A must have worked.
+        ksft_eq(ret, 0, note)
+    else :
+        # We tried to configure parameters that aren't supporteed,
+        # ethtool -A must have failed.
+        ksft_in(ret, (errno.EOPNOTSUPP, errno.EINVAL), note)
+
+def pause_setup(cfg):
+    """ The starting conditions every test counts on, restored on exit:
+        - both ports admin up
+        - link autoneg on on both sides ifsupported
+        - link actually up (carrier on)
+    """
+
+    # Get init pause parameters
+    ret, params = ethtool_ret(f"-a {cfg.ifname}")
+    if ret == 0:
+        defer(cmd, f"ethtool -A {cfg.ifname} rx {onoff(params['rx'])} "
+                   f"tx {onoff(params['tx'])} "
+                   f"autoneg {onoff(params['autonegotiate'])}")
+
+    # Get init link parameters
+    link = ethtool(f"{cfg.ifname}", json=True)[0]
+    if link["auto-negotiation"]:
+        defer(cmd, f"ethtool -s {cfg.ifname} autoneg on")
+    elif "speed" in link and "duplex" in link:
+        defer(cmd, f"ethtool -s {cfg.ifname} autoneg off speed {link['speed']} "
+                   f"duplex {link['duplex'].lower()}")
+
+    # Local interface admin up, link aneg on
+    ip(f"link set {cfg.ifname} up")
+    if link["supports-auto-negotiation"] and not link["auto-negotiation"]:
+        ethtool(f"-s {cfg.ifname} autoneg on")
+
+    # Get remote pause params
+    ret, params = ethtool_ret(f"-a {cfg.remote_ifname}", host=cfg.remote)
+    if ret == 0:
+        defer(cmd, f"ethtool -A {cfg.remote_ifname} rx {onoff(params['rx'])} "
+                   f"tx {onoff(params['tx'])} "
+                   f"autoneg {onoff(params['autonegotiate'])}",
+                   host=cfg.remote)
+
+    # Get remote link params
+    link = ethtool(f"{cfg.remote_ifname}", json=True, host=cfg.remote)[0]
+    if link["auto-negotiation"]:
+        defer(cmd, f"ethtool -s {cfg.remote_ifname} autoneg on",
+              host=cfg.remote)
+    elif "speed" in link and "duplex" in link:
+        defer(cmd, f"ethtool -s {cfg.remote_ifname} autoneg off "
+                   f"speed {link['speed']} duplex {link['duplex'].lower()}",
+                   host=cfg.remote)
+
+    # Remote interface admin up, link aneg on
+    ip(f"link set {cfg.remote_ifname} up", host=cfg.remote)
+    if link["supports-auto-negotiation"] and not link["auto-negotiation"]:
+        ethtool(f"-s {cfg.remote_ifname} autoneg on", host=cfg.remote)
+
+    # Wait for link to become up on both ends
+    if not wait_for_link(cfg):
+        raise KsftFailEx("No link before the test")
+
+# Pause support : Supported linkmodes vs ability to set/get pauseparams
+@ksft_variants(pauseparams_variants)
+@ksft_disruptive
+def pause_test_support(cfg, pauseparams):
+    """ Verify that the supported linkmodes Pause and Asym_Pause match the
+        ability to configure the rx and tx pauseparams.
+
+    Drivers are expected to reject pauseparams they don't support, and
+    accept the ones they support. The supported modes are exposed by
+    the MAC to the PHY layer through phylink mac_capabilities MAC_SYM_PAUSE
+    and MAC_ASYM_PAUSE, or through phylib directly with the
+    phy_support_sym_pause() and phy_support_asym_pause() helpers.
+
+    The expectation is for drivers to refuse setting pauseparams that don't
+    match the Pause and Asym_Pause bits in the supported linkmodes with a
+    -EOPNOTSUPP return value. Unsupported pause params must be rejected.
+
+    Failing this test likely means the MAC driver doesn't implement the
+    set/get_pauseparam, but still sets flow control as supported through
+    phylink mac_capabilities or phylib's pause API. Conversely, the MAC driver
+    may have omitted to indicate its supported Pause modes. Finally, the PHY
+    driver may incorrectly override the Pause and Asym_Pause bits in its
+    supported fields.
+
+    The sequence runs with link autoneg on, then with the link forced
+    (ethtool -s ethX autoneg off): the pause params are accepted or rejected
+    the same way in both cases, and both with pause autoneg off and on.
+    """
+
+    rx = onoff(pauseparams["rx"])
+    tx = onoff(pauseparams["tx"])
+    linkmodes = pauseparams["linkmodes"]
+
+    pause_setup(cfg)
+
+    forced = forced_link_settings(cfg)
+    _, supported = get_local_pause_supported(cfg)
+
+    # We check that what we can configure in the pause params matches what we
+    # support under various contditions : Link aneg on/off, pause aneg on/off
+    ret, _ = ethtool_ret(f"-s {cfg.ifname} autoneg on", is_get = False)
+    if ret != 0:
+        ksft_pr("link autoneg on refused, not tested")
+    else:
+        ret, _ = ethtool_ret(f"-A {cfg.ifname} rx {rx} tx {tx} autoneg off",
+                             is_get = False)
+        expect_pauseparams_set(ret, linkmodes, supported, "link autoneg on")
+
+        ret, _ = ethtool_ret(f"-A {cfg.ifname} rx {rx} tx {tx} autoneg on",
+                             is_get = False)
+        expect_pauseparams_set(ret, linkmodes, supported, "link autoneg on")
+
+    if not forced:
+        ksft_pr("link speed unknown, the forced link is not tested")
+        return
+
+    ret, _ = ethtool_ret(f"-s {cfg.ifname} autoneg off {forced}", is_get = False)
+    if ret != 0:
+        ksft_pr(f"link autoneg off {forced} refused, not tested")
+        return
+
+    ret, _ = ethtool_ret(f"-A {cfg.ifname} rx {rx} tx {tx} autoneg off",
+                         is_get = False)
+    expect_pauseparams_set(ret, linkmodes, supported, "link autoneg off")
+
+    ret, _ = ethtool_ret(f"-A {cfg.ifname} rx {rx} tx {tx} autoneg on",
+                         is_get = False)
+    expect_pauseparams_set(ret, linkmodes, supported, "link autoneg off")
+
+@ksft_variants(pauseparams_variants)
+@ksft_disruptive
+def pause_advertising_test(cfg, pauseparams):
+    """Pause advertisement
+
+    Validate that changing pause params through the ETHTOOL_MSG_PAUSE command
+    translates to a change in the advertised pause params, and that these
+    parameters are correct w.r.t the supported pause params and requested pause
+    params.
+
+    This exercises the .set_pauseparam() ethtool ops for MAC configuration,
+    as well as the reconfiguration of the PHY's advertising and negotiation.
+
+    On non-phylink MACs, the MAC should call phy_set_sym_pause() to update the
+    PHY's advertising, and restart a negotiation with phy_start_aneg() if
+    need be. Failure to do so will result in the wrong advertising parameters.
+
+    On phylink-enabled MACs, phylink deals with the PHY reconfiguration provided
+    the MAC driver calls phylink_ethtool_set_pauseparam().
+
+    Failing this test likely means that the PHY driver is not correctly
+    advertising pause settings, either due to the MAC not triggering a PHY
+    reconfiguration, a misconfiguration of the advertising registers by the PHY,
+    or by mis-handling the phydev->advertising bitmap in the PHY driver directly.
+
+    The validation is made by looking at the advertised modes locally, as well
+    as what the peer's 'lp_advertising' values report.
+    """
+
+    require_pause_supported_allof(cfg, pauseparams["linkmodes"])
+    pause_setup(cfg)
+    lp = controllable_lp(cfg)
+
+    tx = pauseparams["tx"]
+    rx = pauseparams["rx"]
+    adv = pauseparams["linkmodes"]
+    not_adv = [ l for l in ["Pause", "Asym_Pause"] if l not in adv]
+
+    # It's OK to skip here, we're already validating the EOPNOTSUPP behaviour
+    # the pause_test_support test.
+    ret = set_local_pauseparams(cfg, rx, tx, True)
+    if ret == errno.EOPNOTSUPP:
+        raise KsftSkipEx(f"RX {rx} TX {tx} not supported")
+
+    # Wait for link parameters to re-negotiate and link to come back up. It must
+    # come back up, otherwise that means changing pauseparams can bring the
+    # link down.
+    ret = wait_for_aneg(cfg)
+    ksft_eq(ret, True)
+
+    _, linkmodes = get_local_pause_advertising(cfg)
+    for mode in adv:
+        ksft_in(mode, linkmodes,
+                f"rx {rx} tx {tx} aneg on must advertise {adv}")
+
+    for mode in not_adv:
+        ksft_not_in(mode, linkmodes,
+                    f"rx {rx} tx {tx} aneg on must not advertise {not_adv}")
+
+    if not lp:
+        return
+
+    returncode, remote_linkmodes = get_peer_pause_lp_advertising(cfg)
+    if returncode == errno.EOPNOTSUPP:
+        return
+
+    for mode in adv:
+        ksft_in(mode, remote_linkmodes, f"PHY does not advertise {adv}")
+
+    for mode in not_adv:
+        ksft_not_in(mode, remote_linkmodes,
+                    f"PHY incorrectly advertises {not_adv}")
+
+
+# Pause autonegotiation resolution : Resolved pause settings vs configured
+# pauseparams on local device and link partner
+@ksft_variants([
+    # We advertise nothing, all off
+    KsftNamedVariant("local rx off tx off, remote rx off tx off",
+        {"rx": 0, "tx": 0, "lp_rx": 0, "lp_tx": 0, "neg_rx": 0, "neg_tx": 0}),
+
+    # We advertise nothing, all off
+    KsftNamedVariant("local rx off tx off, remote rx off tx on",
+        {"rx": 0, "tx": 0, "lp_rx": 0, "lp_tx": 1, "neg_rx": 0, "neg_tx": 0}),
+
+    # We advertise nothing, all off
+    KsftNamedVariant("local rx off tx off, remote rx on tx off",
+        {"rx": 0, "tx": 0, "lp_rx": 1, "lp_tx": 0, "neg_rx": 0, "neg_tx": 0}),
+
+    # We advertise nothing, all off
+    KsftNamedVariant("local rx off tx off, remote rx on tx on",
+        {"rx": 0, "tx": 0, "lp_rx": 1, "lp_tx": 1, "neg_rx": 0, "neg_tx": 0}),
+
+    # LP advertises nothing, all off
+    KsftNamedVariant("local rx off tx on, remote rx off tx off",
+        {"rx": 0, "tx": 1, "lp_rx": 0, "lp_tx": 0, "neg_rx": 0, "neg_tx": 0}),
+
+    # We advertise Asym, LP advertises Asym, all off
+    KsftNamedVariant("local rx off tx on, remote rx off tx on",
+        {"rx": 0, "tx": 1, "lp_rx": 0, "lp_tx": 1, "neg_rx": 0, "neg_tx": 0}),
+
+    # We advertise Asym, LP advertises Pause + Asym, tx on
+    KsftNamedVariant("local rx off tx on, remote rx on tx off",
+        {"rx": 0, "tx": 1, "lp_rx": 1, "lp_tx": 0, "neg_rx": 0, "neg_tx": 1}),
+
+    # Tricky case :
+    # We advertise Asym, LP advertises Pause, resolves to all off
+    KsftNamedVariant("local rx off tx on, remote rx on tx on",
+        {"rx": 0, "tx": 1, "lp_rx": 1, "lp_tx": 1, "neg_rx": 0, "neg_tx": 0}),
+
+    # LP advertises nothing, all off
+    KsftNamedVariant("local rx on tx off, remote rx off tx off",
+        {"rx": 1, "tx": 0, "lp_rx": 0, "lp_tx": 0, "neg_rx": 0, "neg_tx": 0}),
+
+    # We advertise Pause + Asym , LP advertises Asym, rx on
+    KsftNamedVariant("local rx on tx off, remote rx off tx on",
+        {"rx": 1, "tx": 0, "lp_rx": 0, "lp_tx": 1, "neg_rx": 1, "neg_tx": 0}),
+
+    # Also tricky: Only rx enabled on both ends, but we negotiate rx/tx
+    # We advertise Pause + Asym, LP advertises Pause + Asym, all on
+    KsftNamedVariant("local rx on tx off, remote rx on tx off",
+        {"rx": 1, "tx": 0, "lp_rx": 1, "lp_tx": 0, "neg_rx": 1, "neg_tx": 1}),
+
+    # We advertise Pause + Asym, LP advertises Pause, all on
+    KsftNamedVariant("local rx on tx off, remote rx on tx on",
+        {"rx": 1, "tx": 0, "lp_rx": 1, "lp_tx": 1, "neg_rx": 1, "neg_tx": 1}),
+
+    # LP advertises nothing, all off
+    KsftNamedVariant("local rx on tx on, remote rx off tx off",
+        {"rx": 1, "tx": 1, "lp_rx": 0, "lp_tx": 0, "neg_rx": 0, "neg_tx": 0}),
+
+    # Tricky case :
+    # We advertise Pause, LP advertises Asym, resolves to all off
+    KsftNamedVariant("local rx on tx on, remote rx off tx on",
+        {"rx": 1, "tx": 1, "lp_rx": 0, "lp_tx": 1, "neg_rx": 0, "neg_tx": 0}),
+
+    # We advertise Pause, LP advertises Pause + Asym, all on
+    KsftNamedVariant("local rx on tx on, remote rx on tx off",
+        {"rx": 1, "tx": 1, "lp_rx": 1, "lp_tx": 0, "neg_rx": 1, "neg_tx": 1}),
+
+    # We advertise Pause, LP advertises Pause, all on
+    KsftNamedVariant("local rx on tx on, remote rx on tx on",
+        {"rx": 1, "tx": 1, "lp_rx": 1, "lp_tx": 1, "neg_rx": 1, "neg_tx": 1}),
+])
+@ksft_disruptive
+def pause_aneg_resolution(cfg, settings):
+    """ Verify that rx and tx pause parameters are negotiated according to 802.3
+
+    802.3 dictates the rules for pause negotiation, all 16 cases are tested, one
+    for each combination of Pause and Asym_Pause advertising on the local device
+    and the link-partner.
+
+    This test also verifies that the peer resolved the parameters correctly,
+    to ensure the negotiation is triggered correctly.
+
+    Failing this test can happen if :
+     - The MAC accepts the pause parameters but doesn't trigger a link
+       renegotiation
+     - 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
+    """
+
+    expected_local_rx = settings["neg_rx"]
+    expected_local_tx = settings["neg_tx"]
+
+    required_local_linkmodes = pause_to_linkmodes(settings["rx"],
+                                                  settings["tx"])
+    required_remote_linkmodes = pause_to_linkmodes(settings["lp_rx"],
+                                                   settings["lp_tx"])
+
+    require_pause_supported_allof(cfg, required_local_linkmodes)
+    require_peer_pause_supported_allof(cfg, required_remote_linkmodes)
+    require_controllable_lp(cfg)
+    pause_setup(cfg)
+
+    # There's symmetry between local device and LP on pause negotiation:
+    # - if local resolves all off or all on, LP must resolve the same
+    # - if local resolves RX only, remote must resolve to TX only
+    # - if local resolves TX only, remote must resolve to RX only
+    if expected_local_rx == expected_local_tx:
+        expected_lp_rx = expected_local_rx
+        expected_lp_tx = expected_local_tx
+    else:
+        expected_lp_rx = expected_local_tx
+        expected_lp_tx = expected_local_rx
+
+    # Set pauseparams
+    ret = set_local_pauseparams(cfg, settings["rx"], settings["tx"], True)
+    if ret == errno.EOPNOTSUPP:
+        raise KsftSkipEx(f"RX {settings['rx']} TX {settings['tx']} not supported")
+
+    ksft_eq(wait_for_aneg(cfg), True)
+
+    set_peer_pauseparams(cfg, settings["lp_rx"], settings["lp_tx"], True)
+
+    # Wait for link to re-negotiate
+    ret = wait_for_aneg(cfg)
+
+    # Fail if it doesn't
+    ksft_eq(ret, True)
+
+    if get_local_pause_lp_advertising(cfg)[0] != 0:
+        raise KsftSkipEx("Local device doesn't report the LP's advertising")
+
+    ret, local_pauseparams = get_local_pauseparams(cfg)
+    if ret != 0 or "negotiated" not in local_pauseparams:
+        raise KsftSkipEx("Local device doesn't report the negotiated pause params")
+
+    # check adv
+    _, linkmodes = get_local_pause_advertising(cfg)
+    for mode in required_local_linkmodes:
+        ksft_in(mode, linkmodes,
+                f"local rx {settings['rx']} tx {settings['tx']} must advertise "
+                f"{required_local_linkmodes}")
+
+    _, linkmodes = get_peer_pause_advertising(cfg)
+    for mode in required_remote_linkmodes:
+        ksft_in(mode, linkmodes,
+                f"remote rx {settings['lp_rx']} tx {settings['lp_tx']} must advertise "
+                f"{required_remote_linkmodes}")
+
+    # check lp_adv if available
+    _, linkmodes = get_local_pause_lp_advertising(cfg)
+    for mode in required_remote_linkmodes:
+        ksft_in(mode, linkmodes,
+                f"local lp_adv must show the remote's {required_remote_linkmodes}")
+
+    # check lp_adv on remote
+    ret, linkmodes = get_peer_pause_lp_advertising(cfg)
+    if ret == 0:
+        for mode in required_local_linkmodes:
+            ksft_in(mode, linkmodes,
+                    f"remote lp_adv must show our {required_local_linkmodes}")
+
+    # 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)
+
+@ksft_disruptive
+def pause_autoneg_state_adv(cfg):
+    """Validate that toggling pause advertising changes the advertised linkmodes
+
+    When disabling pause autoneg, we enforce the pause params based on what user
+    asks, instead of relying on the negociation process (which may not be what
+    the user asked for). In forced pause settings, we don't advertise pause and
+    asym_pause bits.
+
+    Failing this test means that .set_pauseparam in the MAC driver doesn't
+    forward to the PHY (in charge of advertising these bits) that we are in
+    fixed pause mode.
+    """
+
+    require_pause_supported_anyof(cfg, ["Pause", "Asym_Pause"])
+    require_controllable_lp(cfg)
+    pause_setup(cfg)
+
+    set_peer_pauseparams(cfg, True, True, True)
+    ksft_eq(wait_for_aneg(cfg), True)
+
+    rx, tx = supported_pauseparams(cfg)
+
+    # Enable all possible pauseparams with pause autoneg
+    ret = set_local_pauseparams(cfg, rx, tx, True)
+    ksft_eq(ret, 0)
+    ksft_eq(wait_for_aneg(cfg), True)
+
+    # Make sure we advertise them
+    ret, adv = get_local_pause_advertising(cfg)
+    ksft_eq(ret, 0)
+    ksft_eq(adv, pause_to_linkmodes(rx, tx))
+
+    # Disable pause autoneg
+    ret = set_local_pauseparams(cfg, rx, tx, False)
+    ksft_eq(ret, 0)
+
+    # This may trigger a link renegociation
+    ksft_eq(wait_for_aneg(cfg), True)
+
+    # We shouldn't be advertising anything anymore
+    ret, adv = get_local_pause_advertising(cfg)
+    ksft_eq(ret, 0)
+    ksft_eq(adv, [])
+
+    # Validate on the LP that we aren't advertising anything
+    ret, adv = get_peer_pause_lp_advertising(cfg)
+    if ret == errno.EOPNOTSUPP:
+        return
+
+    ksft_eq(ret, 0)
+    ksft_eq(adv, [])
+
+@ksft_disruptive
+def pause_autoneg_state_params(cfg):
+    """Validate the pause params when transitioning between fixed pause
+       params and negotiated ones. The goal is to make sure that user
+       intent on the RX and TX pause params are stored when user decides
+       to use negotiated parameters instead. The main gotcha lies on the
+       fact that when pause autoneg is used, the autoneg result may differ
+       from the user intent.
+
+    Failing this test means the MAC driver is overwriting the user intent
+    when switching to forced pause.
+    """
+
+    require_pause_supported_allof(cfg, ["Pause", "Asym_Pause"])
+    require_controllable_lp(cfg)
+    require_peer_pause_supported_allof(cfg, ["Pause"])
+    require_link_autoneg(cfg)
+    pause_setup(cfg)
+
+    # Set peer user intent to RX on TX on, with Pause autoneg on
+    set_peer_pauseparams(cfg, 1, 1, True)
+    ksft_eq(wait_for_aneg(cfg), True)
+
+    # Set the local intent to RX on TX off with pause autoneg
+    ksft_eq(set_local_pauseparams(cfg, 1, 0, True), 0)
+    ksft_eq(wait_for_aneg(cfg), True)
+
+    check_local_pauseparams(cfg, True, 1, 0)
+    # Peer advertisiong Pause + Asym and us advertising Pause means we are
+    # actually using RX on TX on here, which is different than the intent.
+    check_local_advertising(cfg, ["Pause", "Asym_Pause"])
+    check_local_lp_advertising(cfg, ["Pause"])
+
+    # Disable pause autoneg
+    ksft_eq(set_local_pause_autoneg(cfg, False), 0)
+    ksft_eq(wait_for_aneg(cfg), True)
+
+    # The pauseparams must still be what we configured before, and not the
+    # previously negotiated ones
+    check_local_pauseparams(cfg, False, 1, 0)
+
+    # Re-enable autoneg
+    ksft_eq(set_local_pause_autoneg(cfg, True), 0)
+    ksft_eq(wait_for_aneg(cfg), True)
+
+    check_local_pauseparams(cfg, True, 1, 0)
+    # We must be advertising our intent again, and not RX on TX on, which would
+    # be "Pause" only.
+    check_local_advertising(cfg, ["Pause", "Asym_Pause"])
+    check_local_lp_advertising(cfg, ["Pause"])
+
+@ksft_disruptive
+def pause_autoneg_off_while_link_autoneg_on(cfg):
+    """ Validate that when link autoneg is on but pause autoneg is off, we do
+        not use negotiated pause parameters.
+
+        Failing this test means the MAC driver incorrectly accounts for the
+        negotiated pause parameters even with pause aneg off, likely due to
+        confusion between link autoneg and pause autoneg.
+    """
+
+    require_pause_supported_anyof(cfg, ["Pause", "Asym_Pause"])
+    require_controllable_lp(cfg)
+    require_peer_pause_supported_allof(cfg, ["Pause"])
+    require_link_autoneg(cfg)
+    pause_setup(cfg)
+
+    rx, tx = supported_pauseparams(cfg)
+
+    # Enable pause autoneg with all the locally supported modes enabled
+    set_peer_pauseparams(cfg, 1, 1, True)
+    ksft_eq(set_local_pauseparams(cfg, rx, tx, True), 0)
+    ksft_eq(wait_for_aneg(cfg), True)
+
+    # Disable Pause autoneg
+    ksft_eq(set_local_pauseparams(cfg, rx, tx, False), 0)
+    ksft_eq(wait_for_aneg(cfg), True)
+    # Pause autoneg must read "disabled"
+    check_local_pauseparams(cfg, False, rx, tx)
+
+    set_peer_pauseparams(cfg, 0, 0, True)
+    ksft_eq(wait_for_aneg(cfg, link_drop=True), True)
+
+    ip(f"link set {cfg.remote_ifname} down", host=cfg.remote)
+    ip(f"link set {cfg.remote_ifname} up", host=cfg.remote)
+    ksft_eq(wait_for_aneg(cfg, link_drop=True), True)
+
+    # Pause autoneg must still be off even after a link renegotiation
+    check_local_pauseparams(cfg, False, rx, tx)
+
+@ksft_disruptive
+def pause_autoneg_link_autoneg(cfg):
+    """Validate pause autoneg and link autoneg interactions. The link autoneg's
+       admin status (i.e. do we autoneg link parameters or force them) must not
+       impact the pause autoneg status. While link autoneg is disabled, we don't
+       negotiate the pause params, however we must keep pause autoneg on as this
+       is the user intent. When link autoneg is re-enabled, pause params must be
+       derived from the negotiation.
+    """
+
+    require_pause_supported_anyof(cfg, ["Pause", "Asym_Pause"])
+    require_controllable_lp(cfg)
+    require_peer_pause_supported_allof(cfg, ["Pause"])
+    require_link_autoneg(cfg)
+    pause_setup(cfg)
+
+    rx, tx = supported_pauseparams(cfg)
+    adv = pause_to_linkmodes(rx, tx)
+
+    # Enable all possible pause modes and autoneg
+    set_peer_pauseparams(cfg, 1, 1, True)
+    ksft_eq(set_local_pauseparams(cfg, rx, tx, True), 0)
+    ksft_eq(wait_for_aneg(cfg), True)
+
+    check_local_pauseparams(cfg, True, rx, tx)
+    check_local_advertising(cfg, adv)
+    check_local_lp_advertising(cfg, ["Pause"])
+
+    # Disable link autoneg, at the speed and duplex the link runs at
+    forced = forced_link_settings(cfg)
+    if not forced:
+        raise KsftSkipEx("Can't tell what to force the link at")
+
+    ret, _ = ethtool_ret(f"-s {cfg.remote_ifname} autoneg off {forced}",
+                         is_get=False, host=cfg.remote)
+    if ret != 0:
+        raise KsftSkipEx(f"Can't force the peer's link at {forced}")
+
+    ret, _ = ethtool_ret(f"-s {cfg.ifname} autoneg off {forced}",
+                         is_get=False)
+    if ret != 0:
+        raise KsftSkipEx(f"Can't force the link at {forced}")
+
+    if not wait_for_aneg(cfg):
+        raise KsftSkipEx(f"No link when forced at {forced}")
+
+    # We must have pause autoneg still enabled, even if we don't negotiate pause
+    check_local_pauseparams(cfg, True, rx, tx)
+
+    # Re-enable autoneg
+    ethtool(f"-s {cfg.remote_ifname} autoneg on", host=cfg.remote)
+    ethtool(f"-s {cfg.ifname} autoneg on")
+    ksft_eq(wait_for_aneg(cfg), True)
+
+    # Pause autoneg must still be on
+    check_local_pauseparams(cfg, True, rx, tx)
+    check_local_advertising(cfg, adv)
+    check_local_lp_advertising(cfg, ["Pause"])
+
+def main():
+    with NetDrvEpEnv(__file__, nsim_test=False) as cfg:
+        cfg.ethnl = EthtoolFamily()
+        ksft_run([pause_test_support,
+                  pause_advertising_test,
+                  pause_aneg_resolution,
+                  pause_autoneg_state_adv,
+                  pause_autoneg_state_params,
+                  pause_autoneg_off_while_link_autoneg_on,
+                  pause_autoneg_link_autoneg,
+                  ],
+                 args=(cfg, ))
+    ksft_exit()
+
+if __name__ == "__main__":
+    main()

3/2 2026/09/27 14:08 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 16452
							output: 66
							thoughts: 719
						

Thoughts:
**Analysis of the Patch for Fuzzing Eligibility**

Okay, let's break this down. The patch affects `tools/testing/selftests/drivers/net/hw/Makefile` and adds `tools/testing/selftests/drivers/net/hw/pause.py`.  My initial thought is that this is a straightforward case, but I need to be methodical.

First, let me confirm the scope. Yep, the diff *only* touches files within that `tools/testing/selftests/drivers/net/hw/` path.  It's all user-space, no kernel code is touched, and no drivers or kernel headers are impacted.

Now, let's cross-reference this against the negative criteria for fuzzing. Section 2 clearly states that modifications to user-space directories, self-tests, and scripts that don't affect the kernel image (vmlinux) or modules should be excluded from fuzzing.  This patch *definitely* falls squarely into that category.

There's no need to double-check with `git log` or `git diff` this time; the supplied diff clearly shows the context.  It's all self-tests, precisely as the documentation describes.

Therefore, the verdict is clear: `WorthFuzzing=false`. The `FocusSymbols` and `EnableConfigs` lists will be empty. We will set the `Reasoning` to be a concise explanation.

Finally, let me verify that the parameters for `set-results` are correctly formatted: `EnableConfigs` (array of strings or null), `FocusSymbols` (array of strings or null), `Reasoning` (string), and `WorthFuzzing` (boolean). All good.  I'm ready to construct the tool call now.



4/2 2026/09/27 14:08 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The patch only modifies user-space selftests under tools/testing/selftests/drivers/net/hw/ and does not modify the kernel image or modules.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch only modifies user-space selftests under tools/testing/selftests/drivers/net/hw/ and does not modify the kernel image or modules.",
  "WorthFuzzing": false
}

5/1 2026/09/27 14:08 action If
0m Args:
{
  "WorthFuzzing": false
}

Results:
null

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)