19cf9aa77SJakub Kicinski#!/usr/bin/env python3 29cf9aa77SJakub Kicinski# SPDX-License-Identifier: GPL-2.0 39cf9aa77SJakub Kicinski 49cf9aa77SJakub Kicinski""" 59cf9aa77SJakub KicinskiToeplitz Rx hashing test: 69cf9aa77SJakub Kicinski - rxhash (the hash value calculation itself); 79cf9aa77SJakub Kicinski - RSS mapping from rxhash to rx queue; 89cf9aa77SJakub Kicinski - RPS mapping from rxhash to cpu. 99cf9aa77SJakub Kicinski""" 109cf9aa77SJakub Kicinski 119cf9aa77SJakub Kicinskiimport glob 129cf9aa77SJakub Kicinskiimport os 139cf9aa77SJakub Kicinskiimport socket 149cf9aa77SJakub Kicinskifrom lib.py import ksft_run, ksft_exit, ksft_pr 159cf9aa77SJakub Kicinskifrom lib.py import NetDrvEpEnv, EthtoolFamily, NetdevFamily 169cf9aa77SJakub Kicinskifrom lib.py import cmd, bkg, rand_port, defer 179cf9aa77SJakub Kicinskifrom lib.py import ksft_in 189cf9aa77SJakub Kicinskifrom lib.py import ksft_variants, KsftNamedVariant, KsftSkipEx, KsftFailEx 199cf9aa77SJakub Kicinski 2027c512afSJakub Kicinski# "define" for the ID of the Toeplitz hash function 2127c512afSJakub KicinskiETH_RSS_HASH_TOP = 1 2227c512afSJakub Kicinski 239cf9aa77SJakub Kicinski 249cf9aa77SJakub Kicinskidef _check_rps_and_rfs_not_configured(cfg): 259cf9aa77SJakub Kicinski """Verify that RPS is not already configured.""" 269cf9aa77SJakub Kicinski 279cf9aa77SJakub Kicinski for rps_file in glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*/rps_cpus"): 289cf9aa77SJakub Kicinski with open(rps_file, "r", encoding="utf-8") as fp: 299cf9aa77SJakub Kicinski val = fp.read().strip() 309cf9aa77SJakub Kicinski if set(val) - {"0", ","}: 319cf9aa77SJakub Kicinski raise KsftSkipEx(f"RPS already configured on {rps_file}: {val}") 329cf9aa77SJakub Kicinski 339cf9aa77SJakub Kicinski rfs_file = "/proc/sys/net/core/rps_sock_flow_entries" 349cf9aa77SJakub Kicinski with open(rfs_file, "r", encoding="utf-8") as fp: 359cf9aa77SJakub Kicinski val = fp.read().strip() 369cf9aa77SJakub Kicinski if val != "0": 379cf9aa77SJakub Kicinski raise KsftSkipEx(f"RFS already configured {rfs_file}: {val}") 389cf9aa77SJakub Kicinski 399cf9aa77SJakub Kicinski 409cf9aa77SJakub Kicinskidef _get_cpu_for_irq(irq): 419cf9aa77SJakub Kicinski with open(f"/proc/irq/{irq}/smp_affinity_list", "r", 429cf9aa77SJakub Kicinski encoding="utf-8") as fp: 439cf9aa77SJakub Kicinski data = fp.read().strip() 449cf9aa77SJakub Kicinski if "," in data or "-" in data: 459cf9aa77SJakub Kicinski raise KsftFailEx(f"IRQ{irq} is not mapped to a single core: {data}") 469cf9aa77SJakub Kicinski return int(data) 479cf9aa77SJakub Kicinski 489cf9aa77SJakub Kicinski 499cf9aa77SJakub Kicinskidef _get_irq_cpus(cfg): 509cf9aa77SJakub Kicinski """ 519cf9aa77SJakub Kicinski Read the list of IRQs for the device Rx queues. 529cf9aa77SJakub Kicinski """ 539cf9aa77SJakub Kicinski queues = cfg.netnl.queue_get({"ifindex": cfg.ifindex}, dump=True) 549cf9aa77SJakub Kicinski napis = cfg.netnl.napi_get({"ifindex": cfg.ifindex}, dump=True) 559cf9aa77SJakub Kicinski 569cf9aa77SJakub Kicinski # Remap into ID-based dicts 579cf9aa77SJakub Kicinski napis = {n["id"]: n for n in napis} 589cf9aa77SJakub Kicinski queues = {f"{q['type']}{q['id']}": q for q in queues} 599cf9aa77SJakub Kicinski 609cf9aa77SJakub Kicinski cpus = [] 619cf9aa77SJakub Kicinski for rx in range(9999): 629cf9aa77SJakub Kicinski name = f"rx{rx}" 639cf9aa77SJakub Kicinski if name not in queues: 649cf9aa77SJakub Kicinski break 659cf9aa77SJakub Kicinski cpus.append(_get_cpu_for_irq(napis[queues[name]["napi-id"]]["irq"])) 669cf9aa77SJakub Kicinski 679cf9aa77SJakub Kicinski return cpus 689cf9aa77SJakub Kicinski 699cf9aa77SJakub Kicinski 709cf9aa77SJakub Kicinskidef _get_unused_cpus(cfg, count=2): 719cf9aa77SJakub Kicinski """ 729cf9aa77SJakub Kicinski Get CPUs that are not used by Rx queues. 739cf9aa77SJakub Kicinski Returns a list of at least 'count' CPU numbers. 749cf9aa77SJakub Kicinski """ 759cf9aa77SJakub Kicinski 769cf9aa77SJakub Kicinski # Get CPUs used by Rx queues 779cf9aa77SJakub Kicinski rx_cpus = set(_get_irq_cpus(cfg)) 789cf9aa77SJakub Kicinski 799cf9aa77SJakub Kicinski # Get total number of CPUs 809cf9aa77SJakub Kicinski num_cpus = os.cpu_count() 819cf9aa77SJakub Kicinski 829cf9aa77SJakub Kicinski # Find unused CPUs 839cf9aa77SJakub Kicinski unused_cpus = [cpu for cpu in range(num_cpus) if cpu not in rx_cpus] 849cf9aa77SJakub Kicinski 859cf9aa77SJakub Kicinski if len(unused_cpus) < count: 869cf9aa77SJakub Kicinski raise KsftSkipEx(f"Need at {count} CPUs not used by Rx queues, found {len(unused_cpus)}") 879cf9aa77SJakub Kicinski 889cf9aa77SJakub Kicinski return unused_cpus[:count] 899cf9aa77SJakub Kicinski 909cf9aa77SJakub Kicinski 919cf9aa77SJakub Kicinskidef _configure_rps(cfg, rps_cpus): 929cf9aa77SJakub Kicinski """Configure RPS for all Rx queues.""" 939cf9aa77SJakub Kicinski 949cf9aa77SJakub Kicinski mask = 0 959cf9aa77SJakub Kicinski for cpu in rps_cpus: 969cf9aa77SJakub Kicinski mask |= (1 << cpu) 979cf9aa77SJakub Kicinski mask = hex(mask)[2:] 989cf9aa77SJakub Kicinski 999cf9aa77SJakub Kicinski # Set RPS bitmap for all rx queues 1009cf9aa77SJakub Kicinski for rps_file in glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*/rps_cpus"): 1019cf9aa77SJakub Kicinski with open(rps_file, "w", encoding="utf-8") as fp: 1029cf9aa77SJakub Kicinski fp.write(mask) 1039cf9aa77SJakub Kicinski 1049cf9aa77SJakub Kicinski return mask 1059cf9aa77SJakub Kicinski 1069cf9aa77SJakub Kicinski 1079cf9aa77SJakub Kicinskidef _send_traffic(cfg, proto_flag, ipver, port): 1089cf9aa77SJakub Kicinski """Send 20 packets of requested type.""" 1099cf9aa77SJakub Kicinski 1109cf9aa77SJakub Kicinski # Determine protocol and IP version for socat 1119cf9aa77SJakub Kicinski if proto_flag == "-u": 1129cf9aa77SJakub Kicinski proto = "UDP" 1139cf9aa77SJakub Kicinski else: 1149cf9aa77SJakub Kicinski proto = "TCP" 1159cf9aa77SJakub Kicinski 1169cf9aa77SJakub Kicinski baddr = f"[{cfg.addr_v['6']}]" if ipver == "6" else cfg.addr_v["4"] 1179cf9aa77SJakub Kicinski 1189cf9aa77SJakub Kicinski # Run socat in a loop to send traffic periodically 1199cf9aa77SJakub Kicinski # Use sh -c with a loop similar to toeplitz_client.sh 1209cf9aa77SJakub Kicinski socat_cmd = f""" 1219cf9aa77SJakub Kicinski for i in `seq 20`; do 1229cf9aa77SJakub Kicinski echo "msg $i" | socat -{ipver} -t 0.1 - {proto}:{baddr}:{port}; 1239cf9aa77SJakub Kicinski sleep 0.001; 1249cf9aa77SJakub Kicinski done 1259cf9aa77SJakub Kicinski """ 1269cf9aa77SJakub Kicinski 1279cf9aa77SJakub Kicinski cmd(socat_cmd, shell=True, host=cfg.remote) 1289cf9aa77SJakub Kicinski 1299cf9aa77SJakub Kicinski 1309cf9aa77SJakub Kicinskidef _test_variants(): 1319cf9aa77SJakub Kicinski for grp in ["", "rss", "rps"]: 1329cf9aa77SJakub Kicinski for l4 in ["tcp", "udp"]: 1339cf9aa77SJakub Kicinski for l3 in ["4", "6"]: 1349cf9aa77SJakub Kicinski name = f"{l4}_ipv{l3}" 1359cf9aa77SJakub Kicinski if grp: 1369cf9aa77SJakub Kicinski name = f"{grp}_{name}" 1379cf9aa77SJakub Kicinski yield KsftNamedVariant(name, "-" + l4[0], l3, grp) 1389cf9aa77SJakub Kicinski 1399cf9aa77SJakub Kicinski 1409cf9aa77SJakub Kicinski@ksft_variants(_test_variants()) 1419cf9aa77SJakub Kicinskidef test(cfg, proto_flag, ipver, grp): 1429cf9aa77SJakub Kicinski """Run a single toeplitz test.""" 1439cf9aa77SJakub Kicinski 1449cf9aa77SJakub Kicinski cfg.require_ipver(ipver) 1459cf9aa77SJakub Kicinski 1469cf9aa77SJakub Kicinski # Check that rxhash is enabled 1479cf9aa77SJakub Kicinski ksft_in("receive-hashing: on", cmd(f"ethtool -k {cfg.ifname}").stdout) 1489cf9aa77SJakub Kicinski 14927c512afSJakub Kicinski rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}}) 15027c512afSJakub Kicinski # Make sure NIC is configured to use Toeplitz hash, and no key xfrm. 15127c512afSJakub Kicinski if rss.get('hfunc') != ETH_RSS_HASH_TOP or rss.get('input-xfrm'): 15227c512afSJakub Kicinski cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex}, 15327c512afSJakub Kicinski "hfunc": ETH_RSS_HASH_TOP, 15427c512afSJakub Kicinski "input-xfrm": {}}) 15527c512afSJakub Kicinski defer(cfg.ethnl.rss_set, {"header": {"dev-index": cfg.ifindex}, 15627c512afSJakub Kicinski "hfunc": rss.get('hfunc'), 15727c512afSJakub Kicinski "input-xfrm": rss.get('input-xfrm', {}) 15827c512afSJakub Kicinski }) 15927c512afSJakub Kicinski 1609cf9aa77SJakub Kicinski port = rand_port(socket.SOCK_DGRAM) 1619cf9aa77SJakub Kicinski 1629cf9aa77SJakub Kicinski toeplitz_path = cfg.test_dir / "toeplitz" 1639cf9aa77SJakub Kicinski rx_cmd = [ 1649cf9aa77SJakub Kicinski str(toeplitz_path), 1659cf9aa77SJakub Kicinski "-" + ipver, 1669cf9aa77SJakub Kicinski proto_flag, 1679cf9aa77SJakub Kicinski "-d", str(port), 1689cf9aa77SJakub Kicinski "-i", cfg.ifname, 169*5aadc155SJakub Kicinski "-T", "4000", 1709cf9aa77SJakub Kicinski "-s", 1719cf9aa77SJakub Kicinski "-v" 1729cf9aa77SJakub Kicinski ] 1739cf9aa77SJakub Kicinski 1749cf9aa77SJakub Kicinski if grp: 1759cf9aa77SJakub Kicinski _check_rps_and_rfs_not_configured(cfg) 1769cf9aa77SJakub Kicinski if grp == "rss": 1779cf9aa77SJakub Kicinski irq_cpus = ",".join([str(x) for x in _get_irq_cpus(cfg)]) 1789cf9aa77SJakub Kicinski rx_cmd += ["-C", irq_cpus] 1799cf9aa77SJakub Kicinski ksft_pr(f"RSS using CPUs: {irq_cpus}") 1809cf9aa77SJakub Kicinski elif grp == "rps": 1819cf9aa77SJakub Kicinski # Get CPUs not used by Rx queues and configure them for RPS 1829cf9aa77SJakub Kicinski rps_cpus = _get_unused_cpus(cfg, count=2) 1839cf9aa77SJakub Kicinski rps_mask = _configure_rps(cfg, rps_cpus) 1849cf9aa77SJakub Kicinski defer(_configure_rps, cfg, []) 1859cf9aa77SJakub Kicinski rx_cmd += ["-r", rps_mask] 1869cf9aa77SJakub Kicinski ksft_pr(f"RPS using CPUs: {rps_cpus}, mask: {rps_mask}") 1879cf9aa77SJakub Kicinski 1889cf9aa77SJakub Kicinski # Run rx in background, it will exit once it has seen enough packets 1899cf9aa77SJakub Kicinski with bkg(" ".join(rx_cmd), ksft_ready=True, exit_wait=True) as rx_proc: 1909cf9aa77SJakub Kicinski while rx_proc.proc.poll() is None: 1919cf9aa77SJakub Kicinski _send_traffic(cfg, proto_flag, ipver, port) 1929cf9aa77SJakub Kicinski 1939cf9aa77SJakub Kicinski # Check rx result 1949cf9aa77SJakub Kicinski ksft_pr("Receiver output:") 1959cf9aa77SJakub Kicinski ksft_pr(rx_proc.stdout.strip().replace('\n', '\n# ')) 1969cf9aa77SJakub Kicinski if rx_proc.stderr: 1979cf9aa77SJakub Kicinski ksft_pr(rx_proc.stderr.strip().replace('\n', '\n# ')) 1989cf9aa77SJakub Kicinski 1999cf9aa77SJakub Kicinski 2009cf9aa77SJakub Kicinskidef main() -> None: 2019cf9aa77SJakub Kicinski """Ksft boilerplate main.""" 2029cf9aa77SJakub Kicinski 2039cf9aa77SJakub Kicinski with NetDrvEpEnv(__file__) as cfg: 2049cf9aa77SJakub Kicinski cfg.ethnl = EthtoolFamily() 2059cf9aa77SJakub Kicinski cfg.netnl = NetdevFamily() 2069cf9aa77SJakub Kicinski ksft_run(cases=[test], args=(cfg,)) 2079cf9aa77SJakub Kicinski ksft_exit() 2089cf9aa77SJakub Kicinski 2099cf9aa77SJakub Kicinski 2109cf9aa77SJakub Kicinskiif __name__ == "__main__": 2119cf9aa77SJakub Kicinski main() 212