From: Cosmin Ratiu <[email protected]>

Bits and pieces of gro.py are needed by a second test file:
- dmac resolution
- the ethtool/sysfs feature dance
- the HW GRO setup logic
- the gro binary invocation
- the retry loop

Move them to gro_lib.py, installed via TEST_FILES (like psp_lib.py).

hw/gro_hw.py keeps its own copies for now since they diverged.

Signed-off-by: Cosmin Ratiu <[email protected]>
Reviewed-by: Dragos Tatulea <[email protected]>
Signed-off-by: Tariq Toukan <[email protected]>
---
 tools/testing/selftests/drivers/net/Makefile  |   1 +
 tools/testing/selftests/drivers/net/gro.py    | 200 ++----------------
 .../testing/selftests/drivers/net/gro_lib.py  | 194 +++++++++++++++++
 3 files changed, 217 insertions(+), 178 deletions(-)
 create mode 100644 tools/testing/selftests/drivers/net/gro_lib.py

diff --git a/tools/testing/selftests/drivers/net/Makefile 
b/tools/testing/selftests/drivers/net/Makefile
index d68f9ab8955d..be77a15eaf38 100644
--- a/tools/testing/selftests/drivers/net/Makefile
+++ b/tools/testing/selftests/drivers/net/Makefile
@@ -28,6 +28,7 @@ TEST_PROGS := \
 # end of TEST_PROGS
 
 TEST_FILES := \
+       gro_lib.py \
        psp_lib.py \
 # end of TEST_FILES
 
diff --git a/tools/testing/selftests/drivers/net/gro.py 
b/tools/testing/selftests/drivers/net/gro.py
index 6ab8c97880d1..7111095bb479 100755
--- a/tools/testing/selftests/drivers/net/gro.py
+++ b/tools/testing/selftests/drivers/net/gro.py
@@ -37,90 +37,20 @@ Test cases:
 """
 
 import glob
-import os
 import re
 from lib.py import ksft_run, ksft_exit, ksft_pr
-from lib.py import NetDrvEpEnv, KsftFailEx, KsftXfailEx
+from lib.py import NetDrvEpEnv, KsftXfailEx
 from lib.py import NetdevFamily, EthtoolFamily
-from lib.py import bkg, cmd, defer, ethtool, ip
+from lib.py import defer, ethtool
 from lib.py import ksft_variants, KsftNamedVariant
+from gro_lib import run_gro_bin, run_with_retries, set_ethtool_feat
+from gro_lib import set_mtu_restore, setup_hw_gro, write_defer_restore
 
 
 # gro.c uses hardcoded DPORT=8000
 GRO_DPORT = 8000
 
 
-def _resolve_dmac(cfg, ipver):
-    """
-    Find the destination MAC address remote host should use to send packets
-    towards the local host. It may be a router / gateway address.
-    """
-
-    attr = "dmac" + ipver
-    # Cache the response across test cases
-    if hasattr(cfg, attr):
-        return getattr(cfg, attr)
-
-    route = ip(f"-{ipver} route get {cfg.addr_v[ipver]}",
-               json=True, host=cfg.remote)[0]
-    gw = route.get("gateway")
-    # Local L2 segment, address directly
-    if not gw:
-        setattr(cfg, attr, cfg.dev['address'])
-        return getattr(cfg, attr)
-
-    # ping to make sure neighbor is resolved,
-    # bind to an interface, for v6 the GW is likely link local
-    cmd(f"ping -c1 -W0 -I{cfg.remote_ifname} {gw}", host=cfg.remote)
-
-    neigh = ip(f"neigh get {gw} dev {cfg.remote_ifname}",
-               json=True, host=cfg.remote)[0]
-    setattr(cfg, attr, neigh['lladdr'])
-    return getattr(cfg, attr)
-
-
-def _write_defer_restore(cfg, path, val, defer_undo=False):
-    with open(path, "r", encoding="utf-8") as fp:
-        orig_val = fp.read().strip()
-        if str(val) == orig_val:
-            return
-    with open(path, "w", encoding="utf-8") as fp:
-        fp.write(val)
-    if defer_undo:
-        defer(_write_defer_restore, cfg, path, orig_val)
-
-
-def _set_mtu_restore(dev, mtu, host):
-    if dev['mtu'] < mtu:
-        ip(f"link set dev {dev['ifname']} mtu {mtu}", host=host)
-        defer(ip, f"link set dev {dev['ifname']} mtu {dev['mtu']}", host=host)
-
-
-def _set_ethtool_feat(dev, current, feats, host=None):
-    s2n = {True: "on", False: "off"}
-
-    new = ["-K", dev]
-    old = ["-K", dev]
-    no_change = True
-    for name, state in feats.items():
-        new += [name, s2n[state]]
-        old += [name, s2n[current[name]["active"]]]
-
-        if current[name]["active"] != state:
-            no_change = False
-            if current[name]["fixed"]:
-                raise KsftXfailEx(f"Device does not support {name}")
-    if no_change:
-        return
-
-    eth_cmd = ethtool(" ".join(new), host=host)
-    defer(ethtool, " ".join(old), host=host)
-
-    # If ethtool printed something kernel must have modified some features
-    if eth_cmd.stdout:
-        ksft_pr(eth_cmd)
-
-
 def _get_queue_stats(cfg, queue_id):
     """Get stats for a specific Rx queue."""
     cfg.wait_hw_stats_settle()
@@ -182,47 +112,6 @@ def _setup_queue_count(cfg, num_queues):
     ethtool(f"-L {cfg.ifname} combined {num_queues}")
 
 
-def _run_gro_bin(cfg, test_name, protocol=None, num_flows=None,
-                 order_check=False, verbose=False, fail=False):
-    """Run gro binary with given test and return the process result."""
-    if not hasattr(cfg, "bin_remote"):
-        cfg.bin_local = cfg.net_lib_dir / "gro"
-        cfg.bin_remote = cfg.remote.deploy(cfg.bin_local)
-
-    if protocol is None:
-        ipver = cfg.addr_ipver
-        protocol = f"ipv{ipver}"
-    else:
-        ipver = "6" if protocol[-1] == "6" else "4"
-
-    dmac = _resolve_dmac(cfg, ipver)
-
-    base_args = [
-        f"--{protocol}",
-        f"--dmac {dmac}",
-        f"--smac {cfg.remote_dev['address']}",
-        f"--daddr {cfg.addr_v[ipver]}",
-        f"--saddr {cfg.remote_addr_v[ipver]}",
-        f"--test {test_name}",
-    ]
-    if num_flows:
-        base_args.append(f"--num-flows {num_flows}")
-    if order_check:
-        base_args.append("--order-check")
-    if verbose:
-        base_args.append("--verbose")
-
-    args = " ".join(base_args)
-
-    rx_cmd = f"{cfg.bin_local} {args} --rx --iface {cfg.ifname}"
-    tx_cmd = f"{cfg.bin_remote} {args} --iface {cfg.remote_ifname}"
-
-    with bkg(rx_cmd, ksft_ready=True, exit_wait=True, fail=fail) as rx_proc:
-        cmd(tx_cmd, host=cfg.remote)
-
-    return rx_proc
-
-
 def _setup(cfg, mode, test_name):
     """ Setup hardware loopback mode for GRO testing. """
 
@@ -237,60 +126,39 @@ def _setup(cfg, mode, test_name):
 
     # "large_*" tests need at least 4k MTU
     if test_name.startswith("large_"):
-        _set_mtu_restore(cfg.dev, 4096, None)
-        _set_mtu_restore(cfg.remote_dev, 4096, cfg.remote)
+        set_mtu_restore(cfg.dev, 4096, None)
+        set_mtu_restore(cfg.remote_dev, 4096, cfg.remote)
 
     if mode == "sw":
         flush_path = f"/sys/class/net/{cfg.ifname}/gro_flush_timeout"
         irq_path = f"/sys/class/net/{cfg.ifname}/napi_defer_hard_irqs"
 
-        _write_defer_restore(cfg, flush_path, "200000", defer_undo=True)
-        _write_defer_restore(cfg, irq_path, "10", defer_undo=True)
+        write_defer_restore(cfg, flush_path, "200000", defer_undo=True)
+        write_defer_restore(cfg, irq_path, "10", defer_undo=True)
 
-        _set_ethtool_feat(cfg.ifname, cfg.feat,
-                          {"generic-receive-offload": True,
-                           "rx-gro-hw": False,
-                           "large-receive-offload": False})
+        set_ethtool_feat(cfg.ifname, cfg.feat,
+                         {"generic-receive-offload": True,
+                          "rx-gro-hw": False,
+                          "large-receive-offload": False})
     elif mode == "hw":
-        _set_ethtool_feat(cfg.ifname, cfg.feat,
-                          {"generic-receive-offload": False,
-                           "rx-gro-hw": True,
-                           "large-receive-offload": False})
-
-        # Some NICs treat HW GRO as a GRO sub-feature so disabling GRO
-        # will also clear HW GRO. Use a hack of installing XDP generic
-        # to skip SW GRO, even when enabled.
-        feat = ethtool(f"-k {cfg.ifname}", json=True)[0]
-        if not feat["rx-gro-hw"]["active"]:
-            ksft_pr("Driver clears HW GRO and SW GRO is cleared, using generic 
XDP workaround")
-            prog = cfg.net_lib_dir / "xdp_dummy.bpf.o"
-            ip(f"link set dev {cfg.ifname} xdpgeneric obj {prog} sec xdp")
-            defer(ip, f"link set dev {cfg.ifname} xdpgeneric off")
-
-            # Attaching XDP may change features, fetch the latest state
-            feat = ethtool(f"-k {cfg.ifname}", json=True)[0]
-
-            _set_ethtool_feat(cfg.ifname, feat,
-                              {"generic-receive-offload": True,
-                               "rx-gro-hw": True,
-                               "large-receive-offload": False})
+        setup_hw_gro(cfg)
     elif mode == "lro":
         # netdevsim advertises LRO for feature inheritance testing with
         # bonding/team tests but it doesn't actually perform the offload
         cfg.require_nsim(nsim_test=False)
 
-        _set_ethtool_feat(cfg.ifname, cfg.feat,
-                          {"generic-receive-offload": False,
-                           "rx-gro-hw": False,
-                           "large-receive-offload": True})
+        set_ethtool_feat(cfg.ifname, cfg.feat,
+                         {"generic-receive-offload": False,
+                          "rx-gro-hw": False,
+                          "large-receive-offload": True})
 
     try:
         # Disable TSO for local tests
         cfg.require_nsim()  # will raise KsftXfailEx if not running on nsim
 
-        _set_ethtool_feat(cfg.remote_ifname, cfg.remote_feat,
-                          {"tcp-segmentation-offload": False},
-                          host=cfg.remote)
+        set_ethtool_feat(cfg.remote_ifname, cfg.remote_feat,
+                         {"tcp-segmentation-offload": False},
+                         host=cfg.remote)
     except KsftXfailEx:
         pass
 
@@ -356,31 +224,7 @@ def test(cfg, mode, protocol, test_name):
 
     _setup(cfg, mode, test_name)
 
-    # Each test is run 6 times to deflake, because given the receive timing,
-    # not all packets that should coalesce will be considered in the same flow
-    # on every try.
-    max_retries = 6
-    for attempt in range(max_retries):
-        fail_now = attempt >= max_retries - 1
-        rx_proc = _run_gro_bin(cfg, test_name, protocol=protocol,
-                               verbose=True, fail=fail_now)
-
-        if rx_proc.ret == 0:
-            return
-
-        ksft_pr(rx_proc)
-
-        # ret==42 means the receiver detected over-coalescing.
-        # This is unambiguous proof of a bug, retries can only cause
-        # false negatives.
-        if rx_proc.ret == 42:
-            raise KsftFailEx(f"GRO over-coalesced in {protocol}/{test_name}")
-
-        if test_name.startswith("large_") and 
os.environ.get("KSFT_MACHINE_SLOW"):
-            ksft_pr(f"Ignoring {protocol}/{test_name} failure due to slow 
environment")
-            return
-
-        ksft_pr(f"Attempt {attempt + 1}/{max_retries} failed, retrying...")
+    run_with_retries(cfg, test_name, protocol=protocol, verbose=True)
 
 
 def _capacity_variants():
@@ -420,7 +264,7 @@ def test_gro_capacity(cfg, mode, setup_func):
             if queue_id is not None:
                 stats_before = _get_queue_stats(cfg, queue_id)
 
-            rx_proc = _run_gro_bin(cfg, "capacity", num_flows=num_flows)
+            rx_proc = run_gro_bin(cfg, "capacity", num_flows=num_flows)
             output = rx_proc.stdout
 
             if queue_id is not None:
diff --git a/tools/testing/selftests/drivers/net/gro_lib.py 
b/tools/testing/selftests/drivers/net/gro_lib.py
new file mode 100644
index 000000000000..4e3d704397fe
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/gro_lib.py
@@ -0,0 +1,194 @@
+# SPDX-License-Identifier: GPL-2.0
+
+"""Shared helpers for the GRO selftests."""
+
+import os
+
+from lib.py import ksft_pr
+from lib.py import KsftFailEx, KsftXfailEx
+from lib.py import bkg, cmd, defer, ethtool, ip
+
+
+# gro.c exits with this code when it detects over-coalescing
+EXIT_OVER_COALESCE = 42
+
+
+def resolve_dmac(cfg, ipver):
+    """
+    Finds the destination MAC address remote host should use to send packets
+    towards the local host. It may be a router / gateway address.
+    """
+
+    attr = "dmac" + ipver
+    # Cache the response across test cases
+    if hasattr(cfg, attr):
+        return getattr(cfg, attr)
+
+    route = ip(f"-{ipver} route get {cfg.addr_v[ipver]}",
+               json=True, host=cfg.remote)[0]
+    gw = route.get("gateway")
+    # Local L2 segment, address directly
+    if not gw:
+        setattr(cfg, attr, cfg.dev['address'])
+        return getattr(cfg, attr)
+
+    # ping to make sure neighbor is resolved,
+    # bind to an interface, for v6 the GW is likely link local
+    cmd(f"ping -c1 -W0 -I{cfg.remote_ifname} {gw}", host=cfg.remote)
+
+    neigh = ip(f"neigh get {gw} dev {cfg.remote_ifname}",
+               json=True, host=cfg.remote)[0]
+    setattr(cfg, attr, neigh['lladdr'])
+    return getattr(cfg, attr)
+
+
+def write_defer_restore(cfg, path, val, defer_undo=False):
+    """Writes val to a sysfs file, optionally restoring it on test exit."""
+    with open(path, "r", encoding="utf-8") as fp:
+        orig_val = fp.read().strip()
+        if str(val) == orig_val:
+            return
+    with open(path, "w", encoding="utf-8") as fp:
+        fp.write(val)
+    if defer_undo:
+        defer(write_defer_restore, cfg, path, orig_val)
+
+
+def set_mtu_restore(dev, mtu, host):
+    """Raises a device's MTU to at least mtu, restoring it on test exit."""
+    if dev['mtu'] < mtu:
+        ip(f"link set dev {dev['ifname']} mtu {mtu}", host=host)
+        defer(ip, f"link set dev {dev['ifname']} mtu {dev['mtu']}", host=host)
+
+
+def set_ethtool_feat(dev, current, feats, host=None):
+    """Sets ethtool features, restoring them on test exit.
+
+    current is the feature state as reported by "ethtool -k", xfail if a
+    feature which needs changing is fixed.
+    """
+    s2n = {True: "on", False: "off"}
+
+    new = ["-K", dev]
+    old = ["-K", dev]
+    no_change = True
+    for name, state in feats.items():
+        new += [name, s2n[state]]
+        old += [name, s2n[current[name]["active"]]]
+
+        if current[name]["active"] != state:
+            no_change = False
+            if current[name]["fixed"]:
+                raise KsftXfailEx(f"Device does not support {name}")
+    if no_change:
+        return
+
+    eth_cmd = ethtool(" ".join(new), host=host)
+    defer(ethtool, " ".join(old), host=host)
+
+    # If ethtool printed something kernel must have modified some features
+    if eth_cmd.stdout:
+        ksft_pr(eth_cmd)
+
+
+def setup_hw_gro(cfg):
+    """Turns on HW GRO and make sure SW GRO stays out of the way.
+
+    Expects cfg.feat to hold the local device's "ethtool -k" state.
+    """
+    set_ethtool_feat(cfg.ifname, cfg.feat,
+                     {"generic-receive-offload": False,
+                      "rx-gro-hw": True,
+                      "large-receive-offload": False})
+
+    # Some NICs treat HW GRO as a GRO sub-feature so disabling GRO
+    # will also clear HW GRO. Use a hack of installing XDP generic
+    # to skip SW GRO, even when enabled.
+    feat = ethtool(f"-k {cfg.ifname}", json=True)[0]
+    if not feat["rx-gro-hw"]["active"]:
+        ksft_pr("Driver clears HW GRO and SW GRO is cleared, using generic XDP 
workaround")
+        prog = cfg.net_lib_dir / "xdp_dummy.bpf.o"
+        ip(f"link set dev {cfg.ifname} xdpgeneric obj {prog} sec xdp")
+        defer(ip, f"link set dev {cfg.ifname} xdpgeneric off")
+
+        # Attaching XDP may change features, fetch the latest state
+        feat = ethtool(f"-k {cfg.ifname}", json=True)[0]
+
+        set_ethtool_feat(cfg.ifname, feat,
+                         {"generic-receive-offload": True,
+                          "rx-gro-hw": True,
+                          "large-receive-offload": False})
+
+
+# pylint: disable=too-many-arguments,too-many-positional-arguments
+def run_gro_bin(cfg, test_name, protocol=None, num_flows=None,
+                order_check=False, verbose=False, fail=False):
+    """Runs gro binary with given test and return the process result."""
+    if not hasattr(cfg, "bin_remote"):
+        cfg.bin_local = cfg.net_lib_dir / "gro"
+        cfg.bin_remote = cfg.remote.deploy(cfg.bin_local)
+
+    if protocol is None:
+        ipver = cfg.addr_ipver
+        protocol = f"ipv{ipver}"
+    else:
+        ipver = "6" if protocol[-1] == "6" else "4"
+
+    dmac = resolve_dmac(cfg, ipver)
+
+    base_args = [
+        f"--{protocol}",
+        f"--dmac {dmac}",
+        f"--smac {cfg.remote_dev['address']}",
+        f"--daddr {cfg.addr_v[ipver]}",
+        f"--saddr {cfg.remote_addr_v[ipver]}",
+        f"--test {test_name}",
+    ]
+    if num_flows:
+        base_args.append(f"--num-flows {num_flows}")
+    if order_check:
+        base_args.append("--order-check")
+    if verbose:
+        base_args.append("--verbose")
+
+    args = " ".join(base_args)
+
+    rx_cmd = f"{cfg.bin_local} {args} --rx --iface {cfg.ifname}"
+    tx_cmd = f"{cfg.bin_remote} {args} --iface {cfg.remote_ifname}"
+
+    with bkg(rx_cmd, ksft_ready=True, exit_wait=True, fail=fail) as rx_proc:
+        cmd(tx_cmd, host=cfg.remote)
+
+    return rx_proc
+
+
+def run_with_retries(cfg, test_name, protocol=None, **kwargs):
+    """Runs a single gro test case, retrying to deflake it.
+
+    Each test is run 6 times, because given the receive timing, not all
+    packets that should coalesce will be considered in the same flow on
+    every try.  Over-coalescing is a hard failure, retries can only
+    cause false negatives there.
+    """
+    max_retries = 6
+    for attempt in range(max_retries):
+        fail_now = attempt >= max_retries - 1
+        rx_proc = run_gro_bin(cfg, test_name, protocol=protocol,
+                              fail=fail_now, **kwargs)
+
+        if rx_proc.ret == 0:
+            return
+
+        ksft_pr(rx_proc)
+
+        # ret==42 means the receiver detected over-coalescing.
+        # This is unambiguous proof of a bug, retries can only cause
+        # false negatives.
+        if rx_proc.ret == EXIT_OVER_COALESCE:
+            raise KsftFailEx(f"GRO over-coalesced in {protocol}/{test_name}")
+
+        if test_name.startswith("large_") and 
os.environ.get("KSFT_MACHINE_SLOW"):
+            ksft_pr(f"Ignoring {protocol}/{test_name} failure due to slow 
environment")
+            return
+
+        ksft_pr(f"Attempt {attempt + 1}/{max_retries} failed, retrying...")
-- 
2.44.0


Reply via email to