Merge branch 'ethtool-rss-support-creating-and-removing-contexts-via-netlink'

Jakub Kicinski says:

====================
ethtool: rss: support creating and removing contexts via Netlink

This series completes support of RSS configuration via Netlink.
All functionality supported by the IOCTL is now supported by
Netlink. Future series (time allowing) will add:
 - hashing on the flow label, which started this whole thing;
 - pinning the RSS context to a Netlink socket for auto-cleanup.

The first patch is a leftover held back from previous series
to avoid conflicting with Gal's fix.

Next 4 patches refactor existing code to make reusing it for
context creation possible. 2 patches after that add create
and delete commands. Last but not least the test is extended.
====================

Link: https://patch.msgid.link/20250717234343.2328602-1-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski
2025-07-21 18:20:21 -07:00
10 changed files with 603 additions and 72 deletions

View File

@@ -2684,9 +2684,46 @@ operations:
name: rss-ntf
doc: |
Notification for change in RSS configuration.
For additional contexts only modifications are modified, not creation
or removal of the contexts.
For additional contexts only modifications use this notification,
creation and deletion have dedicated messages.
notify: rss-get
-
name: rss-create-act
doc: Create an RSS context.
attribute-set: rss
do:
request: &rss-create-attrs
attributes:
- header
- context
- hfunc
- indir
- hkey
- input-xfrm
reply: *rss-create-attrs
-
name: rss-create-ntf
doc: |
Notification for creation of an additional RSS context.
notify: rss-create-act
-
name: rss-delete-act
doc: Delete an RSS context.
attribute-set: rss
do:
request:
attributes:
- header
- context
-
name: rss-delete-ntf
doc: |
Notification for deletion of an additional RSS context.
attribute-set: rss
event:
attributes:
- header
- context
mcast-groups:
list:

View File

@@ -240,6 +240,8 @@ Userspace to kernel:
``ETHTOOL_MSG_TSCONFIG_GET`` get hw timestamping configuration
``ETHTOOL_MSG_TSCONFIG_SET`` set hw timestamping configuration
``ETHTOOL_MSG_RSS_SET`` set RSS settings
``ETHTOOL_MSG_RSS_CREATE_ACT`` create an additional RSS context
``ETHTOOL_MSG_RSS_DELETE_ACT`` delete an additional RSS context
===================================== =================================
Kernel to userspace:
@@ -294,6 +296,9 @@ Kernel to userspace:
``ETHTOOL_MSG_TSCONFIG_SET_REPLY`` new hw timestamping configuration
``ETHTOOL_MSG_PSE_NTF`` PSE events notification
``ETHTOOL_MSG_RSS_NTF`` RSS settings notification
``ETHTOOL_MSG_RSS_CREATE_ACT_REPLY`` create an additional RSS context
``ETHTOOL_MSG_RSS_CREATE_NTF`` additional RSS context created
``ETHTOOL_MSG_RSS_DELETE_NTF`` additional RSS context deleted
======================================== =================================
``GET`` requests are sent by userspace applications to retrieve device
@@ -2014,6 +2019,42 @@ device needs at least 8 entries - the real table in use will end up being
of 2, so tables which size is not a power of 2 will likely be rejected.
Using table of size 0 will reset the indirection table to the default.
RSS_CREATE_ACT
==============
Request contents:
===================================== ====== ==============================
``ETHTOOL_A_RSS_HEADER`` nested request header
``ETHTOOL_A_RSS_CONTEXT`` u32 context number
``ETHTOOL_A_RSS_HFUNC`` u32 RSS hash func
``ETHTOOL_A_RSS_INDIR`` binary Indir table bytes
``ETHTOOL_A_RSS_HKEY`` binary Hash key bytes
``ETHTOOL_A_RSS_INPUT_XFRM`` u32 RSS input data transformation
===================================== ====== ==============================
Kernel response contents:
===================================== ====== ==============================
``ETHTOOL_A_RSS_HEADER`` nested request header
``ETHTOOL_A_RSS_CONTEXT`` u32 context number
===================================== ====== ==============================
Create an additional RSS context, if ``ETHTOOL_A_RSS_CONTEXT`` is not
specified kernel will allocate one automatically.
RSS_DELETE_ACT
==============
Request contents:
===================================== ====== ==============================
``ETHTOOL_A_RSS_HEADER`` nested request header
``ETHTOOL_A_RSS_CONTEXT`` u32 context number
===================================== ====== ==============================
Delete an additional RSS context.
PLCA_GET_CFG
============

View File

@@ -841,6 +841,8 @@ enum {
ETHTOOL_MSG_TSCONFIG_GET,
ETHTOOL_MSG_TSCONFIG_SET,
ETHTOOL_MSG_RSS_SET,
ETHTOOL_MSG_RSS_CREATE_ACT,
ETHTOOL_MSG_RSS_DELETE_ACT,
__ETHTOOL_MSG_USER_CNT,
ETHTOOL_MSG_USER_MAX = (__ETHTOOL_MSG_USER_CNT - 1)
@@ -898,6 +900,9 @@ enum {
ETHTOOL_MSG_TSCONFIG_SET_REPLY,
ETHTOOL_MSG_PSE_NTF,
ETHTOOL_MSG_RSS_NTF,
ETHTOOL_MSG_RSS_CREATE_ACT_REPLY,
ETHTOOL_MSG_RSS_CREATE_NTF,
ETHTOOL_MSG_RSS_DELETE_NTF,
__ETHTOOL_MSG_KERNEL_CNT,
ETHTOOL_MSG_KERNEL_MAX = (__ETHTOOL_MSG_KERNEL_CNT - 1)

View File

@@ -806,6 +806,40 @@ int ethtool_check_rss_ctx_busy(struct net_device *dev, u32 rss_context)
return rc;
}
struct ethtool_rxfh_context *
ethtool_rxfh_ctx_alloc(const struct ethtool_ops *ops,
u32 indir_size, u32 key_size)
{
size_t indir_bytes, flex_len, key_off, size;
struct ethtool_rxfh_context *ctx;
u32 priv_bytes, indir_max;
u16 key_max;
key_max = max(key_size, ops->rxfh_key_space);
indir_max = max(indir_size, ops->rxfh_indir_space);
priv_bytes = ALIGN(ops->rxfh_priv_size, sizeof(u32));
indir_bytes = array_size(indir_max, sizeof(u32));
key_off = size_add(priv_bytes, indir_bytes);
flex_len = size_add(key_off, key_max);
size = struct_size_t(struct ethtool_rxfh_context, data, flex_len);
ctx = kzalloc(size, GFP_KERNEL_ACCOUNT);
if (!ctx)
return NULL;
ctx->indir_size = indir_size;
ctx->key_size = key_size;
ctx->key_off = key_off;
ctx->priv_size = ops->rxfh_priv_size;
ctx->hfunc = ETH_RSS_HASH_NO_CHANGE;
ctx->input_xfrm = RXH_XFRM_NO_CHANGE;
return ctx;
}
/* Check if fields configured for flow hash are symmetric - if src is included
* so is dst and vice versa.
*/
@@ -829,6 +863,10 @@ int ethtool_check_ops(const struct ethtool_ops *ops)
return -EINVAL;
if (WARN_ON(ops->supported_input_xfrm && !ops->get_rxfh_fields))
return -EINVAL;
if (WARN_ON(ops->supported_input_xfrm &&
ops->rxfh_per_ctx_fields != ops->rxfh_per_ctx_key))
return -EINVAL;
/* NOTE: sufficiently insane drivers may swap ethtool_ops at runtime,
* the fact that ops are checked at registration time does not
* mean the ops attached to a netdev later on are sane.
@@ -1098,5 +1136,6 @@ void ethtool_rxfh_context_lost(struct net_device *dev, u32 context_id)
netdev_err(dev, "device error, RSS context %d lost\n", context_id);
ctx = xa_erase(&dev->ethtool->rss_ctx, context_id);
kfree(ctx);
ethtool_rss_notify(dev, ETHTOOL_MSG_RSS_DELETE_NTF, context_id);
}
EXPORT_SYMBOL(ethtool_rxfh_context_lost);

View File

@@ -43,6 +43,9 @@ bool convert_legacy_settings_to_link_ksettings(
int ethtool_check_max_channel(struct net_device *dev,
struct ethtool_channels channels,
struct genl_info *info);
struct ethtool_rxfh_context *
ethtool_rxfh_ctx_alloc(const struct ethtool_ops *ops,
u32 indir_size, u32 key_size);
int ethtool_check_rss_ctx_busy(struct net_device *dev, u32 rss_context);
int ethtool_rxfh_config_is_sym(u64 rxfh);
@@ -76,9 +79,10 @@ int ethtool_get_module_eeprom_call(struct net_device *dev,
bool __ethtool_dev_mm_supported(struct net_device *dev);
#if IS_ENABLED(CONFIG_ETHTOOL_NETLINK)
void ethtool_rss_notify(struct net_device *dev, u32 rss_context);
void ethtool_rss_notify(struct net_device *dev, u32 type, u32 rss_context);
#else
static inline void ethtool_rss_notify(struct net_device *dev, u32 rss_context)
static inline void
ethtool_rss_notify(struct net_device *dev, u32 type, u32 rss_context)
{
}
#endif

View File

@@ -1105,7 +1105,7 @@ ethtool_set_rxfh_fields(struct net_device *dev, u32 cmd, void __user *useraddr)
if (rc)
return rc;
ethtool_rss_notify(dev, fields.rss_context);
ethtool_rss_notify(dev, ETHTOOL_MSG_RSS_NTF, fields.rss_context);
return 0;
}
@@ -1473,40 +1473,6 @@ static noinline_for_stack int ethtool_get_rxfh(struct net_device *dev,
return ret;
}
static struct ethtool_rxfh_context *
ethtool_rxfh_ctx_alloc(const struct ethtool_ops *ops,
u32 indir_size, u32 key_size)
{
size_t indir_bytes, flex_len, key_off, size;
struct ethtool_rxfh_context *ctx;
u32 priv_bytes, indir_max;
u16 key_max;
key_max = max(key_size, ops->rxfh_key_space);
indir_max = max(indir_size, ops->rxfh_indir_space);
priv_bytes = ALIGN(ops->rxfh_priv_size, sizeof(u32));
indir_bytes = array_size(indir_max, sizeof(u32));
key_off = size_add(priv_bytes, indir_bytes);
flex_len = size_add(key_off, key_max);
size = struct_size_t(struct ethtool_rxfh_context, data, flex_len);
ctx = kzalloc(size, GFP_KERNEL_ACCOUNT);
if (!ctx)
return NULL;
ctx->indir_size = indir_size;
ctx->key_size = key_size;
ctx->key_off = key_off;
ctx->priv_size = ops->rxfh_priv_size;
ctx->hfunc = ETH_RSS_HASH_NO_CHANGE;
ctx->input_xfrm = RXH_XFRM_NO_CHANGE;
return ctx;
}
static noinline_for_stack int ethtool_set_rxfh(struct net_device *dev,
void __user *useraddr)
{
@@ -1520,8 +1486,8 @@ static noinline_for_stack int ethtool_set_rxfh(struct net_device *dev,
struct ethtool_rxnfc rx_rings;
struct ethtool_rxfh rxfh;
bool create = false;
bool mod = false;
u8 *rss_config;
int ntf = 0;
int ret;
if (!ops->get_rxnfc || !ops->set_rxfh)
@@ -1671,20 +1637,25 @@ static noinline_for_stack int ethtool_set_rxfh(struct net_device *dev,
rxfh_dev.input_xfrm = rxfh.input_xfrm;
if (!rxfh.rss_context) {
ntf = ETHTOOL_MSG_RSS_NTF;
ret = ops->set_rxfh(dev, &rxfh_dev, extack);
} else if (create) {
ntf = ETHTOOL_MSG_RSS_CREATE_NTF;
ret = ops->create_rxfh_context(dev, ctx, &rxfh_dev, extack);
/* Make sure driver populates defaults */
WARN_ON_ONCE(!ret && !rxfh_dev.key && ops->rxfh_per_ctx_key &&
!memchr_inv(ethtool_rxfh_context_key(ctx), 0,
ctx->key_size));
} else if (rxfh_dev.rss_delete) {
ntf = ETHTOOL_MSG_RSS_DELETE_NTF;
ret = ops->remove_rxfh_context(dev, ctx, rxfh.rss_context,
extack);
} else {
ntf = ETHTOOL_MSG_RSS_NTF;
ret = ops->modify_rxfh_context(dev, ctx, &rxfh_dev, extack);
}
if (ret) {
ntf = 0;
if (create) {
/* failed to create, free our new tracking entry */
xa_erase(&dev->ethtool->rss_ctx, rxfh.rss_context);
@@ -1692,7 +1663,6 @@ static noinline_for_stack int ethtool_set_rxfh(struct net_device *dev,
}
goto out_unlock;
}
mod = !create && !rxfh_dev.rss_delete;
if (copy_to_user(useraddr + offsetof(struct ethtool_rxfh, rss_context),
&rxfh_dev.rss_context, sizeof(rxfh_dev.rss_context)))
@@ -1732,8 +1702,8 @@ static noinline_for_stack int ethtool_set_rxfh(struct net_device *dev,
mutex_unlock(&dev->ethtool->rss_lock);
out_free:
kfree(rss_config);
if (mod)
ethtool_rss_notify(dev, rxfh.rss_context);
if (ntf)
ethtool_rss_notify(dev, ntf, rxfh.rss_context);
return ret;
}

View File

@@ -81,6 +81,12 @@ static void ethnl_sock_priv_destroy(void *priv)
}
}
u32 ethnl_bcast_seq_next(void)
{
ASSERT_RTNL();
return ++ethnl_bcast_seq;
}
int ethnl_ops_begin(struct net_device *dev)
{
int ret;
@@ -954,6 +960,7 @@ ethnl_default_notify_ops[ETHTOOL_MSG_KERNEL_MAX + 1] = {
[ETHTOOL_MSG_PLCA_NTF] = &ethnl_plca_cfg_request_ops,
[ETHTOOL_MSG_MM_NTF] = &ethnl_mm_request_ops,
[ETHTOOL_MSG_RSS_NTF] = &ethnl_rss_request_ops,
[ETHTOOL_MSG_RSS_CREATE_NTF] = &ethnl_rss_request_ops,
};
/* default notification handler */
@@ -1061,6 +1068,7 @@ static const ethnl_notify_handler_t ethnl_notify_handlers[] = {
[ETHTOOL_MSG_PLCA_NTF] = ethnl_default_notify,
[ETHTOOL_MSG_MM_NTF] = ethnl_default_notify,
[ETHTOOL_MSG_RSS_NTF] = ethnl_default_notify,
[ETHTOOL_MSG_RSS_CREATE_NTF] = ethnl_default_notify,
};
void ethnl_notify(struct net_device *dev, unsigned int cmd,
@@ -1512,6 +1520,20 @@ static const struct genl_ops ethtool_genl_ops[] = {
.policy = ethnl_rss_set_policy,
.maxattr = ARRAY_SIZE(ethnl_rss_set_policy) - 1,
},
{
.cmd = ETHTOOL_MSG_RSS_CREATE_ACT,
.flags = GENL_UNS_ADMIN_PERM,
.doit = ethnl_rss_create_doit,
.policy = ethnl_rss_create_policy,
.maxattr = ARRAY_SIZE(ethnl_rss_create_policy) - 1,
},
{
.cmd = ETHTOOL_MSG_RSS_DELETE_ACT,
.flags = GENL_UNS_ADMIN_PERM,
.doit = ethnl_rss_delete_doit,
.policy = ethnl_rss_delete_policy,
.maxattr = ARRAY_SIZE(ethnl_rss_delete_policy) - 1,
},
};
static const struct genl_multicast_group ethtool_nl_mcgrps[] = {

View File

@@ -10,6 +10,7 @@
struct ethnl_req_info;
u32 ethnl_bcast_seq_next(void);
int ethnl_parse_header_dev_get(struct ethnl_req_info *req_info,
const struct nlattr *nest, struct net *net,
struct netlink_ext_ack *extack,
@@ -485,6 +486,8 @@ extern const struct nla_policy ethnl_pse_get_policy[ETHTOOL_A_PSE_HEADER + 1];
extern const struct nla_policy ethnl_pse_set_policy[ETHTOOL_A_PSE_MAX + 1];
extern const struct nla_policy ethnl_rss_get_policy[ETHTOOL_A_RSS_START_CONTEXT + 1];
extern const struct nla_policy ethnl_rss_set_policy[ETHTOOL_A_RSS_FLOW_HASH + 1];
extern const struct nla_policy ethnl_rss_create_policy[ETHTOOL_A_RSS_INPUT_XFRM + 1];
extern const struct nla_policy ethnl_rss_delete_policy[ETHTOOL_A_RSS_CONTEXT + 1];
extern const struct nla_policy ethnl_plca_get_cfg_policy[ETHTOOL_A_PLCA_HEADER + 1];
extern const struct nla_policy ethnl_plca_set_cfg_policy[ETHTOOL_A_PLCA_MAX + 1];
extern const struct nla_policy ethnl_plca_get_status_policy[ETHTOOL_A_PLCA_HEADER + 1];
@@ -507,6 +510,8 @@ int ethnl_rss_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
int ethnl_tsinfo_start(struct netlink_callback *cb);
int ethnl_tsinfo_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
int ethnl_tsinfo_done(struct netlink_callback *cb);
int ethnl_rss_create_doit(struct sk_buff *skb, struct genl_info *info);
int ethnl_rss_delete_doit(struct sk_buff *skb, struct genl_info *info);
extern const char stats_std_names[__ETHTOOL_STATS_CNT][ETH_GSTRING_LEN];
extern const char stats_eth_phy_names[__ETHTOOL_A_STATS_ETH_PHY_CNT][ETH_GSTRING_LEN];

View File

@@ -113,21 +113,11 @@ rss_prepare_flow_hash(const struct rss_req_info *req, struct net_device *dev,
}
static int
rss_prepare_get(const struct rss_req_info *request, struct net_device *dev,
struct rss_reply_data *data, const struct genl_info *info)
rss_get_data_alloc(struct net_device *dev, struct rss_reply_data *data)
{
struct ethtool_rxfh_param rxfh = {};
const struct ethtool_ops *ops;
const struct ethtool_ops *ops = dev->ethtool_ops;
u32 total_size, indir_bytes;
u8 *rss_config;
int ret;
ops = dev->ethtool_ops;
ret = ethnl_ops_begin(dev);
if (ret < 0)
return ret;
mutex_lock(&dev->ethtool->rss_lock);
data->indir_size = 0;
data->hkey_size = 0;
@@ -139,16 +129,39 @@ rss_prepare_get(const struct rss_req_info *request, struct net_device *dev,
indir_bytes = data->indir_size * sizeof(u32);
total_size = indir_bytes + data->hkey_size;
rss_config = kzalloc(total_size, GFP_KERNEL);
if (!rss_config) {
ret = -ENOMEM;
goto out_unlock;
}
if (!rss_config)
return -ENOMEM;
if (data->indir_size)
data->indir_table = (u32 *)rss_config;
if (data->hkey_size)
data->hkey = rss_config + indir_bytes;
return 0;
}
static void rss_get_data_free(const struct rss_reply_data *data)
{
kfree(data->indir_table);
}
static int
rss_prepare_get(const struct rss_req_info *request, struct net_device *dev,
struct rss_reply_data *data, const struct genl_info *info)
{
const struct ethtool_ops *ops = dev->ethtool_ops;
struct ethtool_rxfh_param rxfh = {};
int ret;
ret = ethnl_ops_begin(dev);
if (ret < 0)
return ret;
mutex_lock(&dev->ethtool->rss_lock);
ret = rss_get_data_alloc(dev, data);
if (ret)
goto out_unlock;
rxfh.indir_size = data->indir_size;
rxfh.indir = data->indir_table;
rxfh.key_size = data->hkey_size;
@@ -166,6 +179,25 @@ rss_prepare_get(const struct rss_req_info *request, struct net_device *dev,
return ret;
}
static void
__rss_prepare_ctx(struct net_device *dev, struct rss_reply_data *data,
struct ethtool_rxfh_context *ctx)
{
if (WARN_ON_ONCE(data->indir_size != ctx->indir_size ||
data->hkey_size != ctx->key_size))
return;
data->no_key_fields = !dev->ethtool_ops->rxfh_per_ctx_key;
data->hfunc = ctx->hfunc;
data->input_xfrm = ctx->input_xfrm;
memcpy(data->indir_table, ethtool_rxfh_context_indir(ctx),
data->indir_size * sizeof(u32));
if (data->hkey_size)
memcpy(data->hkey, ethtool_rxfh_context_key(ctx),
data->hkey_size);
}
static int
rss_prepare_ctx(const struct rss_req_info *request, struct net_device *dev,
struct rss_reply_data *data, const struct genl_info *info)
@@ -175,8 +207,6 @@ rss_prepare_ctx(const struct rss_req_info *request, struct net_device *dev,
u8 *rss_config;
int ret;
data->no_key_fields = !dev->ethtool_ops->rxfh_per_ctx_key;
mutex_lock(&dev->ethtool->rss_lock);
ctx = xa_load(&dev->ethtool->rss_ctx, request->rss_context);
if (!ctx) {
@@ -186,8 +216,6 @@ rss_prepare_ctx(const struct rss_req_info *request, struct net_device *dev,
data->indir_size = ctx->indir_size;
data->hkey_size = ctx->key_size;
data->hfunc = ctx->hfunc;
data->input_xfrm = ctx->input_xfrm;
indir_bytes = data->indir_size * sizeof(u32);
total_size = indir_bytes + data->hkey_size;
@@ -198,13 +226,10 @@ rss_prepare_ctx(const struct rss_req_info *request, struct net_device *dev,
}
data->indir_table = (u32 *)rss_config;
memcpy(data->indir_table, ethtool_rxfh_context_indir(ctx), indir_bytes);
if (data->hkey_size) {
if (data->hkey_size)
data->hkey = rss_config + indir_bytes;
memcpy(data->hkey, ethtool_rxfh_context_key(ctx),
data->hkey_size);
}
__rss_prepare_ctx(dev, data, ctx);
ret = 0;
out_unlock:
@@ -318,7 +343,7 @@ static void rss_cleanup_data(struct ethnl_reply_data *reply_base)
{
const struct rss_reply_data *data = RSS_REPDATA(reply_base);
kfree(data->indir_table);
rss_get_data_free(data);
}
struct rss_nl_dump_ctx {
@@ -461,13 +486,49 @@ int ethnl_rss_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
/* RSS_NTF */
void ethtool_rss_notify(struct net_device *dev, u32 rss_context)
static void ethnl_rss_delete_notify(struct net_device *dev, u32 rss_context)
{
struct sk_buff *ntf;
size_t ntf_size;
void *hdr;
ntf_size = ethnl_reply_header_size() +
nla_total_size(sizeof(u32)); /* _RSS_CONTEXT */
ntf = genlmsg_new(ntf_size, GFP_KERNEL);
if (!ntf)
goto out_warn;
hdr = ethnl_bcastmsg_put(ntf, ETHTOOL_MSG_RSS_DELETE_NTF);
if (!hdr)
goto out_free_ntf;
if (ethnl_fill_reply_header(ntf, dev, ETHTOOL_A_RSS_HEADER) ||
nla_put_u32(ntf, ETHTOOL_A_RSS_CONTEXT, rss_context))
goto out_free_ntf;
genlmsg_end(ntf, hdr);
if (ethnl_multicast(ntf, dev))
goto out_warn;
return;
out_free_ntf:
nlmsg_free(ntf);
out_warn:
pr_warn_once("Failed to send a RSS delete notification");
}
void ethtool_rss_notify(struct net_device *dev, u32 type, u32 rss_context)
{
struct rss_req_info req_info = {
.rss_context = rss_context,
};
ethnl_notify(dev, ETHTOOL_MSG_RSS_NTF, &req_info.base);
if (type == ETHTOOL_MSG_RSS_DELETE_NTF)
ethnl_rss_delete_notify(dev, rss_context);
else
ethnl_notify(dev, type, &req_info.base);
}
/* RSS_SET */
@@ -868,3 +929,277 @@ const struct ethnl_request_ops ethnl_rss_request_ops = {
.set = ethnl_rss_set,
.set_ntf_cmd = ETHTOOL_MSG_RSS_NTF,
};
/* RSS_CREATE */
const struct nla_policy ethnl_rss_create_policy[ETHTOOL_A_RSS_INPUT_XFRM + 1] = {
[ETHTOOL_A_RSS_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
[ETHTOOL_A_RSS_CONTEXT] = NLA_POLICY_MIN(NLA_U32, 1),
[ETHTOOL_A_RSS_HFUNC] = NLA_POLICY_MIN(NLA_U32, 1),
[ETHTOOL_A_RSS_INDIR] = NLA_POLICY_MIN(NLA_BINARY, 1),
[ETHTOOL_A_RSS_HKEY] = NLA_POLICY_MIN(NLA_BINARY, 1),
[ETHTOOL_A_RSS_INPUT_XFRM] =
NLA_POLICY_MAX(NLA_U32, RXH_XFRM_SYM_OR_XOR),
};
static int
ethnl_rss_create_validate(struct net_device *dev, struct genl_info *info)
{
const struct ethtool_ops *ops = dev->ethtool_ops;
struct nlattr **tb = info->attrs;
struct nlattr *bad_attr = NULL;
u32 rss_context, input_xfrm;
if (!ops->create_rxfh_context)
return -EOPNOTSUPP;
rss_context = nla_get_u32_default(tb[ETHTOOL_A_RSS_CONTEXT], 0);
if (ops->rxfh_max_num_contexts &&
ops->rxfh_max_num_contexts <= rss_context) {
NL_SET_BAD_ATTR(info->extack, tb[ETHTOOL_A_RSS_CONTEXT]);
return -ERANGE;
}
if (!ops->rxfh_per_ctx_key) {
bad_attr = bad_attr ?: tb[ETHTOOL_A_RSS_HFUNC];
bad_attr = bad_attr ?: tb[ETHTOOL_A_RSS_HKEY];
bad_attr = bad_attr ?: tb[ETHTOOL_A_RSS_INPUT_XFRM];
}
input_xfrm = nla_get_u32_default(tb[ETHTOOL_A_RSS_INPUT_XFRM], 0);
if (input_xfrm & ~ops->supported_input_xfrm)
bad_attr = bad_attr ?: tb[ETHTOOL_A_RSS_INPUT_XFRM];
if (bad_attr) {
NL_SET_BAD_ATTR(info->extack, bad_attr);
return -EOPNOTSUPP;
}
return 0;
}
static void
ethnl_rss_create_send_ntf(struct sk_buff *rsp, struct net_device *dev)
{
struct nlmsghdr *nlh = (void *)rsp->data;
struct genlmsghdr *genl_hdr;
/* Convert the reply into a notification */
nlh->nlmsg_pid = 0;
nlh->nlmsg_seq = ethnl_bcast_seq_next();
genl_hdr = nlmsg_data(nlh);
genl_hdr->cmd = ETHTOOL_MSG_RSS_CREATE_NTF;
ethnl_multicast(rsp, dev);
}
int ethnl_rss_create_doit(struct sk_buff *skb, struct genl_info *info)
{
bool indir_dflt = false, mod = false, ntf_fail = false;
struct ethtool_rxfh_param rxfh = {};
struct ethtool_rxfh_context *ctx;
struct nlattr **tb = info->attrs;
struct rss_reply_data data = {};
const struct ethtool_ops *ops;
struct rss_req_info req = {};
struct net_device *dev;
struct sk_buff *rsp;
void *hdr;
u32 limit;
int ret;
rsp = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
if (!rsp)
return -ENOMEM;
ret = ethnl_parse_header_dev_get(&req.base, tb[ETHTOOL_A_RSS_HEADER],
genl_info_net(info), info->extack,
true);
if (ret < 0)
goto exit_free_rsp;
dev = req.base.dev;
ops = dev->ethtool_ops;
req.rss_context = nla_get_u32_default(tb[ETHTOOL_A_RSS_CONTEXT], 0);
ret = ethnl_rss_create_validate(dev, info);
if (ret)
goto exit_free_dev;
rtnl_lock();
netdev_lock_ops(dev);
ret = ethnl_ops_begin(dev);
if (ret < 0)
goto exit_dev_unlock;
ret = rss_get_data_alloc(dev, &data);
if (ret)
goto exit_ops;
ret = rss_set_prep_indir(dev, info, &data, &rxfh, &indir_dflt, &mod);
if (ret)
goto exit_clean_data;
ethnl_update_u8(&rxfh.hfunc, tb[ETHTOOL_A_RSS_HFUNC], &mod);
ret = rss_set_prep_hkey(dev, info, &data, &rxfh, &mod);
if (ret)
goto exit_free_indir;
rxfh.input_xfrm = RXH_XFRM_NO_CHANGE;
ethnl_update_u8(&rxfh.input_xfrm, tb[ETHTOOL_A_RSS_INPUT_XFRM], &mod);
ctx = ethtool_rxfh_ctx_alloc(ops, data.indir_size, data.hkey_size);
if (!ctx) {
ret = -ENOMEM;
goto exit_free_hkey;
}
mutex_lock(&dev->ethtool->rss_lock);
if (!req.rss_context) {
limit = ops->rxfh_max_num_contexts ?: U32_MAX;
ret = xa_alloc(&dev->ethtool->rss_ctx, &req.rss_context, ctx,
XA_LIMIT(1, limit - 1), GFP_KERNEL_ACCOUNT);
} else {
ret = xa_insert(&dev->ethtool->rss_ctx,
req.rss_context, ctx, GFP_KERNEL_ACCOUNT);
}
if (ret < 0) {
NL_SET_ERR_MSG_ATTR(info->extack, tb[ETHTOOL_A_RSS_CONTEXT],
"error allocating context ID");
goto err_unlock_free_ctx;
}
rxfh.rss_context = req.rss_context;
ret = ops->create_rxfh_context(dev, ctx, &rxfh, info->extack);
if (ret)
goto err_ctx_id_free;
/* Make sure driver populates defaults */
WARN_ON_ONCE(!rxfh.key && ops->rxfh_per_ctx_key &&
!memchr_inv(ethtool_rxfh_context_key(ctx), 0,
ctx->key_size));
/* Store the config from rxfh to Xarray.. */
rss_set_ctx_update(ctx, tb, &data, &rxfh);
/* .. copy from Xarray to data. */
__rss_prepare_ctx(dev, &data, ctx);
hdr = ethnl_unicast_put(rsp, info->snd_portid, info->snd_seq,
ETHTOOL_MSG_RSS_CREATE_ACT_REPLY);
ntf_fail = ethnl_fill_reply_header(rsp, dev, ETHTOOL_A_RSS_HEADER);
ntf_fail |= rss_fill_reply(rsp, &req.base, &data.base);
if (WARN_ON(!hdr || ntf_fail)) {
ret = -EMSGSIZE;
goto exit_unlock;
}
genlmsg_end(rsp, hdr);
/* Use the same skb for the response and the notification,
* genlmsg_reply() will copy the skb if it has elevated user count.
*/
skb_get(rsp);
ret = genlmsg_reply(rsp, info);
ethnl_rss_create_send_ntf(rsp, dev);
rsp = NULL;
exit_unlock:
mutex_unlock(&dev->ethtool->rss_lock);
exit_free_hkey:
kfree(rxfh.key);
exit_free_indir:
kfree(rxfh.indir);
exit_clean_data:
rss_get_data_free(&data);
exit_ops:
ethnl_ops_complete(dev);
exit_dev_unlock:
netdev_unlock_ops(dev);
rtnl_unlock();
exit_free_dev:
ethnl_parse_header_dev_put(&req.base);
exit_free_rsp:
nlmsg_free(rsp);
return ret;
err_ctx_id_free:
xa_erase(&dev->ethtool->rss_ctx, req.rss_context);
err_unlock_free_ctx:
kfree(ctx);
goto exit_unlock;
}
/* RSS_DELETE */
const struct nla_policy ethnl_rss_delete_policy[ETHTOOL_A_RSS_CONTEXT + 1] = {
[ETHTOOL_A_RSS_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
[ETHTOOL_A_RSS_CONTEXT] = NLA_POLICY_MIN(NLA_U32, 1),
};
int ethnl_rss_delete_doit(struct sk_buff *skb, struct genl_info *info)
{
struct ethtool_rxfh_context *ctx;
struct nlattr **tb = info->attrs;
struct ethnl_req_info req = {};
const struct ethtool_ops *ops;
struct net_device *dev;
u32 rss_context;
int ret;
if (GENL_REQ_ATTR_CHECK(info, ETHTOOL_A_RSS_CONTEXT))
return -EINVAL;
rss_context = nla_get_u32(tb[ETHTOOL_A_RSS_CONTEXT]);
ret = ethnl_parse_header_dev_get(&req, tb[ETHTOOL_A_RSS_HEADER],
genl_info_net(info), info->extack,
true);
if (ret < 0)
return ret;
dev = req.dev;
ops = dev->ethtool_ops;
if (!ops->create_rxfh_context)
goto exit_free_dev;
rtnl_lock();
netdev_lock_ops(dev);
ret = ethnl_ops_begin(dev);
if (ret < 0)
goto exit_dev_unlock;
mutex_lock(&dev->ethtool->rss_lock);
ret = ethtool_check_rss_ctx_busy(dev, rss_context);
if (ret)
goto exit_unlock;
ctx = xa_load(&dev->ethtool->rss_ctx, rss_context);
if (!ctx) {
ret = -ENOENT;
goto exit_unlock;
}
ret = ops->remove_rxfh_context(dev, ctx, rss_context, info->extack);
if (ret)
goto exit_unlock;
WARN_ON(xa_erase(&dev->ethtool->rss_ctx, rss_context) != ctx);
kfree(ctx);
ethnl_rss_delete_notify(dev, rss_context);
exit_unlock:
mutex_unlock(&dev->ethtool->rss_lock);
ethnl_ops_complete(dev);
exit_dev_unlock:
netdev_unlock_ops(dev);
rtnl_unlock();
exit_free_dev:
ethnl_parse_header_dev_put(&req);
return ret;
}

View File

@@ -5,6 +5,7 @@
API level tests for RSS (mostly Netlink vs IOCTL).
"""
import errno
import glob
import random
from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_is, ksft_ne, ksft_raises
@@ -390,6 +391,78 @@ def test_rxfh_fields_ntf(cfg):
ksft_eq(next(ethnl.poll_ntf(duration=0.01), None), None)
def test_rss_ctx_add(cfg):
""" Test creating an additional RSS context via Netlink """
_require_2qs(cfg)
# Test basic creation
ctx = cfg.ethnl.rss_create_act({"header": {"dev-index": cfg.ifindex}})
d = defer(ethtool, f"-X {cfg.ifname} context {ctx.get('context')} delete")
ksft_ne(ctx.get("context", 0), 0)
ksft_ne(set(ctx.get("indir", [0])), {0},
comment="Driver should init the indirection table")
# Try requesting the ID we just got allocated
with ksft_raises(NlError) as cm:
ctx = cfg.ethnl.rss_create_act({
"header": {"dev-index": cfg.ifindex},
"context": ctx.get("context"),
})
ethtool(f"-X {cfg.ifname} context {ctx.get('context')} delete")
d.exec()
ksft_eq(cm.exception.nl_msg.error, -errno.EBUSY)
# Test creating with a specified RSS table, and context ID
ctx_id = ctx.get("context")
ctx = cfg.ethnl.rss_create_act({
"header": {"dev-index": cfg.ifindex},
"context": ctx_id,
"indir": [1],
})
ethtool(f"-X {cfg.ifname} context {ctx.get('context')} delete")
ksft_eq(ctx.get("context"), ctx_id)
ksft_eq(set(ctx.get("indir", [0])), {1})
def test_rss_ctx_ntf(cfg):
""" Test notifications for creating additional RSS contexts """
ethnl = EthtoolFamily()
ethnl.ntf_subscribe("monitor")
# Create / delete via Netlink
ctx = cfg.ethnl.rss_create_act({"header": {"dev-index": cfg.ifindex}})
cfg.ethnl.rss_delete_act({
"header": {"dev-index": cfg.ifindex},
"context": ctx["context"],
})
ntf = next(ethnl.poll_ntf(duration=0.2), None)
if ntf is None:
raise KsftFailEx("[NL] No notification after context creation")
ksft_eq(ntf["name"], "rss-create-ntf")
ksft_eq(ctx, ntf["msg"])
ntf = next(ethnl.poll_ntf(duration=0.2), None)
if ntf is None:
raise KsftFailEx("[NL] No notification after context deletion")
ksft_eq(ntf["name"], "rss-delete-ntf")
# Create / deleve via IOCTL
ctx_id = _ethtool_create(cfg, "--disable-netlink -X", "context new")
ethtool(f"--disable-netlink -X {cfg.ifname} context {ctx_id} delete")
ntf = next(ethnl.poll_ntf(duration=0.2), None)
if ntf is None:
raise KsftFailEx("[IOCTL] No notification after context creation")
ksft_eq(ntf["name"], "rss-create-ntf")
ntf = next(ethnl.poll_ntf(duration=0.2), None)
if ntf is None:
raise KsftFailEx("[IOCTL] No notification after context deletion")
ksft_eq(ntf["name"], "rss-delete-ntf")
def main() -> None:
""" Ksft boiler plate main """