[PATCH net-next 5/5] selftests: rss_drv: Add RSS indirection table resize tests

Björn Töpel posted 5 patches 1 month ago
There is a newer version of this series
[PATCH net-next 5/5] selftests: rss_drv: Add RSS indirection table resize tests
Posted by Björn Töpel 1 month ago
Add resize tests to rss_drv.py. Devices without dynamic table
sizing are skipped via _require_dynamic_indir_size().

resize_periodic: set a periodic table (equal 2), shrink
channels to fold, grow back to unfold. Check the pattern is
preserved. Has main and non-default context variants.

resize_nonperiodic_reject: set a non-periodic table (equal N),
verify that channel reduction is rejected.

Pass queue_count=8 to NetDrvEnv so netdevsim is created with
enough queues for the resize tests.

Signed-off-by: Björn Töpel <bjorn@kernel.org>
---
 .../selftests/drivers/net/hw/rss_drv.py       | 101 +++++++++++++++++-
 1 file changed, 96 insertions(+), 5 deletions(-)

diff --git a/tools/testing/selftests/drivers/net/hw/rss_drv.py b/tools/testing/selftests/drivers/net/hw/rss_drv.py
index 2d1a33189076..8725500f2955 100755
--- a/tools/testing/selftests/drivers/net/hw/rss_drv.py
+++ b/tools/testing/selftests/drivers/net/hw/rss_drv.py
@@ -5,9 +5,9 @@
 Driver-related behavior tests for RSS.
 """
 
-from lib.py import ksft_run, ksft_exit, ksft_ge
-from lib.py import ksft_variants, KsftNamedVariant, KsftSkipEx
-from lib.py import defer, ethtool
+from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_ge
+from lib.py import ksft_variants, KsftNamedVariant, KsftSkipEx, KsftFailEx
+from lib.py import defer, ethtool, CmdExitFailure
 from lib.py import EthtoolFamily, NlError
 from lib.py import NetDrvEnv
 
@@ -45,6 +45,18 @@ def _maybe_create_context(cfg, create_context):
     return ctx_id
 
 
+def _require_dynamic_indir_size(cfg, ch_max):
+    """Skip if the device does not dynamically size its indirection table."""
+    ethtool(f"-X {cfg.ifname} default")
+    ethtool(f"-L {cfg.ifname} combined 2")
+    small = len(_get_rss(cfg)['rss-indirection-table'])
+    ethtool(f"-L {cfg.ifname} combined {ch_max}")
+    large = len(_get_rss(cfg)['rss-indirection-table'])
+
+    if small == large:
+        raise KsftSkipEx("Device does not dynamically size indirection table")
+
+
 @ksft_variants([
     KsftNamedVariant("main", False),
     KsftNamedVariant("ctx", True),
@@ -76,11 +88,90 @@ def indir_size_4x(cfg, create_context):
         _test_rss_indir_size(cfg, test_max, context=ctx_id)
 
 
+@ksft_variants([
+    KsftNamedVariant("main", False),
+    KsftNamedVariant("ctx", True),
+])
+def resize_periodic(cfg, create_context):
+    """Test that a periodic indirection table survives channel changes.
+
+    Set a periodic table (equal 2), reduce channels to trigger a
+    fold, then increase to trigger an unfold. Verify the table pattern
+    is preserved and the size tracks the channel count.
+    """
+    channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})
+    ch_max = channels.get('combined-max', 0)
+    qcnt = channels['combined-count']
+
+    if ch_max < 4:
+        raise KsftSkipEx(f"Not enough queues for the test: max={ch_max}")
+
+    defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")
+    ethtool(f"-L {cfg.ifname} combined {ch_max}")
+
+    _require_dynamic_indir_size(cfg, ch_max)
+
+    ctx_id = _maybe_create_context(cfg, create_context)
+    ctx_ref = f"context {ctx_id}" if ctx_id else ""
+
+    ethtool(f"-X {cfg.ifname} {ctx_ref} equal 2")
+    if not create_context:
+        defer(ethtool, f"-X {cfg.ifname} default")
+
+    orig_size = len(_get_rss(cfg, context=ctx_id)['rss-indirection-table'])
+
+    # Shrink — should fold
+    ethtool(f"-L {cfg.ifname} combined 2")
+    rss = _get_rss(cfg, context=ctx_id)
+    indir = rss['rss-indirection-table']
+
+    ksft_ge(orig_size, len(indir), "Table did not shrink")
+    ksft_eq(set(indir), {0, 1}, "Folded table has wrong queues")
+
+    # Grow back — should unfold
+    ethtool(f"-L {cfg.ifname} combined {ch_max}")
+    rss = _get_rss(cfg, context=ctx_id)
+    indir = rss['rss-indirection-table']
+
+    ksft_eq(len(indir), orig_size, "Table size not restored")
+    ksft_eq(set(indir), {0, 1}, "Unfolded table has wrong queues")
+
+
+def resize_nonperiodic_reject(cfg):
+    """Test that a non-periodic table blocks channel reduction.
+
+    Set equal weight across all queues so the table is not periodic
+    at any smaller size, then verify channel reduction is rejected.
+    """
+    channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})
+    ch_max = channels.get('combined-max', 0)
+    qcnt = channels['combined-count']
+
+    if ch_max < 4:
+        raise KsftSkipEx(f"Not enough queues for the test: max={ch_max}")
+
+    defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")
+    ethtool(f"-L {cfg.ifname} combined {ch_max}")
+
+    _require_dynamic_indir_size(cfg, ch_max)
+
+    ethtool(f"-X {cfg.ifname} equal {ch_max}")
+    defer(ethtool, f"-X {cfg.ifname} default")
+
+    try:
+        ethtool(f"-L {cfg.ifname} combined 2")
+    except CmdExitFailure:
+        pass
+    else:
+        raise KsftFailEx("Channel reduction should fail with non-periodic table")
+
+
 def main() -> None:
     """ Ksft boiler plate main """
-    with NetDrvEnv(__file__) as cfg:
+    with NetDrvEnv(__file__, queue_count=8) as cfg:
         cfg.ethnl = EthtoolFamily()
-        ksft_run([indir_size_4x], args=(cfg, ))
+        ksft_run([indir_size_4x, resize_periodic,
+                  resize_nonperiodic_reject], args=(cfg, ))
     ksft_exit()
 
 
-- 
2.53.0

Re: [PATCH net-next 5/5] selftests: rss_drv: Add RSS indirection table resize tests
Posted by Jakub Kicinski 1 month ago
On Tue,  3 Mar 2026 19:15:33 +0100 Björn Töpel wrote:
> +def _require_dynamic_indir_size(cfg, ch_max):
> +    """Skip if the device does not dynamically size its indirection table."""
> +    ethtool(f"-X {cfg.ifname} default")
> +    ethtool(f"-L {cfg.ifname} combined 2")
> +    small = len(_get_rss(cfg)['rss-indirection-table'])
> +    ethtool(f"-L {cfg.ifname} combined {ch_max}")
> +    large = len(_get_rss(cfg)['rss-indirection-table'])
> +
> +    if small == large:
> +        raise KsftSkipEx("Device does not dynamically size indirection table")
> +
> +
>  @ksft_variants([
>      KsftNamedVariant("main", False),
>      KsftNamedVariant("ctx", True),
> @@ -76,11 +88,90 @@ def indir_size_4x(cfg, create_context):
>          _test_rss_indir_size(cfg, test_max, context=ctx_id)
>  
>  
> +@ksft_variants([
> +    KsftNamedVariant("main", False),
> +    KsftNamedVariant("ctx", True),
> +])
> +def resize_periodic(cfg, create_context):
> +    """Test that a periodic indirection table survives channel changes.
> +
> +    Set a periodic table (equal 2), reduce channels to trigger a
> +    fold, then increase to trigger an unfold. Verify the table pattern
> +    is preserved and the size tracks the channel count.
> +    """
> +    channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})
> +    ch_max = channels.get('combined-max', 0)
> +    qcnt = channels['combined-count']
> +
> +    if ch_max < 4:
> +        raise KsftSkipEx(f"Not enough queues for the test: max={ch_max}")
> +
> +    defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")
> +    ethtool(f"-L {cfg.ifname} combined {ch_max}")
> +
> +    _require_dynamic_indir_size(cfg, ch_max)
> +
> +    ctx_id = _maybe_create_context(cfg, create_context)
> +    ctx_ref = f"context {ctx_id}" if ctx_id else ""
> +
> +    ethtool(f"-X {cfg.ifname} {ctx_ref} equal 2")
> +    if not create_context:
> +        defer(ethtool, f"-X {cfg.ifname} default")
> +
> +    orig_size = len(_get_rss(cfg, context=ctx_id)['rss-indirection-table'])
> +
> +    # Shrink — should fold
> +    ethtool(f"-L {cfg.ifname} combined 2")

I wonder if we may miss some cases because we init the table to [0, 1]
and also only enable only 2 queues. Let's set it to 4 queues here to
potentially catch the driver resetting the table? 2 vs 4 are both tiny
values.

Alternatively we could use netlink to set the inidir table as
 [1, 0] x N
rather than
 [0, 1] x N

and..

> +    rss = _get_rss(cfg, context=ctx_id)
> +    indir = rss['rss-indirection-table']
> +
> +    ksft_ge(orig_size, len(indir), "Table did not shrink")
> +    ksft_eq(set(indir), {0, 1}, "Folded table has wrong queues")

here actually check

	ksft_eq(indir, [1, 0] * (len / 2), "Folded table has wrong queues")

> +    # Grow back — should unfold
> +    ethtool(f"-L {cfg.ifname} combined {ch_max}")
> +    rss = _get_rss(cfg, context=ctx_id)
> +    indir = rss['rss-indirection-table']
> +
> +    ksft_eq(len(indir), orig_size, "Table size not restored")
> +    ksft_eq(set(indir), {0, 1}, "Unfolded table has wrong queues")
> +
> +
> +def resize_nonperiodic_reject(cfg):
> +    """Test that a non-periodic table blocks channel reduction.
> +
> +    Set equal weight across all queues so the table is not periodic
> +    at any smaller size, then verify channel reduction is rejected.
> +    """
> +    channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})
> +    ch_max = channels.get('combined-max', 0)
> +    qcnt = channels['combined-count']
> +
> +    if ch_max < 4:
> +        raise KsftSkipEx(f"Not enough queues for the test: max={ch_max}")
> +
> +    defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")
> +    ethtool(f"-L {cfg.ifname} combined {ch_max}")
> +
> +    _require_dynamic_indir_size(cfg, ch_max)
> +
> +    ethtool(f"-X {cfg.ifname} equal {ch_max}")
> +    defer(ethtool, f"-X {cfg.ifname} default")
> +
> +    try:
> +        ethtool(f"-L {cfg.ifname} combined 2")
> +    except CmdExitFailure:
> +        pass
> +    else:
> +        raise KsftFailEx("Channel reduction should fail with non-periodic table")

grep for ksft_raises()

>  def main() -> None:
>      """ Ksft boiler plate main """
> -    with NetDrvEnv(__file__) as cfg:
> +    with NetDrvEnv(__file__, queue_count=8) as cfg:

the queue_count=8 presumably only applies to netdevsim so it should go

>          cfg.ethnl = EthtoolFamily()
> -        ksft_run([indir_size_4x], args=(cfg, ))
> +        ksft_run([indir_size_4x, resize_periodic,
> +                  resize_nonperiodic_reject], args=(cfg, ))
>      ksft_exit()
>  
>