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