xref: /linux/tools/testing/selftests/drivers/net/hw/rss_ctx.py (revision 896d8946da97332d4dc80fa1937d8dd6b1c35ad4)
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        except CmdExitFailure:
256            pass
257        else:
258            raise Exception(f"Driver didn't prevent us from targeting a nonexistent queue (context {ctx_id})")
259        # change the table to target queues 0 and 2
260        ethtool(f"-X {cfg.ifname} {ctx_ref} weight 1 0 1 0")
261        # ntuple rule therefore targets queues 1 and 3
262        ntuple2 = ethtool_create(cfg, "-N", flow)
263        # should replace existing filter
264        ksft_eq(ntuple, ntuple2)
265        _send_traffic_check(cfg, port, ctx_ref, { 'target': (1, 3),
266                                                  'noise' : (0, 2) })
267        # Setting queue count to 3 should fail, queue 3 is used
268        try:
269            ethtool(f"-L {cfg.ifname} combined 3")
270        except CmdExitFailure:
271            pass
272        else:
273            raise Exception(f"Driver didn't prevent us from deactivating a used queue (context {ctx_id})")
274
275
276def test_rss_resize(cfg):
277    """Test resizing of the RSS table.
278
279    Some devices dynamically increase and decrease the size of the RSS
280    indirection table based on the number of enabled queues.
281    When that happens driver must maintain the balance of entries
282    (preferably duplicating the smaller table).
283    """
284
285    channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})
286    ch_max = channels['combined-max']
287    qcnt = channels['combined-count']
288
289    if ch_max < 2:
290        raise KsftSkipEx(f"Not enough queues for the test: {ch_max}")
291
292    ethtool(f"-L {cfg.ifname} combined 2")
293    defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")
294
295    ethtool(f"-X {cfg.ifname} weight 1 7")
296    defer(ethtool, f"-X {cfg.ifname} default")
297
298    ethtool(f"-L {cfg.ifname} combined {ch_max}")
299    data = get_rss(cfg)
300    ksft_eq(0, min(data['rss-indirection-table']))
301    ksft_eq(1, max(data['rss-indirection-table']))
302
303    ksft_eq(7,
304            data['rss-indirection-table'].count(1) /
305            data['rss-indirection-table'].count(0),
306            f"Table imbalance after resize: {data['rss-indirection-table']}")
307
308
309def test_hitless_key_update(cfg):
310    """Test that flows may be rehashed without impacting traffic.
311
312    Some workloads may want to rehash the flows in response to an imbalance.
313    Most effective way to do that is changing the RSS key. Check that changing
314    the key does not cause link flaps or traffic disruption.
315
316    Disrupting traffic for key update is not a bug, but makes the key
317    update unusable for rehashing under load.
318    """
319    data = get_rss(cfg)
320    key_len = len(data['rss-hash-key'])
321
322    key = _rss_key_rand(key_len)
323
324    tgen = GenerateTraffic(cfg)
325    try:
326        errors0, carrier0 = get_drop_err_sum(cfg)
327        t0 = datetime.datetime.now()
328        ethtool(f"-X {cfg.ifname} hkey " + _rss_key_str(key))
329        t1 = datetime.datetime.now()
330        errors1, carrier1 = get_drop_err_sum(cfg)
331    finally:
332        tgen.wait_pkts_and_stop(5000)
333
334    ksft_lt((t1 - t0).total_seconds(), 0.2)
335    ksft_eq(errors1 - errors1, 0)
336    ksft_eq(carrier1 - carrier0, 0)
337
338
339def test_rss_context_dump(cfg):
340    """
341    Test dumping RSS contexts. This tests mostly exercises the kernel APIs.
342    """
343
344    # Get a random key of the right size
345    data = get_rss(cfg)
346    if 'rss-hash-key' in data:
347        key_data = _rss_key_rand(len(data['rss-hash-key']))
348        key = _rss_key_str(key_data)
349    else:
350        key_data = []
351        key = "ba:ad"
352
353    ids = []
354    try:
355        ids.append(ethtool_create(cfg, "-X", f"context new"))
356        defer(ethtool, f"-X {cfg.ifname} context {ids[-1]} delete")
357
358        ids.append(ethtool_create(cfg, "-X", f"context new weight 1 1"))
359        defer(ethtool, f"-X {cfg.ifname} context {ids[-1]} delete")
360
361        ids.append(ethtool_create(cfg, "-X", f"context new hkey {key}"))
362        defer(ethtool, f"-X {cfg.ifname} context {ids[-1]} delete")
363    except CmdExitFailure:
364        if not ids:
365            raise KsftSkipEx("Unable to add any contexts")
366        ksft_pr(f"Added only {len(ids)} out of 3 contexts")
367
368    expect_tuples = set([(cfg.ifname, -1)] + [(cfg.ifname, ctx_id) for ctx_id in ids])
369
370    # Dump all
371    ctxs = cfg.ethnl.rss_get({}, dump=True)
372    tuples = [(c['header']['dev-name'], c.get('context', -1)) for c in ctxs]
373    ksft_eq(len(tuples), len(set(tuples)), "duplicates in context dump")
374    ctx_tuples = set([ctx for ctx in tuples if ctx[0] == cfg.ifname])
375    ksft_eq(expect_tuples, ctx_tuples)
376
377    # Sanity-check the results
378    for data in ctxs:
379        ksft_ne(set(data['indir']), {0}, "indir table is all zero")
380        ksft_ne(set(data.get('hkey', [1])), {0}, "key is all zero")
381
382        # More specific checks
383        if len(ids) > 1 and data.get('context') == ids[1]:
384            ksft_eq(set(data['indir']), {0, 1},
385                    "ctx1 - indir table mismatch")
386        if len(ids) > 2 and data.get('context') == ids[2]:
387            ksft_eq(data['hkey'], bytes(key_data), "ctx2 - key mismatch")
388
389    # Ifindex filter
390    ctxs = cfg.ethnl.rss_get({'header': {'dev-name': cfg.ifname}}, dump=True)
391    tuples = [(c['header']['dev-name'], c.get('context', -1)) for c in ctxs]
392    ctx_tuples = set(tuples)
393    ksft_eq(len(tuples), len(ctx_tuples), "duplicates in context dump")
394    ksft_eq(expect_tuples, ctx_tuples)
395
396    # Skip ctx 0
397    expect_tuples.remove((cfg.ifname, -1))
398
399    ctxs = cfg.ethnl.rss_get({'start-context': 1}, dump=True)
400    tuples = [(c['header']['dev-name'], c.get('context', -1)) for c in ctxs]
401    ksft_eq(len(tuples), len(set(tuples)), "duplicates in context dump")
402    ctx_tuples = set([ctx for ctx in tuples if ctx[0] == cfg.ifname])
403    ksft_eq(expect_tuples, ctx_tuples)
404
405    # And finally both with ifindex and skip main
406    ctxs = cfg.ethnl.rss_get({'header': {'dev-name': cfg.ifname}, 'start-context': 1}, dump=True)
407    ctx_tuples = set([(c['header']['dev-name'], c.get('context', -1)) for c in ctxs])
408    ksft_eq(expect_tuples, ctx_tuples)
409
410
411def test_rss_context(cfg, ctx_cnt=1, create_with_cfg=None):
412    """
413    Test separating traffic into RSS contexts.
414    The queues will be allocated 2 for each context:
415     ctx0  ctx1  ctx2  ctx3
416    [0 1] [2 3] [4 5] [6 7] ...
417    """
418
419    require_ntuple(cfg)
420
421    requested_ctx_cnt = ctx_cnt
422
423    # Try to allocate more queues when necessary
424    qcnt = len(_get_rx_cnts(cfg))
425    if qcnt < 2 + 2 * ctx_cnt:
426        try:
427            ksft_pr(f"Increasing queue count {qcnt} -> {2 + 2 * ctx_cnt}")
428            ethtool(f"-L {cfg.ifname} combined {2 + 2 * ctx_cnt}")
429            defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")
430        except:
431            raise KsftSkipEx("Not enough queues for the test")
432
433    ports = []
434
435    # Use queues 0 and 1 for normal traffic
436    ethtool(f"-X {cfg.ifname} equal 2")
437    defer(ethtool, f"-X {cfg.ifname} default")
438
439    for i in range(ctx_cnt):
440        want_cfg = f"start {2 + i * 2} equal 2"
441        create_cfg = want_cfg if create_with_cfg else ""
442
443        try:
444            ctx_id = ethtool_create(cfg, "-X", f"context new {create_cfg}")
445            defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
446        except CmdExitFailure:
447            # try to carry on and skip at the end
448            if i == 0:
449                raise
450            ksft_pr(f"Failed to create context {i + 1}, trying to test what we got")
451            ctx_cnt = i
452            break
453
454        _rss_key_check(cfg, context=ctx_id)
455
456        if not create_with_cfg:
457            ethtool(f"-X {cfg.ifname} context {ctx_id} {want_cfg}")
458            _rss_key_check(cfg, context=ctx_id)
459
460        # Sanity check the context we just created
461        data = get_rss(cfg, ctx_id)
462        ksft_eq(min(data['rss-indirection-table']), 2 + i * 2, "Unexpected context cfg: " + str(data))
463        ksft_eq(max(data['rss-indirection-table']), 2 + i * 2 + 1, "Unexpected context cfg: " + str(data))
464
465        ports.append(rand_port())
466        flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {ports[i]} context {ctx_id}"
467        ntuple = ethtool_create(cfg, "-N", flow)
468        defer(ethtool, f"-N {cfg.ifname} delete {ntuple}")
469
470        _ntuple_rule_check(cfg, ntuple, ctx_id)
471
472    for i in range(ctx_cnt):
473        _send_traffic_check(cfg, ports[i], f"context {i}",
474                            { 'target': (2+i*2, 3+i*2),
475                              'noise': (0, 1),
476                              'empty': list(range(2, 2+i*2)) + list(range(4+i*2, 2+2*ctx_cnt)) })
477
478    if requested_ctx_cnt != ctx_cnt:
479        raise KsftSkipEx(f"Tested only {ctx_cnt} contexts, wanted {requested_ctx_cnt}")
480
481
482def test_rss_context4(cfg):
483    test_rss_context(cfg, 4)
484
485
486def test_rss_context32(cfg):
487    test_rss_context(cfg, 32)
488
489
490def test_rss_context4_create_with_cfg(cfg):
491    test_rss_context(cfg, 4, create_with_cfg=True)
492
493
494def test_rss_context_queue_reconfigure(cfg):
495    test_rss_queue_reconfigure(cfg, main_ctx=False)
496
497
498def test_rss_context_out_of_order(cfg, ctx_cnt=4):
499    """
500    Test separating traffic into RSS contexts.
501    Contexts are removed in semi-random order, and steering re-tested
502    to make sure removal doesn't break steering to surviving contexts.
503    Test requires 3 contexts to work.
504    """
505
506    require_ntuple(cfg)
507
508    requested_ctx_cnt = ctx_cnt
509
510    # Try to allocate more queues when necessary
511    qcnt = len(_get_rx_cnts(cfg))
512    if qcnt < 2 + 2 * ctx_cnt:
513        try:
514            ksft_pr(f"Increasing queue count {qcnt} -> {2 + 2 * ctx_cnt}")
515            ethtool(f"-L {cfg.ifname} combined {2 + 2 * ctx_cnt}")
516            defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")
517        except:
518            raise KsftSkipEx("Not enough queues for the test")
519
520    ntuple = []
521    ctx = []
522    ports = []
523
524    def remove_ctx(idx):
525        ntuple[idx].exec()
526        ntuple[idx] = None
527        ctx[idx].exec()
528        ctx[idx] = None
529
530    def check_traffic():
531        for i in range(ctx_cnt):
532            if ctx[i]:
533                expected = {
534                    'target': (2+i*2, 3+i*2),
535                    'noise': (0, 1),
536                    'empty': list(range(2, 2+i*2)) + list(range(4+i*2, 2+2*ctx_cnt))
537                }
538            else:
539                expected = {
540                    'target': (0, 1),
541                    'empty':  range(2, 2+2*ctx_cnt)
542                }
543
544            _send_traffic_check(cfg, ports[i], f"context {i}", expected)
545
546    # Use queues 0 and 1 for normal traffic
547    ethtool(f"-X {cfg.ifname} equal 2")
548    defer(ethtool, f"-X {cfg.ifname} default")
549
550    for i in range(ctx_cnt):
551        ctx_id = ethtool_create(cfg, "-X", f"context new start {2 + i * 2} equal 2")
552        ctx.append(defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete"))
553
554        ports.append(rand_port())
555        flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {ports[i]} context {ctx_id}"
556        ntuple_id = ethtool_create(cfg, "-N", flow)
557        ntuple.append(defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}"))
558
559    check_traffic()
560
561    # Remove middle context
562    remove_ctx(ctx_cnt // 2)
563    check_traffic()
564
565    # Remove first context
566    remove_ctx(0)
567    check_traffic()
568
569    # Remove last context
570    remove_ctx(-1)
571    check_traffic()
572
573    if requested_ctx_cnt != ctx_cnt:
574        raise KsftSkipEx(f"Tested only {ctx_cnt} contexts, wanted {requested_ctx_cnt}")
575
576
577def test_rss_context_overlap(cfg, other_ctx=0):
578    """
579    Test contexts overlapping with each other.
580    Use 4 queues for the main context, but only queues 2 and 3 for context 1.
581    """
582
583    require_ntuple(cfg)
584
585    queue_cnt = len(_get_rx_cnts(cfg))
586    if queue_cnt < 4:
587        try:
588            ksft_pr(f"Increasing queue count {queue_cnt} -> 4")
589            ethtool(f"-L {cfg.ifname} combined 4")
590            defer(ethtool, f"-L {cfg.ifname} combined {queue_cnt}")
591        except:
592            raise KsftSkipEx("Not enough queues for the test")
593
594    if other_ctx == 0:
595        ethtool(f"-X {cfg.ifname} equal 4")
596        defer(ethtool, f"-X {cfg.ifname} default")
597    else:
598        other_ctx = ethtool_create(cfg, "-X", "context new")
599        ethtool(f"-X {cfg.ifname} context {other_ctx} equal 4")
600        defer(ethtool, f"-X {cfg.ifname} context {other_ctx} delete")
601
602    ctx_id = ethtool_create(cfg, "-X", "context new")
603    ethtool(f"-X {cfg.ifname} context {ctx_id} start 2 equal 2")
604    defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
605
606    port = rand_port()
607    if other_ctx:
608        flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {other_ctx}"
609        ntuple_id = ethtool_create(cfg, "-N", flow)
610        ntuple = defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")
611
612    # Test the main context
613    cnts = _get_rx_cnts(cfg)
614    GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000)
615    cnts = _get_rx_cnts(cfg, prev=cnts)
616
617    ksft_ge(sum(cnts[ :4]), 20000, "traffic on main context: " + str(cnts))
618    ksft_ge(sum(cnts[ :2]),  7000, "traffic on main context (1/2): " + str(cnts))
619    ksft_ge(sum(cnts[2:4]),  7000, "traffic on main context (2/2): " + str(cnts))
620    if other_ctx == 0:
621        ksft_eq(sum(cnts[4: ]),     0, "traffic on other queues: " + str(cnts))
622
623    # Now create a rule for context 1 and make sure traffic goes to a subset
624    if other_ctx:
625        ntuple.exec()
626    flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {ctx_id}"
627    ntuple_id = ethtool_create(cfg, "-N", flow)
628    defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")
629
630    cnts = _get_rx_cnts(cfg)
631    GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000)
632    cnts = _get_rx_cnts(cfg, prev=cnts)
633
634    directed = sum(cnts[2:4])
635    ksft_lt(sum(cnts[ :2]), directed / 2, "traffic on main context: " + str(cnts))
636    ksft_ge(directed, 20000, "traffic on extra context: " + str(cnts))
637    if other_ctx == 0:
638        ksft_eq(sum(cnts[4: ]),     0, "traffic on other queues: " + str(cnts))
639
640
641def test_rss_context_overlap2(cfg):
642    test_rss_context_overlap(cfg, True)
643
644
645def test_delete_rss_context_busy(cfg):
646    """
647    Test that deletion returns -EBUSY when an rss context is being used
648    by an ntuple filter.
649    """
650
651    require_ntuple(cfg)
652
653    # create additional rss context
654    ctx_id = ethtool_create(cfg, "-X", "context new")
655    ctx_deleter = defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
656
657    # utilize context from ntuple filter
658    port = rand_port()
659    flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {ctx_id}"
660    ntuple_id = ethtool_create(cfg, "-N", flow)
661    defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")
662
663    # attempt to delete in-use context
664    try:
665        ctx_deleter.exec_only()
666        ctx_deleter.cancel()
667        raise KsftFailEx(f"deleted context {ctx_id} used by rule {ntuple_id}")
668    except CmdExitFailure:
669        pass
670
671
672def test_rss_ntuple_addition(cfg):
673    """
674    Test that the queue offset (ring_cookie) of an ntuple rule is added
675    to the queue number read from the indirection table.
676    """
677
678    require_ntuple(cfg)
679
680    queue_cnt = len(_get_rx_cnts(cfg))
681    if queue_cnt < 4:
682        try:
683            ksft_pr(f"Increasing queue count {queue_cnt} -> 4")
684            ethtool(f"-L {cfg.ifname} combined 4")
685            defer(ethtool, f"-L {cfg.ifname} combined {queue_cnt}")
686        except:
687            raise KsftSkipEx("Not enough queues for the test")
688
689    # Use queue 0 for normal traffic
690    ethtool(f"-X {cfg.ifname} equal 1")
691    defer(ethtool, f"-X {cfg.ifname} default")
692
693    # create additional rss context
694    ctx_id = ethtool_create(cfg, "-X", "context new equal 2")
695    defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
696
697    # utilize context from ntuple filter
698    port = rand_port()
699    flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {ctx_id} action 2"
700    try:
701        ntuple_id = ethtool_create(cfg, "-N", flow)
702    except CmdExitFailure:
703        raise KsftSkipEx("Ntuple filter with RSS and nonzero action not supported")
704    defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")
705
706    _send_traffic_check(cfg, port, f"context {ctx_id}", { 'target': (2, 3),
707                                                          'empty' : (1,),
708                                                          'noise' : (0,) })
709
710
711def main() -> None:
712    with NetDrvEpEnv(__file__, nsim_test=False) as cfg:
713        cfg.ethnl = EthtoolFamily()
714        cfg.netdevnl = NetdevFamily()
715
716        ksft_run([test_rss_key_indir, test_rss_queue_reconfigure,
717                  test_rss_resize, test_hitless_key_update,
718                  test_rss_context, test_rss_context4, test_rss_context32,
719                  test_rss_context_dump, test_rss_context_queue_reconfigure,
720                  test_rss_context_overlap, test_rss_context_overlap2,
721                  test_rss_context_out_of_order, test_rss_context4_create_with_cfg,
722                  test_delete_rss_context_busy, test_rss_ntuple_addition],
723                 args=(cfg, ))
724    ksft_exit()
725
726
727if __name__ == "__main__":
728    main()
729