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