Path: blob/master/tools/testing/selftests/drivers/net/hw/toeplitz.py
49160 views
#!/usr/bin/env python31# SPDX-License-Identifier: GPL-2.023"""4Toeplitz Rx hashing test:5- rxhash (the hash value calculation itself);6- RSS mapping from rxhash to rx queue;7- RPS mapping from rxhash to cpu.8"""910import glob11import os12import socket13from lib.py import ksft_run, ksft_exit, ksft_pr14from lib.py import NetDrvEpEnv, EthtoolFamily, NetdevFamily15from lib.py import cmd, bkg, rand_port, defer16from lib.py import ksft_in17from lib.py import ksft_variants, KsftNamedVariant, KsftSkipEx, KsftFailEx1819# "define" for the ID of the Toeplitz hash function20ETH_RSS_HASH_TOP = 1212223def _check_rps_and_rfs_not_configured(cfg):24"""Verify that RPS is not already configured."""2526for rps_file in glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*/rps_cpus"):27with open(rps_file, "r", encoding="utf-8") as fp:28val = fp.read().strip()29if set(val) - {"0", ","}:30raise KsftSkipEx(f"RPS already configured on {rps_file}: {val}")3132rfs_file = "/proc/sys/net/core/rps_sock_flow_entries"33with open(rfs_file, "r", encoding="utf-8") as fp:34val = fp.read().strip()35if val != "0":36raise KsftSkipEx(f"RFS already configured {rfs_file}: {val}")373839def _get_cpu_for_irq(irq):40with open(f"/proc/irq/{irq}/smp_affinity_list", "r",41encoding="utf-8") as fp:42data = fp.read().strip()43if "," in data or "-" in data:44raise KsftFailEx(f"IRQ{irq} is not mapped to a single core: {data}")45return int(data)464748def _get_irq_cpus(cfg):49"""50Read the list of IRQs for the device Rx queues.51"""52queues = cfg.netnl.queue_get({"ifindex": cfg.ifindex}, dump=True)53napis = cfg.netnl.napi_get({"ifindex": cfg.ifindex}, dump=True)5455# Remap into ID-based dicts56napis = {n["id"]: n for n in napis}57queues = {f"{q['type']}{q['id']}": q for q in queues}5859cpus = []60for rx in range(9999):61name = f"rx{rx}"62if name not in queues:63break64cpus.append(_get_cpu_for_irq(napis[queues[name]["napi-id"]]["irq"]))6566return cpus676869def _get_unused_cpus(cfg, count=2):70"""71Get CPUs that are not used by Rx queues.72Returns a list of at least 'count' CPU numbers.73"""7475# Get CPUs used by Rx queues76rx_cpus = set(_get_irq_cpus(cfg))7778# Get total number of CPUs79num_cpus = os.cpu_count()8081# Find unused CPUs82unused_cpus = [cpu for cpu in range(num_cpus) if cpu not in rx_cpus]8384if len(unused_cpus) < count:85raise KsftSkipEx(f"Need at {count} CPUs not used by Rx queues, found {len(unused_cpus)}")8687return unused_cpus[:count]888990def _configure_rps(cfg, rps_cpus):91"""Configure RPS for all Rx queues."""9293mask = 094for cpu in rps_cpus:95mask |= (1 << cpu)9697mask = hex(mask)9899# Set RPS bitmap for all rx queues100for rps_file in glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*/rps_cpus"):101with open(rps_file, "w", encoding="utf-8") as fp:102# sysfs expects hex without '0x' prefix, toeplitz.c needs the prefix103fp.write(mask[2:])104105return mask106107108def _send_traffic(cfg, proto_flag, ipver, port):109"""Send 20 packets of requested type."""110111# Determine protocol and IP version for socat112if proto_flag == "-u":113proto = "UDP"114else:115proto = "TCP"116117baddr = f"[{cfg.addr_v['6']}]" if ipver == "6" else cfg.addr_v["4"]118119# Run socat in a loop to send traffic periodically120# Use sh -c with a loop similar to toeplitz_client.sh121socat_cmd = f"""122for i in `seq 20`; do123echo "msg $i" | socat -{ipver} -t 0.1 - {proto}:{baddr}:{port};124sleep 0.001;125done126"""127128cmd(socat_cmd, shell=True, host=cfg.remote)129130131def _test_variants():132for grp in ["", "rss", "rps"]:133for l4 in ["tcp", "udp"]:134for l3 in ["4", "6"]:135name = f"{l4}_ipv{l3}"136if grp:137name = f"{grp}_{name}"138yield KsftNamedVariant(name, "-" + l4[0], l3, grp)139140141@ksft_variants(_test_variants())142def test(cfg, proto_flag, ipver, grp):143"""Run a single toeplitz test."""144145cfg.require_ipver(ipver)146147# Check that rxhash is enabled148ksft_in("receive-hashing: on", cmd(f"ethtool -k {cfg.ifname}").stdout)149150rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})151# Make sure NIC is configured to use Toeplitz hash, and no key xfrm.152if rss.get('hfunc') != ETH_RSS_HASH_TOP or rss.get('input-xfrm'):153cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},154"hfunc": ETH_RSS_HASH_TOP,155"input-xfrm": {}})156defer(cfg.ethnl.rss_set, {"header": {"dev-index": cfg.ifindex},157"hfunc": rss.get('hfunc'),158"input-xfrm": rss.get('input-xfrm', {})159})160161port = rand_port(socket.SOCK_DGRAM)162163toeplitz_path = cfg.test_dir / "toeplitz"164rx_cmd = [165str(toeplitz_path),166"-" + ipver,167proto_flag,168"-d", str(port),169"-i", cfg.ifname,170"-T", "4000",171"-s",172"-v"173]174175if grp:176_check_rps_and_rfs_not_configured(cfg)177if grp == "rss":178irq_cpus = ",".join([str(x) for x in _get_irq_cpus(cfg)])179rx_cmd += ["-C", irq_cpus]180ksft_pr(f"RSS using CPUs: {irq_cpus}")181elif grp == "rps":182# Get CPUs not used by Rx queues and configure them for RPS183rps_cpus = _get_unused_cpus(cfg, count=2)184rps_mask = _configure_rps(cfg, rps_cpus)185defer(_configure_rps, cfg, [])186rx_cmd += ["-r", rps_mask]187ksft_pr(f"RPS using CPUs: {rps_cpus}, mask: {rps_mask}")188189# Run rx in background, it will exit once it has seen enough packets190with bkg(" ".join(rx_cmd), ksft_ready=True, exit_wait=True) as rx_proc:191while rx_proc.proc.poll() is None:192_send_traffic(cfg, proto_flag, ipver, port)193194# Check rx result195ksft_pr("Receiver output:")196ksft_pr(rx_proc.stdout.strip().replace('\n', '\n# '))197if rx_proc.stderr:198ksft_pr(rx_proc.stderr.strip().replace('\n', '\n# '))199200201def main() -> None:202"""Ksft boilerplate main."""203204with NetDrvEpEnv(__file__) as cfg:205cfg.ethnl = EthtoolFamily()206cfg.netnl = NetdevFamily()207ksft_run(cases=[test], args=(cfg,))208ksft_exit()209210211if __name__ == "__main__":212main()213214215