Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/tools/testing/selftests/drivers/net/hw/gro_hw.py
171056 views
1
#!/usr/bin/env python3
2
# SPDX-License-Identifier: GPL-2.0
3
4
"""
5
HW GRO tests focusing on device machinery like stats, rather than protocol
6
processing.
7
"""
8
9
import glob
10
import re
11
12
from lib.py import ksft_run, ksft_exit, ksft_pr
13
from lib.py import ksft_eq, ksft_ge, ksft_variants
14
from lib.py import NetDrvEpEnv, NetdevFamily
15
from lib.py import KsftSkipEx
16
from lib.py import bkg, cmd, defer, ethtool, ip
17
18
19
# gro.c uses hardcoded DPORT=8000
20
GRO_DPORT = 8000
21
22
23
def _get_queue_stats(cfg, queue_id):
24
"""Get stats for a specific Rx queue."""
25
cfg.wait_hw_stats_settle()
26
data = cfg.netnl.qstats_get({"ifindex": cfg.ifindex, "scope": ["queue"]},
27
dump=True)
28
for q in data:
29
if q.get('queue-type') == 'rx' and q.get('queue-id') == queue_id:
30
return q
31
return {}
32
33
34
def _resolve_dmac(cfg, ipver):
35
"""Find the destination MAC address for sending packets."""
36
attr = "dmac" + ipver
37
if hasattr(cfg, attr):
38
return getattr(cfg, attr)
39
40
route = ip(f"-{ipver} route get {cfg.addr_v[ipver]}",
41
json=True, host=cfg.remote)[0]
42
gw = route.get("gateway")
43
if not gw:
44
setattr(cfg, attr, cfg.dev['address'])
45
return getattr(cfg, attr)
46
47
cmd(f"ping -c1 -W0 -I{cfg.remote_ifname} {gw}", host=cfg.remote)
48
neigh = ip(f"neigh get {gw} dev {cfg.remote_ifname}",
49
json=True, host=cfg.remote)[0]
50
setattr(cfg, attr, neigh['lladdr'])
51
return getattr(cfg, attr)
52
53
54
def _setup_isolated_queue(cfg):
55
"""Set up an isolated queue for testing using ntuple filter.
56
57
Remove queue 1 from the default RSS context and steer test traffic to it.
58
"""
59
test_queue = 1
60
61
qcnt = len(glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*"))
62
if qcnt < 2:
63
raise KsftSkipEx(f"Need at least 2 queues, have {qcnt}")
64
65
# Remove queue 1 from default RSS context by setting its weight to 0
66
weights = ["1"] * qcnt
67
weights[test_queue] = "0"
68
ethtool(f"-X {cfg.ifname} weight " + " ".join(weights))
69
defer(ethtool, f"-X {cfg.ifname} default")
70
71
# Set up ntuple filter to steer our test traffic to the isolated queue
72
flow = f"flow-type tcp{cfg.addr_ipver} "
73
flow += f"dst-ip {cfg.addr} dst-port {GRO_DPORT} action {test_queue}"
74
output = ethtool(f"-N {cfg.ifname} {flow}").stdout
75
ntuple_id = int(output.split()[-1])
76
defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")
77
78
return test_queue
79
80
81
def _run_gro_test(cfg, test_name, num_flows=None, ignore_fail=False,
82
order_check=False):
83
"""Run gro binary with given test and return output."""
84
if not hasattr(cfg, "bin_remote"):
85
cfg.bin_local = cfg.net_lib_dir / "gro"
86
cfg.bin_remote = cfg.remote.deploy(cfg.bin_local)
87
88
ipver = cfg.addr_ipver
89
protocol = f"--ipv{ipver}"
90
dmac = _resolve_dmac(cfg, ipver)
91
92
base_args = [
93
protocol,
94
f"--dmac {dmac}",
95
f"--smac {cfg.remote_dev['address']}",
96
f"--daddr {cfg.addr}",
97
f"--saddr {cfg.remote_addr_v[ipver]}",
98
f"--test {test_name}",
99
]
100
if num_flows:
101
base_args.append(f"--num-flows {num_flows}")
102
if order_check:
103
base_args.append("--order-check")
104
105
args = " ".join(base_args)
106
107
rx_cmd = f"{cfg.bin_local} {args} --rx --iface {cfg.ifname}"
108
tx_cmd = f"{cfg.bin_remote} {args} --iface {cfg.remote_ifname}"
109
110
with bkg(rx_cmd, ksft_ready=True, exit_wait=True, fail=False) as rx_proc:
111
cmd(tx_cmd, host=cfg.remote)
112
113
if not ignore_fail:
114
ksft_eq(rx_proc.ret, 0)
115
if rx_proc.ret != 0:
116
ksft_pr(rx_proc)
117
118
return rx_proc.stdout
119
120
121
def _require_hw_gro_stats(cfg, queue_id):
122
"""Check if device reports HW GRO stats for the queue."""
123
stats = _get_queue_stats(cfg, queue_id)
124
required = ['rx-packets', 'rx-hw-gro-packets', 'rx-hw-gro-wire-packets']
125
for stat in required:
126
if stat not in stats:
127
raise KsftSkipEx(f"Driver does not report '{stat}' via qstats")
128
129
130
def _set_ethtool_feat(cfg, current, feats):
131
"""Set ethtool features with defer to restore original state."""
132
s2n = {True: "on", False: "off"}
133
134
new = ["-K", cfg.ifname]
135
old = ["-K", cfg.ifname]
136
no_change = True
137
for name, state in feats.items():
138
new += [name, s2n[state]]
139
old += [name, s2n[current[name]["active"]]]
140
141
if current[name]["active"] != state:
142
no_change = False
143
if current[name]["fixed"]:
144
raise KsftSkipEx(f"Device does not support {name}")
145
if no_change:
146
return
147
148
eth_cmd = ethtool(" ".join(new))
149
defer(ethtool, " ".join(old))
150
151
# If ethtool printed something kernel must have modified some features
152
if eth_cmd.stdout:
153
ksft_pr(eth_cmd)
154
155
156
def _setup_hw_gro(cfg):
157
"""Enable HW GRO on the device, disabling SW GRO."""
158
feat = ethtool(f"-k {cfg.ifname}", json=True)[0]
159
160
# Try to disable SW GRO and enable HW GRO
161
_set_ethtool_feat(cfg, feat,
162
{"generic-receive-offload": False,
163
"rx-gro-hw": True,
164
"large-receive-offload": False})
165
166
# Some NICs treat HW GRO as a GRO sub-feature so disabling GRO
167
# will also clear HW GRO. Use a hack of installing XDP generic
168
# to skip SW GRO, even when enabled.
169
feat = ethtool(f"-k {cfg.ifname}", json=True)[0]
170
if not feat["rx-gro-hw"]["active"]:
171
ksft_pr("Driver clears HW GRO when SW GRO is cleared, using generic XDP workaround")
172
prog = cfg.net_lib_dir / "xdp_dummy.bpf.o"
173
ip(f"link set dev {cfg.ifname} xdpgeneric obj {prog} sec xdp")
174
defer(ip, f"link set dev {cfg.ifname} xdpgeneric off")
175
176
# Attaching XDP may change features, fetch the latest state
177
feat = ethtool(f"-k {cfg.ifname}", json=True)[0]
178
179
_set_ethtool_feat(cfg, feat,
180
{"generic-receive-offload": True,
181
"rx-gro-hw": True,
182
"large-receive-offload": False})
183
184
185
def _check_gro_stats(cfg, test_queue, stats_before,
186
expect_rx, expect_gro, expect_wire):
187
"""Validate GRO stats against expected values."""
188
stats_after = _get_queue_stats(cfg, test_queue)
189
190
rx_delta = (stats_after.get('rx-packets', 0) -
191
stats_before.get('rx-packets', 0))
192
gro_delta = (stats_after.get('rx-hw-gro-packets', 0) -
193
stats_before.get('rx-hw-gro-packets', 0))
194
wire_delta = (stats_after.get('rx-hw-gro-wire-packets', 0) -
195
stats_before.get('rx-hw-gro-wire-packets', 0))
196
197
ksft_eq(rx_delta, expect_rx, comment="rx-packets")
198
ksft_eq(gro_delta, expect_gro, comment="rx-hw-gro-packets")
199
ksft_eq(wire_delta, expect_wire, comment="rx-hw-gro-wire-packets")
200
201
202
def test_gro_stats_single(cfg):
203
"""
204
Test that a single packet doesn't affect GRO stats.
205
206
Send a single packet that cannot be coalesced (nothing to coalesce with).
207
GRO stats should not increase since no coalescing occurred.
208
rx-packets should increase by 2 (1 data + 1 FIN).
209
"""
210
_setup_hw_gro(cfg)
211
212
test_queue = _setup_isolated_queue(cfg)
213
_require_hw_gro_stats(cfg, test_queue)
214
215
stats_before = _get_queue_stats(cfg, test_queue)
216
217
_run_gro_test(cfg, "single")
218
219
# 1 data + 1 FIN = 2 rx-packets, no coalescing
220
_check_gro_stats(cfg, test_queue, stats_before,
221
expect_rx=2, expect_gro=0, expect_wire=0)
222
223
224
def test_gro_stats_full(cfg):
225
"""
226
Test GRO stats when overwhelming HW GRO capacity.
227
228
Send 500 flows to exceed HW GRO flow capacity on a single queue.
229
This should result in some packets not being coalesced.
230
Validate that qstats match what gro.c observed.
231
"""
232
_setup_hw_gro(cfg)
233
234
test_queue = _setup_isolated_queue(cfg)
235
_require_hw_gro_stats(cfg, test_queue)
236
237
num_flows = 500
238
stats_before = _get_queue_stats(cfg, test_queue)
239
240
# Run capacity test - will likely fail because not all packets coalesce
241
output = _run_gro_test(cfg, "capacity", num_flows=num_flows,
242
ignore_fail=True)
243
244
# Parse gro.c output: "STATS: received=X wire=Y coalesced=Z"
245
match = re.search(r'STATS: received=(\d+) wire=(\d+) coalesced=(\d+)',
246
output)
247
if not match:
248
raise KsftSkipEx(f"Could not parse gro.c output: {output}")
249
250
rx_frames = int(match.group(2))
251
gro_coalesced = int(match.group(3))
252
253
ksft_ge(gro_coalesced, 1,
254
comment="At least some packets should coalesce")
255
256
# received + 1 FIN, coalesced super-packets, coalesced * 2 wire packets
257
_check_gro_stats(cfg, test_queue, stats_before,
258
expect_rx=rx_frames + 1,
259
expect_gro=gro_coalesced,
260
expect_wire=gro_coalesced * 2)
261
262
263
@ksft_variants([4, 32, 512])
264
def test_gro_order(cfg, num_flows):
265
"""
266
Test that HW GRO preserves packet ordering between flows.
267
268
Packets may get delayed until the aggregate is released,
269
but reordering between aggregates and packet terminating
270
the aggregate and normal packets should not happen.
271
272
Note that this test is stricter than truly required.
273
Reordering packets between flows should not cause issues.
274
This test will also fail if traffic is run over an ECMP fabric.
275
"""
276
_setup_hw_gro(cfg)
277
_setup_isolated_queue(cfg)
278
279
_run_gro_test(cfg, "capacity", num_flows=num_flows, order_check=True)
280
281
282
def main() -> None:
283
""" Ksft boiler plate main """
284
285
with NetDrvEpEnv(__file__, nsim_test=False) as cfg:
286
cfg.netnl = NetdevFamily()
287
ksft_run([test_gro_stats_single,
288
test_gro_stats_full,
289
test_gro_order], args=(cfg,))
290
ksft_exit()
291
292
293
if __name__ == "__main__":
294
main()
295
296