Path: blob/master/tools/testing/selftests/drivers/net/hw/rss_ctx.py
26295 views
#!/usr/bin/env python31# SPDX-License-Identifier: GPL-2.023import datetime4import random5import re6from lib.py import ksft_run, ksft_pr, ksft_exit7from lib.py import ksft_eq, ksft_ne, ksft_ge, ksft_in, ksft_lt, ksft_true, ksft_raises8from lib.py import NetDrvEpEnv9from lib.py import EthtoolFamily, NetdevFamily10from lib.py import KsftSkipEx, KsftFailEx11from lib.py import rand_port12from lib.py import ethtool, ip, defer, GenerateTraffic, CmdExitFailure131415def _rss_key_str(key):16return ":".join(["{:02x}".format(x) for x in key])171819def _rss_key_rand(length):20return [random.randint(0, 255) for _ in range(length)]212223def _rss_key_check(cfg, data=None, context=0):24if data is None:25data = get_rss(cfg, context=context)26if 'rss-hash-key' not in data:27return28non_zero = [x for x in data['rss-hash-key'] if x != 0]29ksft_eq(bool(non_zero), True, comment=f"RSS key is all zero {data['rss-hash-key']}")303132def get_rss(cfg, context=0):33return ethtool(f"-x {cfg.ifname} context {context}", json=True)[0]343536def get_drop_err_sum(cfg):37stats = ip("-s -s link show dev " + cfg.ifname, json=True)[0]38cnt = 039for key in ['errors', 'dropped', 'over_errors', 'fifo_errors',40'length_errors', 'crc_errors', 'missed_errors',41'frame_errors']:42cnt += stats["stats64"]["rx"][key]43return cnt, stats["stats64"]["tx"]["carrier_changes"]444546def ethtool_create(cfg, act, opts):47output = ethtool(f"{act} {cfg.ifname} {opts}").stdout48# Output will be something like: "New RSS context is 1" or49# "Added rule with ID 7", we want the integer from the end50return int(output.split()[-1])515253def require_ntuple(cfg):54features = ethtool(f"-k {cfg.ifname}", json=True)[0]55if not features["ntuple-filters"]["active"]:56# ntuple is more of a capability than a config knob, don't bother57# trying to enable it (until some driver actually needs it).58raise KsftSkipEx("Ntuple filters not enabled on the device: " + str(features["ntuple-filters"]))596061def require_context_cnt(cfg, need_cnt):62# There's no good API to get the context count, so the tests63# which try to add a lot opportunisitically set the count they64# discovered. Careful with test ordering!65if need_cnt and cfg.context_cnt and cfg.context_cnt < need_cnt:66raise KsftSkipEx(f"Test requires at least {need_cnt} contexts, but device only has {cfg.context_cnt}")676869# Get Rx packet counts for all queues, as a simple list of integers70# if @prev is specified the prev counts will be subtracted71def _get_rx_cnts(cfg, prev=None):72cfg.wait_hw_stats_settle()73data = cfg.netdevnl.qstats_get({"ifindex": cfg.ifindex, "scope": ["queue"]}, dump=True)74data = [x for x in data if x['queue-type'] == "rx"]75max_q = max([x["queue-id"] for x in data])76queue_stats = [0] * (max_q + 1)77for q in data:78queue_stats[q["queue-id"]] = q["rx-packets"]79if prev and q["queue-id"] < len(prev):80queue_stats[q["queue-id"]] -= prev[q["queue-id"]]81return queue_stats828384def _send_traffic_check(cfg, port, name, params):85# params is a dict with 3 possible keys:86# - "target": required, which queues we expect to get iperf traffic87# - "empty": optional, which queues should see no traffic at all88# - "noise": optional, which queues we expect to see low traffic;89# used for queues of the main context, since some background90# OS activity may use those queues while we're testing91# the value for each is a list, or some other iterable containing queue ids.9293cnts = _get_rx_cnts(cfg)94GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000)95cnts = _get_rx_cnts(cfg, prev=cnts)9697directed = sum(cnts[i] for i in params['target'])9899ksft_ge(directed, 20000, f"traffic on {name}: " + str(cnts))100if params.get('noise'):101ksft_lt(sum(cnts[i] for i in params['noise']), directed / 2,102f"traffic on other queues ({name})':" + str(cnts))103if params.get('empty'):104ksft_eq(sum(cnts[i] for i in params['empty']), 0,105f"traffic on inactive queues ({name}): " + str(cnts))106107108def _ntuple_rule_check(cfg, rule_id, ctx_id):109"""Check that ntuple rule references RSS context ID"""110text = ethtool(f"-n {cfg.ifname} rule {rule_id}").stdout111pattern = f"RSS Context (ID: )?{ctx_id}"112ksft_true(re.search(pattern, text), "RSS context not referenced in ntuple rule")113114115def test_rss_key_indir(cfg):116"""Test basics like updating the main RSS key and indirection table."""117118qcnt = len(_get_rx_cnts(cfg))119if qcnt < 3:120KsftSkipEx("Device has fewer than 3 queues (or doesn't support queue stats)")121122data = get_rss(cfg)123want_keys = ['rss-hash-key', 'rss-hash-function', 'rss-indirection-table']124for k in want_keys:125if k not in data:126raise KsftFailEx("ethtool results missing key: " + k)127if not data[k]:128raise KsftFailEx(f"ethtool results empty for '{k}': {data[k]}")129130_rss_key_check(cfg, data=data)131key_len = len(data['rss-hash-key'])132133# Set the key134key = _rss_key_rand(key_len)135ethtool(f"-X {cfg.ifname} hkey " + _rss_key_str(key))136137data = get_rss(cfg)138ksft_eq(key, data['rss-hash-key'])139140# Set the indirection table and the key together141key = _rss_key_rand(key_len)142ethtool(f"-X {cfg.ifname} equal 3 hkey " + _rss_key_str(key))143reset_indir = defer(ethtool, f"-X {cfg.ifname} default")144145data = get_rss(cfg)146_rss_key_check(cfg, data=data)147ksft_eq(0, min(data['rss-indirection-table']))148ksft_eq(2, max(data['rss-indirection-table']))149150# Reset indirection table and set the key151key = _rss_key_rand(key_len)152ethtool(f"-X {cfg.ifname} default hkey " + _rss_key_str(key))153data = get_rss(cfg)154_rss_key_check(cfg, data=data)155ksft_eq(0, min(data['rss-indirection-table']))156ksft_eq(qcnt - 1, max(data['rss-indirection-table']))157158# Set the indirection table159ethtool(f"-X {cfg.ifname} equal 2")160data = get_rss(cfg)161ksft_eq(0, min(data['rss-indirection-table']))162ksft_eq(1, max(data['rss-indirection-table']))163164# Check we only get traffic on the first 2 queues165cnts = _get_rx_cnts(cfg)166GenerateTraffic(cfg).wait_pkts_and_stop(20000)167cnts = _get_rx_cnts(cfg, prev=cnts)168# 2 queues, 20k packets, must be at least 5k per queue169ksft_ge(cnts[0], 5000, "traffic on main context (1/2): " + str(cnts))170ksft_ge(cnts[1], 5000, "traffic on main context (2/2): " + str(cnts))171# The other queues should be unused172ksft_eq(sum(cnts[2:]), 0, "traffic on unused queues: " + str(cnts))173174# Restore, and check traffic gets spread again175reset_indir.exec()176177cnts = _get_rx_cnts(cfg)178GenerateTraffic(cfg).wait_pkts_and_stop(20000)179cnts = _get_rx_cnts(cfg, prev=cnts)180# First two queues get less traffic than all the rest181ksft_lt(sum(cnts[:2]), sum(cnts[2:]), "traffic distributed: " + str(cnts))182183184def test_rss_queue_reconfigure(cfg, main_ctx=True):185"""Make sure queue changes can't override requested RSS config.186187By default main RSS table should change to include all queues.188When user sets a specific RSS config the driver should preserve it,189even when queue count changes. Driver should refuse to deactivate190queues used in the user-set RSS config.191"""192193if not main_ctx:194require_ntuple(cfg)195196# Start with 4 queues, an arbitrary known number.197try:198qcnt = len(_get_rx_cnts(cfg))199ethtool(f"-L {cfg.ifname} combined 4")200defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")201except:202raise KsftSkipEx("Not enough queues for the test or qstat not supported")203204if main_ctx:205ctx_id = 0206ctx_ref = ""207else:208ctx_id = ethtool_create(cfg, "-X", "context new")209ctx_ref = f"context {ctx_id}"210defer(ethtool, f"-X {cfg.ifname} {ctx_ref} delete")211212# Indirection table should be distributing to all queues.213data = get_rss(cfg, context=ctx_id)214ksft_eq(0, min(data['rss-indirection-table']))215ksft_eq(3, max(data['rss-indirection-table']))216217# Increase queues, indirection table should be distributing to all queues.218# It's unclear whether tables of additional contexts should be reset, too.219if main_ctx:220ethtool(f"-L {cfg.ifname} combined 5")221data = get_rss(cfg)222ksft_eq(0, min(data['rss-indirection-table']))223ksft_eq(4, max(data['rss-indirection-table']))224ethtool(f"-L {cfg.ifname} combined 4")225226# Configure the table explicitly227port = rand_port()228ethtool(f"-X {cfg.ifname} {ctx_ref} weight 1 0 0 1")229if main_ctx:230other_key = 'empty'231defer(ethtool, f"-X {cfg.ifname} default")232else:233other_key = 'noise'234flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {ctx_id}"235ntuple = ethtool_create(cfg, "-N", flow)236defer(ethtool, f"-N {cfg.ifname} delete {ntuple}")237238_send_traffic_check(cfg, port, ctx_ref, { 'target': (0, 3),239other_key: (1, 2) })240241# We should be able to increase queues, but table should be left untouched242ethtool(f"-L {cfg.ifname} combined 5")243data = get_rss(cfg, context=ctx_id)244ksft_eq({0, 3}, set(data['rss-indirection-table']))245246_send_traffic_check(cfg, port, ctx_ref, { 'target': (0, 3),247other_key: (1, 2, 4) })248249# Setting queue count to 3 should fail, queue 3 is used250try:251ethtool(f"-L {cfg.ifname} combined 3")252except CmdExitFailure:253pass254else:255raise Exception(f"Driver didn't prevent us from deactivating a used queue (context {ctx_id})")256257if not main_ctx:258ethtool(f"-L {cfg.ifname} combined 4")259flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {ctx_id} action 1"260try:261# this targets queue 4, which doesn't exist262ntuple2 = ethtool_create(cfg, "-N", flow)263defer(ethtool, f"-N {cfg.ifname} delete {ntuple2}")264except CmdExitFailure:265pass266else:267raise Exception(f"Driver didn't prevent us from targeting a nonexistent queue (context {ctx_id})")268# change the table to target queues 0 and 2269ethtool(f"-X {cfg.ifname} {ctx_ref} weight 1 0 1 0")270# ntuple rule therefore targets queues 1 and 3271try:272ntuple2 = ethtool_create(cfg, "-N", flow)273except CmdExitFailure:274ksft_pr("Driver does not support rss + queue offset")275return276277defer(ethtool, f"-N {cfg.ifname} delete {ntuple2}")278# should replace existing filter279ksft_eq(ntuple, ntuple2)280_send_traffic_check(cfg, port, ctx_ref, { 'target': (1, 3),281'noise' : (0, 2) })282# Setting queue count to 3 should fail, queue 3 is used283try:284ethtool(f"-L {cfg.ifname} combined 3")285except CmdExitFailure:286pass287else:288raise Exception(f"Driver didn't prevent us from deactivating a used queue (context {ctx_id})")289290291def test_rss_resize(cfg):292"""Test resizing of the RSS table.293294Some devices dynamically increase and decrease the size of the RSS295indirection table based on the number of enabled queues.296When that happens driver must maintain the balance of entries297(preferably duplicating the smaller table).298"""299300channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})301ch_max = channels['combined-max']302qcnt = channels['combined-count']303304if ch_max < 2:305raise KsftSkipEx(f"Not enough queues for the test: {ch_max}")306307ethtool(f"-L {cfg.ifname} combined 2")308defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")309310ethtool(f"-X {cfg.ifname} weight 1 7")311defer(ethtool, f"-X {cfg.ifname} default")312313ethtool(f"-L {cfg.ifname} combined {ch_max}")314data = get_rss(cfg)315ksft_eq(0, min(data['rss-indirection-table']))316ksft_eq(1, max(data['rss-indirection-table']))317318ksft_eq(7,319data['rss-indirection-table'].count(1) /320data['rss-indirection-table'].count(0),321f"Table imbalance after resize: {data['rss-indirection-table']}")322323324def test_hitless_key_update(cfg):325"""Test that flows may be rehashed without impacting traffic.326327Some workloads may want to rehash the flows in response to an imbalance.328Most effective way to do that is changing the RSS key. Check that changing329the key does not cause link flaps or traffic disruption.330331Disrupting traffic for key update is not a bug, but makes the key332update unusable for rehashing under load.333"""334data = get_rss(cfg)335key_len = len(data['rss-hash-key'])336337key = _rss_key_rand(key_len)338339tgen = GenerateTraffic(cfg)340try:341errors0, carrier0 = get_drop_err_sum(cfg)342t0 = datetime.datetime.now()343ethtool(f"-X {cfg.ifname} hkey " + _rss_key_str(key))344t1 = datetime.datetime.now()345errors1, carrier1 = get_drop_err_sum(cfg)346finally:347tgen.wait_pkts_and_stop(5000)348349ksft_lt((t1 - t0).total_seconds(), 0.2)350ksft_eq(errors1 - errors1, 0)351ksft_eq(carrier1 - carrier0, 0)352353354def test_rss_context_dump(cfg):355"""356Test dumping RSS contexts. This tests mostly exercises the kernel APIs.357"""358359# Get a random key of the right size360data = get_rss(cfg)361if 'rss-hash-key' in data:362key_data = _rss_key_rand(len(data['rss-hash-key']))363key = _rss_key_str(key_data)364else:365key_data = []366key = "ba:ad"367368ids = []369try:370ids.append(ethtool_create(cfg, "-X", f"context new"))371defer(ethtool, f"-X {cfg.ifname} context {ids[-1]} delete")372373ids.append(ethtool_create(cfg, "-X", f"context new weight 1 1"))374defer(ethtool, f"-X {cfg.ifname} context {ids[-1]} delete")375376ids.append(ethtool_create(cfg, "-X", f"context new hkey {key}"))377defer(ethtool, f"-X {cfg.ifname} context {ids[-1]} delete")378except CmdExitFailure:379if not ids:380raise KsftSkipEx("Unable to add any contexts")381ksft_pr(f"Added only {len(ids)} out of 3 contexts")382383expect_tuples = set([(cfg.ifname, -1)] + [(cfg.ifname, ctx_id) for ctx_id in ids])384385# Dump all386ctxs = cfg.ethnl.rss_get({}, dump=True)387tuples = [(c['header']['dev-name'], c.get('context', -1)) for c in ctxs]388ksft_eq(len(tuples), len(set(tuples)), "duplicates in context dump")389ctx_tuples = set([ctx for ctx in tuples if ctx[0] == cfg.ifname])390ksft_eq(expect_tuples, ctx_tuples)391392# Sanity-check the results393for data in ctxs:394ksft_ne(set(data.get('indir', [1])), {0}, "indir table is all zero")395ksft_ne(set(data.get('hkey', [1])), {0}, "key is all zero")396397# More specific checks398if len(ids) > 1 and data.get('context') == ids[1]:399ksft_eq(set(data['indir']), {0, 1},400"ctx1 - indir table mismatch")401if len(ids) > 2 and data.get('context') == ids[2]:402ksft_eq(data['hkey'], bytes(key_data), "ctx2 - key mismatch")403404# Ifindex filter405ctxs = cfg.ethnl.rss_get({'header': {'dev-name': cfg.ifname}}, dump=True)406tuples = [(c['header']['dev-name'], c.get('context', -1)) for c in ctxs]407ctx_tuples = set(tuples)408ksft_eq(len(tuples), len(ctx_tuples), "duplicates in context dump")409ksft_eq(expect_tuples, ctx_tuples)410411# Skip ctx 0412expect_tuples.remove((cfg.ifname, -1))413414ctxs = cfg.ethnl.rss_get({'start-context': 1}, dump=True)415tuples = [(c['header']['dev-name'], c.get('context', -1)) for c in ctxs]416ksft_eq(len(tuples), len(set(tuples)), "duplicates in context dump")417ctx_tuples = set([ctx for ctx in tuples if ctx[0] == cfg.ifname])418ksft_eq(expect_tuples, ctx_tuples)419420# And finally both with ifindex and skip main421ctxs = cfg.ethnl.rss_get({'header': {'dev-name': cfg.ifname}, 'start-context': 1}, dump=True)422ctx_tuples = set([(c['header']['dev-name'], c.get('context', -1)) for c in ctxs])423ksft_eq(expect_tuples, ctx_tuples)424425426def test_rss_context(cfg, ctx_cnt=1, create_with_cfg=None):427"""428Test separating traffic into RSS contexts.429The queues will be allocated 2 for each context:430ctx0 ctx1 ctx2 ctx3431[0 1] [2 3] [4 5] [6 7] ...432"""433434require_ntuple(cfg)435436requested_ctx_cnt = ctx_cnt437438# Try to allocate more queues when necessary439qcnt = len(_get_rx_cnts(cfg))440if qcnt < 2 + 2 * ctx_cnt:441try:442ksft_pr(f"Increasing queue count {qcnt} -> {2 + 2 * ctx_cnt}")443ethtool(f"-L {cfg.ifname} combined {2 + 2 * ctx_cnt}")444defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")445except:446raise KsftSkipEx("Not enough queues for the test")447448ports = []449450# Use queues 0 and 1 for normal traffic451ethtool(f"-X {cfg.ifname} equal 2")452defer(ethtool, f"-X {cfg.ifname} default")453454for i in range(ctx_cnt):455want_cfg = f"start {2 + i * 2} equal 2"456create_cfg = want_cfg if create_with_cfg else ""457458try:459ctx_id = ethtool_create(cfg, "-X", f"context new {create_cfg}")460defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")461except CmdExitFailure:462# try to carry on and skip at the end463if i == 0:464raise465ksft_pr(f"Failed to create context {i + 1}, trying to test what we got")466ctx_cnt = i467if cfg.context_cnt is None:468cfg.context_cnt = ctx_cnt469break470471_rss_key_check(cfg, context=ctx_id)472473if not create_with_cfg:474ethtool(f"-X {cfg.ifname} context {ctx_id} {want_cfg}")475_rss_key_check(cfg, context=ctx_id)476477# Sanity check the context we just created478data = get_rss(cfg, ctx_id)479ksft_eq(min(data['rss-indirection-table']), 2 + i * 2, "Unexpected context cfg: " + str(data))480ksft_eq(max(data['rss-indirection-table']), 2 + i * 2 + 1, "Unexpected context cfg: " + str(data))481482ports.append(rand_port())483flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {ports[i]} context {ctx_id}"484ntuple = ethtool_create(cfg, "-N", flow)485defer(ethtool, f"-N {cfg.ifname} delete {ntuple}")486487_ntuple_rule_check(cfg, ntuple, ctx_id)488489for i in range(ctx_cnt):490_send_traffic_check(cfg, ports[i], f"context {i}",491{ 'target': (2+i*2, 3+i*2),492'noise': (0, 1),493'empty': list(range(2, 2+i*2)) + list(range(4+i*2, 2+2*ctx_cnt)) })494495if requested_ctx_cnt != ctx_cnt:496raise KsftSkipEx(f"Tested only {ctx_cnt} contexts, wanted {requested_ctx_cnt}")497498499def test_rss_context4(cfg):500test_rss_context(cfg, 4)501502503def test_rss_context32(cfg):504test_rss_context(cfg, 32)505506507def test_rss_context4_create_with_cfg(cfg):508test_rss_context(cfg, 4, create_with_cfg=True)509510511def test_rss_context_queue_reconfigure(cfg):512test_rss_queue_reconfigure(cfg, main_ctx=False)513514515def test_rss_context_out_of_order(cfg, ctx_cnt=4):516"""517Test separating traffic into RSS contexts.518Contexts are removed in semi-random order, and steering re-tested519to make sure removal doesn't break steering to surviving contexts.520Test requires 3 contexts to work.521"""522523require_ntuple(cfg)524require_context_cnt(cfg, 4)525526# Try to allocate more queues when necessary527qcnt = len(_get_rx_cnts(cfg))528if qcnt < 2 + 2 * ctx_cnt:529try:530ksft_pr(f"Increasing queue count {qcnt} -> {2 + 2 * ctx_cnt}")531ethtool(f"-L {cfg.ifname} combined {2 + 2 * ctx_cnt}")532defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")533except:534raise KsftSkipEx("Not enough queues for the test")535536ntuple = []537ctx = []538ports = []539540def remove_ctx(idx):541ntuple[idx].exec()542ntuple[idx] = None543ctx[idx].exec()544ctx[idx] = None545546def check_traffic():547for i in range(ctx_cnt):548if ctx[i]:549expected = {550'target': (2+i*2, 3+i*2),551'noise': (0, 1),552'empty': list(range(2, 2+i*2)) + list(range(4+i*2, 2+2*ctx_cnt))553}554else:555expected = {556'target': (0, 1),557'empty': range(2, 2+2*ctx_cnt)558}559560_send_traffic_check(cfg, ports[i], f"context {i}", expected)561562# Use queues 0 and 1 for normal traffic563ethtool(f"-X {cfg.ifname} equal 2")564defer(ethtool, f"-X {cfg.ifname} default")565566for i in range(ctx_cnt):567ctx_id = ethtool_create(cfg, "-X", f"context new start {2 + i * 2} equal 2")568ctx.append(defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete"))569570ports.append(rand_port())571flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {ports[i]} context {ctx_id}"572ntuple_id = ethtool_create(cfg, "-N", flow)573ntuple.append(defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}"))574575check_traffic()576577# Remove middle context578remove_ctx(ctx_cnt // 2)579check_traffic()580581# Remove first context582remove_ctx(0)583check_traffic()584585# Remove last context586remove_ctx(-1)587check_traffic()588589590def test_rss_context_overlap(cfg, other_ctx=0):591"""592Test contexts overlapping with each other.593Use 4 queues for the main context, but only queues 2 and 3 for context 1.594"""595596require_ntuple(cfg)597if other_ctx:598require_context_cnt(cfg, 2)599600queue_cnt = len(_get_rx_cnts(cfg))601if queue_cnt < 4:602try:603ksft_pr(f"Increasing queue count {queue_cnt} -> 4")604ethtool(f"-L {cfg.ifname} combined 4")605defer(ethtool, f"-L {cfg.ifname} combined {queue_cnt}")606except:607raise KsftSkipEx("Not enough queues for the test")608609if other_ctx == 0:610ethtool(f"-X {cfg.ifname} equal 4")611defer(ethtool, f"-X {cfg.ifname} default")612else:613other_ctx = ethtool_create(cfg, "-X", "context new")614ethtool(f"-X {cfg.ifname} context {other_ctx} equal 4")615defer(ethtool, f"-X {cfg.ifname} context {other_ctx} delete")616617ctx_id = ethtool_create(cfg, "-X", "context new")618ethtool(f"-X {cfg.ifname} context {ctx_id} start 2 equal 2")619defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")620621port = rand_port()622if other_ctx:623flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {other_ctx}"624ntuple_id = ethtool_create(cfg, "-N", flow)625ntuple = defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")626627# Test the main context628cnts = _get_rx_cnts(cfg)629GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000)630cnts = _get_rx_cnts(cfg, prev=cnts)631632ksft_ge(sum(cnts[ :4]), 20000, "traffic on main context: " + str(cnts))633ksft_ge(sum(cnts[ :2]), 7000, "traffic on main context (1/2): " + str(cnts))634ksft_ge(sum(cnts[2:4]), 7000, "traffic on main context (2/2): " + str(cnts))635if other_ctx == 0:636ksft_eq(sum(cnts[4: ]), 0, "traffic on other queues: " + str(cnts))637638# Now create a rule for context 1 and make sure traffic goes to a subset639if other_ctx:640ntuple.exec()641flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {ctx_id}"642ntuple_id = ethtool_create(cfg, "-N", flow)643defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")644645cnts = _get_rx_cnts(cfg)646GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000)647cnts = _get_rx_cnts(cfg, prev=cnts)648649directed = sum(cnts[2:4])650ksft_lt(sum(cnts[ :2]), directed / 2, "traffic on main context: " + str(cnts))651ksft_ge(directed, 20000, "traffic on extra context: " + str(cnts))652if other_ctx == 0:653ksft_eq(sum(cnts[4: ]), 0, "traffic on other queues: " + str(cnts))654655656def test_rss_context_overlap2(cfg):657test_rss_context_overlap(cfg, True)658659660def test_flow_add_context_missing(cfg):661"""662Test that we are not allowed to add a rule pointing to an RSS context663which was never created.664"""665666require_ntuple(cfg)667668# Find a context which doesn't exist669for ctx_id in range(1, 100):670try:671get_rss(cfg, context=ctx_id)672except CmdExitFailure:673break674675with ksft_raises(CmdExitFailure) as cm:676flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port 1234 context {ctx_id}"677ntuple_id = ethtool_create(cfg, "-N", flow)678ethtool(f"-N {cfg.ifname} delete {ntuple_id}")679if cm.exception:680ksft_in('Invalid argument', cm.exception.cmd.stderr)681682683def test_delete_rss_context_busy(cfg):684"""685Test that deletion returns -EBUSY when an rss context is being used686by an ntuple filter.687"""688689require_ntuple(cfg)690691# create additional rss context692ctx_id = ethtool_create(cfg, "-X", "context new")693ctx_deleter = defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")694695# utilize context from ntuple filter696port = rand_port()697flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {ctx_id}"698ntuple_id = ethtool_create(cfg, "-N", flow)699defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")700701# attempt to delete in-use context702try:703ctx_deleter.exec_only()704ctx_deleter.cancel()705raise KsftFailEx(f"deleted context {ctx_id} used by rule {ntuple_id}")706except CmdExitFailure:707pass708709710def test_rss_ntuple_addition(cfg):711"""712Test that the queue offset (ring_cookie) of an ntuple rule is added713to the queue number read from the indirection table.714"""715716require_ntuple(cfg)717718queue_cnt = len(_get_rx_cnts(cfg))719if queue_cnt < 4:720try:721ksft_pr(f"Increasing queue count {queue_cnt} -> 4")722ethtool(f"-L {cfg.ifname} combined 4")723defer(ethtool, f"-L {cfg.ifname} combined {queue_cnt}")724except:725raise KsftSkipEx("Not enough queues for the test")726727# Use queue 0 for normal traffic728ethtool(f"-X {cfg.ifname} equal 1")729defer(ethtool, f"-X {cfg.ifname} default")730731# create additional rss context732ctx_id = ethtool_create(cfg, "-X", "context new equal 2")733defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")734735# utilize context from ntuple filter736port = rand_port()737flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {ctx_id} action 2"738try:739ntuple_id = ethtool_create(cfg, "-N", flow)740except CmdExitFailure:741raise KsftSkipEx("Ntuple filter with RSS and nonzero action not supported")742defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")743744_send_traffic_check(cfg, port, f"context {ctx_id}", { 'target': (2, 3),745'empty' : (1,),746'noise' : (0,) })747748749def test_rss_default_context_rule(cfg):750"""751Allocate a port, direct this port to context 0, then create a new RSS752context and steer all TCP traffic to it (context 1). Verify that:753* Traffic to the specific port continues to use queues of the main754context (0/1).755* Traffic to any other TCP port is redirected to the new context756(queues 2/3).757"""758759require_ntuple(cfg)760761queue_cnt = len(_get_rx_cnts(cfg))762if queue_cnt < 4:763try:764ksft_pr(f"Increasing queue count {queue_cnt} -> 4")765ethtool(f"-L {cfg.ifname} combined 4")766defer(ethtool, f"-L {cfg.ifname} combined {queue_cnt}")767except Exception as exc:768raise KsftSkipEx("Not enough queues for the test") from exc769770# Use queues 0 and 1 for the main context771ethtool(f"-X {cfg.ifname} equal 2")772defer(ethtool, f"-X {cfg.ifname} default")773774# Create a new RSS context that uses queues 2 and 3775ctx_id = ethtool_create(cfg, "-X", "context new start 2 equal 2")776defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")777778# Generic low-priority rule: redirect all TCP traffic to the new context.779# Give it an explicit higher location number (lower priority).780flow_generic = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} context {ctx_id} loc 1"781ethtool(f"-N {cfg.ifname} {flow_generic}")782defer(ethtool, f"-N {cfg.ifname} delete 1")783784# Specific high-priority rule for a random port that should stay on context 0.785# Assign loc 0 so it is evaluated before the generic rule.786port_main = rand_port()787flow_main = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port_main} context 0 loc 0"788ethtool(f"-N {cfg.ifname} {flow_main}")789defer(ethtool, f"-N {cfg.ifname} delete 0")790791_ntuple_rule_check(cfg, 1, ctx_id)792793# Verify that traffic matching the specific rule still goes to queues 0/1794_send_traffic_check(cfg, port_main, "context 0",795{ 'target': (0, 1),796'empty' : (2, 3) })797798# And that traffic for any other port is steered to the new context799port_other = rand_port()800_send_traffic_check(cfg, port_other, f"context {ctx_id}",801{ 'target': (2, 3),802'noise' : (0, 1) })803804805def main() -> None:806with NetDrvEpEnv(__file__, nsim_test=False) as cfg:807cfg.context_cnt = None808cfg.ethnl = EthtoolFamily()809cfg.netdevnl = NetdevFamily()810811ksft_run([test_rss_key_indir, test_rss_queue_reconfigure,812test_rss_resize, test_hitless_key_update,813test_rss_context, test_rss_context4, test_rss_context32,814test_rss_context_dump, test_rss_context_queue_reconfigure,815test_rss_context_overlap, test_rss_context_overlap2,816test_rss_context_out_of_order, test_rss_context4_create_with_cfg,817test_flow_add_context_missing,818test_delete_rss_context_busy, test_rss_ntuple_addition,819test_rss_default_context_rule],820args=(cfg, ))821ksft_exit()822823824if __name__ == "__main__":825main()826827828