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