xref: /linux/tools/testing/selftests/drivers/net/hw/rss_ctx.py (revision 9410645520e9b820069761f3450ef6661418e279)
1f898c16aSJakub Kicinski#!/usr/bin/env python3
2f898c16aSJakub Kicinski# SPDX-License-Identifier: GPL-2.0
3f898c16aSJakub Kicinski
4f898c16aSJakub Kicinskiimport datetime
5f898c16aSJakub Kicinskiimport random
6*c1ad8ef8SJakub Kicinskifrom lib.py import ksft_run, ksft_pr, ksft_exit, ksft_eq, ksft_ne, ksft_ge, ksft_lt
7f898c16aSJakub Kicinskifrom lib.py import NetDrvEpEnv
87e3e5b0bSJakub Kicinskifrom lib.py import EthtoolFamily, NetdevFamily
9f898c16aSJakub Kicinskifrom lib.py import KsftSkipEx
10f898c16aSJakub Kicinskifrom lib.py import rand_port
110759356bSJakub Kicinskifrom lib.py import ethtool, ip, defer, GenerateTraffic, CmdExitFailure
12f898c16aSJakub Kicinski
13f898c16aSJakub Kicinski
14f898c16aSJakub Kicinskidef _rss_key_str(key):
15f898c16aSJakub Kicinski    return ":".join(["{:02x}".format(x) for x in key])
16f898c16aSJakub Kicinski
17f898c16aSJakub Kicinski
18f898c16aSJakub Kicinskidef _rss_key_rand(length):
19f898c16aSJakub Kicinski    return [random.randint(0, 255) for _ in range(length)]
20f898c16aSJakub Kicinski
21f898c16aSJakub Kicinski
220d6ccfe6SJakub Kicinskidef _rss_key_check(cfg, data=None, context=0):
230d6ccfe6SJakub Kicinski    if data is None:
240d6ccfe6SJakub Kicinski        data = get_rss(cfg, context=context)
250d6ccfe6SJakub Kicinski    if 'rss-hash-key' not in data:
260d6ccfe6SJakub Kicinski        return
270d6ccfe6SJakub Kicinski    non_zero = [x for x in data['rss-hash-key'] if x != 0]
280d6ccfe6SJakub Kicinski    ksft_eq(bool(non_zero), True, comment=f"RSS key is all zero {data['rss-hash-key']}")
290d6ccfe6SJakub Kicinski
300d6ccfe6SJakub Kicinski
31f898c16aSJakub Kicinskidef get_rss(cfg, context=0):
32f898c16aSJakub Kicinski    return ethtool(f"-x {cfg.ifname} context {context}", json=True)[0]
33f898c16aSJakub Kicinski
34f898c16aSJakub Kicinski
35f898c16aSJakub Kicinskidef get_drop_err_sum(cfg):
36f898c16aSJakub Kicinski    stats = ip("-s -s link show dev " + cfg.ifname, json=True)[0]
37f898c16aSJakub Kicinski    cnt = 0
38f898c16aSJakub Kicinski    for key in ['errors', 'dropped', 'over_errors', 'fifo_errors',
39f898c16aSJakub Kicinski                'length_errors', 'crc_errors', 'missed_errors',
40f898c16aSJakub Kicinski                'frame_errors']:
41f898c16aSJakub Kicinski        cnt += stats["stats64"]["rx"][key]
42f898c16aSJakub Kicinski    return cnt, stats["stats64"]["tx"]["carrier_changes"]
43f898c16aSJakub Kicinski
44f898c16aSJakub Kicinski
45f898c16aSJakub Kicinskidef ethtool_create(cfg, act, opts):
46f898c16aSJakub Kicinski    output = ethtool(f"{act} {cfg.ifname} {opts}").stdout
47f898c16aSJakub Kicinski    # Output will be something like: "New RSS context is 1" or
48f898c16aSJakub Kicinski    # "Added rule with ID 7", we want the integer from the end
49f898c16aSJakub Kicinski    return int(output.split()[-1])
50f898c16aSJakub Kicinski
51f898c16aSJakub Kicinski
52f898c16aSJakub Kicinskidef require_ntuple(cfg):
53f898c16aSJakub Kicinski    features = ethtool(f"-k {cfg.ifname}", json=True)[0]
54f898c16aSJakub Kicinski    if not features["ntuple-filters"]["active"]:
55f898c16aSJakub Kicinski        # ntuple is more of a capability than a config knob, don't bother
56f898c16aSJakub Kicinski        # trying to enable it (until some driver actually needs it).
57f898c16aSJakub Kicinski        raise KsftSkipEx("Ntuple filters not enabled on the device: " + str(features["ntuple-filters"]))
58f898c16aSJakub Kicinski
59f898c16aSJakub Kicinski
60f898c16aSJakub Kicinski# Get Rx packet counts for all queues, as a simple list of integers
61f898c16aSJakub Kicinski# if @prev is specified the prev counts will be subtracted
62f898c16aSJakub Kicinskidef _get_rx_cnts(cfg, prev=None):
63f898c16aSJakub Kicinski    cfg.wait_hw_stats_settle()
64f898c16aSJakub Kicinski    data = cfg.netdevnl.qstats_get({"ifindex": cfg.ifindex, "scope": ["queue"]}, dump=True)
65f898c16aSJakub Kicinski    data = [x for x in data if x['queue-type'] == "rx"]
66f898c16aSJakub Kicinski    max_q = max([x["queue-id"] for x in data])
67f898c16aSJakub Kicinski    queue_stats = [0] * (max_q + 1)
68f898c16aSJakub Kicinski    for q in data:
69f898c16aSJakub Kicinski        queue_stats[q["queue-id"]] = q["rx-packets"]
70f898c16aSJakub Kicinski        if prev and q["queue-id"] < len(prev):
71f898c16aSJakub Kicinski            queue_stats[q["queue-id"]] -= prev[q["queue-id"]]
72f898c16aSJakub Kicinski    return queue_stats
73f898c16aSJakub Kicinski
74f898c16aSJakub Kicinski
75847aa551SJakub Kicinskidef _send_traffic_check(cfg, port, name, params):
76847aa551SJakub Kicinski    # params is a dict with 3 possible keys:
77847aa551SJakub Kicinski    #  - "target": required, which queues we expect to get iperf traffic
78847aa551SJakub Kicinski    #  - "empty": optional, which queues should see no traffic at all
79847aa551SJakub Kicinski    #  - "noise": optional, which queues we expect to see low traffic;
80847aa551SJakub Kicinski    #             used for queues of the main context, since some background
81847aa551SJakub Kicinski    #             OS activity may use those queues while we're testing
82847aa551SJakub Kicinski    # the value for each is a list, or some other iterable containing queue ids.
83847aa551SJakub Kicinski
84847aa551SJakub Kicinski    cnts = _get_rx_cnts(cfg)
85847aa551SJakub Kicinski    GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000)
86847aa551SJakub Kicinski    cnts = _get_rx_cnts(cfg, prev=cnts)
87847aa551SJakub Kicinski
88847aa551SJakub Kicinski    directed = sum(cnts[i] for i in params['target'])
89847aa551SJakub Kicinski
90847aa551SJakub Kicinski    ksft_ge(directed, 20000, f"traffic on {name}: " + str(cnts))
91847aa551SJakub Kicinski    if params.get('noise'):
92847aa551SJakub Kicinski        ksft_lt(sum(cnts[i] for i in params['noise']), directed / 2,
9310fbe8c0SJakub Kicinski                f"traffic on other queues ({name})':" + str(cnts))
94847aa551SJakub Kicinski    if params.get('empty'):
95847aa551SJakub Kicinski        ksft_eq(sum(cnts[i] for i in params['empty']), 0,
9610fbe8c0SJakub Kicinski                f"traffic on inactive queues ({name}): " + str(cnts))
97847aa551SJakub Kicinski
98847aa551SJakub Kicinski
99f898c16aSJakub Kicinskidef test_rss_key_indir(cfg):
100a0aab7d7SJakub Kicinski    """Test basics like updating the main RSS key and indirection table."""
101a0aab7d7SJakub Kicinski
1020d6ccfe6SJakub Kicinski    qcnt = len(_get_rx_cnts(cfg))
1030d6ccfe6SJakub Kicinski    if qcnt < 3:
1040d6ccfe6SJakub Kicinski        KsftSkipEx("Device has fewer than 3 queues (or doesn't support queue stats)")
105f898c16aSJakub Kicinski
106f898c16aSJakub Kicinski    data = get_rss(cfg)
107f898c16aSJakub Kicinski    want_keys = ['rss-hash-key', 'rss-hash-function', 'rss-indirection-table']
108f898c16aSJakub Kicinski    for k in want_keys:
109f898c16aSJakub Kicinski        if k not in data:
110f898c16aSJakub Kicinski            raise KsftFailEx("ethtool results missing key: " + k)
111f898c16aSJakub Kicinski        if not data[k]:
112f898c16aSJakub Kicinski            raise KsftFailEx(f"ethtool results empty for '{k}': {data[k]}")
113f898c16aSJakub Kicinski
1140d6ccfe6SJakub Kicinski    _rss_key_check(cfg, data=data)
115f898c16aSJakub Kicinski    key_len = len(data['rss-hash-key'])
116f898c16aSJakub Kicinski
117f898c16aSJakub Kicinski    # Set the key
118f898c16aSJakub Kicinski    key = _rss_key_rand(key_len)
119f898c16aSJakub Kicinski    ethtool(f"-X {cfg.ifname} hkey " + _rss_key_str(key))
120f898c16aSJakub Kicinski
121f898c16aSJakub Kicinski    data = get_rss(cfg)
122f898c16aSJakub Kicinski    ksft_eq(key, data['rss-hash-key'])
123f898c16aSJakub Kicinski
1240d6ccfe6SJakub Kicinski    # Set the indirection table and the key together
1250d6ccfe6SJakub Kicinski    key = _rss_key_rand(key_len)
1260d6ccfe6SJakub Kicinski    ethtool(f"-X {cfg.ifname} equal 3 hkey " + _rss_key_str(key))
1270d6ccfe6SJakub Kicinski    reset_indir = defer(ethtool, f"-X {cfg.ifname} default")
1280d6ccfe6SJakub Kicinski
1290d6ccfe6SJakub Kicinski    data = get_rss(cfg)
1300d6ccfe6SJakub Kicinski    _rss_key_check(cfg, data=data)
1310d6ccfe6SJakub Kicinski    ksft_eq(0, min(data['rss-indirection-table']))
1320d6ccfe6SJakub Kicinski    ksft_eq(2, max(data['rss-indirection-table']))
1330d6ccfe6SJakub Kicinski
1340d6ccfe6SJakub Kicinski    # Reset indirection table and set the key
1350d6ccfe6SJakub Kicinski    key = _rss_key_rand(key_len)
1360d6ccfe6SJakub Kicinski    ethtool(f"-X {cfg.ifname} default hkey " + _rss_key_str(key))
1370d6ccfe6SJakub Kicinski    data = get_rss(cfg)
1380d6ccfe6SJakub Kicinski    _rss_key_check(cfg, data=data)
1390d6ccfe6SJakub Kicinski    ksft_eq(0, min(data['rss-indirection-table']))
1400d6ccfe6SJakub Kicinski    ksft_eq(qcnt - 1, max(data['rss-indirection-table']))
1410d6ccfe6SJakub Kicinski
142f898c16aSJakub Kicinski    # Set the indirection table
143f898c16aSJakub Kicinski    ethtool(f"-X {cfg.ifname} equal 2")
144f898c16aSJakub Kicinski    data = get_rss(cfg)
145f898c16aSJakub Kicinski    ksft_eq(0, min(data['rss-indirection-table']))
146f898c16aSJakub Kicinski    ksft_eq(1, max(data['rss-indirection-table']))
147f898c16aSJakub Kicinski
148f898c16aSJakub Kicinski    # Check we only get traffic on the first 2 queues
149f898c16aSJakub Kicinski    cnts = _get_rx_cnts(cfg)
150f898c16aSJakub Kicinski    GenerateTraffic(cfg).wait_pkts_and_stop(20000)
151f898c16aSJakub Kicinski    cnts = _get_rx_cnts(cfg, prev=cnts)
152f898c16aSJakub Kicinski    # 2 queues, 20k packets, must be at least 5k per queue
153f898c16aSJakub Kicinski    ksft_ge(cnts[0], 5000, "traffic on main context (1/2): " + str(cnts))
154f898c16aSJakub Kicinski    ksft_ge(cnts[1], 5000, "traffic on main context (2/2): " + str(cnts))
155f898c16aSJakub Kicinski    # The other queues should be unused
156f898c16aSJakub Kicinski    ksft_eq(sum(cnts[2:]), 0, "traffic on unused queues: " + str(cnts))
157f898c16aSJakub Kicinski
158f898c16aSJakub Kicinski    # Restore, and check traffic gets spread again
159a0aab7d7SJakub Kicinski    reset_indir.exec()
160f898c16aSJakub Kicinski
161f898c16aSJakub Kicinski    cnts = _get_rx_cnts(cfg)
162f898c16aSJakub Kicinski    GenerateTraffic(cfg).wait_pkts_and_stop(20000)
163f898c16aSJakub Kicinski    cnts = _get_rx_cnts(cfg, prev=cnts)
164f898c16aSJakub Kicinski    # First two queues get less traffic than all the rest
165f898c16aSJakub Kicinski    ksft_lt(sum(cnts[:2]), sum(cnts[2:]), "traffic distributed: " + str(cnts))
166f898c16aSJakub Kicinski
167f898c16aSJakub Kicinski
168e2c9703dSJakub Kicinskidef test_rss_queue_reconfigure(cfg, main_ctx=True):
169e2c9703dSJakub Kicinski    """Make sure queue changes can't override requested RSS config.
170e2c9703dSJakub Kicinski
171e2c9703dSJakub Kicinski    By default main RSS table should change to include all queues.
172e2c9703dSJakub Kicinski    When user sets a specific RSS config the driver should preserve it,
173e2c9703dSJakub Kicinski    even when queue count changes. Driver should refuse to deactivate
174e2c9703dSJakub Kicinski    queues used in the user-set RSS config.
175e2c9703dSJakub Kicinski    """
176e2c9703dSJakub Kicinski
177e2c9703dSJakub Kicinski    if not main_ctx:
178e2c9703dSJakub Kicinski        require_ntuple(cfg)
179e2c9703dSJakub Kicinski
180e2c9703dSJakub Kicinski    # Start with 4 queues, an arbitrary known number.
181e2c9703dSJakub Kicinski    try:
182e2c9703dSJakub Kicinski        qcnt = len(_get_rx_cnts(cfg))
183e2c9703dSJakub Kicinski        ethtool(f"-L {cfg.ifname} combined 4")
184e2c9703dSJakub Kicinski        defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")
185e2c9703dSJakub Kicinski    except:
186e2c9703dSJakub Kicinski        raise KsftSkipEx("Not enough queues for the test or qstat not supported")
187e2c9703dSJakub Kicinski
188e2c9703dSJakub Kicinski    if main_ctx:
189e2c9703dSJakub Kicinski        ctx_id = 0
190e2c9703dSJakub Kicinski        ctx_ref = ""
191e2c9703dSJakub Kicinski    else:
192e2c9703dSJakub Kicinski        ctx_id = ethtool_create(cfg, "-X", "context new")
193e2c9703dSJakub Kicinski        ctx_ref = f"context {ctx_id}"
194e2c9703dSJakub Kicinski        defer(ethtool, f"-X {cfg.ifname} {ctx_ref} delete")
195e2c9703dSJakub Kicinski
196e2c9703dSJakub Kicinski    # Indirection table should be distributing to all queues.
197e2c9703dSJakub Kicinski    data = get_rss(cfg, context=ctx_id)
198e2c9703dSJakub Kicinski    ksft_eq(0, min(data['rss-indirection-table']))
199e2c9703dSJakub Kicinski    ksft_eq(3, max(data['rss-indirection-table']))
200e2c9703dSJakub Kicinski
201e2c9703dSJakub Kicinski    # Increase queues, indirection table should be distributing to all queues.
202e2c9703dSJakub Kicinski    # It's unclear whether tables of additional contexts should be reset, too.
203e2c9703dSJakub Kicinski    if main_ctx:
204e2c9703dSJakub Kicinski        ethtool(f"-L {cfg.ifname} combined 5")
205e2c9703dSJakub Kicinski        data = get_rss(cfg)
206e2c9703dSJakub Kicinski        ksft_eq(0, min(data['rss-indirection-table']))
207e2c9703dSJakub Kicinski        ksft_eq(4, max(data['rss-indirection-table']))
208e2c9703dSJakub Kicinski        ethtool(f"-L {cfg.ifname} combined 4")
209e2c9703dSJakub Kicinski
210e2c9703dSJakub Kicinski    # Configure the table explicitly
211e2c9703dSJakub Kicinski    port = rand_port()
212e2c9703dSJakub Kicinski    ethtool(f"-X {cfg.ifname} {ctx_ref} weight 1 0 0 1")
213e2c9703dSJakub Kicinski    if main_ctx:
214e2c9703dSJakub Kicinski        other_key = 'empty'
215e2c9703dSJakub Kicinski        defer(ethtool, f"-X {cfg.ifname} default")
216e2c9703dSJakub Kicinski    else:
217e2c9703dSJakub Kicinski        other_key = 'noise'
218e2c9703dSJakub Kicinski        flow = f"flow-type tcp{cfg.addr_ipver} dst-port {port} context {ctx_id}"
219e2c9703dSJakub Kicinski        ntuple = ethtool_create(cfg, "-N", flow)
220e2c9703dSJakub Kicinski        defer(ethtool, f"-N {cfg.ifname} delete {ntuple}")
221e2c9703dSJakub Kicinski
222e2c9703dSJakub Kicinski    _send_traffic_check(cfg, port, ctx_ref, { 'target': (0, 3),
223e2c9703dSJakub Kicinski                                              other_key: (1, 2) })
224e2c9703dSJakub Kicinski
225e2c9703dSJakub Kicinski    # We should be able to increase queues, but table should be left untouched
226e2c9703dSJakub Kicinski    ethtool(f"-L {cfg.ifname} combined 5")
227e2c9703dSJakub Kicinski    data = get_rss(cfg, context=ctx_id)
228e2c9703dSJakub Kicinski    ksft_eq({0, 3}, set(data['rss-indirection-table']))
229e2c9703dSJakub Kicinski
230e2c9703dSJakub Kicinski    _send_traffic_check(cfg, port, ctx_ref, { 'target': (0, 3),
231e2c9703dSJakub Kicinski                                              other_key: (1, 2, 4) })
232e2c9703dSJakub Kicinski
233e2c9703dSJakub Kicinski    # Setting queue count to 3 should fail, queue 3 is used
234e2c9703dSJakub Kicinski    try:
235e2c9703dSJakub Kicinski        ethtool(f"-L {cfg.ifname} combined 3")
236e2c9703dSJakub Kicinski    except CmdExitFailure:
237e2c9703dSJakub Kicinski        pass
238e2c9703dSJakub Kicinski    else:
239e2c9703dSJakub Kicinski        raise Exception(f"Driver didn't prevent us from deactivating a used queue (context {ctx_id})")
240e2c9703dSJakub Kicinski
241e2c9703dSJakub Kicinski
2427e3e5b0bSJakub Kicinskidef test_rss_resize(cfg):
2437e3e5b0bSJakub Kicinski    """Test resizing of the RSS table.
2447e3e5b0bSJakub Kicinski
2457e3e5b0bSJakub Kicinski    Some devices dynamically increase and decrease the size of the RSS
2467e3e5b0bSJakub Kicinski    indirection table based on the number of enabled queues.
2477e3e5b0bSJakub Kicinski    When that happens driver must maintain the balance of entries
2487e3e5b0bSJakub Kicinski    (preferably duplicating the smaller table).
2497e3e5b0bSJakub Kicinski    """
2507e3e5b0bSJakub Kicinski
2517e3e5b0bSJakub Kicinski    channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})
2527e3e5b0bSJakub Kicinski    ch_max = channels['combined-max']
2537e3e5b0bSJakub Kicinski    qcnt = channels['combined-count']
2547e3e5b0bSJakub Kicinski
2557e3e5b0bSJakub Kicinski    if ch_max < 2:
2567e3e5b0bSJakub Kicinski        raise KsftSkipEx(f"Not enough queues for the test: {ch_max}")
2577e3e5b0bSJakub Kicinski
2587e3e5b0bSJakub Kicinski    ethtool(f"-L {cfg.ifname} combined 2")
2597e3e5b0bSJakub Kicinski    defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")
2607e3e5b0bSJakub Kicinski
2617e3e5b0bSJakub Kicinski    ethtool(f"-X {cfg.ifname} weight 1 7")
2627e3e5b0bSJakub Kicinski    defer(ethtool, f"-X {cfg.ifname} default")
2637e3e5b0bSJakub Kicinski
2647e3e5b0bSJakub Kicinski    ethtool(f"-L {cfg.ifname} combined {ch_max}")
2657e3e5b0bSJakub Kicinski    data = get_rss(cfg)
2667e3e5b0bSJakub Kicinski    ksft_eq(0, min(data['rss-indirection-table']))
2677e3e5b0bSJakub Kicinski    ksft_eq(1, max(data['rss-indirection-table']))
2687e3e5b0bSJakub Kicinski
2697e3e5b0bSJakub Kicinski    ksft_eq(7,
2707e3e5b0bSJakub Kicinski            data['rss-indirection-table'].count(1) /
2717e3e5b0bSJakub Kicinski            data['rss-indirection-table'].count(0),
2727e3e5b0bSJakub Kicinski            f"Table imbalance after resize: {data['rss-indirection-table']}")
2737e3e5b0bSJakub Kicinski
2747e3e5b0bSJakub Kicinski
275933048feSJakub Kicinskidef test_hitless_key_update(cfg):
276933048feSJakub Kicinski    """Test that flows may be rehashed without impacting traffic.
277933048feSJakub Kicinski
278933048feSJakub Kicinski    Some workloads may want to rehash the flows in response to an imbalance.
279933048feSJakub Kicinski    Most effective way to do that is changing the RSS key. Check that changing
280933048feSJakub Kicinski    the key does not cause link flaps or traffic disruption.
281933048feSJakub Kicinski
282933048feSJakub Kicinski    Disrupting traffic for key update is not a bug, but makes the key
283933048feSJakub Kicinski    update unusable for rehashing under load.
284933048feSJakub Kicinski    """
285933048feSJakub Kicinski    data = get_rss(cfg)
286933048feSJakub Kicinski    key_len = len(data['rss-hash-key'])
287933048feSJakub Kicinski
288933048feSJakub Kicinski    key = _rss_key_rand(key_len)
289933048feSJakub Kicinski
290933048feSJakub Kicinski    tgen = GenerateTraffic(cfg)
291933048feSJakub Kicinski    try:
292933048feSJakub Kicinski        errors0, carrier0 = get_drop_err_sum(cfg)
293933048feSJakub Kicinski        t0 = datetime.datetime.now()
294933048feSJakub Kicinski        ethtool(f"-X {cfg.ifname} hkey " + _rss_key_str(key))
295933048feSJakub Kicinski        t1 = datetime.datetime.now()
296933048feSJakub Kicinski        errors1, carrier1 = get_drop_err_sum(cfg)
297933048feSJakub Kicinski    finally:
298933048feSJakub Kicinski        tgen.wait_pkts_and_stop(5000)
299933048feSJakub Kicinski
300933048feSJakub Kicinski    ksft_lt((t1 - t0).total_seconds(), 0.2)
301933048feSJakub Kicinski    ksft_eq(errors1 - errors1, 0)
302933048feSJakub Kicinski    ksft_eq(carrier1 - carrier0, 0)
303933048feSJakub Kicinski
304933048feSJakub Kicinski
305*c1ad8ef8SJakub Kicinskidef test_rss_context_dump(cfg):
306*c1ad8ef8SJakub Kicinski    """
307*c1ad8ef8SJakub Kicinski    Test dumping RSS contexts. This tests mostly exercises the kernel APIs.
308*c1ad8ef8SJakub Kicinski    """
309*c1ad8ef8SJakub Kicinski
310*c1ad8ef8SJakub Kicinski    # Get a random key of the right size
311*c1ad8ef8SJakub Kicinski    data = get_rss(cfg)
312*c1ad8ef8SJakub Kicinski    if 'rss-hash-key' in data:
313*c1ad8ef8SJakub Kicinski        key_data = _rss_key_rand(len(data['rss-hash-key']))
314*c1ad8ef8SJakub Kicinski        key = _rss_key_str(key_data)
315*c1ad8ef8SJakub Kicinski    else:
316*c1ad8ef8SJakub Kicinski        key_data = []
317*c1ad8ef8SJakub Kicinski        key = "ba:ad"
318*c1ad8ef8SJakub Kicinski
319*c1ad8ef8SJakub Kicinski    ids = []
320*c1ad8ef8SJakub Kicinski    try:
321*c1ad8ef8SJakub Kicinski        ids.append(ethtool_create(cfg, "-X", f"context new"))
322*c1ad8ef8SJakub Kicinski        defer(ethtool, f"-X {cfg.ifname} context {ids[-1]} delete")
323*c1ad8ef8SJakub Kicinski
324*c1ad8ef8SJakub Kicinski        ids.append(ethtool_create(cfg, "-X", f"context new weight 1 1"))
325*c1ad8ef8SJakub Kicinski        defer(ethtool, f"-X {cfg.ifname} context {ids[-1]} delete")
326*c1ad8ef8SJakub Kicinski
327*c1ad8ef8SJakub Kicinski        ids.append(ethtool_create(cfg, "-X", f"context new hkey {key}"))
328*c1ad8ef8SJakub Kicinski        defer(ethtool, f"-X {cfg.ifname} context {ids[-1]} delete")
329*c1ad8ef8SJakub Kicinski    except CmdExitFailure:
330*c1ad8ef8SJakub Kicinski        if not ids:
331*c1ad8ef8SJakub Kicinski            raise KsftSkipEx("Unable to add any contexts")
332*c1ad8ef8SJakub Kicinski        ksft_pr(f"Added only {len(ids)} out of 3 contexts")
333*c1ad8ef8SJakub Kicinski
334*c1ad8ef8SJakub Kicinski    expect_tuples = set([(cfg.ifname, -1)] + [(cfg.ifname, ctx_id) for ctx_id in ids])
335*c1ad8ef8SJakub Kicinski
336*c1ad8ef8SJakub Kicinski    # Dump all
337*c1ad8ef8SJakub Kicinski    ctxs = cfg.ethnl.rss_get({}, dump=True)
338*c1ad8ef8SJakub Kicinski    tuples = [(c['header']['dev-name'], c.get('context', -1)) for c in ctxs]
339*c1ad8ef8SJakub Kicinski    ksft_eq(len(tuples), len(set(tuples)), "duplicates in context dump")
340*c1ad8ef8SJakub Kicinski    ctx_tuples = set([ctx for ctx in tuples if ctx[0] == cfg.ifname])
341*c1ad8ef8SJakub Kicinski    ksft_eq(expect_tuples, ctx_tuples)
342*c1ad8ef8SJakub Kicinski
343*c1ad8ef8SJakub Kicinski    # Sanity-check the results
344*c1ad8ef8SJakub Kicinski    for data in ctxs:
345*c1ad8ef8SJakub Kicinski        ksft_ne(set(data['indir']), {0}, "indir table is all zero")
346*c1ad8ef8SJakub Kicinski        ksft_ne(set(data.get('hkey', [1])), {0}, "key is all zero")
347*c1ad8ef8SJakub Kicinski
348*c1ad8ef8SJakub Kicinski        # More specific checks
349*c1ad8ef8SJakub Kicinski        if len(ids) > 1 and data.get('context') == ids[1]:
350*c1ad8ef8SJakub Kicinski            ksft_eq(set(data['indir']), {0, 1},
351*c1ad8ef8SJakub Kicinski                    "ctx1 - indir table mismatch")
352*c1ad8ef8SJakub Kicinski        if len(ids) > 2 and data.get('context') == ids[2]:
353*c1ad8ef8SJakub Kicinski            ksft_eq(data['hkey'], bytes(key_data), "ctx2 - key mismatch")
354*c1ad8ef8SJakub Kicinski
355*c1ad8ef8SJakub Kicinski    # Ifindex filter
356*c1ad8ef8SJakub Kicinski    ctxs = cfg.ethnl.rss_get({'header': {'dev-name': cfg.ifname}}, dump=True)
357*c1ad8ef8SJakub Kicinski    tuples = [(c['header']['dev-name'], c.get('context', -1)) for c in ctxs]
358*c1ad8ef8SJakub Kicinski    ctx_tuples = set(tuples)
359*c1ad8ef8SJakub Kicinski    ksft_eq(len(tuples), len(ctx_tuples), "duplicates in context dump")
360*c1ad8ef8SJakub Kicinski    ksft_eq(expect_tuples, ctx_tuples)
361*c1ad8ef8SJakub Kicinski
362*c1ad8ef8SJakub Kicinski    # Skip ctx 0
363*c1ad8ef8SJakub Kicinski    expect_tuples.remove((cfg.ifname, -1))
364*c1ad8ef8SJakub Kicinski
365*c1ad8ef8SJakub Kicinski    ctxs = cfg.ethnl.rss_get({'start-context': 1}, dump=True)
366*c1ad8ef8SJakub Kicinski    tuples = [(c['header']['dev-name'], c.get('context', -1)) for c in ctxs]
367*c1ad8ef8SJakub Kicinski    ksft_eq(len(tuples), len(set(tuples)), "duplicates in context dump")
368*c1ad8ef8SJakub Kicinski    ctx_tuples = set([ctx for ctx in tuples if ctx[0] == cfg.ifname])
369*c1ad8ef8SJakub Kicinski    ksft_eq(expect_tuples, ctx_tuples)
370*c1ad8ef8SJakub Kicinski
371*c1ad8ef8SJakub Kicinski    # And finally both with ifindex and skip main
372*c1ad8ef8SJakub Kicinski    ctxs = cfg.ethnl.rss_get({'header': {'dev-name': cfg.ifname}, 'start-context': 1}, dump=True)
373*c1ad8ef8SJakub Kicinski    ctx_tuples = set([(c['header']['dev-name'], c.get('context', -1)) for c in ctxs])
374*c1ad8ef8SJakub Kicinski    ksft_eq(expect_tuples, ctx_tuples)
375*c1ad8ef8SJakub Kicinski
376*c1ad8ef8SJakub Kicinski
377f898c16aSJakub Kicinskidef test_rss_context(cfg, ctx_cnt=1, create_with_cfg=None):
378f898c16aSJakub Kicinski    """
379f898c16aSJakub Kicinski    Test separating traffic into RSS contexts.
380f898c16aSJakub Kicinski    The queues will be allocated 2 for each context:
381f898c16aSJakub Kicinski     ctx0  ctx1  ctx2  ctx3
382f898c16aSJakub Kicinski    [0 1] [2 3] [4 5] [6 7] ...
383f898c16aSJakub Kicinski    """
384f898c16aSJakub Kicinski
385f898c16aSJakub Kicinski    require_ntuple(cfg)
386f898c16aSJakub Kicinski
387f898c16aSJakub Kicinski    requested_ctx_cnt = ctx_cnt
388f898c16aSJakub Kicinski
389f898c16aSJakub Kicinski    # Try to allocate more queues when necessary
390f898c16aSJakub Kicinski    qcnt = len(_get_rx_cnts(cfg))
3910759356bSJakub Kicinski    if qcnt < 2 + 2 * ctx_cnt:
392f898c16aSJakub Kicinski        try:
393f898c16aSJakub Kicinski            ksft_pr(f"Increasing queue count {qcnt} -> {2 + 2 * ctx_cnt}")
394f898c16aSJakub Kicinski            ethtool(f"-L {cfg.ifname} combined {2 + 2 * ctx_cnt}")
3950759356bSJakub Kicinski            defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")
396f898c16aSJakub Kicinski        except:
397f898c16aSJakub Kicinski            raise KsftSkipEx("Not enough queues for the test")
398f898c16aSJakub Kicinski
399f898c16aSJakub Kicinski    ports = []
4000759356bSJakub Kicinski
401f898c16aSJakub Kicinski    # Use queues 0 and 1 for normal traffic
402f898c16aSJakub Kicinski    ethtool(f"-X {cfg.ifname} equal 2")
4030759356bSJakub Kicinski    defer(ethtool, f"-X {cfg.ifname} default")
404f898c16aSJakub Kicinski
405f898c16aSJakub Kicinski    for i in range(ctx_cnt):
406f898c16aSJakub Kicinski        want_cfg = f"start {2 + i * 2} equal 2"
407f898c16aSJakub Kicinski        create_cfg = want_cfg if create_with_cfg else ""
408f898c16aSJakub Kicinski
409f898c16aSJakub Kicinski        try:
4100759356bSJakub Kicinski            ctx_id = ethtool_create(cfg, "-X", f"context new {create_cfg}")
4110759356bSJakub Kicinski            defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
412f898c16aSJakub Kicinski        except CmdExitFailure:
413f898c16aSJakub Kicinski            # try to carry on and skip at the end
414f898c16aSJakub Kicinski            if i == 0:
415f898c16aSJakub Kicinski                raise
416f898c16aSJakub Kicinski            ksft_pr(f"Failed to create context {i + 1}, trying to test what we got")
417f898c16aSJakub Kicinski            ctx_cnt = i
418f898c16aSJakub Kicinski            break
419f898c16aSJakub Kicinski
4200d6ccfe6SJakub Kicinski        _rss_key_check(cfg, context=ctx_id)
4210d6ccfe6SJakub Kicinski
422f898c16aSJakub Kicinski        if not create_with_cfg:
4230759356bSJakub Kicinski            ethtool(f"-X {cfg.ifname} context {ctx_id} {want_cfg}")
4240d6ccfe6SJakub Kicinski            _rss_key_check(cfg, context=ctx_id)
425f898c16aSJakub Kicinski
426f898c16aSJakub Kicinski        # Sanity check the context we just created
4270759356bSJakub Kicinski        data = get_rss(cfg, ctx_id)
428f898c16aSJakub Kicinski        ksft_eq(min(data['rss-indirection-table']), 2 + i * 2, "Unexpected context cfg: " + str(data))
429f898c16aSJakub Kicinski        ksft_eq(max(data['rss-indirection-table']), 2 + i * 2 + 1, "Unexpected context cfg: " + str(data))
430f898c16aSJakub Kicinski
431f898c16aSJakub Kicinski        ports.append(rand_port())
4320759356bSJakub Kicinski        flow = f"flow-type tcp{cfg.addr_ipver} dst-port {ports[i]} context {ctx_id}"
4330759356bSJakub Kicinski        ntuple = ethtool_create(cfg, "-N", flow)
4340759356bSJakub Kicinski        defer(ethtool, f"-N {cfg.ifname} delete {ntuple}")
435f898c16aSJakub Kicinski
436f898c16aSJakub Kicinski    for i in range(ctx_cnt):
437847aa551SJakub Kicinski        _send_traffic_check(cfg, ports[i], f"context {i}",
438847aa551SJakub Kicinski                            { 'target': (2+i*2, 3+i*2),
439847aa551SJakub Kicinski                              'noise': (0, 1),
440847aa551SJakub Kicinski                              'empty': list(range(2, 2+i*2)) + list(range(4+i*2, 2+2*ctx_cnt)) })
441f898c16aSJakub Kicinski
442f898c16aSJakub Kicinski    if requested_ctx_cnt != ctx_cnt:
443f898c16aSJakub Kicinski        raise KsftSkipEx(f"Tested only {ctx_cnt} contexts, wanted {requested_ctx_cnt}")
444f898c16aSJakub Kicinski
445f898c16aSJakub Kicinski
446f898c16aSJakub Kicinskidef test_rss_context4(cfg):
447f898c16aSJakub Kicinski    test_rss_context(cfg, 4)
448f898c16aSJakub Kicinski
449f898c16aSJakub Kicinski
450f898c16aSJakub Kicinskidef test_rss_context32(cfg):
451f898c16aSJakub Kicinski    test_rss_context(cfg, 32)
452f898c16aSJakub Kicinski
453f898c16aSJakub Kicinski
454f898c16aSJakub Kicinskidef test_rss_context4_create_with_cfg(cfg):
455f898c16aSJakub Kicinski    test_rss_context(cfg, 4, create_with_cfg=True)
456f898c16aSJakub Kicinski
457f898c16aSJakub Kicinski
458e2c9703dSJakub Kicinskidef test_rss_context_queue_reconfigure(cfg):
459e2c9703dSJakub Kicinski    test_rss_queue_reconfigure(cfg, main_ctx=False)
460e2c9703dSJakub Kicinski
461e2c9703dSJakub Kicinski
462f898c16aSJakub Kicinskidef test_rss_context_out_of_order(cfg, ctx_cnt=4):
463f898c16aSJakub Kicinski    """
464f898c16aSJakub Kicinski    Test separating traffic into RSS contexts.
465f898c16aSJakub Kicinski    Contexts are removed in semi-random order, and steering re-tested
466f898c16aSJakub Kicinski    to make sure removal doesn't break steering to surviving contexts.
467f898c16aSJakub Kicinski    Test requires 3 contexts to work.
468f898c16aSJakub Kicinski    """
469f898c16aSJakub Kicinski
470f898c16aSJakub Kicinski    require_ntuple(cfg)
471f898c16aSJakub Kicinski
472f898c16aSJakub Kicinski    requested_ctx_cnt = ctx_cnt
473f898c16aSJakub Kicinski
474f898c16aSJakub Kicinski    # Try to allocate more queues when necessary
475f898c16aSJakub Kicinski    qcnt = len(_get_rx_cnts(cfg))
4760759356bSJakub Kicinski    if qcnt < 2 + 2 * ctx_cnt:
477f898c16aSJakub Kicinski        try:
478f898c16aSJakub Kicinski            ksft_pr(f"Increasing queue count {qcnt} -> {2 + 2 * ctx_cnt}")
479f898c16aSJakub Kicinski            ethtool(f"-L {cfg.ifname} combined {2 + 2 * ctx_cnt}")
4800759356bSJakub Kicinski            defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")
481f898c16aSJakub Kicinski        except:
482f898c16aSJakub Kicinski            raise KsftSkipEx("Not enough queues for the test")
483f898c16aSJakub Kicinski
484f898c16aSJakub Kicinski    ntuple = []
4850759356bSJakub Kicinski    ctx = []
486f898c16aSJakub Kicinski    ports = []
487f898c16aSJakub Kicinski
488f898c16aSJakub Kicinski    def remove_ctx(idx):
4890759356bSJakub Kicinski        ntuple[idx].exec()
490f898c16aSJakub Kicinski        ntuple[idx] = None
4910759356bSJakub Kicinski        ctx[idx].exec()
4920759356bSJakub Kicinski        ctx[idx] = None
493f898c16aSJakub Kicinski
494f898c16aSJakub Kicinski    def check_traffic():
495f898c16aSJakub Kicinski        for i in range(ctx_cnt):
4960759356bSJakub Kicinski            if ctx[i]:
497847aa551SJakub Kicinski                expected = {
498847aa551SJakub Kicinski                    'target': (2+i*2, 3+i*2),
499847aa551SJakub Kicinski                    'noise': (0, 1),
500847aa551SJakub Kicinski                    'empty': list(range(2, 2+i*2)) + list(range(4+i*2, 2+2*ctx_cnt))
501847aa551SJakub Kicinski                }
502f898c16aSJakub Kicinski            else:
503847aa551SJakub Kicinski                expected = {
504847aa551SJakub Kicinski                    'target': (0, 1),
505847aa551SJakub Kicinski                    'empty':  range(2, 2+2*ctx_cnt)
506847aa551SJakub Kicinski                }
507847aa551SJakub Kicinski
508847aa551SJakub Kicinski            _send_traffic_check(cfg, ports[i], f"context {i}", expected)
509f898c16aSJakub Kicinski
510f898c16aSJakub Kicinski    # Use queues 0 and 1 for normal traffic
511f898c16aSJakub Kicinski    ethtool(f"-X {cfg.ifname} equal 2")
5120759356bSJakub Kicinski    defer(ethtool, f"-X {cfg.ifname} default")
513f898c16aSJakub Kicinski
514f898c16aSJakub Kicinski    for i in range(ctx_cnt):
5150759356bSJakub Kicinski        ctx_id = ethtool_create(cfg, "-X", f"context new start {2 + i * 2} equal 2")
5160759356bSJakub Kicinski        ctx.append(defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete"))
517f898c16aSJakub Kicinski
518f898c16aSJakub Kicinski        ports.append(rand_port())
5190759356bSJakub Kicinski        flow = f"flow-type tcp{cfg.addr_ipver} dst-port {ports[i]} context {ctx_id}"
5200759356bSJakub Kicinski        ntuple_id = ethtool_create(cfg, "-N", flow)
5210759356bSJakub Kicinski        ntuple.append(defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}"))
522f898c16aSJakub Kicinski
523f898c16aSJakub Kicinski    check_traffic()
524f898c16aSJakub Kicinski
525f898c16aSJakub Kicinski    # Remove middle context
526f898c16aSJakub Kicinski    remove_ctx(ctx_cnt // 2)
527f898c16aSJakub Kicinski    check_traffic()
528f898c16aSJakub Kicinski
529f898c16aSJakub Kicinski    # Remove first context
530f898c16aSJakub Kicinski    remove_ctx(0)
531f898c16aSJakub Kicinski    check_traffic()
532f898c16aSJakub Kicinski
533f898c16aSJakub Kicinski    # Remove last context
534f898c16aSJakub Kicinski    remove_ctx(-1)
535f898c16aSJakub Kicinski    check_traffic()
536f898c16aSJakub Kicinski
537f898c16aSJakub Kicinski    if requested_ctx_cnt != ctx_cnt:
538f898c16aSJakub Kicinski        raise KsftSkipEx(f"Tested only {ctx_cnt} contexts, wanted {requested_ctx_cnt}")
539f898c16aSJakub Kicinski
540f898c16aSJakub Kicinski
541f898c16aSJakub Kicinskidef test_rss_context_overlap(cfg, other_ctx=0):
542f898c16aSJakub Kicinski    """
543f898c16aSJakub Kicinski    Test contexts overlapping with each other.
544f898c16aSJakub Kicinski    Use 4 queues for the main context, but only queues 2 and 3 for context 1.
545f898c16aSJakub Kicinski    """
546f898c16aSJakub Kicinski
547f898c16aSJakub Kicinski    require_ntuple(cfg)
548f898c16aSJakub Kicinski
549f898c16aSJakub Kicinski    queue_cnt = len(_get_rx_cnts(cfg))
5500759356bSJakub Kicinski    if queue_cnt < 4:
551f898c16aSJakub Kicinski        try:
552f898c16aSJakub Kicinski            ksft_pr(f"Increasing queue count {queue_cnt} -> 4")
553f898c16aSJakub Kicinski            ethtool(f"-L {cfg.ifname} combined 4")
5540759356bSJakub Kicinski            defer(ethtool, f"-L {cfg.ifname} combined {queue_cnt}")
555f898c16aSJakub Kicinski        except:
556f898c16aSJakub Kicinski            raise KsftSkipEx("Not enough queues for the test")
557f898c16aSJakub Kicinski
558f898c16aSJakub Kicinski    if other_ctx == 0:
559f898c16aSJakub Kicinski        ethtool(f"-X {cfg.ifname} equal 4")
5600759356bSJakub Kicinski        defer(ethtool, f"-X {cfg.ifname} default")
561f898c16aSJakub Kicinski    else:
562f898c16aSJakub Kicinski        other_ctx = ethtool_create(cfg, "-X", "context new")
563f898c16aSJakub Kicinski        ethtool(f"-X {cfg.ifname} context {other_ctx} equal 4")
5640759356bSJakub Kicinski        defer(ethtool, f"-X {cfg.ifname} context {other_ctx} delete")
565f898c16aSJakub Kicinski
566f898c16aSJakub Kicinski    ctx_id = ethtool_create(cfg, "-X", "context new")
567f898c16aSJakub Kicinski    ethtool(f"-X {cfg.ifname} context {ctx_id} start 2 equal 2")
5680759356bSJakub Kicinski    defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
569f898c16aSJakub Kicinski
570f898c16aSJakub Kicinski    port = rand_port()
571f898c16aSJakub Kicinski    if other_ctx:
572f898c16aSJakub Kicinski        flow = f"flow-type tcp{cfg.addr_ipver} dst-port {port} context {other_ctx}"
5730759356bSJakub Kicinski        ntuple_id = ethtool_create(cfg, "-N", flow)
5740759356bSJakub Kicinski        ntuple = defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")
575f898c16aSJakub Kicinski
576f898c16aSJakub Kicinski    # Test the main context
577f898c16aSJakub Kicinski    cnts = _get_rx_cnts(cfg)
578f898c16aSJakub Kicinski    GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000)
579f898c16aSJakub Kicinski    cnts = _get_rx_cnts(cfg, prev=cnts)
580f898c16aSJakub Kicinski
581f898c16aSJakub Kicinski    ksft_ge(sum(cnts[ :4]), 20000, "traffic on main context: " + str(cnts))
582f898c16aSJakub Kicinski    ksft_ge(sum(cnts[ :2]),  7000, "traffic on main context (1/2): " + str(cnts))
583f898c16aSJakub Kicinski    ksft_ge(sum(cnts[2:4]),  7000, "traffic on main context (2/2): " + str(cnts))
584f898c16aSJakub Kicinski    if other_ctx == 0:
585f898c16aSJakub Kicinski        ksft_eq(sum(cnts[4: ]),     0, "traffic on other queues: " + str(cnts))
586f898c16aSJakub Kicinski
587f898c16aSJakub Kicinski    # Now create a rule for context 1 and make sure traffic goes to a subset
588f898c16aSJakub Kicinski    if other_ctx:
5890759356bSJakub Kicinski        ntuple.exec()
590f898c16aSJakub Kicinski    flow = f"flow-type tcp{cfg.addr_ipver} dst-port {port} context {ctx_id}"
5910759356bSJakub Kicinski    ntuple_id = ethtool_create(cfg, "-N", flow)
5920759356bSJakub Kicinski    defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")
593f898c16aSJakub Kicinski
594f898c16aSJakub Kicinski    cnts = _get_rx_cnts(cfg)
595f898c16aSJakub Kicinski    GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000)
596f898c16aSJakub Kicinski    cnts = _get_rx_cnts(cfg, prev=cnts)
597f898c16aSJakub Kicinski
5980b877458SJakub Kicinski    directed = sum(cnts[2:4])
5990b877458SJakub Kicinski    ksft_lt(sum(cnts[ :2]), directed / 2, "traffic on main context: " + str(cnts))
6000b877458SJakub Kicinski    ksft_ge(directed, 20000, "traffic on extra context: " + str(cnts))
601f898c16aSJakub Kicinski    if other_ctx == 0:
602f898c16aSJakub Kicinski        ksft_eq(sum(cnts[4: ]),     0, "traffic on other queues: " + str(cnts))
603f898c16aSJakub Kicinski
604f898c16aSJakub Kicinski
605f898c16aSJakub Kicinskidef test_rss_context_overlap2(cfg):
606f898c16aSJakub Kicinski    test_rss_context_overlap(cfg, True)
607f898c16aSJakub Kicinski
608f898c16aSJakub Kicinski
609f898c16aSJakub Kicinskidef main() -> None:
610f898c16aSJakub Kicinski    with NetDrvEpEnv(__file__, nsim_test=False) as cfg:
6117e3e5b0bSJakub Kicinski        cfg.ethnl = EthtoolFamily()
612f898c16aSJakub Kicinski        cfg.netdevnl = NetdevFamily()
613f898c16aSJakub Kicinski
614e2c9703dSJakub Kicinski        ksft_run([test_rss_key_indir, test_rss_queue_reconfigure,
615933048feSJakub Kicinski                  test_rss_resize, test_hitless_key_update,
616f898c16aSJakub Kicinski                  test_rss_context, test_rss_context4, test_rss_context32,
617*c1ad8ef8SJakub Kicinski                  test_rss_context_dump, test_rss_context_queue_reconfigure,
618f898c16aSJakub Kicinski                  test_rss_context_overlap, test_rss_context_overlap2,
619f898c16aSJakub Kicinski                  test_rss_context_out_of_order, test_rss_context4_create_with_cfg],
620f898c16aSJakub Kicinski                 args=(cfg, ))
621f898c16aSJakub Kicinski    ksft_exit()
622f898c16aSJakub Kicinski
623f898c16aSJakub Kicinski
624f898c16aSJakub Kicinskiif __name__ == "__main__":
625f898c16aSJakub Kicinski    main()
626