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 <bjorn@kernel.org>
---
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 <linux/debugfs.h>
+#include <linux/ethtool.h>
#include <linux/random.h>
#include <net/netdev_queues.h>
@@ -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
On Tue, 3 Mar 2026 19:15:31 +0100 Björn Töpel wrote: > 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. Let's not bother with netdevsim support? Let the HW tests cover this? netdevsim was usual when we didn't have HW tests.
On Wed, 4 Mar 2026 at 01:07, Jakub Kicinski <kuba@kernel.org> wrote: > > On Tue, 3 Mar 2026 19:15:31 +0100 Björn Töpel wrote: > > 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. > > Let's not bother with netdevsim support? > Let the HW tests cover this? > netdevsim was usual when we didn't have HW tests. Fair enough!
© 2016 - 2026 Red Hat, Inc.