Path: blob/master/tools/testing/selftests/drivers/net/hw/gro_hw.py
171056 views
#!/usr/bin/env python31# SPDX-License-Identifier: GPL-2.023"""4HW GRO tests focusing on device machinery like stats, rather than protocol5processing.6"""78import glob9import re1011from lib.py import ksft_run, ksft_exit, ksft_pr12from lib.py import ksft_eq, ksft_ge, ksft_variants13from lib.py import NetDrvEpEnv, NetdevFamily14from lib.py import KsftSkipEx15from lib.py import bkg, cmd, defer, ethtool, ip161718# gro.c uses hardcoded DPORT=800019GRO_DPORT = 8000202122def _get_queue_stats(cfg, queue_id):23"""Get stats for a specific Rx queue."""24cfg.wait_hw_stats_settle()25data = cfg.netnl.qstats_get({"ifindex": cfg.ifindex, "scope": ["queue"]},26dump=True)27for q in data:28if q.get('queue-type') == 'rx' and q.get('queue-id') == queue_id:29return q30return {}313233def _resolve_dmac(cfg, ipver):34"""Find the destination MAC address for sending packets."""35attr = "dmac" + ipver36if hasattr(cfg, attr):37return getattr(cfg, attr)3839route = ip(f"-{ipver} route get {cfg.addr_v[ipver]}",40json=True, host=cfg.remote)[0]41gw = route.get("gateway")42if not gw:43setattr(cfg, attr, cfg.dev['address'])44return getattr(cfg, attr)4546cmd(f"ping -c1 -W0 -I{cfg.remote_ifname} {gw}", host=cfg.remote)47neigh = ip(f"neigh get {gw} dev {cfg.remote_ifname}",48json=True, host=cfg.remote)[0]49setattr(cfg, attr, neigh['lladdr'])50return getattr(cfg, attr)515253def _setup_isolated_queue(cfg):54"""Set up an isolated queue for testing using ntuple filter.5556Remove queue 1 from the default RSS context and steer test traffic to it.57"""58test_queue = 15960qcnt = len(glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*"))61if qcnt < 2:62raise KsftSkipEx(f"Need at least 2 queues, have {qcnt}")6364# Remove queue 1 from default RSS context by setting its weight to 065weights = ["1"] * qcnt66weights[test_queue] = "0"67ethtool(f"-X {cfg.ifname} weight " + " ".join(weights))68defer(ethtool, f"-X {cfg.ifname} default")6970# Set up ntuple filter to steer our test traffic to the isolated queue71flow = f"flow-type tcp{cfg.addr_ipver} "72flow += f"dst-ip {cfg.addr} dst-port {GRO_DPORT} action {test_queue}"73output = ethtool(f"-N {cfg.ifname} {flow}").stdout74ntuple_id = int(output.split()[-1])75defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")7677return test_queue787980def _run_gro_test(cfg, test_name, num_flows=None, ignore_fail=False,81order_check=False):82"""Run gro binary with given test and return output."""83if not hasattr(cfg, "bin_remote"):84cfg.bin_local = cfg.net_lib_dir / "gro"85cfg.bin_remote = cfg.remote.deploy(cfg.bin_local)8687ipver = cfg.addr_ipver88protocol = f"--ipv{ipver}"89dmac = _resolve_dmac(cfg, ipver)9091base_args = [92protocol,93f"--dmac {dmac}",94f"--smac {cfg.remote_dev['address']}",95f"--daddr {cfg.addr}",96f"--saddr {cfg.remote_addr_v[ipver]}",97f"--test {test_name}",98]99if num_flows:100base_args.append(f"--num-flows {num_flows}")101if order_check:102base_args.append("--order-check")103104args = " ".join(base_args)105106rx_cmd = f"{cfg.bin_local} {args} --rx --iface {cfg.ifname}"107tx_cmd = f"{cfg.bin_remote} {args} --iface {cfg.remote_ifname}"108109with bkg(rx_cmd, ksft_ready=True, exit_wait=True, fail=False) as rx_proc:110cmd(tx_cmd, host=cfg.remote)111112if not ignore_fail:113ksft_eq(rx_proc.ret, 0)114if rx_proc.ret != 0:115ksft_pr(rx_proc)116117return rx_proc.stdout118119120def _require_hw_gro_stats(cfg, queue_id):121"""Check if device reports HW GRO stats for the queue."""122stats = _get_queue_stats(cfg, queue_id)123required = ['rx-packets', 'rx-hw-gro-packets', 'rx-hw-gro-wire-packets']124for stat in required:125if stat not in stats:126raise KsftSkipEx(f"Driver does not report '{stat}' via qstats")127128129def _set_ethtool_feat(cfg, current, feats):130"""Set ethtool features with defer to restore original state."""131s2n = {True: "on", False: "off"}132133new = ["-K", cfg.ifname]134old = ["-K", cfg.ifname]135no_change = True136for name, state in feats.items():137new += [name, s2n[state]]138old += [name, s2n[current[name]["active"]]]139140if current[name]["active"] != state:141no_change = False142if current[name]["fixed"]:143raise KsftSkipEx(f"Device does not support {name}")144if no_change:145return146147eth_cmd = ethtool(" ".join(new))148defer(ethtool, " ".join(old))149150# If ethtool printed something kernel must have modified some features151if eth_cmd.stdout:152ksft_pr(eth_cmd)153154155def _setup_hw_gro(cfg):156"""Enable HW GRO on the device, disabling SW GRO."""157feat = ethtool(f"-k {cfg.ifname}", json=True)[0]158159# Try to disable SW GRO and enable HW GRO160_set_ethtool_feat(cfg, feat,161{"generic-receive-offload": False,162"rx-gro-hw": True,163"large-receive-offload": False})164165# Some NICs treat HW GRO as a GRO sub-feature so disabling GRO166# will also clear HW GRO. Use a hack of installing XDP generic167# to skip SW GRO, even when enabled.168feat = ethtool(f"-k {cfg.ifname}", json=True)[0]169if not feat["rx-gro-hw"]["active"]:170ksft_pr("Driver clears HW GRO when SW GRO is cleared, using generic XDP workaround")171prog = cfg.net_lib_dir / "xdp_dummy.bpf.o"172ip(f"link set dev {cfg.ifname} xdpgeneric obj {prog} sec xdp")173defer(ip, f"link set dev {cfg.ifname} xdpgeneric off")174175# Attaching XDP may change features, fetch the latest state176feat = ethtool(f"-k {cfg.ifname}", json=True)[0]177178_set_ethtool_feat(cfg, feat,179{"generic-receive-offload": True,180"rx-gro-hw": True,181"large-receive-offload": False})182183184def _check_gro_stats(cfg, test_queue, stats_before,185expect_rx, expect_gro, expect_wire):186"""Validate GRO stats against expected values."""187stats_after = _get_queue_stats(cfg, test_queue)188189rx_delta = (stats_after.get('rx-packets', 0) -190stats_before.get('rx-packets', 0))191gro_delta = (stats_after.get('rx-hw-gro-packets', 0) -192stats_before.get('rx-hw-gro-packets', 0))193wire_delta = (stats_after.get('rx-hw-gro-wire-packets', 0) -194stats_before.get('rx-hw-gro-wire-packets', 0))195196ksft_eq(rx_delta, expect_rx, comment="rx-packets")197ksft_eq(gro_delta, expect_gro, comment="rx-hw-gro-packets")198ksft_eq(wire_delta, expect_wire, comment="rx-hw-gro-wire-packets")199200201def test_gro_stats_single(cfg):202"""203Test that a single packet doesn't affect GRO stats.204205Send a single packet that cannot be coalesced (nothing to coalesce with).206GRO stats should not increase since no coalescing occurred.207rx-packets should increase by 2 (1 data + 1 FIN).208"""209_setup_hw_gro(cfg)210211test_queue = _setup_isolated_queue(cfg)212_require_hw_gro_stats(cfg, test_queue)213214stats_before = _get_queue_stats(cfg, test_queue)215216_run_gro_test(cfg, "single")217218# 1 data + 1 FIN = 2 rx-packets, no coalescing219_check_gro_stats(cfg, test_queue, stats_before,220expect_rx=2, expect_gro=0, expect_wire=0)221222223def test_gro_stats_full(cfg):224"""225Test GRO stats when overwhelming HW GRO capacity.226227Send 500 flows to exceed HW GRO flow capacity on a single queue.228This should result in some packets not being coalesced.229Validate that qstats match what gro.c observed.230"""231_setup_hw_gro(cfg)232233test_queue = _setup_isolated_queue(cfg)234_require_hw_gro_stats(cfg, test_queue)235236num_flows = 500237stats_before = _get_queue_stats(cfg, test_queue)238239# Run capacity test - will likely fail because not all packets coalesce240output = _run_gro_test(cfg, "capacity", num_flows=num_flows,241ignore_fail=True)242243# Parse gro.c output: "STATS: received=X wire=Y coalesced=Z"244match = re.search(r'STATS: received=(\d+) wire=(\d+) coalesced=(\d+)',245output)246if not match:247raise KsftSkipEx(f"Could not parse gro.c output: {output}")248249rx_frames = int(match.group(2))250gro_coalesced = int(match.group(3))251252ksft_ge(gro_coalesced, 1,253comment="At least some packets should coalesce")254255# received + 1 FIN, coalesced super-packets, coalesced * 2 wire packets256_check_gro_stats(cfg, test_queue, stats_before,257expect_rx=rx_frames + 1,258expect_gro=gro_coalesced,259expect_wire=gro_coalesced * 2)260261262@ksft_variants([4, 32, 512])263def test_gro_order(cfg, num_flows):264"""265Test that HW GRO preserves packet ordering between flows.266267Packets may get delayed until the aggregate is released,268but reordering between aggregates and packet terminating269the aggregate and normal packets should not happen.270271Note that this test is stricter than truly required.272Reordering packets between flows should not cause issues.273This test will also fail if traffic is run over an ECMP fabric.274"""275_setup_hw_gro(cfg)276_setup_isolated_queue(cfg)277278_run_gro_test(cfg, "capacity", num_flows=num_flows, order_check=True)279280281def main() -> None:282""" Ksft boiler plate main """283284with NetDrvEpEnv(__file__, nsim_test=False) as cfg:285cfg.netnl = NetdevFamily()286ksft_run([test_gro_stats_single,287test_gro_stats_full,288test_gro_order], args=(cfg,))289ksft_exit()290291292if __name__ == "__main__":293main()294295296