mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-05-16 18:22:00 -04:00
selftests: rss_drv: Add RSS indirection table resize tests
Add resize tests to rss_drv.py. Devices without dynamic table sizing are skipped via _require_dynamic_indir_size(). resize_periodic: set a periodic 4-entry table, shrink channels to fold, grow back to unfold. Check the exact pattern is preserved. Has main and non-default context variants. resize_below_user_size_reject: send a periodic table with user_size between the big and small device table sizes. Verify that shrinking below user_size is rejected even though the table is periodic. Has main and non-default context variants. resize_nonperiodic_reject: set a non-periodic table (equal N), verify that channel reduction is rejected. resize_nonperiodic_no_corruption: verify a failed resize leaves both the indirection table contents and the channel count unchanged. Signed-off-by: Björn Töpel <bjorn@kernel.org> Link: https://patch.msgid.link/20260320085826.1957255-5-bjorn@kernel.org Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
committed by
Jakub Kicinski
parent
57cdfe0dc7
commit
10329ce492
@@ -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, ksft_raises
|
||||
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,224 @@ 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 non-default periodic table ([3, 2, 1, 0] x N) via netlink,
|
||||
reduce channels to trigger a fold, then increase to trigger an
|
||||
unfold. Using a reversed pattern (instead of [0, 1, 2, 3]) ensures
|
||||
the test can distinguish a correct fold from a driver that silently
|
||||
resets the table to defaults. Verify the exact 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}")
|
||||
|
||||
_require_dynamic_indir_size(cfg, ch_max)
|
||||
|
||||
ctx_id = _maybe_create_context(cfg, create_context)
|
||||
|
||||
# Set a non-default periodic pattern via netlink.
|
||||
# Send only 4 entries (user_size=4) so the kernel replicates it
|
||||
# to fill the device table. This allows folding down to 4 entries.
|
||||
rss = _get_rss(cfg, context=ctx_id)
|
||||
orig_size = len(rss['rss-indirection-table'])
|
||||
pattern = [3, 2, 1, 0]
|
||||
req = {'header': {'dev-index': cfg.ifindex}, 'indir': pattern}
|
||||
if ctx_id:
|
||||
req['context'] = ctx_id
|
||||
else:
|
||||
defer(ethtool, f"-X {cfg.ifname} default")
|
||||
cfg.ethnl.rss_set(req)
|
||||
|
||||
# Shrink — should fold
|
||||
ethtool(f"-L {cfg.ifname} combined 4")
|
||||
rss = _get_rss(cfg, context=ctx_id)
|
||||
indir = rss['rss-indirection-table']
|
||||
|
||||
ksft_ge(orig_size, len(indir), "Table did not shrink")
|
||||
ksft_eq(indir, [3, 2, 1, 0] * (len(indir) // 4),
|
||||
"Folded table has wrong pattern")
|
||||
|
||||
# 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(indir, [3, 2, 1, 0] * (len(indir) // 4),
|
||||
"Unfolded table has wrong pattern")
|
||||
|
||||
|
||||
@ksft_variants([
|
||||
KsftNamedVariant("main", False),
|
||||
KsftNamedVariant("ctx", True),
|
||||
])
|
||||
def resize_below_user_size_reject(cfg, create_context):
|
||||
"""Test that shrinking below user_size is rejected.
|
||||
|
||||
Send a table via netlink whose size (user_size) sits between
|
||||
the small and large device table sizes. The table is periodic,
|
||||
so folding would normally succeed, but the user_size floor must
|
||||
prevent it.
|
||||
"""
|
||||
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}")
|
||||
|
||||
_require_dynamic_indir_size(cfg, ch_max)
|
||||
|
||||
ctx_id = _maybe_create_context(cfg, create_context)
|
||||
|
||||
# Measure the table size at max channels
|
||||
rss = _get_rss(cfg, context=ctx_id)
|
||||
big_size = len(rss['rss-indirection-table'])
|
||||
|
||||
# Measure the table size at reduced channels
|
||||
ethtool(f"-L {cfg.ifname} combined 4")
|
||||
rss = _get_rss(cfg, context=ctx_id)
|
||||
small_size = len(rss['rss-indirection-table'])
|
||||
ethtool(f"-L {cfg.ifname} combined {ch_max}")
|
||||
|
||||
if small_size >= big_size:
|
||||
raise KsftSkipEx("Table did not shrink at reduced channels")
|
||||
|
||||
# Find a user_size
|
||||
user_size = None
|
||||
for div in [2, 4]:
|
||||
candidate = big_size // div
|
||||
if candidate > small_size and big_size % candidate == 0:
|
||||
user_size = candidate
|
||||
break
|
||||
if user_size is None:
|
||||
raise KsftSkipEx("No suitable user_size between small and big table")
|
||||
|
||||
# Send a periodic sub-table of exactly user_size entries.
|
||||
# Pattern safe for 4 channels.
|
||||
pattern = [0, 1, 2, 3] * (user_size // 4)
|
||||
if len(pattern) != user_size:
|
||||
raise KsftSkipEx(f"user_size ({user_size}) not divisible by 4")
|
||||
req = {'header': {'dev-index': cfg.ifindex}, 'indir': pattern}
|
||||
if ctx_id:
|
||||
req['context'] = ctx_id
|
||||
else:
|
||||
defer(ethtool, f"-X {cfg.ifname} default")
|
||||
cfg.ethnl.rss_set(req)
|
||||
|
||||
# Shrink channels — table would go to small_size < user_size.
|
||||
# The table is periodic so folding would work, but user_size
|
||||
# floor must reject it.
|
||||
with ksft_raises(CmdExitFailure):
|
||||
ethtool(f"-L {cfg.ifname} combined 4")
|
||||
|
||||
|
||||
@ksft_variants([
|
||||
KsftNamedVariant("main", False),
|
||||
KsftNamedVariant("ctx", True),
|
||||
])
|
||||
def resize_nonperiodic_reject(cfg, create_context):
|
||||
"""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.
|
||||
An additional context with a periodic table is created to verify
|
||||
that validation catches the non-periodic one even when others
|
||||
are fine.
|
||||
"""
|
||||
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}")
|
||||
|
||||
_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 ""
|
||||
|
||||
# Create an extra context with a periodic (foldable) table so that
|
||||
# the validation must iterate all contexts to find the bad one.
|
||||
extra_ctx = _maybe_create_context(cfg, True)
|
||||
ethtool(f"-X {cfg.ifname} context {extra_ctx} equal 2")
|
||||
|
||||
ethtool(f"-X {cfg.ifname} {ctx_ref} equal {ch_max}")
|
||||
if not create_context:
|
||||
defer(ethtool, f"-X {cfg.ifname} default")
|
||||
|
||||
with ksft_raises(CmdExitFailure):
|
||||
ethtool(f"-L {cfg.ifname} combined 2")
|
||||
|
||||
|
||||
@ksft_variants([
|
||||
KsftNamedVariant("main", False),
|
||||
KsftNamedVariant("ctx", True),
|
||||
])
|
||||
def resize_nonperiodic_no_corruption(cfg, create_context):
|
||||
"""Test that a failed resize does not corrupt table or channel count.
|
||||
|
||||
Set a non-periodic table, attempt a channel reduction (which must
|
||||
fail), then verify both the indirection table contents and the
|
||||
channel count are unchanged.
|
||||
"""
|
||||
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}")
|
||||
|
||||
_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 {ch_max}")
|
||||
if not create_context:
|
||||
defer(ethtool, f"-X {cfg.ifname} default")
|
||||
|
||||
rss_before = _get_rss(cfg, context=ctx_id)
|
||||
|
||||
with ksft_raises(CmdExitFailure):
|
||||
ethtool(f"-L {cfg.ifname} combined 2")
|
||||
|
||||
rss_after = _get_rss(cfg, context=ctx_id)
|
||||
ksft_eq(rss_after['rss-indirection-table'],
|
||||
rss_before['rss-indirection-table'],
|
||||
"Indirection table corrupted after failed resize")
|
||||
|
||||
channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})
|
||||
ksft_eq(channels['combined-count'], ch_max,
|
||||
"Channel count changed after failed resize")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
""" Ksft boiler plate main """
|
||||
with NetDrvEnv(__file__) as cfg:
|
||||
cfg.ethnl = EthtoolFamily()
|
||||
ksft_run([indir_size_4x], args=(cfg, ))
|
||||
ksft_run([indir_size_4x, resize_periodic,
|
||||
resize_below_user_size_reject,
|
||||
resize_nonperiodic_reject,
|
||||
resize_nonperiodic_no_corruption], args=(cfg, ))
|
||||
ksft_exit()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user