1#!/usr/bin/env python3 2# SPDX-License-Identifier: GPL-2.0 3 4""" 5Toeplitz Rx hashing test: 6 - rxhash (the hash value calculation itself); 7 - RSS mapping from rxhash to rx queue; 8 - RPS mapping from rxhash to cpu. 9""" 10 11import glob 12import os 13import socket 14from lib.py import ksft_run, ksft_exit, ksft_pr 15from lib.py import NetDrvEpEnv, EthtoolFamily, NetdevFamily 16from lib.py import cmd, bkg, rand_port, defer 17from lib.py import ksft_in 18from lib.py import ksft_variants, KsftNamedVariant, KsftSkipEx, KsftFailEx 19 20# "define" for the ID of the Toeplitz hash function 21ETH_RSS_HASH_TOP = 1 22 23 24def _check_rps_and_rfs_not_configured(cfg): 25 """Verify that RPS is not already configured.""" 26 27 for rps_file in glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*/rps_cpus"): 28 with open(rps_file, "r", encoding="utf-8") as fp: 29 val = fp.read().strip() 30 if set(val) - {"0", ","}: 31 raise KsftSkipEx(f"RPS already configured on {rps_file}: {val}") 32 33 rfs_file = "/proc/sys/net/core/rps_sock_flow_entries" 34 with open(rfs_file, "r", encoding="utf-8") as fp: 35 val = fp.read().strip() 36 if val != "0": 37 raise KsftSkipEx(f"RFS already configured {rfs_file}: {val}") 38 39 40def _get_cpu_for_irq(irq): 41 with open(f"/proc/irq/{irq}/smp_affinity_list", "r", 42 encoding="utf-8") as fp: 43 data = fp.read().strip() 44 if "," in data or "-" in data: 45 raise KsftFailEx(f"IRQ{irq} is not mapped to a single core: {data}") 46 return int(data) 47 48 49def _get_irq_cpus(cfg): 50 """ 51 Read the list of IRQs for the device Rx queues. 52 """ 53 queues = cfg.netnl.queue_get({"ifindex": cfg.ifindex}, dump=True) 54 napis = cfg.netnl.napi_get({"ifindex": cfg.ifindex}, dump=True) 55 56 # Remap into ID-based dicts 57 napis = {n["id"]: n for n in napis} 58 queues = {f"{q['type']}{q['id']}": q for q in queues} 59 60 cpus = [] 61 for rx in range(9999): 62 name = f"rx{rx}" 63 if name not in queues: 64 break 65 cpus.append(_get_cpu_for_irq(napis[queues[name]["napi-id"]]["irq"])) 66 67 return cpus 68 69 70def _get_unused_cpus(cfg, count=2): 71 """ 72 Get CPUs that are not used by Rx queues. 73 Returns a list of at least 'count' CPU numbers. 74 """ 75 76 # Get CPUs used by Rx queues 77 rx_cpus = set(_get_irq_cpus(cfg)) 78 79 # Get total number of CPUs 80 num_cpus = os.cpu_count() 81 82 # Find unused CPUs 83 unused_cpus = [cpu for cpu in range(num_cpus) if cpu not in rx_cpus] 84 85 if len(unused_cpus) < count: 86 raise KsftSkipEx(f"Need at {count} CPUs not used by Rx queues, found {len(unused_cpus)}") 87 88 return unused_cpus[:count] 89 90 91def _configure_rps(cfg, rps_cpus): 92 """Configure RPS for all Rx queues.""" 93 94 mask = 0 95 for cpu in rps_cpus: 96 mask |= (1 << cpu) 97 98 mask = hex(mask) 99 100 # Set RPS bitmap for all rx queues 101 for rps_file in glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*/rps_cpus"): 102 with open(rps_file, "w", encoding="utf-8") as fp: 103 # sysfs expects hex without '0x' prefix, toeplitz.c needs the prefix 104 fp.write(mask[2:]) 105 106 return mask 107 108 109def _send_traffic(cfg, proto_flag, ipver, port): 110 """Send 20 packets of requested type.""" 111 112 # Determine protocol and IP version for socat 113 if proto_flag == "-u": 114 proto = "UDP" 115 else: 116 proto = "TCP" 117 118 baddr = f"[{cfg.addr_v['6']}]" if ipver == "6" else cfg.addr_v["4"] 119 120 # Run socat in a loop to send traffic periodically 121 # Use sh -c with a loop similar to toeplitz_client.sh 122 socat_cmd = f""" 123 for i in `seq 20`; do 124 echo "msg $i" | socat -{ipver} -t 0.1 - {proto}:{baddr}:{port}; 125 sleep 0.001; 126 done 127 """ 128 129 cmd(socat_cmd, shell=True, host=cfg.remote) 130 131 132def _test_variants(): 133 for grp in ["", "rss", "rps"]: 134 for l4 in ["tcp", "udp"]: 135 for l3 in ["4", "6"]: 136 name = f"{l4}_ipv{l3}" 137 if grp: 138 name = f"{grp}_{name}" 139 yield KsftNamedVariant(name, "-" + l4[0], l3, grp) 140 141 142@ksft_variants(_test_variants()) 143def test(cfg, proto_flag, ipver, grp): 144 """Run a single toeplitz test.""" 145 146 cfg.require_ipver(ipver) 147 148 # Check that rxhash is enabled 149 ksft_in("receive-hashing: on", cmd(f"ethtool -k {cfg.ifname}").stdout) 150 151 rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}}) 152 # Make sure NIC is configured to use Toeplitz hash, and no key xfrm. 153 if rss.get('hfunc') != ETH_RSS_HASH_TOP or rss.get('input-xfrm'): 154 cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex}, 155 "hfunc": ETH_RSS_HASH_TOP, 156 "input-xfrm": {}}) 157 defer(cfg.ethnl.rss_set, {"header": {"dev-index": cfg.ifindex}, 158 "hfunc": rss.get('hfunc'), 159 "input-xfrm": rss.get('input-xfrm', {}) 160 }) 161 162 port = rand_port(socket.SOCK_DGRAM) 163 164 toeplitz_path = cfg.test_dir / "toeplitz" 165 rx_cmd = [ 166 str(toeplitz_path), 167 "-" + ipver, 168 proto_flag, 169 "-d", str(port), 170 "-i", cfg.ifname, 171 "-T", "4000", 172 "-s", 173 "-v" 174 ] 175 176 if grp: 177 _check_rps_and_rfs_not_configured(cfg) 178 if grp == "rss": 179 irq_cpus = ",".join([str(x) for x in _get_irq_cpus(cfg)]) 180 rx_cmd += ["-C", irq_cpus] 181 ksft_pr(f"RSS using CPUs: {irq_cpus}") 182 elif grp == "rps": 183 # Get CPUs not used by Rx queues and configure them for RPS 184 rps_cpus = _get_unused_cpus(cfg, count=2) 185 rps_mask = _configure_rps(cfg, rps_cpus) 186 defer(_configure_rps, cfg, []) 187 rx_cmd += ["-r", rps_mask] 188 ksft_pr(f"RPS using CPUs: {rps_cpus}, mask: {rps_mask}") 189 190 # Run rx in background, it will exit once it has seen enough packets 191 with bkg(" ".join(rx_cmd), ksft_ready=True, exit_wait=True) as rx_proc: 192 while rx_proc.proc.poll() is None: 193 _send_traffic(cfg, proto_flag, ipver, port) 194 195 # Check rx result 196 ksft_pr("Receiver output:") 197 ksft_pr(rx_proc.stdout.strip().replace('\n', '\n# ')) 198 if rx_proc.stderr: 199 ksft_pr(rx_proc.stderr.strip().replace('\n', '\n# ')) 200 201 202def main() -> None: 203 """Ksft boilerplate main.""" 204 205 with NetDrvEpEnv(__file__) as cfg: 206 cfg.ethnl = EthtoolFamily() 207 cfg.netnl = NetdevFamily() 208 ksft_run(cases=[test], args=(cfg,)) 209 ksft_exit() 210 211 212if __name__ == "__main__": 213 main() 214