* [PATCH net-next v5 0/3] ethtool: Dynamic RSS context indirection table resizing
@ 2026-03-17 8:33 Björn Töpel
2026-03-17 8:33 ` [PATCH net-next v5 1/3] ethtool: Add RSS indirection table resize helpers Björn Töpel
` (2 more replies)
0 siblings, 3 replies; 8+ messages in thread
From: Björn Töpel @ 2026-03-17 8:33 UTC (permalink / raw)
To: Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, netdev,
linux-kselftest
Cc: Björn Töpel, Willem de Bruijn, Shuah Khan,
Maxime Chevallier, Andrew Lunn
Hi!
[ Michael and Pavan, I removed your Tested-by:/Reviewed-by: although
the changes were pretty small. Please re-add if you're still OK with
it. ]
Some NICs (e.g. bnxt) change their RSS indirection table size based on
the queue count, because the hardware table is a shared resource. The
ethtool core locks ctx->indir_size at context creation, so drivers
have to reject channel changes when RSS contexts exist.
This series adds resize helpers and wires them up in bnxt.
Patch 1 adds core helpers:
ethtool_rxfh_can_resize() - read-only validation
ethtool_rxfh_resize() - fold/unfold a raw table in place
ethtool_rxfh_ctxs_can_resize() - validate all non-default contexts
ethtool_rxfh_ctxs_resize() - resize all non-default contexts,
with locking and RSS_NTF notifications
Both ethtool_rxfh_can_resize() and ethtool_rxfh_resize() now take a
user_size parameter: the number of indirection table entries the user
originally provided. When shrinking, the table will not fold below
this floor, preserving user intent.
The user_size is tracked in ctx->indir_user_size for non-default RSS
contexts and in dev->ethtool->rss_indir_user_size for context 0. It
is set when the indirection table is configured via netlink or ioctl,
and cleared to zero on reset-to-default.
Patch 2 uses them in bnxt_set_channels(). Validation runs before
bnxt_close_nic(); actual resize is deferred until after. RSS table
size only changes on P5 chips with older firmware.
Patch 3 adds HW tests in rss_drv.py (devices without dynamic table
sizing are skipped):
resize_periodic - fold/unfold with a non-default [3,2,1,0]
sub-table (user_size=4), verifying exact content preservation
(main + ctx)
resize_below_user_size_reject - periodic sub-table with user_size
between big and small device table sizes; verifies that shrinking
below user_size is rejected even when the table is periodic
(main + ctx)
resize_nonperiodic_reject - non-periodic table blocks channel
reduction, with an extra periodic context to exercise
multi-context validation (main + ctx)
resize_nonperiodic_no_corruption - failed resize leaves table
contents and channel count unchanged (main + ctx)
Running the tests:
# On real hardware
sudo NETIF=eth0 ./rss_drv.py
Changes v4 -> v5:
- Track user-provided indirection table size (user_size) as a resize
floor. Added indir_user_size to ethtool_rxfh_context and
rss_indir_user_size to ethtool_netdev_state. ethtool_rxfh_can_resize()
and ethtool_rxfh_resize() now take a user_size parameter and reject
shrinking below it. (Jakub)
- Propagated user_size out of rss_set_prep_indir() and stored it on
successful set in both netlink and ioctl paths.
- resize_periodic test now sends a 4-entry sub-table (user_size=4)
instead of replicating to full device table size
- Added resize_below_user_size_reject test to verify user_size floor.
- Removed "Open items" section - user_size tracking is now implemented.
Changes v3 -> v4:
- Rebased onto net-next
- Added Reviewed-by: from Michael
- Added missing Cc: to make the pwbots happier
Changes v2 -> v3:
- Changed ethtool_rxfh_can_resize() to return bool instead of int;
true means resize is possible, false means it is not. Inverted
callers accordingly. (Jakub)
- Added Tested-by from Pavan
Changes v1 -> v2:
- Dropped netdevsim support and netdevsim selftest (Jakub)
- Split ethtool_rxfh_contexts_resize_all() into separate validate
(ethtool_rxfh_ctxs_can_resize) and apply (ethtool_rxfh_ctxs_resize)
so drivers can validate before closing the device (Jakub)
- Shortened helper names (Jakub)
- Replaced scoped_guard(mutex) with explicit mutex_lock/unlock
(Jakub)
- Removed defensive zero-size check, bare expressions instead of != 0
comparisons, ! instead of == 0 (Jakub)
- In bnxt, moved bnxt_check_rings() before RSS validation and
deferred actual resize to after bnxt_close_nic() (Jakub, Michael)
- Added comment that RSS table size only changes on P5 chips with
older firmware (Michael)
- Use non-default [3,2,1,0]xN pattern set via netlink to distinguish
correct fold from driver resetting to defaults (Jakub)
- Check exact indirection table pattern, not just set(indir) (Jakub)
- Use ksft_raises() instead of try/except/else (Jakub)
- Removed queue_count=8 from NetDrvEnv (Jakub)
- Added ksft_variants to resize_nonperiodic_reject for ctx coverage
- Added extra periodic context in reject test for multi-context
validation coverage
- Added resize_nonperiodic_no_corruption test
Björn Töpel (3):
ethtool: Add RSS indirection table resize helpers
bnxt_en: Resize RSS contexts on channel count change
selftests: rss_drv: Add RSS indirection table resize tests
.../net/ethernet/broadcom/bnxt/bnxt_ethtool.c | 38 ++-
include/linux/ethtool.h | 11 +
net/ethtool/common.c | 150 +++++++++++
net/ethtool/ioctl.c | 15 +-
net/ethtool/rss.c | 24 +-
.../selftests/drivers/net/hw/rss_drv.py | 233 +++++++++++++++++-
6 files changed, 450 insertions(+), 21 deletions(-)
base-commit: 348baefbb635cbb448e154f38c93657d4cf23936
--
2.53.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH net-next v5 1/3] ethtool: Add RSS indirection table resize helpers
2026-03-17 8:33 [PATCH net-next v5 0/3] ethtool: Dynamic RSS context indirection table resizing Björn Töpel
@ 2026-03-17 8:33 ` Björn Töpel
2026-03-17 22:46 ` Jakub Kicinski
2026-03-17 8:33 ` [PATCH net-next v5 2/3] bnxt_en: Resize RSS contexts on channel count change Björn Töpel
2026-03-17 8:33 ` [PATCH net-next v5 3/3] selftests: rss_drv: Add RSS indirection table resize tests Björn Töpel
2 siblings, 1 reply; 8+ messages in thread
From: Björn Töpel @ 2026-03-17 8:33 UTC (permalink / raw)
To: Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, netdev,
linux-kselftest
Cc: Björn Töpel, Willem de Bruijn, Shuah Khan,
Maxime Chevallier, Andrew Lunn
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_can_resize() checks whether a table can be resized
without modifying it.
ethtool_rxfh_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.
Both ethtool_rxfh_can_resize() and ethtool_rxfh_resize() take a
user_size parameter: the number of indirection table entries the user
originally provided. When shrinking, the table will not fold below
this floor.
ethtool_rxfh_ctxs_can_resize() validates all non-default RSS contexts
can be resized. ethtool_rxfh_ctxs_resize() applies the resize and
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 <bjorn@kernel.org>
---
include/linux/ethtool.h | 11 +++
net/ethtool/common.c | 150 ++++++++++++++++++++++++++++++++++++++++
net/ethtool/ioctl.c | 15 ++--
| 24 +++++--
4 files changed, 190 insertions(+), 10 deletions(-)
diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h
index 83c375840835..6283a8cf3004 100644
--- a/include/linux/ethtool.h
+++ b/include/linux/ethtool.h
@@ -182,10 +182,13 @@ static inline u32 ethtool_rxfh_indir_default(u32 index, u32 n_rx_rings)
* of %RXH_XFRM_*.
* @indir_configured: indir has been specified (at create time or subsequently)
* @key_configured: hkey has been specified (at create time or subsequently)
+ * @indir_user_size: number of entries the user provided for the indirection
+ * table.
*/
struct ethtool_rxfh_context {
u32 indir_size;
u32 key_size;
+ u32 indir_user_size;
u16 priv_size;
u8 hfunc;
u8 input_xfrm;
@@ -214,6 +217,11 @@ static inline u8 *ethtool_rxfh_context_key(struct ethtool_rxfh_context *ctx)
}
void ethtool_rxfh_context_lost(struct net_device *dev, u32 context_id);
+bool ethtool_rxfh_can_resize(const u32 *tbl, u32 old_size, u32 new_size,
+ u32 user_size);
+int ethtool_rxfh_resize(u32 *tbl, u32 old_size, u32 new_size, u32 user_size);
+int ethtool_rxfh_ctxs_can_resize(struct net_device *dev, u32 new_indir_size);
+void ethtool_rxfh_ctxs_resize(struct net_device *dev, u32 new_indir_size);
struct link_mode_info {
int speed;
@@ -1333,12 +1341,15 @@ int ethtool_virtdev_set_link_ksettings(struct net_device *dev,
* @rss_ctx: XArray of custom RSS contexts
* @rss_lock: Protects entries in @rss_ctx. May be taken from
* within RTNL.
+ * @rss_indir_user_size: number of indirection table entries the user
+ * provided for the default (context 0) RSS table.
* @wol_enabled: Wake-on-LAN is enabled
* @module_fw_flash_in_progress: Module firmware flashing is in progress.
*/
struct ethtool_netdev_state {
struct xarray rss_ctx;
struct mutex rss_lock;
+ u32 rss_indir_user_size;
unsigned wol_enabled:1;
unsigned module_fw_flash_in_progress:1;
};
diff --git a/net/ethtool/common.c b/net/ethtool/common.c
index e252cf20c22f..0f8b5c84aba6 100644
--- a/net/ethtool/common.c
+++ b/net/ethtool/common.c
@@ -1204,6 +1204,156 @@ void ethtool_rxfh_context_lost(struct net_device *dev, u32 context_id)
}
EXPORT_SYMBOL(ethtool_rxfh_context_lost);
+static bool 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_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
+ * @user_size: number of entries the user configured (lower bound),
+ *
+ * Validate that @tbl can be resized from @old_size to @new_size without
+ * data loss. When shrinking, @new_size must not be smaller than @user_size.
+ * Read-only; does not modify the table.
+ *
+ * Return: true if resize is possible, false otherwise.
+ */
+bool ethtool_rxfh_can_resize(const u32 *tbl, u32 old_size, u32 new_size,
+ u32 user_size)
+{
+ if (new_size == old_size)
+ return true;
+
+ if (new_size < old_size) {
+ if (user_size && new_size < user_size)
+ return false;
+ if (old_size % new_size)
+ return false;
+ if (!rxfh_indir_is_periodic(tbl, old_size, new_size))
+ return false;
+ return true;
+ }
+
+ if (new_size % old_size)
+ return false;
+ return true;
+}
+EXPORT_SYMBOL(ethtool_rxfh_can_resize);
+
+/* Resize without validation; caller must have called can_resize first */
+static void __ethtool_rxfh_resize(u32 *tbl, u32 old_size, u32 new_size)
+{
+ u32 i;
+
+ /* Grow: replicate existing pattern; shrink is a no-op on the data */
+ for (i = old_size; i < new_size; i++)
+ tbl[i] = tbl[i % old_size];
+}
+
+/**
+ * ethtool_rxfh_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
+ * @user_size: number of entries the user configured (lower bound),
+ *
+ * Resize an RSS indirection table in place. When folding (shrinking),
+ * the table must be periodic with period @new_size and @new_size must
+ * not be smaller than @user_size.
+ * 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_resize(u32 *tbl, u32 old_size, u32 new_size, u32 user_size)
+{
+ if (!ethtool_rxfh_can_resize(tbl, old_size, new_size, user_size))
+ return -EINVAL;
+
+ __ethtool_rxfh_resize(tbl, old_size, new_size);
+ return 0;
+}
+EXPORT_SYMBOL(ethtool_rxfh_resize);
+
+/**
+ * ethtool_rxfh_ctxs_can_resize - Validate resize for all RSS contexts
+ * @dev: network device
+ * @new_indir_size: new indirection table size
+ *
+ * Validate that the indirection tables of all non-default RSS contexts
+ * can be resized to @new_indir_size. Read-only; does not modify any
+ * context. Intended to be paired with ethtool_rxfh_ctxs_resize().
+ *
+ * Return: 0 if all contexts can be resized, negative errno on failure.
+ */
+int ethtool_rxfh_ctxs_can_resize(struct net_device *dev,
+ u32 new_indir_size)
+{
+ struct ethtool_rxfh_context *ctx;
+ unsigned long context;
+ int ret = 0;
+
+ if (!dev->ethtool_ops->rxfh_indir_space ||
+ new_indir_size > dev->ethtool_ops->rxfh_indir_space)
+ return -EINVAL;
+
+ mutex_lock(&dev->ethtool->rss_lock);
+ xa_for_each(&dev->ethtool->rss_ctx, context, ctx) {
+ u32 *indir = ethtool_rxfh_context_indir(ctx);
+
+ if (!ethtool_rxfh_can_resize(indir, ctx->indir_size,
+ new_indir_size,
+ ctx->indir_user_size)) {
+ ret = -EINVAL;
+ goto unlock;
+ }
+ }
+unlock:
+ mutex_unlock(&dev->ethtool->rss_lock);
+ return ret;
+}
+EXPORT_SYMBOL(ethtool_rxfh_ctxs_can_resize);
+
+/**
+ * ethtool_rxfh_ctxs_resize - 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. Caller must have validated with
+ * ethtool_rxfh_ctxs_can_resize() first. An %ETHTOOL_MSG_RSS_NTF is
+ * sent for each resized context.
+ *
+ * Notifications are sent outside the RSS lock to avoid holding the
+ * mutex during notification delivery.
+ */
+void ethtool_rxfh_ctxs_resize(struct net_device *dev, u32 new_indir_size)
+{
+ struct ethtool_rxfh_context *ctx;
+ unsigned long context;
+
+ mutex_lock(&dev->ethtool->rss_lock);
+ xa_for_each(&dev->ethtool->rss_ctx, context, ctx) {
+ __ethtool_rxfh_resize(ethtool_rxfh_context_indir(ctx),
+ ctx->indir_size, new_indir_size);
+ ctx->indir_size = new_indir_size;
+ }
+ mutex_unlock(&dev->ethtool->rss_lock);
+
+ xa_for_each(&dev->ethtool->rss_ctx, context, ctx)
+ ethtool_rss_notify(dev, ETHTOOL_MSG_RSS_NTF, context);
+}
+EXPORT_SYMBOL(ethtool_rxfh_ctxs_resize);
+
enum ethtool_link_medium ethtool_str_to_medium(const char *str)
{
int i;
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index ff4b4780d6af..c60b92bf09a4 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -1403,10 +1403,13 @@ static noinline_for_stack int ethtool_set_rxfh_indir(struct net_device *dev,
goto out_unlock;
/* indicate whether rxfh was set to default */
- if (user_size == 0)
+ if (user_size == 0) {
dev->priv_flags &= ~IFF_RXFH_CONFIGURED;
- else
+ dev->ethtool->rss_indir_user_size = 0;
+ } else {
dev->priv_flags |= IFF_RXFH_CONFIGURED;
+ dev->ethtool->rss_indir_user_size = rxfh_dev.indir_size;
+ }
out_unlock:
mutex_unlock(&dev->ethtool->rss_lock);
@@ -1720,10 +1723,13 @@ static noinline_for_stack int ethtool_set_rxfh(struct net_device *dev,
if (!rxfh_dev.rss_context) {
/* indicate whether rxfh was set to default */
- if (rxfh.indir_size == 0)
+ if (rxfh.indir_size == 0) {
dev->priv_flags &= ~IFF_RXFH_CONFIGURED;
- else if (rxfh.indir_size != ETH_RXFH_INDIR_NO_CHANGE)
+ dev->ethtool->rss_indir_user_size = 0;
+ } else if (rxfh.indir_size != ETH_RXFH_INDIR_NO_CHANGE) {
dev->priv_flags |= IFF_RXFH_CONFIGURED;
+ dev->ethtool->rss_indir_user_size = dev_indir_size;
+ }
}
/* Update rss_ctx tracking */
if (rxfh_dev.rss_delete) {
@@ -1736,6 +1742,7 @@ static noinline_for_stack int ethtool_set_rxfh(struct net_device *dev,
ctx->indir_configured =
rxfh.indir_size &&
rxfh.indir_size != ETH_RXFH_INDIR_NO_CHANGE;
+ ctx->indir_user_size = dev_indir_size;
}
if (rxfh_dev.key) {
memcpy(ethtool_rxfh_context_key(ctx), rxfh_dev.key,
--git a/net/ethtool/rss.c b/net/ethtool/rss.c
index da5934cceb07..93a0f1de4cee 100644
--- a/net/ethtool/rss.c
+++ b/net/ethtool/rss.c
@@ -618,7 +618,7 @@ ethnl_rss_set_validate(struct ethnl_req_info *req_info, struct genl_info *info)
static int
rss_set_prep_indir(struct net_device *dev, struct genl_info *info,
struct rss_reply_data *data, struct ethtool_rxfh_param *rxfh,
- bool *reset, bool *mod)
+ bool *reset, bool *mod, u32 *user_sizep)
{
struct netlink_ext_ack *extack = info->extack;
struct nlattr **tb = info->attrs;
@@ -685,6 +685,7 @@ rss_set_prep_indir(struct net_device *dev, struct genl_info *info,
}
*mod |= memcmp(rxfh->indir, data->indir_table, data->indir_size);
+ *user_sizep = user_size;
return 0;
@@ -833,6 +834,7 @@ ethnl_rss_set(struct ethnl_req_info *req_info, struct genl_info *info)
struct nlattr **tb = info->attrs;
struct rss_reply_data data = {};
const struct ethtool_ops *ops;
+ u32 indir_user_size = 0;
int ret;
ops = dev->ethtool_ops;
@@ -844,7 +846,8 @@ ethnl_rss_set(struct ethnl_req_info *req_info, struct genl_info *info)
rxfh.rss_context = request->rss_context;
- ret = rss_set_prep_indir(dev, info, &data, &rxfh, &indir_reset, &mod);
+ ret = rss_set_prep_indir(dev, info, &data, &rxfh, &indir_reset, &mod,
+ &indir_user_size);
if (ret)
goto exit_clean_data;
indir_mod = !!tb[ETHTOOL_A_RSS_INDIR];
@@ -889,12 +892,17 @@ ethnl_rss_set(struct ethnl_req_info *req_info, struct genl_info *info)
if (ret)
goto exit_unlock;
- if (ctx)
+ if (ctx) {
rss_set_ctx_update(ctx, tb, &data, &rxfh);
- else if (indir_reset)
+ if (indir_user_size)
+ ctx->indir_user_size = indir_user_size;
+ } else if (indir_reset) {
dev->priv_flags &= ~IFF_RXFH_CONFIGURED;
- else if (indir_mod)
+ dev->ethtool->rss_indir_user_size = 0;
+ } else if (indir_mod) {
dev->priv_flags |= IFF_RXFH_CONFIGURED;
+ dev->ethtool->rss_indir_user_size = indir_user_size;
+ }
exit_unlock:
mutex_unlock(&dev->ethtool->rss_lock);
@@ -998,6 +1006,7 @@ int ethnl_rss_create_doit(struct sk_buff *skb, struct genl_info *info)
struct rss_reply_data data = {};
const struct ethtool_ops *ops;
struct rss_req_info req = {};
+ u32 indir_user_size = 0;
struct net_device *dev;
struct sk_buff *rsp;
void *hdr;
@@ -1034,7 +1043,8 @@ int ethnl_rss_create_doit(struct sk_buff *skb, struct genl_info *info)
if (ret)
goto exit_ops;
- ret = rss_set_prep_indir(dev, info, &data, &rxfh, &indir_dflt, &mod);
+ ret = rss_set_prep_indir(dev, info, &data, &rxfh, &indir_dflt, &mod,
+ &indir_user_size);
if (ret)
goto exit_clean_data;
@@ -1080,6 +1090,8 @@ int ethnl_rss_create_doit(struct sk_buff *skb, struct genl_info *info)
/* Store the config from rxfh to Xarray.. */
rss_set_ctx_update(ctx, tb, &data, &rxfh);
+ if (indir_user_size)
+ ctx->indir_user_size = indir_user_size;
/* .. copy from Xarray to data. */
__rss_prepare_ctx(dev, &data, ctx);
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH net-next v5 2/3] bnxt_en: Resize RSS contexts on channel count change
2026-03-17 8:33 [PATCH net-next v5 0/3] ethtool: Dynamic RSS context indirection table resizing Björn Töpel
2026-03-17 8:33 ` [PATCH net-next v5 1/3] ethtool: Add RSS indirection table resize helpers Björn Töpel
@ 2026-03-17 8:33 ` Björn Töpel
2026-03-17 22:53 ` Jakub Kicinski
2026-03-17 8:33 ` [PATCH net-next v5 3/3] selftests: rss_drv: Add RSS indirection table resize tests Björn Töpel
2 siblings, 1 reply; 8+ messages in thread
From: Björn Töpel @ 2026-03-17 8:33 UTC (permalink / raw)
To: Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, netdev,
linux-kselftest
Cc: Björn Töpel, Willem de Bruijn, Shuah Khan,
Maxime Chevallier, Andrew Lunn
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 the device is closed; actual resize is deferred until after
bnxt_close_nic():
1. ethtool_rxfh_can_resize() checks context 0, passing
dev->ethtool->rss_indir_user_size as the user_size floor.
2. ethtool_rxfh_ctxs_can_resize() validates all non-default contexts.
3. After bnxt_close_nic(), ethtool_rxfh_resize() applies context 0
changes, and ethtool_rxfh_ctxs_resize() resizes non-default
contexts.
RSS table size only changes on P5 chips with older firmware; newer
firmware always uses the largest table size.
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 <bjorn@kernel.org>
---
.../net/ethernet/broadcom/bnxt/bnxt_ethtool.c | 38 +++++++++++++++----
1 file changed, 31 insertions(+), 7 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c
index 48e8e3be70d3..469f4720c1d7 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c
@@ -942,6 +942,7 @@ static int bnxt_set_channels(struct net_device *dev,
{
struct bnxt *bp = netdev_priv(dev);
int req_tx_rings, req_rx_rings, tcs;
+ u32 new_tbl_size = 0, old_tbl_size;
bool sh = false;
int tx_xdp = 0;
int rc = 0;
@@ -977,19 +978,34 @@ 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) || bp->num_rss_ctx)) {
- netdev_warn(dev, "RSS table size change required, RSS table entries must be default (with no additional RSS contexts present) to proceed\n");
- return -EINVAL;
- }
-
rc = bnxt_check_rings(bp, req_tx_rings, req_rx_rings, sh, tcs, tx_xdp);
if (rc) {
netdev_warn(dev, "Unable to allocate the requested rings\n");
return rc;
}
+ /* RSS table size only changes on P5 chips with older firmware;
+ * newer firmware always uses the largest table size.
+ */
+ if (bnxt_get_nr_rss_ctxs(bp, req_rx_rings) !=
+ bnxt_get_nr_rss_ctxs(bp, bp->rx_nr_rings)) {
+ new_tbl_size = bnxt_get_nr_rss_ctxs(bp, req_rx_rings) *
+ BNXT_RSS_TABLE_ENTRIES_P5;
+ old_tbl_size = bnxt_get_rxfh_indir_size(dev);
+
+ if (netif_is_rxfh_configured(dev) &&
+ !ethtool_rxfh_can_resize(bp->rss_indir_tbl,
+ old_tbl_size, new_tbl_size,
+ dev->ethtool->rss_indir_user_size)) {
+ netdev_warn(dev, "RSS table resize not possible\n");
+ return -EINVAL;
+ }
+
+ rc = ethtool_rxfh_ctxs_can_resize(dev, new_tbl_size);
+ if (rc)
+ return rc;
+ }
+
if (netif_running(dev)) {
if (BNXT_PF(bp)) {
/* TODO CHIMP_FW: Send message to all VF's
@@ -999,6 +1015,14 @@ static int bnxt_set_channels(struct net_device *dev,
bnxt_close_nic(bp, true, false);
}
+ if (new_tbl_size) {
+ if (netif_is_rxfh_configured(dev))
+ ethtool_rxfh_resize(bp->rss_indir_tbl,
+ old_tbl_size, new_tbl_size,
+ dev->ethtool->rss_indir_user_size);
+ ethtool_rxfh_ctxs_resize(dev, new_tbl_size);
+ }
+
if (sh) {
bp->flags |= BNXT_FLAG_SHARED_RINGS;
bp->rx_nr_rings = channel->combined_count;
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH net-next v5 3/3] selftests: rss_drv: Add RSS indirection table resize tests
2026-03-17 8:33 [PATCH net-next v5 0/3] ethtool: Dynamic RSS context indirection table resizing Björn Töpel
2026-03-17 8:33 ` [PATCH net-next v5 1/3] ethtool: Add RSS indirection table resize helpers Björn Töpel
2026-03-17 8:33 ` [PATCH net-next v5 2/3] bnxt_en: Resize RSS contexts on channel count change Björn Töpel
@ 2026-03-17 8:33 ` Björn Töpel
2 siblings, 0 replies; 8+ messages in thread
From: Björn Töpel @ 2026-03-17 8:33 UTC (permalink / raw)
To: Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, netdev,
linux-kselftest
Cc: Björn Töpel, Willem de Bruijn, Shuah Khan,
Maxime Chevallier, Andrew Lunn
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>
---
| 233 +++++++++++++++++-
1 file changed, 229 insertions(+), 4 deletions(-)
--git a/tools/testing/selftests/drivers/net/hw/rss_drv.py b/tools/testing/selftests/drivers/net/hw/rss_drv.py
index 2d1a33189076..bd59dace6e15 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, 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()
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH net-next v5 1/3] ethtool: Add RSS indirection table resize helpers
2026-03-17 8:33 ` [PATCH net-next v5 1/3] ethtool: Add RSS indirection table resize helpers Björn Töpel
@ 2026-03-17 22:46 ` Jakub Kicinski
2026-03-18 9:27 ` Björn Töpel
0 siblings, 1 reply; 8+ messages in thread
From: Jakub Kicinski @ 2026-03-17 22:46 UTC (permalink / raw)
To: Björn Töpel
Cc: Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, netdev, linux-kselftest,
Willem de Bruijn, Shuah Khan, Maxime Chevallier, Andrew Lunn
On Tue, 17 Mar 2026 09:33:34 +0100 Björn Töpel wrote:
> static int
> rss_set_prep_indir(struct net_device *dev, struct genl_info *info,
> struct rss_reply_data *data, struct ethtool_rxfh_param *rxfh,
> - bool *reset, bool *mod)
> + bool *reset, bool *mod, u32 *user_sizep)
> {
> struct netlink_ext_ack *extack = info->extack;
> struct nlattr **tb = info->attrs;
> @@ -685,6 +685,7 @@ rss_set_prep_indir(struct net_device *dev, struct genl_info *info,
> }
>
> *mod |= memcmp(rxfh->indir, data->indir_table, data->indir_size);
> + *user_sizep = user_size;
>
> return 0;
Weak preference for return user_size; instead of output arg, 7th, "p"?
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net-next v5 2/3] bnxt_en: Resize RSS contexts on channel count change
2026-03-17 8:33 ` [PATCH net-next v5 2/3] bnxt_en: Resize RSS contexts on channel count change Björn Töpel
@ 2026-03-17 22:53 ` Jakub Kicinski
2026-03-18 9:28 ` Björn Töpel
0 siblings, 1 reply; 8+ messages in thread
From: Jakub Kicinski @ 2026-03-17 22:53 UTC (permalink / raw)
To: Björn Töpel
Cc: Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, netdev, linux-kselftest,
Willem de Bruijn, Shuah Khan, Maxime Chevallier, Andrew Lunn
On Tue, 17 Mar 2026 09:33:35 +0100 Björn Töpel wrote:
> + if (netif_is_rxfh_configured(dev) &&
> + !ethtool_rxfh_can_resize(bp->rss_indir_tbl,
> + old_tbl_size, new_tbl_size,
> + dev->ethtool->rss_indir_user_size)) {
> + netdev_warn(dev, "RSS table resize not possible\n");
> + return -EINVAL;
> + }
Now that we know the size of the table that user has set
we can skip the netif_is_rxfh_configured(dev) and depend fully on
ethtool_rxfh_can_resize(... rss_indir_user_size) ?
Otherwise we're not making much use of dev->ethtool->rss_indir_user_size
(the one for the default context / context 0).
Regardless of the table size user provided IFF_RXFH_CONFIGURED will be
set.
Which brings me to my last point - I think you can delete
IFF_RXFH_CONFIGURED and have netif_is_rxfh_configured(dev)
operate on ..user_size ? We should probably have a new helper
for when driver lost control over rings and has to reset indir table
forgetting user config (bnxt mlx5 clearing IFF_RXFH_CONFIGURED).
Helper should clear the state and issue a Netlink notification?
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net-next v5 1/3] ethtool: Add RSS indirection table resize helpers
2026-03-17 22:46 ` Jakub Kicinski
@ 2026-03-18 9:27 ` Björn Töpel
0 siblings, 0 replies; 8+ messages in thread
From: Björn Töpel @ 2026-03-18 9:27 UTC (permalink / raw)
To: Jakub Kicinski
Cc: Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, netdev, linux-kselftest,
Willem de Bruijn, Shuah Khan, Maxime Chevallier, Andrew Lunn
Jakub Kicinski <kuba@kernel.org> writes:
> On Tue, 17 Mar 2026 09:33:34 +0100 Björn Töpel wrote:
>> static int
>> rss_set_prep_indir(struct net_device *dev, struct genl_info *info,
>> struct rss_reply_data *data, struct ethtool_rxfh_param *rxfh,
>> - bool *reset, bool *mod)
>> + bool *reset, bool *mod, u32 *user_sizep)
>> {
>> struct netlink_ext_ack *extack = info->extack;
>> struct nlattr **tb = info->attrs;
>> @@ -685,6 +685,7 @@ rss_set_prep_indir(struct net_device *dev, struct genl_info *info,
>> }
>>
>> *mod |= memcmp(rxfh->indir, data->indir_table, data->indir_size);
>> + *user_sizep = user_size;
>>
>> return 0;
>
> Weak preference for return user_size; instead of output arg, 7th, "p"?
Good point! The 7th and p does have a bad smell to it...
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net-next v5 2/3] bnxt_en: Resize RSS contexts on channel count change
2026-03-17 22:53 ` Jakub Kicinski
@ 2026-03-18 9:28 ` Björn Töpel
0 siblings, 0 replies; 8+ messages in thread
From: Björn Töpel @ 2026-03-18 9:28 UTC (permalink / raw)
To: Jakub Kicinski
Cc: Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, netdev, linux-kselftest,
Willem de Bruijn, Shuah Khan, Maxime Chevallier, Andrew Lunn
Jakub Kicinski <kuba@kernel.org> writes:
> On Tue, 17 Mar 2026 09:33:35 +0100 Björn Töpel wrote:
>> + if (netif_is_rxfh_configured(dev) &&
>> + !ethtool_rxfh_can_resize(bp->rss_indir_tbl,
>> + old_tbl_size, new_tbl_size,
>> + dev->ethtool->rss_indir_user_size)) {
>> + netdev_warn(dev, "RSS table resize not possible\n");
>> + return -EINVAL;
>> + }
>
> Now that we know the size of the table that user has set
> we can skip the netif_is_rxfh_configured(dev) and depend fully on
> ethtool_rxfh_can_resize(... rss_indir_user_size) ?
>
> Otherwise we're not making much use of dev->ethtool->rss_indir_user_size
> (the one for the default context / context 0).
> Regardless of the table size user provided IFF_RXFH_CONFIGURED will be
> set.
>
> Which brings me to my last point - I think you can delete
> IFF_RXFH_CONFIGURED and have netif_is_rxfh_configured(dev)
> operate on ..user_size ? We should probably have a new helper
> for when driver lost control over rings and has to reset indir table
> forgetting user config (bnxt mlx5 clearing IFF_RXFH_CONFIGURED).
> Helper should clear the state and issue a Netlink notification?
All good points -- I'll try that! Thanks!
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-03-18 9:28 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-17 8:33 [PATCH net-next v5 0/3] ethtool: Dynamic RSS context indirection table resizing Björn Töpel
2026-03-17 8:33 ` [PATCH net-next v5 1/3] ethtool: Add RSS indirection table resize helpers Björn Töpel
2026-03-17 22:46 ` Jakub Kicinski
2026-03-18 9:27 ` Björn Töpel
2026-03-17 8:33 ` [PATCH net-next v5 2/3] bnxt_en: Resize RSS contexts on channel count change Björn Töpel
2026-03-17 22:53 ` Jakub Kicinski
2026-03-18 9:28 ` Björn Töpel
2026-03-17 8:33 ` [PATCH net-next v5 3/3] selftests: rss_drv: Add RSS indirection table resize tests Björn Töpel
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.