Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/tools/testing/selftests/drivers/net/hw/toeplitz.py
49160 views
1
#!/usr/bin/env python3
2
# SPDX-License-Identifier: GPL-2.0
3
4
"""
5
Toeplitz 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
11
import glob
12
import os
13
import socket
14
from lib.py import ksft_run, ksft_exit, ksft_pr
15
from lib.py import NetDrvEpEnv, EthtoolFamily, NetdevFamily
16
from lib.py import cmd, bkg, rand_port, defer
17
from lib.py import ksft_in
18
from lib.py import ksft_variants, KsftNamedVariant, KsftSkipEx, KsftFailEx
19
20
# "define" for the ID of the Toeplitz hash function
21
ETH_RSS_HASH_TOP = 1
22
23
24
def _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
40
def _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
49
def _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
70
def _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
91
def _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
109
def _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
132
def _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())
143
def 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
202
def 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
212
if __name__ == "__main__":
213
main()
214
215