The core locks ctx->indir_size when an RSS context is created. Some NICs (e.g. bnxt) change their indirection table size based on the channel count, because the hardware table is a shared resource. This forces drivers to reject channel changes when RSS contexts exist. Add helpers to resize indirection tables: ethtool_rxfh_indir_can_resize() checks whether a table can be resized without modifying it. ethtool_rxfh_indir_resize() resizes a raw u32 table in place. Folding (shrink) requires the table to be periodic at the new size; non-periodic tables are rejected. Unfolding (grow) replicates the existing pattern. Sizes must be multiples of each other. ethtool_rxfh_contexts_resize_all() resizes all non-default RSS contexts. Validates every context before modifying any, so either all succeed or none are changed. Sends ETHTOOL_MSG_RSS_NTF per resized context after releasing rss_lock. No reallocation is needed because ethtool_rxfh_ctx_alloc() reserves space for rxfh_indir_space entries, and key_off is based on that maximum. Signed-off-by: Björn Töpel --- include/linux/ethtool.h | 3 + net/ethtool/common.c | 126 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+) diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h index 798abec67a1b..add7d16ca398 100644 --- a/include/linux/ethtool.h +++ b/include/linux/ethtool.h @@ -214,6 +214,9 @@ static inline u8 *ethtool_rxfh_context_key(struct ethtool_rxfh_context *ctx) } void ethtool_rxfh_context_lost(struct net_device *dev, u32 context_id); +int ethtool_rxfh_indir_can_resize(const u32 *tbl, u32 old_size, u32 new_size); +int ethtool_rxfh_indir_resize(u32 *tbl, u32 old_size, u32 new_size); +int ethtool_rxfh_contexts_resize_all(struct net_device *dev, u32 new_indir_size); struct link_mode_info { int speed; diff --git a/net/ethtool/common.c b/net/ethtool/common.c index e252cf20c22f..8df84ff9efa4 100644 --- a/net/ethtool/common.c +++ b/net/ethtool/common.c @@ -1204,6 +1204,132 @@ void ethtool_rxfh_context_lost(struct net_device *dev, u32 context_id) } EXPORT_SYMBOL(ethtool_rxfh_context_lost); +static bool ethtool_rxfh_indir_is_periodic(const u32 *tbl, u32 old_size, u32 new_size) +{ + u32 i; + + for (i = new_size; i < old_size; i++) + if (tbl[i] != tbl[i % new_size]) + return false; + return true; +} + +/** + * ethtool_rxfh_indir_can_resize - Check if an indirection table can be resized + * @tbl: indirection table + * @old_size: current number of entries in the table + * @new_size: desired number of entries + * + * Validate that @tbl can be resized from @old_size to @new_size without + * data loss. Read-only; does not modify the table. + * + * Return: 0 if resize is possible, -%EINVAL otherwise. + */ +int ethtool_rxfh_indir_can_resize(const u32 *tbl, u32 old_size, u32 new_size) +{ + if (old_size == 0 || new_size == 0) + return -EINVAL; + if (new_size == old_size) + return 0; + + if (new_size < old_size) { + if (old_size % new_size != 0) + return -EINVAL; + if (!ethtool_rxfh_indir_is_periodic(tbl, old_size, new_size)) + return -EINVAL; + return 0; + } + + if (new_size % old_size != 0) + return -EINVAL; + return 0; +} +EXPORT_SYMBOL(ethtool_rxfh_indir_can_resize); + +/* Resize without validation; caller must have called can_resize first */ +static void __ethtool_rxfh_indir_resize(u32 *tbl, u32 old_size, u32 new_size) +{ + u32 i; + + /* Unfold (grow): replicate existing pattern; fold is a no-op on the data */ + for (i = old_size; i < new_size; i++) + tbl[i] = tbl[i % old_size]; +} + +/** + * ethtool_rxfh_indir_resize - Fold or unfold an indirection table + * @tbl: indirection table (must have room for max(old_size, new_size) entries) + * @old_size: current number of entries in the table + * @new_size: desired number of entries + * + * Resize an RSS indirection table in place. When folding (shrinking), + * the table must be periodic with period @new_size; otherwise the + * operation is rejected. When unfolding (growing), the existing + * pattern is replicated. Both directions require the sizes to be + * multiples of each other. + * + * Return: 0 on success, -%EINVAL on failure (no mutation on failure). + */ +int ethtool_rxfh_indir_resize(u32 *tbl, u32 old_size, u32 new_size) +{ + int ret; + + ret = ethtool_rxfh_indir_can_resize(tbl, old_size, new_size); + if (ret) + return ret; + + __ethtool_rxfh_indir_resize(tbl, old_size, new_size); + return 0; +} +EXPORT_SYMBOL(ethtool_rxfh_indir_resize); + +/** + * ethtool_rxfh_contexts_resize_all - Resize all RSS context indirection tables + * @dev: network device + * @new_indir_size: new indirection table size + * + * Resize the indirection table of every non-default RSS context to + * @new_indir_size. Intended to be called from drivers when the + * device's indirection table size changes (e.g. on channel count + * change). An %ETHTOOL_MSG_RSS_NTF is sent for each resized context. + * + * All contexts are validated before any are modified, so either all + * contexts are resized or none are. + * + * Return: 0 on success, negative errno on failure. + */ +int ethtool_rxfh_contexts_resize_all(struct net_device *dev, u32 new_indir_size) +{ + struct ethtool_rxfh_context *ctx; + unsigned long context; + int ret; + + if (dev->ethtool_ops->rxfh_indir_space == 0 || + new_indir_size > dev->ethtool_ops->rxfh_indir_space) + return -EINVAL; + + scoped_guard(mutex, &dev->ethtool->rss_lock) { + xa_for_each(&dev->ethtool->rss_ctx, context, ctx) { + ret = ethtool_rxfh_indir_can_resize(ethtool_rxfh_context_indir(ctx), + ctx->indir_size, new_indir_size); + if (ret) + return ret; + } + + xa_for_each(&dev->ethtool->rss_ctx, context, ctx) { + __ethtool_rxfh_indir_resize(ethtool_rxfh_context_indir(ctx), + ctx->indir_size, new_indir_size); + ctx->indir_size = new_indir_size; + } + } + + xa_for_each(&dev->ethtool->rss_ctx, context, ctx) + ethtool_rss_notify(dev, ETHTOOL_MSG_RSS_NTF, context); + + return 0; +} +EXPORT_SYMBOL(ethtool_rxfh_contexts_resize_all); + enum ethtool_link_medium ethtool_str_to_medium(const char *str) { int i; -- 2.53.0 bnxt_set_channels() rejects channel changes that alter the RSS table size when IFF_RXFH_CONFIGURED is set, because non-default context sizes were locked at creation. Replace the rejection with the new resize helpers. All validation runs before any mutation: 1. ethtool_rxfh_indir_can_resize() checks context 0. 2. ethtool_rxfh_contexts_resize_all() validates and resizes all non-default contexts (all-or-none). 3. ethtool_rxfh_indir_resize() applies context 0 changes. When context 0 uses defaults (!IFF_RXFH_CONFIGURED), steps 1 and 3 are skipped; the driver regenerates the table via bnxt_set_dflt_rss_indir_tbl(). Signed-off-by: Björn Töpel --- .../net/ethernet/broadcom/bnxt/bnxt_ethtool.c | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c index 26fcd52c8a61..7608e5d95630 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c @@ -977,11 +977,24 @@ static int bnxt_set_channels(struct net_device *dev, tx_xdp = req_rx_rings; } - if (bnxt_get_nr_rss_ctxs(bp, req_rx_rings) != - bnxt_get_nr_rss_ctxs(bp, bp->rx_nr_rings) && - netif_is_rxfh_configured(dev)) { - netdev_warn(dev, "RSS table size change required, RSS table entries must be default to proceed\n"); - return -EINVAL; + if (bnxt_get_nr_rss_ctxs(bp, req_rx_rings) != bnxt_get_nr_rss_ctxs(bp, bp->rx_nr_rings)) { + u32 new_size = bnxt_get_nr_rss_ctxs(bp, req_rx_rings) * BNXT_RSS_TABLE_ENTRIES_P5; + u32 old_size = bnxt_get_rxfh_indir_size(dev); + + /* Validate context 0 can be resized before mutating anything */ + if (netif_is_rxfh_configured(dev) && + ethtool_rxfh_indir_can_resize(bp->rss_indir_tbl, old_size, new_size)) { + netdev_warn(dev, "RSS table size change not supported with current indirection table\n"); + return -EINVAL; + } + + rc = ethtool_rxfh_contexts_resize_all(dev, new_size); + if (rc) + return rc; + + /* All validated; apply context 0 resize */ + if (netif_is_rxfh_configured(dev)) + ethtool_rxfh_indir_resize(bp->rss_indir_tbl, old_size, new_size); } rc = bnxt_check_rings(bp, req_tx_rings, req_rx_rings, sh, tcs, tx_xdp); -- 2.53.0 Add RSS indirection table, hash key, and non-default RSS context support to netdevsim. The create/modify/remove context callbacks are no-ops; the core manages context state in its xarray. The table size is dynamic: roundup_pow_of_two(channels) * 16, capped at NSIM_RSS_INDIR_MAX (128). This mimics drivers like bnxt where the table size changes with the queue count. nsim_set_channels() uses the core resize helpers to fold/unfold tables on channel count changes, exercising the full resize path. Signed-off-by: Björn Töpel --- drivers/net/netdevsim/ethtool.c | 119 +++++++++++++++++++++++++++++- drivers/net/netdevsim/netdevsim.h | 4 + 2 files changed, 121 insertions(+), 2 deletions(-) diff --git a/drivers/net/netdevsim/ethtool.c b/drivers/net/netdevsim/ethtool.c index 36a201533aae..c6d60b467054 100644 --- a/drivers/net/netdevsim/ethtool.c +++ b/drivers/net/netdevsim/ethtool.c @@ -2,6 +2,7 @@ // Copyright (c) 2020 Facebook #include +#include #include #include @@ -117,19 +118,121 @@ nsim_wake_queues(struct net_device *dev) rcu_read_unlock(); } +static u32 nsim_get_rx_ring_count(struct net_device *dev) +{ + struct netdevsim *ns = netdev_priv(dev); + + return ns->ethtool.channels; +} + +static u32 nsim_rss_indir_size(u32 channels) +{ + return min_t(u32, roundup_pow_of_two(channels) * 16, NSIM_RSS_INDIR_MAX); +} + +static u32 nsim_get_rxfh_indir_size(struct net_device *dev) +{ + struct netdevsim *ns = netdev_priv(dev); + + return nsim_rss_indir_size(ns->ethtool.channels); +} + +static u32 nsim_get_rxfh_key_size(struct net_device *dev) +{ + return NSIM_RSS_HKEY_SIZE; +} + +static int nsim_get_rxfh(struct net_device *dev, struct ethtool_rxfh_param *rxfh) +{ + u32 indir_size = nsim_get_rxfh_indir_size(dev); + struct netdevsim *ns = netdev_priv(dev); + + rxfh->hfunc = ETH_RSS_HASH_TOP; + + if (rxfh->indir) + memcpy(rxfh->indir, ns->ethtool.rss_indir_tbl, indir_size * sizeof(u32)); + if (rxfh->key) + memcpy(rxfh->key, ns->ethtool.rss_hkey, NSIM_RSS_HKEY_SIZE); + + return 0; +} + +static int nsim_set_rxfh(struct net_device *dev, struct ethtool_rxfh_param *rxfh, + struct netlink_ext_ack *extack) +{ + u32 indir_size = nsim_get_rxfh_indir_size(dev); + struct netdevsim *ns = netdev_priv(dev); + + if (rxfh->indir) + memcpy(ns->ethtool.rss_indir_tbl, rxfh->indir, indir_size * sizeof(u32)); + if (rxfh->key) + memcpy(ns->ethtool.rss_hkey, rxfh->key, NSIM_RSS_HKEY_SIZE); + + return 0; +} + +static int nsim_create_rxfh_context(struct net_device *dev, struct ethtool_rxfh_context *ctx, + const struct ethtool_rxfh_param *rxfh, + struct netlink_ext_ack *extack) +{ + return 0; +} + +static int nsim_modify_rxfh_context(struct net_device *dev, struct ethtool_rxfh_context *ctx, + const struct ethtool_rxfh_param *rxfh, + struct netlink_ext_ack *extack) +{ + return 0; +} + +static int nsim_remove_rxfh_context(struct net_device *dev, struct ethtool_rxfh_context *ctx, + u32 rss_context, struct netlink_ext_ack *extack) +{ + return 0; +} + +static void nsim_rss_init(struct netdevsim *ns) +{ + u32 i, indir_size = nsim_rss_indir_size(ns->ethtool.channels); + + for (i = 0; i < indir_size; i++) + ns->ethtool.rss_indir_tbl[i] = ethtool_rxfh_indir_default(i, ns->ethtool.channels); +} + static int nsim_set_channels(struct net_device *dev, struct ethtool_channels *ch) { struct netdevsim *ns = netdev_priv(dev); + u32 old_indir_size, new_indir_size; int err; - err = netif_set_real_num_queues(dev, ch->combined_count, - ch->combined_count); + old_indir_size = nsim_get_rxfh_indir_size(dev); + new_indir_size = nsim_rss_indir_size(ch->combined_count); + + if (old_indir_size != new_indir_size) { + if (netif_is_rxfh_configured(dev) && + ethtool_rxfh_indir_can_resize(ns->ethtool.rss_indir_tbl, + old_indir_size, new_indir_size)) + return -EINVAL; + + err = ethtool_rxfh_contexts_resize_all(dev, new_indir_size); + if (err) + return err; + + if (netif_is_rxfh_configured(dev)) + ethtool_rxfh_indir_resize(ns->ethtool.rss_indir_tbl, + old_indir_size, new_indir_size); + } + + err = netif_set_real_num_queues(dev, ch->combined_count, ch->combined_count); if (err) return err; ns->ethtool.channels = ch->combined_count; + if (old_indir_size != new_indir_size && !netif_is_rxfh_configured(dev)) + nsim_rss_init(ns); + /* Only wake up queues if devices are linked */ if (rcu_access_pointer(ns->peer)) nsim_wake_queues(dev); @@ -209,6 +312,7 @@ static const struct ethtool_ops nsim_ethtool_ops = { .supported_coalesce_params = ETHTOOL_COALESCE_ALL_PARAMS, .supported_ring_params = ETHTOOL_RING_USE_TCP_DATA_SPLIT | ETHTOOL_RING_USE_HDS_THRS, + .rxfh_indir_space = NSIM_RSS_INDIR_MAX, .get_pause_stats = nsim_get_pause_stats, .get_pauseparam = nsim_get_pauseparam, .set_pauseparam = nsim_set_pauseparam, @@ -218,10 +322,18 @@ static const struct ethtool_ops nsim_ethtool_ops = { .set_ringparam = nsim_set_ringparam, .get_channels = nsim_get_channels, .set_channels = nsim_set_channels, + .get_rx_ring_count = nsim_get_rx_ring_count, .get_fecparam = nsim_get_fecparam, .set_fecparam = nsim_set_fecparam, .get_fec_stats = nsim_get_fec_stats, .get_ts_info = nsim_get_ts_info, + .get_rxfh_indir_size = nsim_get_rxfh_indir_size, + .get_rxfh_key_size = nsim_get_rxfh_key_size, + .get_rxfh = nsim_get_rxfh, + .set_rxfh = nsim_set_rxfh, + .create_rxfh_context = nsim_create_rxfh_context, + .modify_rxfh_context = nsim_modify_rxfh_context, + .remove_rxfh_context = nsim_remove_rxfh_context, }; static void nsim_ethtool_ring_init(struct netdevsim *ns) @@ -250,6 +362,9 @@ void nsim_ethtool_init(struct netdevsim *ns) ns->ethtool.channels = ns->nsim_bus_dev->num_queues; + nsim_rss_init(ns); + get_random_bytes(ns->ethtool.rss_hkey, NSIM_RSS_HKEY_SIZE); + ethtool = debugfs_create_dir("ethtool", ns->nsim_dev_port->ddir); debugfs_create_u32("get_err", 0600, ethtool, &ns->ethtool.get_err); diff --git a/drivers/net/netdevsim/netdevsim.h b/drivers/net/netdevsim/netdevsim.h index f767fc8a7505..3c6dd9a98deb 100644 --- a/drivers/net/netdevsim/netdevsim.h +++ b/drivers/net/netdevsim/netdevsim.h @@ -82,6 +82,8 @@ struct nsim_ethtool_pauseparam { bool report_stats_tx; }; +#define NSIM_RSS_INDIR_MAX 128 +#define NSIM_RSS_HKEY_SIZE 40 struct nsim_ethtool { u32 get_err; u32 set_err; @@ -90,6 +92,8 @@ struct nsim_ethtool { struct ethtool_coalesce coalesce; struct ethtool_ringparam ring; struct ethtool_fecparam fec; + u32 rss_indir_tbl[NSIM_RSS_INDIR_MAX]; + u8 rss_hkey[NSIM_RSS_HKEY_SIZE]; }; struct nsim_rq { -- 2.53.0 Test fold/unfold of RSS indirection tables on channel changes using netdevsim: - default table regenerates on channel change - periodic table folds on shrink and unfolds on grow - non-periodic table blocks channel reduction - non-default RSS context resizes on channel change - non-periodic non-default context blocks fold - table and channel count unchanged after failed reduction Signed-off-by: Björn Töpel --- .../selftests/drivers/net/netdevsim/Makefile | 1 + .../drivers/net/netdevsim/ethtool-rss.sh | 123 ++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100755 tools/testing/selftests/drivers/net/netdevsim/ethtool-rss.sh diff --git a/tools/testing/selftests/drivers/net/netdevsim/Makefile b/tools/testing/selftests/drivers/net/netdevsim/Makefile index 1a228c5430f5..d764d08dff1a 100644 --- a/tools/testing/selftests/drivers/net/netdevsim/Makefile +++ b/tools/testing/selftests/drivers/net/netdevsim/Makefile @@ -8,6 +8,7 @@ TEST_PROGS := \ ethtool-features.sh \ ethtool-fec.sh \ ethtool-pause.sh \ + ethtool-rss.sh \ fib.sh \ fib_notifications.sh \ hw_stats_l3.sh \ diff --git a/tools/testing/selftests/drivers/net/netdevsim/ethtool-rss.sh b/tools/testing/selftests/drivers/net/netdevsim/ethtool-rss.sh new file mode 100755 index 000000000000..1c1876f888ba --- /dev/null +++ b/tools/testing/selftests/drivers/net/netdevsim/ethtool-rss.sh @@ -0,0 +1,123 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-only +# +# Test RSS indirection table resize on channel count changes. +# Exercises ethtool_rxfh_indir_resize() and +# ethtool_rxfh_contexts_resize_all() via netdevsim. + +source ethtool-common.sh + +# Create device with 8 queues +NSIM_NETDEV=$(make_netdev 1 8) + +set -o pipefail + +# --- Test 1: Default table regenerates on channel change --- +s=$(ethtool --json -x $NSIM_NETDEV | jq '.[0]["rss-indirection-table"] | length') +check $? "$s" "128" # roundup_pow_of_two(8) * 16 = 128 + +ethtool -L $NSIM_NETDEV combined 2 +s=$(ethtool --json -x $NSIM_NETDEV | jq '.[0]["rss-indirection-table"] | length') +check $? "$s" "32" # roundup_pow_of_two(2) * 16 = 32 + +ethtool -L $NSIM_NETDEV combined 8 + +# --- Test 2: Periodic context 0 table folds on channel reduction --- +ethtool -X $NSIM_NETDEV equal 2 +s=$(ethtool --json -x $NSIM_NETDEV | jq '[.[0]["rss-indirection-table"][]] | max') +check $? "$s" "1" + +ethtool -L $NSIM_NETDEV combined 2 +s=$(ethtool --json -x $NSIM_NETDEV | jq '.[0]["rss-indirection-table"] | length') +check $? "$s" "32" +s=$(ethtool --json -x $NSIM_NETDEV | jq '[.[0]["rss-indirection-table"][]] | max') +check $? "$s" "1" + +# Grow back — should unfold +ethtool -L $NSIM_NETDEV combined 8 +s=$(ethtool --json -x $NSIM_NETDEV | jq '.[0]["rss-indirection-table"] | length') +check $? "$s" "128" +s=$(ethtool --json -x $NSIM_NETDEV | jq '[.[0]["rss-indirection-table"][]] | max') +check $? "$s" "1" + +ethtool -X $NSIM_NETDEV default + +# --- Test 3: Non-periodic context 0 table blocks fold --- +ethtool -X $NSIM_NETDEV equal 8 + +ethtool -L $NSIM_NETDEV combined 2 2>/dev/null +if [ $? -ne 0 ]; then + ((num_passes++)) +else + echo "Expected channel reduction to fail with non-periodic table" + ((num_errors++)) +fi + +ethtool -X $NSIM_NETDEV default + +# --- Test 4: Non-default context resizes on channel change --- +ctx_id=$(ethtool -X $NSIM_NETDEV context new equal 2 2>/dev/null | awk '{print $NF}') +if [ -z "$ctx_id" ]; then + echo "SKIP: context creation failed" +else + s=$(ethtool --json -x $NSIM_NETDEV context $ctx_id | jq '.[0]["rss-indirection-table"] | length') + check $? "$s" "128" + + ethtool -L $NSIM_NETDEV combined 2 + s=$(ethtool --json -x $NSIM_NETDEV context $ctx_id | jq '.[0]["rss-indirection-table"] | length') + check $? "$s" "32" + s=$(ethtool --json -x $NSIM_NETDEV context $ctx_id | jq '[.[0]["rss-indirection-table"][]] | max') + check $? "$s" "1" + + # Grow back + ethtool -L $NSIM_NETDEV combined 8 + s=$(ethtool --json -x $NSIM_NETDEV context $ctx_id | jq '.[0]["rss-indirection-table"] | length') + check $? "$s" "128" + s=$(ethtool --json -x $NSIM_NETDEV context $ctx_id | jq '[.[0]["rss-indirection-table"][]] | max') + check $? "$s" "1" + + ethtool -X $NSIM_NETDEV context $ctx_id delete +fi + +# --- Test 5: Non-periodic non-default context blocks fold --- +ctx_id=$(ethtool -X $NSIM_NETDEV context new equal 8 2>/dev/null | awk '{print $NF}') +if [ -z "$ctx_id" ]; then + echo "SKIP: context creation failed" +else + ethtool -L $NSIM_NETDEV combined 2 2>/dev/null + if [ $? -ne 0 ]; then + ((num_passes++)) + else + echo "Expected channel reduction to fail with non-periodic context" + ((num_errors++)) + fi + + ethtool -X $NSIM_NETDEV context $ctx_id delete +fi + +# --- Test 6: Table unchanged after failed channel reduction --- +ethtool -X $NSIM_NETDEV equal 8 +s_before=$(ethtool --json -x $NSIM_NETDEV | jq '.[0]["rss-indirection-table"]') + +ethtool -L $NSIM_NETDEV combined 2 2>/dev/null +s_after=$(ethtool --json -x $NSIM_NETDEV | jq '.[0]["rss-indirection-table"]') +check $? "$s_after" "$s_before" + +ethtool -X $NSIM_NETDEV default + +# --- Test 7: Channel count unchanged after failed reduction --- +ethtool -X $NSIM_NETDEV equal 8 +ethtool -L $NSIM_NETDEV combined 2 2>/dev/null +s=$(ethtool --json -l $NSIM_NETDEV | jq '.[0]["combined-count"]') +check $? "$s" "8" + +ethtool -X $NSIM_NETDEV default + +# --- Results --- +if [ $num_errors -eq 0 ]; then + echo "PASSED all $((num_passes)) checks" + exit 0 +else + echo "FAILED $num_errors/$((num_errors+num_passes)) checks" + exit 1 +fi -- 2.53.0 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 --- .../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