diff options
Diffstat (limited to 'tools/testing/selftests/drivers/net/hw/rss_ctx.py')
| -rwxr-xr-x | tools/testing/selftests/drivers/net/hw/rss_ctx.py | 243 |
1 files changed, 223 insertions, 20 deletions
diff --git a/tools/testing/selftests/drivers/net/hw/rss_ctx.py b/tools/testing/selftests/drivers/net/hw/rss_ctx.py index ed7e405682f0..f36f76d6ca59 100755 --- a/tools/testing/selftests/drivers/net/hw/rss_ctx.py +++ b/tools/testing/selftests/drivers/net/hw/rss_ctx.py @@ -4,13 +4,16 @@ import datetime import random import re +import time +from lib.py import ksft_disruptive from lib.py import ksft_run, ksft_pr, ksft_exit from lib.py import ksft_eq, ksft_ne, ksft_ge, ksft_in, ksft_lt, ksft_true, ksft_raises from lib.py import NetDrvEpEnv -from lib.py import EthtoolFamily, NetdevFamily +from lib.py import EthtoolFamily, NetdevFamily, NlError from lib.py import KsftSkipEx, KsftFailEx -from lib.py import rand_port -from lib.py import ethtool, ip, defer, GenerateTraffic, CmdExitFailure +from lib.py import rand_port, rand_ports +from lib.py import cmd, ethtool, ip, defer, CmdExitFailure, wait_file +from lib.py import GenerateTraffic def _rss_key_str(key): @@ -54,9 +57,10 @@ def ethtool_create(cfg, act, opts): def require_ntuple(cfg): features = ethtool(f"-k {cfg.ifname}", json=True)[0] if not features["ntuple-filters"]["active"]: - # ntuple is more of a capability than a config knob, don't bother - # trying to enable it (until some driver actually needs it). - raise KsftSkipEx("Ntuple filters not enabled on the device: " + str(features["ntuple-filters"])) + if features["ntuple-filters"]["fixed"]: + raise KsftSkipEx("Device does not support ntuple-filters") + ethtool(f"-K {cfg.ifname} ntuple-filters on") + defer(ethtool, f"-K {cfg.ifname} ntuple-filters off") def require_context_cnt(cfg, need_cnt): @@ -163,9 +167,17 @@ def test_rss_key_indir(cfg): ksft_eq(1, max(data['rss-indirection-table'])) # Check we only get traffic on the first 2 queues - cnts = _get_rx_cnts(cfg) - GenerateTraffic(cfg).wait_pkts_and_stop(20000) - cnts = _get_rx_cnts(cfg, prev=cnts) + + # Retry a few times in case the flows skew to a single queue. + attempts = 3 + for attempt in range(attempts): + cnts = _get_rx_cnts(cfg) + GenerateTraffic(cfg).wait_pkts_and_stop(20000) + cnts = _get_rx_cnts(cfg, prev=cnts) + if cnts[0] >= 5000 and cnts[1] >= 5000: + break + ksft_pr(f"Skewed queue distribution, attempt {attempt + 1}/{attempts}: " + str(cnts)) + # 2 queues, 20k packets, must be at least 5k per queue ksft_ge(cnts[0], 5000, "traffic on main context (1/2): " + str(cnts)) ksft_ge(cnts[1], 5000, "traffic on main context (2/2): " + str(cnts)) @@ -175,9 +187,18 @@ def test_rss_key_indir(cfg): # Restore, and check traffic gets spread again reset_indir.exec() - cnts = _get_rx_cnts(cfg) - GenerateTraffic(cfg).wait_pkts_and_stop(20000) - cnts = _get_rx_cnts(cfg, prev=cnts) + for attempt in range(attempts): + cnts = _get_rx_cnts(cfg) + GenerateTraffic(cfg).wait_pkts_and_stop(20000) + cnts = _get_rx_cnts(cfg, prev=cnts) + if qcnt > 4: + if sum(cnts[:2]) < sum(cnts[2:]): + break + else: + if cnts[2] >= 3500: + break + ksft_pr(f"Skewed queue distribution, attempt {attempt + 1}/{attempts}: " + str(cnts)) + if qcnt > 4: # First two queues get less traffic than all the rest ksft_lt(sum(cnts[:2]), sum(cnts[2:]), @@ -354,7 +375,7 @@ def test_hitless_key_update(cfg): tgen.wait_pkts_and_stop(5000) ksft_lt((t1 - t0).total_seconds(), 0.15) - ksft_eq(errors1 - errors1, 0) + ksft_eq(errors1 - errors0, 0) ksft_eq(carrier1 - carrier0, 0) @@ -452,7 +473,7 @@ def test_rss_context(cfg, ctx_cnt=1, create_with_cfg=None): except: raise KsftSkipEx("Not enough queues for the test") - ports = [] + ports = rand_ports(ctx_cnt) # Use queues 0 and 1 for normal traffic ethtool(f"-X {cfg.ifname} equal 2") @@ -486,7 +507,6 @@ def test_rss_context(cfg, ctx_cnt=1, create_with_cfg=None): ksft_eq(min(data['rss-indirection-table']), 2 + i * 2, "Unexpected context cfg: " + str(data)) ksft_eq(max(data['rss-indirection-table']), 2 + i * 2 + 1, "Unexpected context cfg: " + str(data)) - ports.append(rand_port()) flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {ports[i]} context {ctx_id}" ntuple = ethtool_create(cfg, "-N", flow) defer(ethtool, f"-N {cfg.ifname} delete {ntuple}") @@ -542,7 +562,7 @@ def test_rss_context_out_of_order(cfg, ctx_cnt=4): ntuple = [] ctx = [] - ports = [] + ports = rand_ports(ctx_cnt) def remove_ctx(idx): ntuple[idx].exec() @@ -574,7 +594,6 @@ def test_rss_context_out_of_order(cfg, ctx_cnt=4): ctx_id = ethtool_create(cfg, "-X", f"context new start {2 + i * 2} equal 2") ctx.append(defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")) - ports.append(rand_port()) flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {ports[i]} context {ctx_id}" ntuple_id = ethtool_create(cfg, "-N", flow) ntuple.append(defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")) @@ -788,9 +807,10 @@ def test_rss_default_context_rule(cfg): ethtool(f"-N {cfg.ifname} {flow_generic}") defer(ethtool, f"-N {cfg.ifname} delete 1") + ports = rand_ports(2) # Specific high-priority rule for a random port that should stay on context 0. # Assign loc 0 so it is evaluated before the generic rule. - port_main = rand_port() + port_main = ports[0] flow_main = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port_main} context 0 loc 0" ethtool(f"-N {cfg.ifname} {flow_main}") defer(ethtool, f"-N {cfg.ifname} delete 0") @@ -803,12 +823,192 @@ def test_rss_default_context_rule(cfg): 'empty' : (2, 3) }) # And that traffic for any other port is steered to the new context - port_other = rand_port() + port_other = ports[1] _send_traffic_check(cfg, port_other, f"context {ctx_id}", { 'target': (2, 3), 'noise' : (0, 1) }) +def _set_flow_hash(cfg, fl_type, fields, context=0): + req = {"header": {"dev-index": cfg.ifindex}, + "flow-hash": {fl_type: fields}} + if context: + req["context"] = context + cfg.ethnl.rss_set(req) + + +def _get_flow_hash(cfg, fl_type, context=0): + req = {"header": {"dev-index": cfg.ifindex}} + if context: + req["context"] = context + rss = cfg.ethnl.rss_get(req) + return rss.get("flow-hash", {}).get(fl_type, set()) + + +def test_rss_context_flow_hash(cfg): + """ + Validate, with traffic, that an additional RSS context honors the + flow-hash field selection. If the driver lacks per-context field + configuration ("ops->rxfh_per_ctx_fields") fall back to setting the + fields on the main context, which the kernel applies device-wide. + """ + + require_ntuple(cfg) + + queue_cnt = len(_get_rx_cnts(cfg)) + if queue_cnt < 6: + try: + ksft_pr(f"Increasing queue count {queue_cnt} -> 6") + ethtool(f"-L {cfg.ifname} combined 6") + defer(ethtool, f"-L {cfg.ifname} combined {queue_cnt}") + except CmdExitFailure as exc: + raise KsftSkipEx("Not enough queues for the test") from exc + + fl_type = f"tcp{cfg.addr_ipver}" + if not _get_flow_hash(cfg, fl_type): + raise KsftSkipEx(f"Device does not report flow-hash for {fl_type}") + + # Reserve queues 0/1 for main, build a new context spanning 2..5 + ethtool(f"-X {cfg.ifname} equal 2") + defer(ethtool, f"-X {cfg.ifname} default") + ctx_id = ethtool_create(cfg, "-X", "context new start 2 equal 4") + defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete") + + port = rand_port() + flow = f"flow-type {fl_type} dst-ip {cfg.addr} dst-port {port} context {ctx_id}" + ntuple = ethtool_create(cfg, "-N", flow) + defer(ethtool, f"-N {cfg.ifname} delete {ntuple}") + + ip_only = {"ip-src", "ip-dst"} + ip_l4 = ip_only | {"l4-b-0-1", "l4-b-2-3"} + + # Try per-context flow-hash; fall back to main context if unsupported. + cfg_ctx = ctx_id + try: + orig = _get_flow_hash(cfg, fl_type, context=ctx_id) + _set_flow_hash(cfg, fl_type, ip_only, context=ctx_id) + except NlError: + ksft_pr("Per-context flow-hash not supported, using device-wide") + cfg_ctx = 0 + orig = _get_flow_hash(cfg, fl_type) + _set_flow_hash(cfg, fl_type, ip_only) + defer(_set_flow_hash, cfg, fl_type, orig, context=cfg_ctx) + + def measure(): + cnts = _get_rx_cnts(cfg) + GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000) + cnts = _get_rx_cnts(cfg, prev=cnts) + ctx_cnts = cnts[2:6] + directed = sum(ctx_cnts) + used = sum(1 for c in ctx_cnts if c > directed / 200) + return cnts, directed, used + + # IP-only hash: iperf3 streams share src/dst IP, all should land on the + # same queue inside the context's range. + cnts, directed, used = measure() + ksft_ge(directed, 20000, f"traffic on context {ctx_id} (IP-only): {cnts}") + ksft_eq(used, 1, f"IP-only hash should use one queue in context {ctx_id}, got: {cnts}") + + # IP+L4 hash: streams have distinct src ports, traffic should spread. + _set_flow_hash(cfg, fl_type, ip_l4, context=cfg_ctx) + + cnts, directed, used = measure() + ksft_ge(directed, 20000, f"traffic on context {ctx_id} (IP+L4): {cnts}") + ksft_ge(used, 2, f"IP+L4 hash should spread across context {ctx_id} queues, got: {cnts}") + + +@ksft_disruptive +def test_rss_context_persist_ifupdown(cfg, pre_down=False): + """ + Test that RSS contexts and their associated ntuple filters persist across + an interface down/up cycle. + + """ + + require_ntuple(cfg) + + qcnt = len(_get_rx_cnts(cfg)) + if qcnt < 6: + try: + ethtool(f"-L {cfg.ifname} combined 6") + defer(ethtool, f"-L {cfg.ifname} combined {qcnt}") + except Exception as exc: + raise KsftSkipEx("Not enough queues for the test") from exc + + ethtool(f"-X {cfg.ifname} equal 2") + defer(ethtool, f"-X {cfg.ifname} default") + + ifup = defer(ip, f"link set dev {cfg.ifname} up") + if pre_down: + ip(f"link set dev {cfg.ifname} down") + + try: + ctx1_id = ethtool_create(cfg, "-X", "context new start 2 equal 2") + defer(ethtool, f"-X {cfg.ifname} context {ctx1_id} delete") + except CmdExitFailure as exc: + raise KsftSkipEx("Create context not supported with interface down") from exc + + ctx2_id = ethtool_create(cfg, "-X", "context new start 4 equal 2") + defer(ethtool, f"-X {cfg.ifname} context {ctx2_id} delete") + + port_ctx2 = rand_port() + flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port_ctx2} context {ctx2_id}" + ntuple_id = ethtool_create(cfg, "-N", flow) + defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}") + + if not pre_down: + ip(f"link set dev {cfg.ifname} down") + ifup.exec() + + wait_file(f"/sys/class/net/{cfg.ifname}/carrier", + lambda x: x.strip() == "1", deadline=20) + + remote_addr = cfg.remote_addr_v[cfg.addr_ipver] + for _ in range(10): + if cmd(f"ping -c 1 -W 1 {remote_addr}", fail=False).ret == 0: + break + time.sleep(1) + else: + raise KsftSkipEx("Cannot reach remote host after interface up") + + ctxs = cfg.ethnl.rss_get({'header': {'dev-name': cfg.ifname}}, dump=True) + + data1 = [c for c in ctxs if c.get('context') == ctx1_id] + ksft_eq(len(data1), 1, f"Context {ctx1_id} should persist after ifup") + + data2 = [c for c in ctxs if c.get('context') == ctx2_id] + ksft_eq(len(data2), 1, f"Context {ctx2_id} should persist after ifup") + + _ntuple_rule_check(cfg, ntuple_id, ctx2_id) + + cnts = _get_rx_cnts(cfg) + GenerateTraffic(cfg).wait_pkts_and_stop(20000) + cnts = _get_rx_cnts(cfg, prev=cnts) + + main_traffic = sum(cnts[0:2]) + ksft_ge(main_traffic, 18000, f"Main context traffic distribution: {cnts}") + ksft_lt(sum(cnts[2:6]), 500, f"Other context queues should be mostly empty: {cnts}") + + _send_traffic_check(cfg, port_ctx2, f"context {ctx2_id}", + {'target': (4, 5), + 'noise': (0, 1), + 'empty': (2, 3)}) + + +def test_rss_context_persist_create_and_ifdown(cfg): + """ + Create RSS contexts then cycle the interface down and up. + """ + test_rss_context_persist_ifupdown(cfg, pre_down=False) + + +def test_rss_context_persist_ifdown_and_create(cfg): + """ + Bring interface down first, then create RSS contexts and bring up. + """ + test_rss_context_persist_ifupdown(cfg, pre_down=True) + + def main() -> None: with NetDrvEpEnv(__file__, nsim_test=False) as cfg: cfg.context_cnt = None @@ -823,7 +1023,10 @@ def main() -> None: test_rss_context_out_of_order, test_rss_context4_create_with_cfg, test_flow_add_context_missing, test_delete_rss_context_busy, test_rss_ntuple_addition, - test_rss_default_context_rule], + test_rss_default_context_rule, + test_rss_context_flow_hash, + test_rss_context_persist_create_and_ifdown, + test_rss_context_persist_ifdown_and_create], args=(cfg, )) ksft_exit() |
