Path: blob/master/tools/testing/selftests/drivers/net/hw/toeplitz.py
122962 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 = 121# Must match RPS_MAX_CPUS in toeplitz.c22RPS_MAX_CPUS = 16232425def _check_rps_and_rfs_not_configured(cfg):26"""Verify that RPS is not already configured."""2728for rps_file in glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*/rps_cpus"):29with open(rps_file, "r", encoding="utf-8") as fp:30val = fp.read().strip()31if set(val) - {"0", ","}:32raise KsftSkipEx(f"RPS already configured on {rps_file}: {val}")3334rfs_file = "/proc/sys/net/core/rps_sock_flow_entries"35with open(rfs_file, "r", encoding="utf-8") as fp:36val = fp.read().strip()37if val != "0":38raise KsftSkipEx(f"RFS already configured {rfs_file}: {val}")394041def _get_cpu_for_irq(irq):42with open(f"/proc/irq/{irq}/smp_affinity_list", "r",43encoding="utf-8") as fp:44data = fp.read().strip()45if "," in data or "-" in data:46raise KsftFailEx(f"IRQ{irq} is not mapped to a single core: {data}")47return int(data)484950def _get_irq_cpus(cfg):51"""52Read the list of IRQs for the device Rx queues.53"""54queues = cfg.netnl.queue_get({"ifindex": cfg.ifindex}, dump=True)55napis = cfg.netnl.napi_get({"ifindex": cfg.ifindex}, dump=True)5657# Remap into ID-based dicts58napis = {n["id"]: n for n in napis}59queues = {f"{q['type']}{q['id']}": q for q in queues}6061cpus = []62for rx in range(9999):63name = f"rx{rx}"64if name not in queues:65break66cpus.append(_get_cpu_for_irq(napis[queues[name]["napi-id"]]["irq"]))6768return cpus697071def _get_unused_rps_cpus(cfg, count=2):72"""73Get CPUs that are not used by Rx queues for RPS.74Returns a list of at least 'count' CPU numbers within75the RPS_MAX_CPUS supported range.76"""7778# Get CPUs used by Rx queues79rx_cpus = set(_get_irq_cpus(cfg))8081# Get total number of CPUs, capped by RPS_MAX_CPUS82num_cpus = min(os.cpu_count(), RPS_MAX_CPUS)8384# Find unused CPUs85unused_cpus = [cpu for cpu in range(num_cpus) if cpu not in rx_cpus]8687if len(unused_cpus) < count:88raise KsftSkipEx(f"Need at least {count} CPUs in range 0..{num_cpus - 1} not used by Rx queues, found {len(unused_cpus)}")8990return unused_cpus[:count]919293def _configure_rps(cfg, rps_cpus):94"""Configure RPS for all Rx queues."""9596mask = 097for cpu in rps_cpus:98mask |= (1 << cpu)99100mask = hex(mask)101102# Set RPS bitmap for all rx queues103for rps_file in glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*/rps_cpus"):104with open(rps_file, "w", encoding="utf-8") as fp:105# sysfs expects hex without '0x' prefix, toeplitz.c needs the prefix106fp.write(mask[2:])107108return mask109110111def _send_traffic(cfg, proto_flag, ipver, port):112"""Send 20 packets of requested type."""113114# Determine protocol and IP version for socat115if proto_flag == "-u":116proto = "UDP"117else:118proto = "TCP"119120baddr = f"[{cfg.addr_v['6']}]" if ipver == "6" else cfg.addr_v["4"]121122# Run socat in a loop to send traffic periodically123# Use sh -c with a loop similar to toeplitz_client.sh124socat_cmd = f"""125for i in `seq 20`; do126echo "msg $i" | socat -{ipver} -t 0.1 - {proto}:{baddr}:{port};127sleep 0.001;128done129"""130131cmd(socat_cmd, shell=True, host=cfg.remote)132133134def _test_variants():135for grp in ["", "rss", "rps"]:136for l4 in ["tcp", "udp"]:137for l3 in ["4", "6"]:138name = f"{l4}_ipv{l3}"139if grp:140name = f"{grp}_{name}"141yield KsftNamedVariant(name, "-" + l4[0], l3, grp)142143144@ksft_variants(_test_variants())145def test(cfg, proto_flag, ipver, grp):146"""Run a single toeplitz test."""147148cfg.require_ipver(ipver)149150# Check that rxhash is enabled151ksft_in("receive-hashing: on", cmd(f"ethtool -k {cfg.ifname}").stdout)152153rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})154# Make sure NIC is configured to use Toeplitz hash, and no key xfrm.155if rss.get('hfunc') != ETH_RSS_HASH_TOP or rss.get('input-xfrm'):156cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},157"hfunc": ETH_RSS_HASH_TOP,158"input-xfrm": {}})159defer(cfg.ethnl.rss_set, {"header": {"dev-index": cfg.ifindex},160"hfunc": rss.get('hfunc'),161"input-xfrm": rss.get('input-xfrm', {})162})163164port = rand_port(socket.SOCK_DGRAM)165166toeplitz_path = cfg.test_dir / "toeplitz"167rx_cmd = [168str(toeplitz_path),169"-" + ipver,170proto_flag,171"-d", str(port),172"-i", cfg.ifname,173"-T", "4000",174"-s",175"-v"176]177178if grp:179_check_rps_and_rfs_not_configured(cfg)180if grp == "rss":181irq_cpus = ",".join([str(x) for x in _get_irq_cpus(cfg)])182rx_cmd += ["-C", irq_cpus]183ksft_pr(f"RSS using CPUs: {irq_cpus}")184elif grp == "rps":185# Get CPUs not used by Rx queues and configure them for RPS186rps_cpus = _get_unused_rps_cpus(cfg, count=2)187rps_mask = _configure_rps(cfg, rps_cpus)188defer(_configure_rps, cfg, [])189rx_cmd += ["-r", rps_mask]190ksft_pr(f"RPS using CPUs: {rps_cpus}, mask: {rps_mask}")191192# Run rx in background, it will exit once it has seen enough packets193with bkg(" ".join(rx_cmd), ksft_ready=True, exit_wait=True) as rx_proc:194while rx_proc.proc.poll() is None:195_send_traffic(cfg, proto_flag, ipver, port)196197# Check rx result198ksft_pr("Receiver output:")199ksft_pr(rx_proc.stdout.strip().replace('\n', '\n# '))200if rx_proc.stderr:201ksft_pr(rx_proc.stderr.strip().replace('\n', '\n# '))202203204def main() -> None:205"""Ksft boilerplate main."""206207with NetDrvEpEnv(__file__) as cfg:208cfg.ethnl = EthtoolFamily()209cfg.netnl = NetdevFamily()210ksft_run(cases=[test], args=(cfg,))211ksft_exit()212213214if __name__ == "__main__":215main()216217218