xref: /linux/tools/testing/selftests/drivers/net/hw/rss_ctx.py (revision 3cf0a98fea776adb09087e521fe150c295a4b031)
1#!/usr/bin/env python3
2# SPDX-License-Identifier: GPL-2.0
3
4import datetime
5import random
6import re
7from lib.py import ksft_run, ksft_pr, ksft_exit, ksft_eq, ksft_ne, ksft_ge, ksft_lt, ksft_true
8from lib.py import NetDrvEpEnv
9from lib.py import EthtoolFamily, NetdevFamily
10from lib.py import KsftSkipEx, KsftFailEx
11from lib.py import rand_port
12from lib.py import ethtool, ip, defer, GenerateTraffic, CmdExitFailure
13
14
15def _rss_key_str(key):
16    return ":".join(["{:02x}".format(x) for x in key])
17
18
19def _rss_key_rand(length):
20    return [random.randint(0, 255) for _ in range(length)]
21
22
23def _rss_key_check(cfg, data=None, context=0):
24    if data is None:
25        data = get_rss(cfg, context=context)
26    if 'rss-hash-key' not in data:
27        return
28    non_zero = [x for x in data['rss-hash-key'] if x != 0]
29    ksft_eq(bool(non_zero), True, comment=f"RSS key is all zero {data['rss-hash-key']}")
30
31
32def get_rss(cfg, context=0):
33    return ethtool(f"-x {cfg.ifname} context {context}", json=True)[0]
34
35
36def get_drop_err_sum(cfg):
37    stats = ip("-s -s link show dev " + cfg.ifname, json=True)[0]
38    cnt = 0
39    for key in ['errors', 'dropped', 'over_errors', 'fifo_errors',
40                'length_errors', 'crc_errors', 'missed_errors',
41                'frame_errors']:
42        cnt += stats["stats64"]["rx"][key]
43    return cnt, stats["stats64"]["tx"]["carrier_changes"]
44
45
46def ethtool_create(cfg, act, opts):
47    output = ethtool(f"{act} {cfg.ifname} {opts}").stdout
48    # Output will be something like: "New RSS context is 1" or
49    # "Added rule with ID 7", we want the integer from the end
50    return int(output.split()[-1])
51
52
53def require_ntuple(cfg):
54    features = ethtool(f"-k {cfg.ifname}", json=True)[0]
55    if not features["ntuple-filters"]["active"]:
56        # ntuple is more of a capability than a config knob, don't bother
57        # trying to enable it (until some driver actually needs it).
58        raise KsftSkipEx("Ntuple filters not enabled on the device: " + str(features["ntuple-filters"]))
59
60
61# Get Rx packet counts for all queues, as a simple list of integers
62# if @prev is specified the prev counts will be subtracted
63def _get_rx_cnts(cfg, prev=None):
64    cfg.wait_hw_stats_settle()
65    data = cfg.netdevnl.qstats_get({"ifindex": cfg.ifindex, "scope": ["queue"]}, dump=True)
66    data = [x for x in data if x['queue-type'] == "rx"]
67    max_q = max([x["queue-id"] for x in data])
68    queue_stats = [0] * (max_q + 1)
69    for q in data:
70        queue_stats[q["queue-id"]] = q["rx-packets"]
71        if prev and q["queue-id"] < len(prev):
72            queue_stats[q["queue-id"]] -= prev[q["queue-id"]]
73    return queue_stats
74
75
76def _send_traffic_check(cfg, port, name, params):
77    # params is a dict with 3 possible keys:
78    #  - "target": required, which queues we expect to get iperf traffic
79    #  - "empty": optional, which queues should see no traffic at all
80    #  - "noise": optional, which queues we expect to see low traffic;
81    #             used for queues of the main context, since some background
82    #             OS activity may use those queues while we're testing
83    # the value for each is a list, or some other iterable containing queue ids.
84
85    cnts = _get_rx_cnts(cfg)
86    GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000)
87    cnts = _get_rx_cnts(cfg, prev=cnts)
88
89    directed = sum(cnts[i] for i in params['target'])
90
91    ksft_ge(directed, 20000, f"traffic on {name}: " + str(cnts))
92    if params.get('noise'):
93        ksft_lt(sum(cnts[i] for i in params['noise']), directed / 2,
94                f"traffic on other queues ({name})':" + str(cnts))
95    if params.get('empty'):
96        ksft_eq(sum(cnts[i] for i in params['empty']), 0,
97                f"traffic on inactive queues ({name}): " + str(cnts))
98
99
100def _ntuple_rule_check(cfg, rule_id, ctx_id):
101    """Check that ntuple rule references RSS context ID"""
102    text = ethtool(f"-n {cfg.ifname} rule {rule_id}").stdout
103    pattern = f"RSS Context (ID: )?{ctx_id}"
104    ksft_true(re.search(pattern, text), "RSS context not referenced in ntuple rule")
105
106
107def test_rss_key_indir(cfg):
108    """Test basics like updating the main RSS key and indirection table."""
109
110    qcnt = len(_get_rx_cnts(cfg))
111    if qcnt < 3:
112        KsftSkipEx("Device has fewer than 3 queues (or doesn't support queue stats)")
113
114    data = get_rss(cfg)
115    want_keys = ['rss-hash-key', 'rss-hash-function', 'rss-indirection-table']
116    for k in want_keys:
117        if k not in data:
118            raise KsftFailEx("ethtool results missing key: " + k)
119        if not data[k]:
120            raise KsftFailEx(f"ethtool results empty for '{k}': {data[k]}")
121
122    _rss_key_check(cfg, data=data)
123    key_len = len(data['rss-hash-key'])
124
125    # Set the key
126    key = _rss_key_rand(key_len)
127    ethtool(f"-X {cfg.ifname} hkey " + _rss_key_str(key))
128
129    data = get_rss(cfg)
130    ksft_eq(key, data['rss-hash-key'])
131
132    # Set the indirection table and the key together
133    key = _rss_key_rand(key_len)
134    ethtool(f"-X {cfg.ifname} equal 3 hkey " + _rss_key_str(key))
135    reset_indir = defer(ethtool, f"-X {cfg.ifname} default")
136
137    data = get_rss(cfg)
138    _rss_key_check(cfg, data=data)
139    ksft_eq(0, min(data['rss-indirection-table']))
140    ksft_eq(2, max(data['rss-indirection-table']))
141
142    # Reset indirection table and set the key
143    key = _rss_key_rand(key_len)
144    ethtool(f"-X {cfg.ifname} default hkey " + _rss_key_str(key))
145    data = get_rss(cfg)
146    _rss_key_check(cfg, data=data)
147    ksft_eq(0, min(data['rss-indirection-table']))
148    ksft_eq(qcnt - 1, max(data['rss-indirection-table']))
149
150    # Set the indirection table
151    ethtool(f"-X {cfg.ifname} equal 2")
152    data = get_rss(cfg)
153    ksft_eq(0, min(data['rss-indirection-table']))
154    ksft_eq(1, max(data['rss-indirection-table']))
155
156    # Check we only get traffic on the first 2 queues
157    cnts = _get_rx_cnts(cfg)
158    GenerateTraffic(cfg).wait_pkts_and_stop(20000)
159    cnts = _get_rx_cnts(cfg, prev=cnts)
160    # 2 queues, 20k packets, must be at least 5k per queue
161    ksft_ge(cnts[0], 5000, "traffic on main context (1/2): " + str(cnts))
162    ksft_ge(cnts[1], 5000, "traffic on main context (2/2): " + str(cnts))
163    # The other queues should be unused
164    ksft_eq(sum(cnts[2:]), 0, "traffic on unused queues: " + str(cnts))
165
166    # Restore, and check traffic gets spread again
167    reset_indir.exec()
168
169    cnts = _get_rx_cnts(cfg)
170    GenerateTraffic(cfg).wait_pkts_and_stop(20000)
171    cnts = _get_rx_cnts(cfg, prev=cnts)
172    # First two queues get less traffic than all the rest
173    ksft_lt(sum(cnts[:2]), sum(cnts[2:]), "traffic distributed: " + str(cnts))
174
175
176def test_rss_queue_reconfigure(cfg, main_ctx=True):
177    """Make sure queue changes can't override requested RSS config.
178
179    By default main RSS table should change to include all queues.
180    When user sets a specific RSS config the driver should preserve it,
181    even when queue count changes. Driver should refuse to deactivate
182    queues used in the user-set RSS config.
183    """
184
185    if not main_ctx:
186        require_ntuple(cfg)
187
188    # Start with 4 queues, an arbitrary known number.
189    try:
190        qcnt = len(_get_rx_cnts(cfg))
191        ethtool(f"-L {cfg.ifname} combined 4")
192        defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")
193    except:
194        raise KsftSkipEx("Not enough queues for the test or qstat not supported")
195
196    if main_ctx:
197        ctx_id = 0
198        ctx_ref = ""
199    else:
200        ctx_id = ethtool_create(cfg, "-X", "context new")
201        ctx_ref = f"context {ctx_id}"
202        defer(ethtool, f"-X {cfg.ifname} {ctx_ref} delete")
203
204    # Indirection table should be distributing to all queues.
205    data = get_rss(cfg, context=ctx_id)
206    ksft_eq(0, min(data['rss-indirection-table']))
207    ksft_eq(3, max(data['rss-indirection-table']))
208
209    # Increase queues, indirection table should be distributing to all queues.
210    # It's unclear whether tables of additional contexts should be reset, too.
211    if main_ctx:
212        ethtool(f"-L {cfg.ifname} combined 5")
213        data = get_rss(cfg)
214        ksft_eq(0, min(data['rss-indirection-table']))
215        ksft_eq(4, max(data['rss-indirection-table']))
216        ethtool(f"-L {cfg.ifname} combined 4")
217
218    # Configure the table explicitly
219    port = rand_port()
220    ethtool(f"-X {cfg.ifname} {ctx_ref} weight 1 0 0 1")
221    if main_ctx:
222        other_key = 'empty'
223        defer(ethtool, f"-X {cfg.ifname} default")
224    else:
225        other_key = 'noise'
226        flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {ctx_id}"
227        ntuple = ethtool_create(cfg, "-N", flow)
228        defer(ethtool, f"-N {cfg.ifname} delete {ntuple}")
229
230    _send_traffic_check(cfg, port, ctx_ref, { 'target': (0, 3),
231                                              other_key: (1, 2) })
232
233    # We should be able to increase queues, but table should be left untouched
234    ethtool(f"-L {cfg.ifname} combined 5")
235    data = get_rss(cfg, context=ctx_id)
236    ksft_eq({0, 3}, set(data['rss-indirection-table']))
237
238    _send_traffic_check(cfg, port, ctx_ref, { 'target': (0, 3),
239                                              other_key: (1, 2, 4) })
240
241    # Setting queue count to 3 should fail, queue 3 is used
242    try:
243        ethtool(f"-L {cfg.ifname} combined 3")
244    except CmdExitFailure:
245        pass
246    else:
247        raise Exception(f"Driver didn't prevent us from deactivating a used queue (context {ctx_id})")
248
249    if not main_ctx:
250        ethtool(f"-L {cfg.ifname} combined 4")
251        flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {ctx_id} action 1"
252        try:
253            # this targets queue 4, which doesn't exist
254            ntuple2 = ethtool_create(cfg, "-N", flow)
255            defer(ethtool, f"-N {cfg.ifname} delete {ntuple2}")
256        except CmdExitFailure:
257            pass
258        else:
259            raise Exception(f"Driver didn't prevent us from targeting a nonexistent queue (context {ctx_id})")
260        # change the table to target queues 0 and 2
261        ethtool(f"-X {cfg.ifname} {ctx_ref} weight 1 0 1 0")
262        # ntuple rule therefore targets queues 1 and 3
263        try:
264            ntuple2 = ethtool_create(cfg, "-N", flow)
265        except CmdExitFailure:
266            ksft_pr("Driver does not support rss + queue offset")
267            return
268
269        defer(ethtool, f"-N {cfg.ifname} delete {ntuple2}")
270        # should replace existing filter
271        ksft_eq(ntuple, ntuple2)
272        _send_traffic_check(cfg, port, ctx_ref, { 'target': (1, 3),
273                                                  'noise' : (0, 2) })
274        # Setting queue count to 3 should fail, queue 3 is used
275        try:
276            ethtool(f"-L {cfg.ifname} combined 3")
277        except CmdExitFailure:
278            pass
279        else:
280            raise Exception(f"Driver didn't prevent us from deactivating a used queue (context {ctx_id})")
281
282
283def test_rss_resize(cfg):
284    """Test resizing of the RSS table.
285
286    Some devices dynamically increase and decrease the size of the RSS
287    indirection table based on the number of enabled queues.
288    When that happens driver must maintain the balance of entries
289    (preferably duplicating the smaller table).
290    """
291
292    channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})
293    ch_max = channels['combined-max']
294    qcnt = channels['combined-count']
295
296    if ch_max < 2:
297        raise KsftSkipEx(f"Not enough queues for the test: {ch_max}")
298
299    ethtool(f"-L {cfg.ifname} combined 2")
300    defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")
301
302    ethtool(f"-X {cfg.ifname} weight 1 7")
303    defer(ethtool, f"-X {cfg.ifname} default")
304
305    ethtool(f"-L {cfg.ifname} combined {ch_max}")
306    data = get_rss(cfg)
307    ksft_eq(0, min(data['rss-indirection-table']))
308    ksft_eq(1, max(data['rss-indirection-table']))
309
310    ksft_eq(7,
311            data['rss-indirection-table'].count(1) /
312            data['rss-indirection-table'].count(0),
313            f"Table imbalance after resize: {data['rss-indirection-table']}")
314
315
316def test_hitless_key_update(cfg):
317    """Test that flows may be rehashed without impacting traffic.
318
319    Some workloads may want to rehash the flows in response to an imbalance.
320    Most effective way to do that is changing the RSS key. Check that changing
321    the key does not cause link flaps or traffic disruption.
322
323    Disrupting traffic for key update is not a bug, but makes the key
324    update unusable for rehashing under load.
325    """
326    data = get_rss(cfg)
327    key_len = len(data['rss-hash-key'])
328
329    key = _rss_key_rand(key_len)
330
331    tgen = GenerateTraffic(cfg)
332    try:
333        errors0, carrier0 = get_drop_err_sum(cfg)
334        t0 = datetime.datetime.now()
335        ethtool(f"-X {cfg.ifname} hkey " + _rss_key_str(key))
336        t1 = datetime.datetime.now()
337        errors1, carrier1 = get_drop_err_sum(cfg)
338    finally:
339        tgen.wait_pkts_and_stop(5000)
340
341    ksft_lt((t1 - t0).total_seconds(), 0.2)
342    ksft_eq(errors1 - errors1, 0)
343    ksft_eq(carrier1 - carrier0, 0)
344
345
346def test_rss_context_dump(cfg):
347    """
348    Test dumping RSS contexts. This tests mostly exercises the kernel APIs.
349    """
350
351    # Get a random key of the right size
352    data = get_rss(cfg)
353    if 'rss-hash-key' in data:
354        key_data = _rss_key_rand(len(data['rss-hash-key']))
355        key = _rss_key_str(key_data)
356    else:
357        key_data = []
358        key = "ba:ad"
359
360    ids = []
361    try:
362        ids.append(ethtool_create(cfg, "-X", f"context new"))
363        defer(ethtool, f"-X {cfg.ifname} context {ids[-1]} delete")
364
365        ids.append(ethtool_create(cfg, "-X", f"context new weight 1 1"))
366        defer(ethtool, f"-X {cfg.ifname} context {ids[-1]} delete")
367
368        ids.append(ethtool_create(cfg, "-X", f"context new hkey {key}"))
369        defer(ethtool, f"-X {cfg.ifname} context {ids[-1]} delete")
370    except CmdExitFailure:
371        if not ids:
372            raise KsftSkipEx("Unable to add any contexts")
373        ksft_pr(f"Added only {len(ids)} out of 3 contexts")
374
375    expect_tuples = set([(cfg.ifname, -1)] + [(cfg.ifname, ctx_id) for ctx_id in ids])
376
377    # Dump all
378    ctxs = cfg.ethnl.rss_get({}, dump=True)
379    tuples = [(c['header']['dev-name'], c.get('context', -1)) for c in ctxs]
380    ksft_eq(len(tuples), len(set(tuples)), "duplicates in context dump")
381    ctx_tuples = set([ctx for ctx in tuples if ctx[0] == cfg.ifname])
382    ksft_eq(expect_tuples, ctx_tuples)
383
384    # Sanity-check the results
385    for data in ctxs:
386        ksft_ne(set(data['indir']), {0}, "indir table is all zero")
387        ksft_ne(set(data.get('hkey', [1])), {0}, "key is all zero")
388
389        # More specific checks
390        if len(ids) > 1 and data.get('context') == ids[1]:
391            ksft_eq(set(data['indir']), {0, 1},
392                    "ctx1 - indir table mismatch")
393        if len(ids) > 2 and data.get('context') == ids[2]:
394            ksft_eq(data['hkey'], bytes(key_data), "ctx2 - key mismatch")
395
396    # Ifindex filter
397    ctxs = cfg.ethnl.rss_get({'header': {'dev-name': cfg.ifname}}, dump=True)
398    tuples = [(c['header']['dev-name'], c.get('context', -1)) for c in ctxs]
399    ctx_tuples = set(tuples)
400    ksft_eq(len(tuples), len(ctx_tuples), "duplicates in context dump")
401    ksft_eq(expect_tuples, ctx_tuples)
402
403    # Skip ctx 0
404    expect_tuples.remove((cfg.ifname, -1))
405
406    ctxs = cfg.ethnl.rss_get({'start-context': 1}, dump=True)
407    tuples = [(c['header']['dev-name'], c.get('context', -1)) for c in ctxs]
408    ksft_eq(len(tuples), len(set(tuples)), "duplicates in context dump")
409    ctx_tuples = set([ctx for ctx in tuples if ctx[0] == cfg.ifname])
410    ksft_eq(expect_tuples, ctx_tuples)
411
412    # And finally both with ifindex and skip main
413    ctxs = cfg.ethnl.rss_get({'header': {'dev-name': cfg.ifname}, 'start-context': 1}, dump=True)
414    ctx_tuples = set([(c['header']['dev-name'], c.get('context', -1)) for c in ctxs])
415    ksft_eq(expect_tuples, ctx_tuples)
416
417
418def test_rss_context(cfg, ctx_cnt=1, create_with_cfg=None):
419    """
420    Test separating traffic into RSS contexts.
421    The queues will be allocated 2 for each context:
422     ctx0  ctx1  ctx2  ctx3
423    [0 1] [2 3] [4 5] [6 7] ...
424    """
425
426    require_ntuple(cfg)
427
428    requested_ctx_cnt = ctx_cnt
429
430    # Try to allocate more queues when necessary
431    qcnt = len(_get_rx_cnts(cfg))
432    if qcnt < 2 + 2 * ctx_cnt:
433        try:
434            ksft_pr(f"Increasing queue count {qcnt} -> {2 + 2 * ctx_cnt}")
435            ethtool(f"-L {cfg.ifname} combined {2 + 2 * ctx_cnt}")
436            defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")
437        except:
438            raise KsftSkipEx("Not enough queues for the test")
439
440    ports = []
441
442    # Use queues 0 and 1 for normal traffic
443    ethtool(f"-X {cfg.ifname} equal 2")
444    defer(ethtool, f"-X {cfg.ifname} default")
445
446    for i in range(ctx_cnt):
447        want_cfg = f"start {2 + i * 2} equal 2"
448        create_cfg = want_cfg if create_with_cfg else ""
449
450        try:
451            ctx_id = ethtool_create(cfg, "-X", f"context new {create_cfg}")
452            defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
453        except CmdExitFailure:
454            # try to carry on and skip at the end
455            if i == 0:
456                raise
457            ksft_pr(f"Failed to create context {i + 1}, trying to test what we got")
458            ctx_cnt = i
459            break
460
461        _rss_key_check(cfg, context=ctx_id)
462
463        if not create_with_cfg:
464            ethtool(f"-X {cfg.ifname} context {ctx_id} {want_cfg}")
465            _rss_key_check(cfg, context=ctx_id)
466
467        # Sanity check the context we just created
468        data = get_rss(cfg, ctx_id)
469        ksft_eq(min(data['rss-indirection-table']), 2 + i * 2, "Unexpected context cfg: " + str(data))
470        ksft_eq(max(data['rss-indirection-table']), 2 + i * 2 + 1, "Unexpected context cfg: " + str(data))
471
472        ports.append(rand_port())
473        flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {ports[i]} context {ctx_id}"
474        ntuple = ethtool_create(cfg, "-N", flow)
475        defer(ethtool, f"-N {cfg.ifname} delete {ntuple}")
476
477        _ntuple_rule_check(cfg, ntuple, ctx_id)
478
479    for i in range(ctx_cnt):
480        _send_traffic_check(cfg, ports[i], f"context {i}",
481                            { 'target': (2+i*2, 3+i*2),
482                              'noise': (0, 1),
483                              'empty': list(range(2, 2+i*2)) + list(range(4+i*2, 2+2*ctx_cnt)) })
484
485    if requested_ctx_cnt != ctx_cnt:
486        raise KsftSkipEx(f"Tested only {ctx_cnt} contexts, wanted {requested_ctx_cnt}")
487
488
489def test_rss_context4(cfg):
490    test_rss_context(cfg, 4)
491
492
493def test_rss_context32(cfg):
494    test_rss_context(cfg, 32)
495
496
497def test_rss_context4_create_with_cfg(cfg):
498    test_rss_context(cfg, 4, create_with_cfg=True)
499
500
501def test_rss_context_queue_reconfigure(cfg):
502    test_rss_queue_reconfigure(cfg, main_ctx=False)
503
504
505def test_rss_context_out_of_order(cfg, ctx_cnt=4):
506    """
507    Test separating traffic into RSS contexts.
508    Contexts are removed in semi-random order, and steering re-tested
509    to make sure removal doesn't break steering to surviving contexts.
510    Test requires 3 contexts to work.
511    """
512
513    require_ntuple(cfg)
514
515    requested_ctx_cnt = ctx_cnt
516
517    # Try to allocate more queues when necessary
518    qcnt = len(_get_rx_cnts(cfg))
519    if qcnt < 2 + 2 * ctx_cnt:
520        try:
521            ksft_pr(f"Increasing queue count {qcnt} -> {2 + 2 * ctx_cnt}")
522            ethtool(f"-L {cfg.ifname} combined {2 + 2 * ctx_cnt}")
523            defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")
524        except:
525            raise KsftSkipEx("Not enough queues for the test")
526
527    ntuple = []
528    ctx = []
529    ports = []
530
531    def remove_ctx(idx):
532        ntuple[idx].exec()
533        ntuple[idx] = None
534        ctx[idx].exec()
535        ctx[idx] = None
536
537    def check_traffic():
538        for i in range(ctx_cnt):
539            if ctx[i]:
540                expected = {
541                    'target': (2+i*2, 3+i*2),
542                    'noise': (0, 1),
543                    'empty': list(range(2, 2+i*2)) + list(range(4+i*2, 2+2*ctx_cnt))
544                }
545            else:
546                expected = {
547                    'target': (0, 1),
548                    'empty':  range(2, 2+2*ctx_cnt)
549                }
550
551            _send_traffic_check(cfg, ports[i], f"context {i}", expected)
552
553    # Use queues 0 and 1 for normal traffic
554    ethtool(f"-X {cfg.ifname} equal 2")
555    defer(ethtool, f"-X {cfg.ifname} default")
556
557    for i in range(ctx_cnt):
558        ctx_id = ethtool_create(cfg, "-X", f"context new start {2 + i * 2} equal 2")
559        ctx.append(defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete"))
560
561        ports.append(rand_port())
562        flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {ports[i]} context {ctx_id}"
563        ntuple_id = ethtool_create(cfg, "-N", flow)
564        ntuple.append(defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}"))
565
566    check_traffic()
567
568    # Remove middle context
569    remove_ctx(ctx_cnt // 2)
570    check_traffic()
571
572    # Remove first context
573    remove_ctx(0)
574    check_traffic()
575
576    # Remove last context
577    remove_ctx(-1)
578    check_traffic()
579
580    if requested_ctx_cnt != ctx_cnt:
581        raise KsftSkipEx(f"Tested only {ctx_cnt} contexts, wanted {requested_ctx_cnt}")
582
583
584def test_rss_context_overlap(cfg, other_ctx=0):
585    """
586    Test contexts overlapping with each other.
587    Use 4 queues for the main context, but only queues 2 and 3 for context 1.
588    """
589
590    require_ntuple(cfg)
591
592    queue_cnt = len(_get_rx_cnts(cfg))
593    if queue_cnt < 4:
594        try:
595            ksft_pr(f"Increasing queue count {queue_cnt} -> 4")
596            ethtool(f"-L {cfg.ifname} combined 4")
597            defer(ethtool, f"-L {cfg.ifname} combined {queue_cnt}")
598        except:
599            raise KsftSkipEx("Not enough queues for the test")
600
601    if other_ctx == 0:
602        ethtool(f"-X {cfg.ifname} equal 4")
603        defer(ethtool, f"-X {cfg.ifname} default")
604    else:
605        other_ctx = ethtool_create(cfg, "-X", "context new")
606        ethtool(f"-X {cfg.ifname} context {other_ctx} equal 4")
607        defer(ethtool, f"-X {cfg.ifname} context {other_ctx} delete")
608
609    ctx_id = ethtool_create(cfg, "-X", "context new")
610    ethtool(f"-X {cfg.ifname} context {ctx_id} start 2 equal 2")
611    defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
612
613    port = rand_port()
614    if other_ctx:
615        flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {other_ctx}"
616        ntuple_id = ethtool_create(cfg, "-N", flow)
617        ntuple = defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")
618
619    # Test the main context
620    cnts = _get_rx_cnts(cfg)
621    GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000)
622    cnts = _get_rx_cnts(cfg, prev=cnts)
623
624    ksft_ge(sum(cnts[ :4]), 20000, "traffic on main context: " + str(cnts))
625    ksft_ge(sum(cnts[ :2]),  7000, "traffic on main context (1/2): " + str(cnts))
626    ksft_ge(sum(cnts[2:4]),  7000, "traffic on main context (2/2): " + str(cnts))
627    if other_ctx == 0:
628        ksft_eq(sum(cnts[4: ]),     0, "traffic on other queues: " + str(cnts))
629
630    # Now create a rule for context 1 and make sure traffic goes to a subset
631    if other_ctx:
632        ntuple.exec()
633    flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {ctx_id}"
634    ntuple_id = ethtool_create(cfg, "-N", flow)
635    defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")
636
637    cnts = _get_rx_cnts(cfg)
638    GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000)
639    cnts = _get_rx_cnts(cfg, prev=cnts)
640
641    directed = sum(cnts[2:4])
642    ksft_lt(sum(cnts[ :2]), directed / 2, "traffic on main context: " + str(cnts))
643    ksft_ge(directed, 20000, "traffic on extra context: " + str(cnts))
644    if other_ctx == 0:
645        ksft_eq(sum(cnts[4: ]),     0, "traffic on other queues: " + str(cnts))
646
647
648def test_rss_context_overlap2(cfg):
649    test_rss_context_overlap(cfg, True)
650
651
652def test_delete_rss_context_busy(cfg):
653    """
654    Test that deletion returns -EBUSY when an rss context is being used
655    by an ntuple filter.
656    """
657
658    require_ntuple(cfg)
659
660    # create additional rss context
661    ctx_id = ethtool_create(cfg, "-X", "context new")
662    ctx_deleter = defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
663
664    # utilize context from ntuple filter
665    port = rand_port()
666    flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {ctx_id}"
667    ntuple_id = ethtool_create(cfg, "-N", flow)
668    defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")
669
670    # attempt to delete in-use context
671    try:
672        ctx_deleter.exec_only()
673        ctx_deleter.cancel()
674        raise KsftFailEx(f"deleted context {ctx_id} used by rule {ntuple_id}")
675    except CmdExitFailure:
676        pass
677
678
679def test_rss_ntuple_addition(cfg):
680    """
681    Test that the queue offset (ring_cookie) of an ntuple rule is added
682    to the queue number read from the indirection table.
683    """
684
685    require_ntuple(cfg)
686
687    queue_cnt = len(_get_rx_cnts(cfg))
688    if queue_cnt < 4:
689        try:
690            ksft_pr(f"Increasing queue count {queue_cnt} -> 4")
691            ethtool(f"-L {cfg.ifname} combined 4")
692            defer(ethtool, f"-L {cfg.ifname} combined {queue_cnt}")
693        except:
694            raise KsftSkipEx("Not enough queues for the test")
695
696    # Use queue 0 for normal traffic
697    ethtool(f"-X {cfg.ifname} equal 1")
698    defer(ethtool, f"-X {cfg.ifname} default")
699
700    # create additional rss context
701    ctx_id = ethtool_create(cfg, "-X", "context new equal 2")
702    defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
703
704    # utilize context from ntuple filter
705    port = rand_port()
706    flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {ctx_id} action 2"
707    try:
708        ntuple_id = ethtool_create(cfg, "-N", flow)
709    except CmdExitFailure:
710        raise KsftSkipEx("Ntuple filter with RSS and nonzero action not supported")
711    defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")
712
713    _send_traffic_check(cfg, port, f"context {ctx_id}", { 'target': (2, 3),
714                                                          'empty' : (1,),
715                                                          'noise' : (0,) })
716
717
718def main() -> None:
719    with NetDrvEpEnv(__file__, nsim_test=False) as cfg:
720        cfg.ethnl = EthtoolFamily()
721        cfg.netdevnl = NetdevFamily()
722
723        ksft_run([test_rss_key_indir, test_rss_queue_reconfigure,
724                  test_rss_resize, test_hitless_key_update,
725                  test_rss_context, test_rss_context4, test_rss_context32,
726                  test_rss_context_dump, test_rss_context_queue_reconfigure,
727                  test_rss_context_overlap, test_rss_context_overlap2,
728                  test_rss_context_out_of_order, test_rss_context4_create_with_cfg,
729                  test_delete_rss_context_busy, test_rss_ntuple_addition],
730                 args=(cfg, ))
731    ksft_exit()
732
733
734if __name__ == "__main__":
735    main()
736