* [PATCH net-next v7 1/4] ethtool: Track user-provided RSS indirection table size
2026-03-20 8:58 [PATCH net-next v7 0/4] ethtool: Dynamic RSS context indirection table resizing Björn Töpel
@ 2026-03-20 8:58 ` Björn Töpel
2026-03-20 8:58 ` [PATCH net-next v7 2/4] ethtool: Add RSS indirection table resize helpers Björn Töpel
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Björn Töpel @ 2026-03-20 8:58 UTC (permalink / raw)
To: Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Saeed Mahameed,
Tariq Toukan, Mark Bloch, Leon Romanovsky, Simon Horman,
Shuah Khan, netdev
Cc: Björn Töpel, Maxime Chevallier, Gal Pressman,
Willem de Bruijn, linux-kernel, linux-rdma, linux-kselftest
Track the number of indirection table entries the user originally
provided (context 0/default as well!).
Replace IFF_RXFH_CONFIGURED with rss_indir_user_size: the flag is
redundant now that user_size captures the same information.
Add ethtool_rxfh_indir_lost() for drivers that must reset the
indirection table.
Convert bnxt and mlx5 to use it.
Signed-off-by: Björn Töpel <bjorn@kernel.org>
---
drivers/net/ethernet/broadcom/bnxt/bnxt.c | 3 +-
.../net/ethernet/mellanox/mlx5/core/en_main.c | 21 ++++++++------
include/linux/ethtool.h | 7 +++++
include/linux/netdevice.h | 7 +----
net/ethtool/common.c | 28 +++++++++++++++++++
net/ethtool/ioctl.c | 9 +++---
| 24 ++++++++++------
7 files changed, 70 insertions(+), 29 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
index 604966a398f5..84eb53b4172b 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
@@ -8118,8 +8118,7 @@ static int __bnxt_reserve_rings(struct bnxt *bp)
(bnxt_get_nr_rss_ctxs(bp, bp->rx_nr_rings) !=
bnxt_get_nr_rss_ctxs(bp, rx_rings) ||
bnxt_get_max_rss_ring(bp) >= rx_rings)) {
- netdev_warn(bp->dev, "RSS table entries reverting to default\n");
- bp->dev->priv_flags &= ~IFF_RXFH_CONFIGURED;
+ ethtool_rxfh_indir_lost(bp->dev);
}
}
bp->rx_nr_rings = rx_rings;
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c
index f7009da94f0b..4429b4058daa 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c
@@ -6483,12 +6483,23 @@ int mlx5e_attach_netdev(struct mlx5e_priv *priv)
/* max number of channels may have changed */
max_nch = mlx5e_calc_max_nch(priv->mdev, priv->netdev, profile);
+
+ /* Locking is required by ethtool_rxfh_indir_lost() (sends
+ * ETHTOOL_MSG_RSS_NTF) and by netif_set_real_num_*_queues in case
+ * the netdev has been registered by this point (if this function
+ * was called in the reload or resume flow).
+ */
+ if (need_lock) {
+ rtnl_lock();
+ netdev_lock(priv->netdev);
+ }
+
if (priv->channels.params.num_channels > max_nch) {
mlx5_core_warn(priv->mdev, "MLX5E: Reducing number of channels to %d\n", max_nch);
/* Reducing the number of channels - RXFH has to be reset, and
* mlx5e_num_channels_changed below will build the RQT.
*/
- priv->netdev->priv_flags &= ~IFF_RXFH_CONFIGURED;
+ ethtool_rxfh_indir_lost(priv->netdev);
priv->channels.params.num_channels = max_nch;
if (priv->channels.params.mqprio.mode == TC_MQPRIO_MODE_CHANNEL) {
mlx5_core_warn(priv->mdev, "MLX5E: Disabling MQPRIO channel mode\n");
@@ -6505,15 +6516,7 @@ int mlx5e_attach_netdev(struct mlx5e_priv *priv)
/* 1. Set the real number of queues in the kernel the first time.
* 2. Set our default XPS cpumask.
* 3. Build the RQT.
- *
- * Locking is required by netif_set_real_num_*_queues in case the
- * netdev has been registered by this point (if this function was called
- * in the reload or resume flow).
*/
- if (need_lock) {
- rtnl_lock();
- netdev_lock(priv->netdev);
- }
err = mlx5e_num_channels_changed(priv);
if (need_lock) {
netdev_unlock(priv->netdev);
diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h
index 656d465bcd06..34ca9261de82 100644
--- a/include/linux/ethtool.h
+++ b/include/linux/ethtool.h
@@ -176,6 +176,8 @@ static inline u32 ethtool_rxfh_indir_default(u32 index, u32 n_rx_rings)
* struct ethtool_rxfh_context - a custom RSS context configuration
* @indir_size: Number of u32 entries in indirection table
* @key_size: Size of hash key, in bytes
+ * @indir_user_size: number of user provided entries for the
+ * indirection table
* @priv_size: Size of driver private data, in bytes
* @hfunc: RSS hash function identifier. One of the %ETH_RSS_HASH_*
* @input_xfrm: Defines how the input data is transformed. Valid values are one
@@ -186,6 +188,7 @@ static inline u32 ethtool_rxfh_indir_default(u32 index, u32 n_rx_rings)
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,7 @@ static inline u8 *ethtool_rxfh_context_key(struct ethtool_rxfh_context *ctx)
}
void ethtool_rxfh_context_lost(struct net_device *dev, u32 context_id);
+void ethtool_rxfh_indir_lost(struct net_device *dev);
struct link_mode_info {
int speed;
@@ -1337,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 user provided entries for the default
+ * (context 0) indirection 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/include/linux/netdevice.h b/include/linux/netdevice.h
index 7ca01eb3f7d2..e82de6831e05 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -1716,7 +1716,6 @@ struct net_device_ops {
* @IFF_OPENVSWITCH: device is a Open vSwitch master
* @IFF_L3MDEV_SLAVE: device is enslaved to an L3 master device
* @IFF_TEAM: device is a team device
- * @IFF_RXFH_CONFIGURED: device has had Rx Flow indirection table configured
* @IFF_PHONY_HEADROOM: the headroom value is controlled by an external
* entity (i.e. the master device for bridged veth)
* @IFF_MACSEC: device is a MACsec device
@@ -1752,7 +1751,6 @@ enum netdev_priv_flags {
IFF_OPENVSWITCH = 1<<20,
IFF_L3MDEV_SLAVE = 1<<21,
IFF_TEAM = 1<<22,
- IFF_RXFH_CONFIGURED = 1<<23,
IFF_PHONY_HEADROOM = 1<<24,
IFF_MACSEC = 1<<25,
IFF_NO_RX_HANDLER = 1<<26,
@@ -5569,10 +5567,7 @@ static inline bool netif_is_lag_port(const struct net_device *dev)
return netif_is_bond_slave(dev) || netif_is_team_port(dev);
}
-static inline bool netif_is_rxfh_configured(const struct net_device *dev)
-{
- return dev->priv_flags & IFF_RXFH_CONFIGURED;
-}
+bool netif_is_rxfh_configured(const struct net_device *dev);
static inline bool netif_is_failover(const struct net_device *dev)
{
diff --git a/net/ethtool/common.c b/net/ethtool/common.c
index e252cf20c22f..d7d832fa9e00 100644
--- a/net/ethtool/common.c
+++ b/net/ethtool/common.c
@@ -1204,6 +1204,34 @@ void ethtool_rxfh_context_lost(struct net_device *dev, u32 context_id)
}
EXPORT_SYMBOL(ethtool_rxfh_context_lost);
+bool netif_is_rxfh_configured(const struct net_device *dev)
+{
+ return dev->ethtool->rss_indir_user_size;
+}
+EXPORT_SYMBOL(netif_is_rxfh_configured);
+
+/**
+ * ethtool_rxfh_indir_lost - Notify core that the RSS indirection table was lost
+ * @dev: network device
+ *
+ * Drivers should call this when the device can no longer maintain the
+ * user-configured indirection table, typically after a HW fault recovery
+ * that reduced the maximum queue count. Marks the default RSS context
+ * indirection table as unconfigured and sends an %ETHTOOL_MSG_RSS_NTF
+ * notification.
+ */
+void ethtool_rxfh_indir_lost(struct net_device *dev)
+{
+ WARN_ONCE(!rtnl_is_locked() &&
+ !lockdep_is_held_type(&dev->ethtool->rss_lock, -1),
+ "RSS context lock assertion failed\n");
+
+ netdev_err(dev, "device error, RSS indirection table lost\n");
+ dev->ethtool->rss_indir_user_size = 0;
+ ethtool_rss_notify(dev, ETHTOOL_MSG_RSS_NTF, 0);
+}
+EXPORT_SYMBOL(ethtool_rxfh_indir_lost);
+
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..3d31a5a041e3 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -1404,9 +1404,9 @@ static noinline_for_stack int ethtool_set_rxfh_indir(struct net_device *dev,
/* indicate whether rxfh was set to default */
if (user_size == 0)
- dev->priv_flags &= ~IFF_RXFH_CONFIGURED;
+ 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);
@@ -1721,9 +1721,9 @@ 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)
- dev->priv_flags &= ~IFF_RXFH_CONFIGURED;
+ 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 +1736,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..e6fc6e64fb27 100644
--- a/net/ethtool/rss.c
+++ b/net/ethtool/rss.c
@@ -686,7 +686,7 @@ rss_set_prep_indir(struct net_device *dev, struct genl_info *info,
*mod |= memcmp(rxfh->indir, data->indir_table, data->indir_size);
- return 0;
+ return user_size;
err_free:
kfree(rxfh->indir);
@@ -833,6 +833,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;
int ret;
ops = dev->ethtool_ops;
@@ -845,8 +846,9 @@ 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);
- if (ret)
+ if (ret < 0)
goto exit_clean_data;
+ indir_user_size = ret;
indir_mod = !!tb[ETHTOOL_A_RSS_INDIR];
rxfh.hfunc = data.hfunc;
@@ -889,12 +891,15 @@ 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)
- dev->priv_flags &= ~IFF_RXFH_CONFIGURED;
- else if (indir_mod)
- dev->priv_flags |= IFF_RXFH_CONFIGURED;
+ if (indir_user_size)
+ ctx->indir_user_size = indir_user_size;
+ } else if (indir_reset) {
+ dev->ethtool->rss_indir_user_size = 0;
+ } else if (indir_mod) {
+ dev->ethtool->rss_indir_user_size = indir_user_size;
+ }
exit_unlock:
mutex_unlock(&dev->ethtool->rss_lock);
@@ -999,6 +1004,7 @@ int ethnl_rss_create_doit(struct sk_buff *skb, struct genl_info *info)
const struct ethtool_ops *ops;
struct rss_req_info req = {};
struct net_device *dev;
+ u32 indir_user_size;
struct sk_buff *rsp;
void *hdr;
u32 limit;
@@ -1035,8 +1041,9 @@ int ethnl_rss_create_doit(struct sk_buff *skb, struct genl_info *info)
goto exit_ops;
ret = rss_set_prep_indir(dev, info, &data, &rxfh, &indir_dflt, &mod);
- if (ret)
+ if (ret < 0)
goto exit_clean_data;
+ indir_user_size = ret;
ethnl_update_u8(&rxfh.hfunc, tb[ETHTOOL_A_RSS_HFUNC], &mod);
@@ -1080,6 +1087,7 @@ 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);
+ 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] 6+ messages in thread* [PATCH net-next v7 2/4] ethtool: Add RSS indirection table resize helpers
2026-03-20 8:58 [PATCH net-next v7 0/4] ethtool: Dynamic RSS context indirection table resizing Björn Töpel
2026-03-20 8:58 ` [PATCH net-next v7 1/4] ethtool: Track user-provided RSS indirection table size Björn Töpel
@ 2026-03-20 8:58 ` Björn Töpel
2026-03-20 8:58 ` [PATCH net-next v7 3/4] bnxt_en: Resize RSS contexts on channel count change Björn Töpel
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Björn Töpel @ 2026-03-20 8:58 UTC (permalink / raw)
To: Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Saeed Mahameed,
Tariq Toukan, Mark Bloch, Leon Romanovsky, Simon Horman,
Shuah Khan, netdev
Cc: Björn Töpel, Maxime Chevallier, Gal Pressman,
Willem de Bruijn, linux-kernel, linux-rdma, linux-kselftest
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 driver helpers to resize indirection tables:
ethtool_rxfh_indir_can_resize() checks whether the default context
indirection table can be resized.
ethtool_rxfh_indir_resize() resizes the default context 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_ctxs_can_resize() validates all non-default RSS contexts
can be resized.
ethtool_rxfh_ctxs_resize() applies the resize.
Signed-off-by: Björn Töpel <bjorn@kernel.org>
---
include/linux/ethtool.h | 6 ++
net/ethtool/common.c | 155 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 161 insertions(+)
diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h
index 34ca9261de82..1cb0740ba331 100644
--- a/include/linux/ethtool.h
+++ b/include/linux/ethtool.h
@@ -218,6 +218,12 @@ static inline u8 *ethtool_rxfh_context_key(struct ethtool_rxfh_context *ctx)
void ethtool_rxfh_context_lost(struct net_device *dev, u32 context_id);
void ethtool_rxfh_indir_lost(struct net_device *dev);
+bool ethtool_rxfh_indir_can_resize(struct net_device *dev, const u32 *tbl,
+ u32 old_size, u32 new_size);
+void ethtool_rxfh_indir_resize(struct net_device *dev, u32 *tbl,
+ u32 old_size, u32 new_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;
diff --git a/net/ethtool/common.c b/net/ethtool/common.c
index d7d832fa9e00..ec9e45b5cc89 100644
--- a/net/ethtool/common.c
+++ b/net/ethtool/common.c
@@ -1232,6 +1232,161 @@ void ethtool_rxfh_indir_lost(struct net_device *dev)
}
EXPORT_SYMBOL(ethtool_rxfh_indir_lost);
+static bool ethtool_rxfh_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;
+}
+
+static 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 (!user_size)
+ return true;
+
+ if (new_size < old_size) {
+ if (new_size < user_size)
+ return false;
+ if (old_size % new_size)
+ return false;
+ if (!ethtool_rxfh_is_periodic(tbl, old_size, new_size))
+ return false;
+ return true;
+ }
+
+ if (new_size % old_size)
+ return false;
+ return true;
+}
+
+/* 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_indir_can_resize - Check if context 0 indir table can resize
+ * @dev: network device
+ * @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. Uses the user_size floor from context 0. When user_size is
+ * zero the table is not user-configured and resize always succeeds.
+ * Read-only; does not modify the table.
+ *
+ * Return: true if resize is possible, false otherwise.
+ */
+bool ethtool_rxfh_indir_can_resize(struct net_device *dev, const u32 *tbl,
+ u32 old_size, u32 new_size)
+{
+ return ethtool_rxfh_can_resize(tbl, old_size, new_size,
+ dev->ethtool->rss_indir_user_size);
+}
+EXPORT_SYMBOL(ethtool_rxfh_indir_can_resize);
+
+/**
+ * ethtool_rxfh_indir_resize - Fold or unfold context 0 indirection table
+ * @dev: network device
+ * @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 the default RSS context indirection table in place. Caller
+ * must have validated with ethtool_rxfh_indir_can_resize() first.
+ */
+void ethtool_rxfh_indir_resize(struct net_device *dev, u32 *tbl,
+ u32 old_size, u32 new_size)
+{
+ if (!dev->ethtool->rss_indir_user_size)
+ return;
+
+ ethtool_rxfh_resize(tbl, old_size, new_size);
+}
+EXPORT_SYMBOL(ethtool_rxfh_indir_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;
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH net-next v7 3/4] bnxt_en: Resize RSS contexts on channel count change
2026-03-20 8:58 [PATCH net-next v7 0/4] ethtool: Dynamic RSS context indirection table resizing Björn Töpel
2026-03-20 8:58 ` [PATCH net-next v7 1/4] ethtool: Track user-provided RSS indirection table size Björn Töpel
2026-03-20 8:58 ` [PATCH net-next v7 2/4] ethtool: Add RSS indirection table resize helpers Björn Töpel
@ 2026-03-20 8:58 ` Björn Töpel
2026-03-20 8:58 ` [PATCH net-next v7 4/4] selftests: rss_drv: Add RSS indirection table resize tests Björn Töpel
2026-03-24 1:50 ` [PATCH net-next v7 0/4] ethtool: Dynamic RSS context indirection table resizing patchwork-bot+netdevbpf
4 siblings, 0 replies; 6+ messages in thread
From: Björn Töpel @ 2026-03-20 8:58 UTC (permalink / raw)
To: Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Saeed Mahameed,
Tariq Toukan, Mark Bloch, Leon Romanovsky, Simon Horman,
Shuah Khan, netdev
Cc: Björn Töpel, Maxime Chevallier, Gal Pressman,
Willem de Bruijn, linux-kernel, linux-rdma, linux-kselftest
bnxt_set_channels() previously rejected channel changes that alter the
RSS table size when RSS contexts exist, because non-default context
sizes were locked at creation.
Replace the rejection with the new resize helpers.
RSS table size only changes on P5 chips with older firmware; newer
firmware always uses the largest table size.
Signed-off-by: Björn Töpel <bjorn@kernel.org>
---
.../net/ethernet/broadcom/bnxt/bnxt_ethtool.c | 35 +++++++++++++++----
1 file changed, 28 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..b87ac2bb43dd 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,33 @@ 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 (!ethtool_rxfh_indir_can_resize(dev, bp->rss_indir_tbl,
+ old_tbl_size,
+ new_tbl_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 +1014,12 @@ static int bnxt_set_channels(struct net_device *dev,
bnxt_close_nic(bp, true, false);
}
+ if (new_tbl_size) {
+ ethtool_rxfh_indir_resize(dev, bp->rss_indir_tbl,
+ old_tbl_size, new_tbl_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] 6+ messages in thread* [PATCH net-next v7 4/4] selftests: rss_drv: Add RSS indirection table resize tests
2026-03-20 8:58 [PATCH net-next v7 0/4] ethtool: Dynamic RSS context indirection table resizing Björn Töpel
` (2 preceding siblings ...)
2026-03-20 8:58 ` [PATCH net-next v7 3/4] bnxt_en: Resize RSS contexts on channel count change Björn Töpel
@ 2026-03-20 8:58 ` Björn Töpel
2026-03-24 1:50 ` [PATCH net-next v7 0/4] ethtool: Dynamic RSS context indirection table resizing patchwork-bot+netdevbpf
4 siblings, 0 replies; 6+ messages in thread
From: Björn Töpel @ 2026-03-20 8:58 UTC (permalink / raw)
To: Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Saeed Mahameed,
Tariq Toukan, Mark Bloch, Leon Romanovsky, Simon Horman,
Shuah Khan, netdev
Cc: Björn Töpel, Maxime Chevallier, Gal Pressman,
Willem de Bruijn, linux-kernel, linux-rdma, linux-kselftest
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] 6+ messages in thread* Re: [PATCH net-next v7 0/4] ethtool: Dynamic RSS context indirection table resizing
2026-03-20 8:58 [PATCH net-next v7 0/4] ethtool: Dynamic RSS context indirection table resizing Björn Töpel
` (3 preceding siblings ...)
2026-03-20 8:58 ` [PATCH net-next v7 4/4] selftests: rss_drv: Add RSS indirection table resize tests Björn Töpel
@ 2026-03-24 1:50 ` patchwork-bot+netdevbpf
4 siblings, 0 replies; 6+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-03-24 1:50 UTC (permalink / raw)
To: =?utf-8?b?QmrDtnJuIFTDtnBlbCA8Ympvcm5Aa2VybmVsLm9yZz4=?=
Cc: michael.chan, pavan.chebbi, andrew+netdev, davem, edumazet, kuba,
pabeni, saeedm, tariqt, mbloch, leon, horms, shuah, netdev,
maxime.chevallier, gal, willemb, linux-kernel, linux-rdma,
linux-kselftest
Hello:
This series was applied to netdev/net-next.git (main)
by Jakub Kicinski <kuba@kernel.org>:
On Fri, 20 Mar 2026 09:58:20 +0100 you wrote:
> Hi!
>
> 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.
>
> [...]
Here is the summary with links:
- [net-next,v7,1/4] ethtool: Track user-provided RSS indirection table size
https://git.kernel.org/netdev/net-next/c/0475f9e779b4
- [net-next,v7,2/4] ethtool: Add RSS indirection table resize helpers
https://git.kernel.org/netdev/net-next/c/02bcb20083b2
- [net-next,v7,3/4] bnxt_en: Resize RSS contexts on channel count change
https://git.kernel.org/netdev/net-next/c/57cdfe0dc70b
- [net-next,v7,4/4] selftests: rss_drv: Add RSS indirection table resize tests
https://git.kernel.org/netdev/net-next/c/10329ce49285
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 6+ messages in thread