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