mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-05-06 03:06:50 -04:00
Merge branch 'ethtool-rss-support-rss_set-via-netlink'
Jakub Kicinski says: ==================== ethtool: rss: support RSS_SET via Netlink Support configuring RSS settings via Netlink. Creating and removing contexts remains for the following series. v2: https://lore.kernel.org/20250714222729.743282-1-kuba@kernel.org v1: https://lore.kernel.org/20250711015303.3688717-1-kuba@kernel.org ==================== Link: https://patch.msgid.link/20250716000331.1378807-1-kuba@kernel.org Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
@@ -158,6 +158,26 @@ definitions:
|
||||
-
|
||||
name: pse-event-sw-pw-control-error
|
||||
doc: PSE faced an error managing the power control from software
|
||||
-
|
||||
name: input-xfrm
|
||||
doc: RSS hash function transformations.
|
||||
type: flags
|
||||
enum-name:
|
||||
name-prefix: rxh-xfrm-
|
||||
header: linux/ethtool.h
|
||||
entries:
|
||||
-
|
||||
name: sym-xor
|
||||
doc: >-
|
||||
XOR the corresponding source and destination fields of each specified
|
||||
protocol. Both copies of the XOR'ed fields are fed into the RSS and
|
||||
RXHASH calculation. Note that this XORing reduces the input set
|
||||
entropy and could be exploited to reduce the RSS queue spread.
|
||||
-
|
||||
name: sym-or-xor
|
||||
doc: >-
|
||||
Similar to SYM_XOR, except that one copy of the XOR'ed fields is
|
||||
replaced by an OR of the same fields.
|
||||
-
|
||||
name: rxfh-fields
|
||||
name-prefix: rxh-
|
||||
@@ -1621,6 +1641,7 @@ attribute-sets:
|
||||
-
|
||||
name: input-xfrm
|
||||
type: u32
|
||||
enum: input-xfrm
|
||||
-
|
||||
name: start-context
|
||||
type: u32
|
||||
@@ -2643,6 +2664,22 @@ operations:
|
||||
attributes:
|
||||
- header
|
||||
- events
|
||||
-
|
||||
name: rss-set
|
||||
doc: Set RSS params.
|
||||
|
||||
attribute-set: rss
|
||||
|
||||
do:
|
||||
request:
|
||||
attributes:
|
||||
- header
|
||||
- context
|
||||
- hfunc
|
||||
- indir
|
||||
- hkey
|
||||
- input-xfrm
|
||||
- flow-hash
|
||||
-
|
||||
name: rss-ntf
|
||||
doc: |
|
||||
|
||||
@@ -239,6 +239,7 @@ Userspace to kernel:
|
||||
``ETHTOOL_MSG_PHY_GET`` get Ethernet PHY information
|
||||
``ETHTOOL_MSG_TSCONFIG_GET`` get hw timestamping configuration
|
||||
``ETHTOOL_MSG_TSCONFIG_SET`` set hw timestamping configuration
|
||||
``ETHTOOL_MSG_RSS_SET`` set RSS settings
|
||||
===================================== =================================
|
||||
|
||||
Kernel to userspace:
|
||||
@@ -292,6 +293,7 @@ Kernel to userspace:
|
||||
``ETHTOOL_MSG_TSCONFIG_GET_REPLY`` hw timestamping configuration
|
||||
``ETHTOOL_MSG_TSCONFIG_SET_REPLY`` new hw timestamping configuration
|
||||
``ETHTOOL_MSG_PSE_NTF`` PSE events notification
|
||||
``ETHTOOL_MSG_RSS_NTF`` RSS settings notification
|
||||
======================================== =================================
|
||||
|
||||
``GET`` requests are sent by userspace applications to retrieve device
|
||||
@@ -1989,6 +1991,29 @@ hfunc. Current supported options are symmetric-xor and symmetric-or-xor.
|
||||
ETHTOOL_A_RSS_FLOW_HASH carries per-flow type bitmask of which header
|
||||
fields are included in the hash calculation.
|
||||
|
||||
RSS_SET
|
||||
=======
|
||||
|
||||
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
|
||||
``ETHTOOL_A_RSS_FLOW_HASH`` nested Header fields included in hash
|
||||
===================================== ====== ==============================
|
||||
|
||||
``ETHTOOL_A_RSS_INDIR`` is the minimal RSS table the user expects. Kernel and
|
||||
the device driver may replicate the table if its smaller than smallest table
|
||||
size supported by the device. For example if user requests ``[0, 1]`` but the
|
||||
device needs at least 8 entries - the real table in use will end up being
|
||||
``[0, 1, 0, 1, 0, 1, 0, 1]``. Most devices require the table size to be power
|
||||
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.
|
||||
|
||||
PLCA_GET_CFG
|
||||
============
|
||||
|
||||
@@ -2440,7 +2465,7 @@ are netlink only.
|
||||
``ETHTOOL_GPFLAGS`` ``ETHTOOL_MSG_PRIVFLAGS_GET``
|
||||
``ETHTOOL_SPFLAGS`` ``ETHTOOL_MSG_PRIVFLAGS_SET``
|
||||
``ETHTOOL_GRXFH`` ``ETHTOOL_MSG_RSS_GET``
|
||||
``ETHTOOL_SRXFH`` n/a
|
||||
``ETHTOOL_SRXFH`` ``ETHTOOL_MSG_RSS_SET``
|
||||
``ETHTOOL_GGRO`` ``ETHTOOL_MSG_FEATURES_GET``
|
||||
``ETHTOOL_SGRO`` ``ETHTOOL_MSG_FEATURES_SET``
|
||||
``ETHTOOL_GRXRINGS`` n/a
|
||||
@@ -2455,7 +2480,7 @@ are netlink only.
|
||||
``ETHTOOL_GRXNTUPLE`` n/a
|
||||
``ETHTOOL_GSSET_INFO`` ``ETHTOOL_MSG_STRSET_GET``
|
||||
``ETHTOOL_GRXFHINDIR`` ``ETHTOOL_MSG_RSS_GET``
|
||||
``ETHTOOL_SRXFHINDIR`` n/a
|
||||
``ETHTOOL_SRXFHINDIR`` ``ETHTOOL_MSG_RSS_SET``
|
||||
``ETHTOOL_GFEATURES`` ``ETHTOOL_MSG_FEATURES_GET``
|
||||
``ETHTOOL_SFEATURES`` ``ETHTOOL_MSG_FEATURES_SET``
|
||||
``ETHTOOL_GCHANNELS`` ``ETHTOOL_MSG_CHANNELS_GET``
|
||||
|
||||
@@ -840,6 +840,7 @@ enum {
|
||||
ETHTOOL_MSG_PHY_GET,
|
||||
ETHTOOL_MSG_TSCONFIG_GET,
|
||||
ETHTOOL_MSG_TSCONFIG_SET,
|
||||
ETHTOOL_MSG_RSS_SET,
|
||||
|
||||
__ETHTOOL_MSG_USER_CNT,
|
||||
ETHTOOL_MSG_USER_MAX = (__ETHTOOL_MSG_USER_CNT - 1)
|
||||
|
||||
@@ -806,6 +806,21 @@ int ethtool_check_rss_ctx_busy(struct net_device *dev, u32 rss_context)
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Check if fields configured for flow hash are symmetric - if src is included
|
||||
* so is dst and vice versa.
|
||||
*/
|
||||
int ethtool_rxfh_config_is_sym(u64 rxfh)
|
||||
{
|
||||
bool sym;
|
||||
|
||||
sym = rxfh == (rxfh & (RXH_IP_SRC | RXH_IP_DST |
|
||||
RXH_L4_B_0_1 | RXH_L4_B_2_3));
|
||||
sym &= !!(rxfh & RXH_IP_SRC) == !!(rxfh & RXH_IP_DST);
|
||||
sym &= !!(rxfh & RXH_L4_B_0_1) == !!(rxfh & RXH_L4_B_2_3);
|
||||
|
||||
return sym;
|
||||
}
|
||||
|
||||
int ethtool_check_ops(const struct ethtool_ops *ops)
|
||||
{
|
||||
if (WARN_ON(ops->set_coalesce && !ops->supported_coalesce_params))
|
||||
|
||||
@@ -44,6 +44,7 @@ int ethtool_check_max_channel(struct net_device *dev,
|
||||
struct ethtool_channels channels,
|
||||
struct genl_info *info);
|
||||
int ethtool_check_rss_ctx_busy(struct net_device *dev, u32 rss_context);
|
||||
int ethtool_rxfh_config_is_sym(u64 rxfh);
|
||||
|
||||
void ethtool_ringparam_get_cfg(struct net_device *dev,
|
||||
struct ethtool_ringparam *param,
|
||||
|
||||
@@ -1027,9 +1027,7 @@ static int ethtool_check_xfrm_rxfh(u32 input_xfrm, u64 rxfh)
|
||||
*/
|
||||
if ((input_xfrm != RXH_XFRM_NO_CHANGE &&
|
||||
input_xfrm & (RXH_XFRM_SYM_XOR | RXH_XFRM_SYM_OR_XOR)) &&
|
||||
((rxfh & ~(RXH_IP_SRC | RXH_IP_DST | RXH_L4_B_0_1 | RXH_L4_B_2_3)) ||
|
||||
(!!(rxfh & RXH_IP_SRC) ^ !!(rxfh & RXH_IP_DST)) ||
|
||||
(!!(rxfh & RXH_L4_B_0_1) ^ !!(rxfh & RXH_L4_B_2_3))))
|
||||
!ethtool_rxfh_config_is_sym(rxfh))
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -405,6 +405,7 @@ ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = {
|
||||
[ETHTOOL_MSG_PSE_GET] = ðnl_pse_request_ops,
|
||||
[ETHTOOL_MSG_PSE_SET] = ðnl_pse_request_ops,
|
||||
[ETHTOOL_MSG_RSS_GET] = ðnl_rss_request_ops,
|
||||
[ETHTOOL_MSG_RSS_SET] = ðnl_rss_request_ops,
|
||||
[ETHTOOL_MSG_PLCA_GET_CFG] = ðnl_plca_cfg_request_ops,
|
||||
[ETHTOOL_MSG_PLCA_SET_CFG] = ðnl_plca_cfg_request_ops,
|
||||
[ETHTOOL_MSG_PLCA_GET_STATUS] = ðnl_plca_status_request_ops,
|
||||
@@ -1504,6 +1505,13 @@ static const struct genl_ops ethtool_genl_ops[] = {
|
||||
.policy = ethnl_tsconfig_set_policy,
|
||||
.maxattr = ARRAY_SIZE(ethnl_tsconfig_set_policy) - 1,
|
||||
},
|
||||
{
|
||||
.cmd = ETHTOOL_MSG_RSS_SET,
|
||||
.flags = GENL_UNS_ADMIN_PERM,
|
||||
.doit = ethnl_default_set_doit,
|
||||
.policy = ethnl_rss_set_policy,
|
||||
.maxattr = ARRAY_SIZE(ethnl_rss_set_policy) - 1,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
|
||||
|
||||
@@ -484,6 +484,7 @@ extern const struct nla_policy ethnl_module_set_policy[ETHTOOL_A_MODULE_POWER_MO
|
||||
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_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];
|
||||
|
||||
@@ -218,6 +218,10 @@ rss_prepare(const struct rss_req_info *request, struct net_device *dev,
|
||||
{
|
||||
rss_prepare_flow_hash(request, dev, data, info);
|
||||
|
||||
/* Coming from RSS_SET, driver may only have flow_hash_fields ops */
|
||||
if (!dev->ethtool_ops->get_rxfh)
|
||||
return 0;
|
||||
|
||||
if (request->rss_context)
|
||||
return rss_prepare_ctx(request, dev, data, info);
|
||||
return rss_prepare_get(request, dev, data, info);
|
||||
@@ -466,6 +470,387 @@ void ethtool_rss_notify(struct net_device *dev, u32 rss_context)
|
||||
ethnl_notify(dev, ETHTOOL_MSG_RSS_NTF, &req_info.base);
|
||||
}
|
||||
|
||||
/* RSS_SET */
|
||||
|
||||
#define RFH_MASK (RXH_L2DA | RXH_VLAN | RXH_IP_SRC | RXH_IP_DST | \
|
||||
RXH_L3_PROTO | RXH_L4_B_0_1 | RXH_L4_B_2_3 | \
|
||||
RXH_GTP_TEID | RXH_DISCARD)
|
||||
|
||||
static const struct nla_policy ethnl_rss_flows_policy[] = {
|
||||
[ETHTOOL_A_FLOW_ETHER] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
[ETHTOOL_A_FLOW_IP4] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
[ETHTOOL_A_FLOW_IP6] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
[ETHTOOL_A_FLOW_TCP4] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
[ETHTOOL_A_FLOW_UDP4] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
[ETHTOOL_A_FLOW_SCTP4] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
[ETHTOOL_A_FLOW_AH_ESP4] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
[ETHTOOL_A_FLOW_TCP6] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
[ETHTOOL_A_FLOW_UDP6] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
[ETHTOOL_A_FLOW_SCTP6] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
[ETHTOOL_A_FLOW_AH_ESP6] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
[ETHTOOL_A_FLOW_AH4] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
[ETHTOOL_A_FLOW_ESP4] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
[ETHTOOL_A_FLOW_AH6] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
[ETHTOOL_A_FLOW_ESP6] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
[ETHTOOL_A_FLOW_GTPU4] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
[ETHTOOL_A_FLOW_GTPU6] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
[ETHTOOL_A_FLOW_GTPC4] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
[ETHTOOL_A_FLOW_GTPC6] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
[ETHTOOL_A_FLOW_GTPC_TEID4] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
[ETHTOOL_A_FLOW_GTPC_TEID6] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
[ETHTOOL_A_FLOW_GTPU_EH4] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
[ETHTOOL_A_FLOW_GTPU_EH6] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
[ETHTOOL_A_FLOW_GTPU_UL4] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
[ETHTOOL_A_FLOW_GTPU_UL6] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
[ETHTOOL_A_FLOW_GTPU_DL4] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
[ETHTOOL_A_FLOW_GTPU_DL6] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
|
||||
};
|
||||
|
||||
const struct nla_policy ethnl_rss_set_policy[ETHTOOL_A_RSS_FLOW_HASH + 1] = {
|
||||
[ETHTOOL_A_RSS_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
|
||||
[ETHTOOL_A_RSS_CONTEXT] = { .type = NLA_U32, },
|
||||
[ETHTOOL_A_RSS_HFUNC] = NLA_POLICY_MIN(NLA_U32, 1),
|
||||
[ETHTOOL_A_RSS_INDIR] = { .type = NLA_BINARY, },
|
||||
[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),
|
||||
[ETHTOOL_A_RSS_FLOW_HASH] = NLA_POLICY_NESTED(ethnl_rss_flows_policy),
|
||||
};
|
||||
|
||||
static int
|
||||
ethnl_rss_set_validate(struct ethnl_req_info *req_info, struct genl_info *info)
|
||||
{
|
||||
const struct ethtool_ops *ops = req_info->dev->ethtool_ops;
|
||||
struct rss_req_info *request = RSS_REQINFO(req_info);
|
||||
struct nlattr **tb = info->attrs;
|
||||
struct nlattr *bad_attr = NULL;
|
||||
u32 input_xfrm;
|
||||
|
||||
if (request->rss_context && !ops->create_rxfh_context)
|
||||
bad_attr = bad_attr ?: tb[ETHTOOL_A_RSS_CONTEXT];
|
||||
|
||||
if (request->rss_context && !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 (tb[ETHTOOL_A_RSS_FLOW_HASH] && !ops->set_rxfh_fields)
|
||||
bad_attr = bad_attr ?: tb[ETHTOOL_A_RSS_FLOW_HASH];
|
||||
if (request->rss_context &&
|
||||
tb[ETHTOOL_A_RSS_FLOW_HASH] && !ops->rxfh_per_ctx_fields)
|
||||
bad_attr = bad_attr ?: tb[ETHTOOL_A_RSS_FLOW_HASH];
|
||||
|
||||
if (bad_attr) {
|
||||
NL_SET_BAD_ATTR(info->extack, bad_attr);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
const struct ethtool_ops *ops = dev->ethtool_ops;
|
||||
struct netlink_ext_ack *extack = info->extack;
|
||||
struct nlattr **tb = info->attrs;
|
||||
struct ethtool_rxnfc rx_rings;
|
||||
size_t alloc_size;
|
||||
u32 user_size;
|
||||
int i, err;
|
||||
|
||||
if (!tb[ETHTOOL_A_RSS_INDIR])
|
||||
return 0;
|
||||
if (!data->indir_size || !ops->get_rxnfc)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
rx_rings.cmd = ETHTOOL_GRXRINGS;
|
||||
err = ops->get_rxnfc(dev, &rx_rings, NULL);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (nla_len(tb[ETHTOOL_A_RSS_INDIR]) % 4) {
|
||||
NL_SET_BAD_ATTR(info->extack, tb[ETHTOOL_A_RSS_INDIR]);
|
||||
return -EINVAL;
|
||||
}
|
||||
user_size = nla_len(tb[ETHTOOL_A_RSS_INDIR]) / 4;
|
||||
if (!user_size) {
|
||||
if (rxfh->rss_context) {
|
||||
NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_RSS_INDIR],
|
||||
"can't reset table for a context");
|
||||
return -EINVAL;
|
||||
}
|
||||
*reset = true;
|
||||
} else if (data->indir_size % user_size) {
|
||||
NL_SET_ERR_MSG_ATTR_FMT(extack, tb[ETHTOOL_A_RSS_INDIR],
|
||||
"size (%d) mismatch with device indir table (%d)",
|
||||
user_size, data->indir_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rxfh->indir_size = data->indir_size;
|
||||
alloc_size = array_size(data->indir_size, sizeof(rxfh->indir[0]));
|
||||
rxfh->indir = kzalloc(alloc_size, GFP_KERNEL);
|
||||
if (!rxfh->indir)
|
||||
return -ENOMEM;
|
||||
|
||||
nla_memcpy(rxfh->indir, tb[ETHTOOL_A_RSS_INDIR], alloc_size);
|
||||
for (i = 0; i < user_size; i++) {
|
||||
if (rxfh->indir[i] < rx_rings.data)
|
||||
continue;
|
||||
|
||||
NL_SET_ERR_MSG_ATTR_FMT(extack, tb[ETHTOOL_A_RSS_INDIR],
|
||||
"entry %d: queue out of range (%d)",
|
||||
i, rxfh->indir[i]);
|
||||
err = -EINVAL;
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
if (user_size) {
|
||||
/* Replicate the user-provided table to fill the device table */
|
||||
for (i = user_size; i < data->indir_size; i++)
|
||||
rxfh->indir[i] = rxfh->indir[i % user_size];
|
||||
} else {
|
||||
for (i = 0; i < data->indir_size; i++)
|
||||
rxfh->indir[i] =
|
||||
ethtool_rxfh_indir_default(i, rx_rings.data);
|
||||
}
|
||||
|
||||
*mod |= memcmp(rxfh->indir, data->indir_table, data->indir_size);
|
||||
|
||||
return 0;
|
||||
|
||||
err_free:
|
||||
kfree(rxfh->indir);
|
||||
rxfh->indir = NULL;
|
||||
return err;
|
||||
}
|
||||
|
||||
static int
|
||||
rss_set_prep_hkey(struct net_device *dev, struct genl_info *info,
|
||||
struct rss_reply_data *data, struct ethtool_rxfh_param *rxfh,
|
||||
bool *mod)
|
||||
{
|
||||
struct nlattr **tb = info->attrs;
|
||||
|
||||
if (!tb[ETHTOOL_A_RSS_HKEY])
|
||||
return 0;
|
||||
|
||||
if (nla_len(tb[ETHTOOL_A_RSS_HKEY]) != data->hkey_size) {
|
||||
NL_SET_BAD_ATTR(info->extack, tb[ETHTOOL_A_RSS_HKEY]);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rxfh->key_size = data->hkey_size;
|
||||
rxfh->key = kmemdup(data->hkey, data->hkey_size, GFP_KERNEL);
|
||||
if (!rxfh->key)
|
||||
return -ENOMEM;
|
||||
|
||||
ethnl_update_binary(rxfh->key, rxfh->key_size, tb[ETHTOOL_A_RSS_HKEY],
|
||||
mod);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
rss_check_rxfh_fields_sym(struct net_device *dev, struct genl_info *info,
|
||||
struct rss_reply_data *data, bool xfrm_sym)
|
||||
{
|
||||
struct nlattr **tb = info->attrs;
|
||||
int i;
|
||||
|
||||
if (!xfrm_sym)
|
||||
return 0;
|
||||
if (!data->has_flow_hash) {
|
||||
NL_SET_ERR_MSG_ATTR(info->extack, tb[ETHTOOL_A_RSS_INPUT_XFRM],
|
||||
"hash field config not reported");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (i = 1; i < __ETHTOOL_A_FLOW_CNT; i++)
|
||||
if (data->flow_hash[i] >= 0 &&
|
||||
!ethtool_rxfh_config_is_sym(data->flow_hash[i])) {
|
||||
NL_SET_ERR_MSG_ATTR(info->extack,
|
||||
tb[ETHTOOL_A_RSS_INPUT_XFRM],
|
||||
"hash field config is not symmetric");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
ethnl_set_rss_fields(struct net_device *dev, struct genl_info *info,
|
||||
u32 rss_context, struct rss_reply_data *data,
|
||||
bool xfrm_sym, bool *mod)
|
||||
{
|
||||
struct nlattr *flow_nest = info->attrs[ETHTOOL_A_RSS_FLOW_HASH];
|
||||
struct nlattr *flows[ETHTOOL_A_FLOW_MAX + 1];
|
||||
const struct ethtool_ops *ops;
|
||||
int i, ret;
|
||||
|
||||
ops = dev->ethtool_ops;
|
||||
|
||||
ret = rss_check_rxfh_fields_sym(dev, info, data, xfrm_sym);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!flow_nest)
|
||||
return 0;
|
||||
|
||||
ret = nla_parse_nested(flows, ARRAY_SIZE(ethnl_rss_flows_policy) - 1,
|
||||
flow_nest, ethnl_rss_flows_policy, info->extack);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
for (i = 1; i < __ETHTOOL_A_FLOW_CNT; i++) {
|
||||
struct ethtool_rxfh_fields fields = {
|
||||
.flow_type = ethtool_rxfh_ft_nl2ioctl[i],
|
||||
.rss_context = rss_context,
|
||||
};
|
||||
|
||||
if (!flows[i])
|
||||
continue;
|
||||
|
||||
fields.data = nla_get_u32(flows[i]);
|
||||
if (data->has_flow_hash && data->flow_hash[i] == fields.data)
|
||||
continue;
|
||||
|
||||
if (xfrm_sym && !ethtool_rxfh_config_is_sym(fields.data)) {
|
||||
NL_SET_ERR_MSG_ATTR(info->extack, flows[i],
|
||||
"conflict with xfrm-input");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = ops->set_rxfh_fields(dev, &fields, info->extack);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*mod = true;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
rss_set_ctx_update(struct ethtool_rxfh_context *ctx, struct nlattr **tb,
|
||||
struct rss_reply_data *data, struct ethtool_rxfh_param *rxfh)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (rxfh->indir) {
|
||||
for (i = 0; i < data->indir_size; i++)
|
||||
ethtool_rxfh_context_indir(ctx)[i] = rxfh->indir[i];
|
||||
ctx->indir_configured = !!nla_len(tb[ETHTOOL_A_RSS_INDIR]);
|
||||
}
|
||||
if (rxfh->key) {
|
||||
memcpy(ethtool_rxfh_context_key(ctx), rxfh->key,
|
||||
data->hkey_size);
|
||||
ctx->key_configured = !!rxfh->key_size;
|
||||
}
|
||||
if (rxfh->hfunc != ETH_RSS_HASH_NO_CHANGE)
|
||||
ctx->hfunc = rxfh->hfunc;
|
||||
if (rxfh->input_xfrm != RXH_XFRM_NO_CHANGE)
|
||||
ctx->input_xfrm = rxfh->input_xfrm;
|
||||
}
|
||||
|
||||
static int
|
||||
ethnl_rss_set(struct ethnl_req_info *req_info, struct genl_info *info)
|
||||
{
|
||||
bool indir_reset = false, indir_mod, xfrm_sym = false;
|
||||
struct rss_req_info *request = RSS_REQINFO(req_info);
|
||||
struct ethtool_rxfh_context *ctx = NULL;
|
||||
struct net_device *dev = req_info->dev;
|
||||
bool mod = false, fields_mod = false;
|
||||
struct ethtool_rxfh_param rxfh = {};
|
||||
struct nlattr **tb = info->attrs;
|
||||
struct rss_reply_data data = {};
|
||||
const struct ethtool_ops *ops;
|
||||
int ret;
|
||||
|
||||
ops = dev->ethtool_ops;
|
||||
data.base.dev = dev;
|
||||
|
||||
ret = rss_prepare(request, dev, &data, info);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
rxfh.rss_context = request->rss_context;
|
||||
|
||||
ret = rss_set_prep_indir(dev, info, &data, &rxfh, &indir_reset, &mod);
|
||||
if (ret)
|
||||
goto exit_clean_data;
|
||||
indir_mod = !!tb[ETHTOOL_A_RSS_INDIR];
|
||||
|
||||
rxfh.hfunc = data.hfunc;
|
||||
ethnl_update_u8(&rxfh.hfunc, tb[ETHTOOL_A_RSS_HFUNC], &mod);
|
||||
if (rxfh.hfunc == data.hfunc)
|
||||
rxfh.hfunc = ETH_RSS_HASH_NO_CHANGE;
|
||||
|
||||
ret = rss_set_prep_hkey(dev, info, &data, &rxfh, &mod);
|
||||
if (ret)
|
||||
goto exit_free_indir;
|
||||
|
||||
rxfh.input_xfrm = data.input_xfrm;
|
||||
ethnl_update_u8(&rxfh.input_xfrm, tb[ETHTOOL_A_RSS_INPUT_XFRM], &mod);
|
||||
/* For drivers which don't support input_xfrm it will be set to 0xff
|
||||
* in the RSS context info. In all other case input_xfrm != 0 means
|
||||
* symmetric hashing is requested.
|
||||
*/
|
||||
if (!request->rss_context || ops->rxfh_per_ctx_key)
|
||||
xfrm_sym = rxfh.input_xfrm || data.input_xfrm;
|
||||
if (rxfh.input_xfrm == data.input_xfrm)
|
||||
rxfh.input_xfrm = RXH_XFRM_NO_CHANGE;
|
||||
|
||||
mutex_lock(&dev->ethtool->rss_lock);
|
||||
if (request->rss_context) {
|
||||
ctx = xa_load(&dev->ethtool->rss_ctx, request->rss_context);
|
||||
if (!ctx) {
|
||||
ret = -ENOENT;
|
||||
goto exit_unlock;
|
||||
}
|
||||
}
|
||||
|
||||
ret = ethnl_set_rss_fields(dev, info, request->rss_context,
|
||||
&data, xfrm_sym, &fields_mod);
|
||||
if (ret)
|
||||
goto exit_unlock;
|
||||
|
||||
if (!mod)
|
||||
ret = 0; /* nothing to tell the driver */
|
||||
else if (!ops->set_rxfh)
|
||||
ret = -EOPNOTSUPP;
|
||||
else if (!rxfh.rss_context)
|
||||
ret = ops->set_rxfh(dev, &rxfh, info->extack);
|
||||
else
|
||||
ret = ops->modify_rxfh_context(dev, ctx, &rxfh, info->extack);
|
||||
if (ret)
|
||||
goto exit_unlock;
|
||||
|
||||
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;
|
||||
|
||||
exit_unlock:
|
||||
mutex_unlock(&dev->ethtool->rss_lock);
|
||||
kfree(rxfh.key);
|
||||
exit_free_indir:
|
||||
kfree(rxfh.indir);
|
||||
exit_clean_data:
|
||||
rss_cleanup_data(&data.base);
|
||||
|
||||
return ret ?: mod || fields_mod;
|
||||
}
|
||||
|
||||
const struct ethnl_request_ops ethnl_rss_request_ops = {
|
||||
.request_cmd = ETHTOOL_MSG_RSS_GET,
|
||||
.reply_cmd = ETHTOOL_MSG_RSS_GET_REPLY,
|
||||
@@ -478,4 +863,8 @@ const struct ethnl_request_ops ethnl_rss_request_ops = {
|
||||
.reply_size = rss_reply_size,
|
||||
.fill_reply = rss_fill_reply,
|
||||
.cleanup_data = rss_cleanup_data,
|
||||
|
||||
.set_validate = ethnl_rss_set_validate,
|
||||
.set = ethnl_rss_set,
|
||||
.set_ntf_cmd = ETHTOOL_MSG_RSS_NTF,
|
||||
};
|
||||
|
||||
@@ -575,7 +575,9 @@ class YnlFamily(SpecFamily):
|
||||
elif attr["type"] == 'string':
|
||||
attr_payload = str(value).encode('ascii') + b'\x00'
|
||||
elif attr["type"] == 'binary':
|
||||
if isinstance(value, bytes):
|
||||
if value is None:
|
||||
attr_payload = b''
|
||||
elif isinstance(value, bytes):
|
||||
attr_payload = value
|
||||
elif isinstance(value, str):
|
||||
if attr.display_hint:
|
||||
@@ -584,6 +586,9 @@ class YnlFamily(SpecFamily):
|
||||
attr_payload = bytes.fromhex(value)
|
||||
elif isinstance(value, dict) and attr.struct_name:
|
||||
attr_payload = self._encode_struct(attr.struct_name, value)
|
||||
elif isinstance(value, list) and attr.sub_type in NlAttr.type_formats:
|
||||
format = NlAttr.get_format(attr.sub_type)
|
||||
attr_payload = b''.join([format.pack(x) for x in value])
|
||||
else:
|
||||
raise Exception(f'Unknown type for binary attribute, value: {value}')
|
||||
elif attr['type'] in NlAttr.type_formats or attr.is_auto_scalar:
|
||||
|
||||
@@ -6,13 +6,21 @@ API level tests for RSS (mostly Netlink vs IOCTL).
|
||||
"""
|
||||
|
||||
import glob
|
||||
from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_is, ksft_ne
|
||||
import random
|
||||
from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_is, ksft_ne, ksft_raises
|
||||
from lib.py import KsftSkipEx, KsftFailEx
|
||||
from lib.py import defer, ethtool
|
||||
from lib.py import EthtoolFamily
|
||||
from lib.py import defer, ethtool, CmdExitFailure
|
||||
from lib.py import EthtoolFamily, NlError
|
||||
from lib.py import NetDrvEnv
|
||||
|
||||
|
||||
def _require_2qs(cfg):
|
||||
qcnt = len(glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*"))
|
||||
if qcnt < 2:
|
||||
raise KsftSkipEx(f"Local has only {qcnt} queues")
|
||||
return qcnt
|
||||
|
||||
|
||||
def _ethtool_create(cfg, act, opts):
|
||||
output = ethtool(f"{act} {cfg.ifname} {opts}").stdout
|
||||
# Output will be something like: "New RSS context is 1" or
|
||||
@@ -52,15 +60,101 @@ def _ethtool_get_cfg(cfg, fl_type, to_nl=False):
|
||||
return ret
|
||||
|
||||
|
||||
def test_rxfh_nl_set_fail(cfg):
|
||||
"""
|
||||
Test error path of Netlink SET.
|
||||
"""
|
||||
_require_2qs(cfg)
|
||||
|
||||
ethnl = EthtoolFamily()
|
||||
ethnl.ntf_subscribe("monitor")
|
||||
|
||||
with ksft_raises(NlError):
|
||||
ethnl.rss_set({"header": {"dev-name": "lo"},
|
||||
"indir": None})
|
||||
|
||||
with ksft_raises(NlError):
|
||||
ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
|
||||
"indir": [100000]})
|
||||
ntf = next(ethnl.poll_ntf(duration=0.2), None)
|
||||
ksft_is(ntf, None)
|
||||
|
||||
|
||||
def test_rxfh_nl_set_indir(cfg):
|
||||
"""
|
||||
Test setting indirection table via Netlink.
|
||||
"""
|
||||
qcnt = _require_2qs(cfg)
|
||||
|
||||
# Test some SETs with a value
|
||||
reset = defer(cfg.ethnl.rss_set,
|
||||
{"header": {"dev-index": cfg.ifindex}, "indir": None})
|
||||
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
|
||||
"indir": [1]})
|
||||
rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
|
||||
ksft_eq(set(rss.get("indir", [-1])), {1})
|
||||
|
||||
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
|
||||
"indir": [0, 1]})
|
||||
rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
|
||||
ksft_eq(set(rss.get("indir", [-1])), {0, 1})
|
||||
|
||||
# Make sure we can't set the queue count below max queue used
|
||||
with ksft_raises(CmdExitFailure):
|
||||
ethtool(f"-L {cfg.ifname} combined 0 rx 1")
|
||||
with ksft_raises(CmdExitFailure):
|
||||
ethtool(f"-L {cfg.ifname} combined 1 rx 0")
|
||||
|
||||
# Test reset back to default
|
||||
reset.exec()
|
||||
rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
|
||||
ksft_eq(set(rss.get("indir", [-1])), set(range(qcnt)))
|
||||
|
||||
|
||||
def test_rxfh_nl_set_indir_ctx(cfg):
|
||||
"""
|
||||
Test setting indirection table for a custom context via Netlink.
|
||||
"""
|
||||
_require_2qs(cfg)
|
||||
|
||||
# Get setting for ctx 0, we'll make sure they don't get clobbered
|
||||
dflt = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
|
||||
|
||||
# Create context
|
||||
ctx_id = _ethtool_create(cfg, "-X", "context new")
|
||||
defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
|
||||
|
||||
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
|
||||
"context": ctx_id, "indir": [1]})
|
||||
rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex},
|
||||
"context": ctx_id})
|
||||
ksft_eq(set(rss.get("indir", [-1])), {1})
|
||||
|
||||
ctx0 = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
|
||||
ksft_eq(ctx0, dflt)
|
||||
|
||||
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
|
||||
"context": ctx_id, "indir": [0, 1]})
|
||||
rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex},
|
||||
"context": ctx_id})
|
||||
ksft_eq(set(rss.get("indir", [-1])), {0, 1})
|
||||
|
||||
ctx0 = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
|
||||
ksft_eq(ctx0, dflt)
|
||||
|
||||
# Make sure we can't set the queue count below max queue used
|
||||
with ksft_raises(CmdExitFailure):
|
||||
ethtool(f"-L {cfg.ifname} combined 0 rx 1")
|
||||
with ksft_raises(CmdExitFailure):
|
||||
ethtool(f"-L {cfg.ifname} combined 1 rx 0")
|
||||
|
||||
|
||||
def test_rxfh_indir_ntf(cfg):
|
||||
"""
|
||||
Check that Netlink notifications are generated when RSS indirection
|
||||
table was modified.
|
||||
"""
|
||||
|
||||
qcnt = len(glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*"))
|
||||
if qcnt < 2:
|
||||
raise KsftSkipEx(f"Local has only {qcnt} queues")
|
||||
_require_2qs(cfg)
|
||||
|
||||
ethnl = EthtoolFamily()
|
||||
ethnl.ntf_subscribe("monitor")
|
||||
@@ -88,10 +182,7 @@ def test_rxfh_indir_ctx_ntf(cfg):
|
||||
Check that Netlink notifications are generated when RSS indirection
|
||||
table was modified on an additional RSS context.
|
||||
"""
|
||||
|
||||
qcnt = len(glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*"))
|
||||
if qcnt < 2:
|
||||
raise KsftSkipEx(f"Local has only {qcnt} queues")
|
||||
_require_2qs(cfg)
|
||||
|
||||
ctx_id = _ethtool_create(cfg, "-X", "context new")
|
||||
defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
|
||||
@@ -109,6 +200,38 @@ def test_rxfh_indir_ctx_ntf(cfg):
|
||||
ksft_eq(set(ntf["msg"]["indir"]), {1})
|
||||
|
||||
|
||||
def test_rxfh_nl_set_key(cfg):
|
||||
"""
|
||||
Test setting hashing key via Netlink.
|
||||
"""
|
||||
|
||||
dflt = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
|
||||
defer(cfg.ethnl.rss_set,
|
||||
{"header": {"dev-index": cfg.ifindex},
|
||||
"hkey": dflt["hkey"], "indir": None})
|
||||
|
||||
# Empty key should error out
|
||||
with ksft_raises(NlError) as cm:
|
||||
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
|
||||
"hkey": None})
|
||||
ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.hkey')
|
||||
|
||||
# Set key to random
|
||||
mod = random.randbytes(len(dflt["hkey"]))
|
||||
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
|
||||
"hkey": mod})
|
||||
rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
|
||||
ksft_eq(rss.get("hkey", [-1]), mod)
|
||||
|
||||
# Set key to random and indir tbl to something at once
|
||||
mod = random.randbytes(len(dflt["hkey"]))
|
||||
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
|
||||
"indir": [0, 1], "hkey": mod})
|
||||
rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
|
||||
ksft_eq(rss.get("hkey", [-1]), mod)
|
||||
ksft_eq(set(rss.get("indir", [-1])), {0, 1})
|
||||
|
||||
|
||||
def test_rxfh_fields(cfg):
|
||||
"""
|
||||
Test reading Rx Flow Hash over Netlink.
|
||||
@@ -124,10 +247,154 @@ def test_rxfh_fields(cfg):
|
||||
comment="Config for " + fl_type)
|
||||
|
||||
|
||||
def test_rxfh_fields_set(cfg):
|
||||
""" Test configuring Rx Flow Hash over Netlink. """
|
||||
|
||||
flow_types = ["tcp4", "tcp6", "udp4", "udp6"]
|
||||
|
||||
# Collect current settings
|
||||
cfg_old = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
|
||||
# symmetric hashing is config-order-sensitive make sure we leave
|
||||
# symmetric mode, or make the flow-hash sym-compatible first
|
||||
changes = [{"flow-hash": cfg_old["flow-hash"],},
|
||||
{"input-xfrm": cfg_old.get("input-xfrm", {}),}]
|
||||
if cfg_old.get("input-xfrm"):
|
||||
changes = list(reversed(changes))
|
||||
for old in changes:
|
||||
defer(cfg.ethnl.rss_set, {"header": {"dev-index": cfg.ifindex},} | old)
|
||||
|
||||
# symmetric hashing prevents some of the configs below
|
||||
if cfg_old.get("input-xfrm"):
|
||||
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
|
||||
"input-xfrm": {}})
|
||||
|
||||
for fl_type in flow_types:
|
||||
cur = _ethtool_get_cfg(cfg, fl_type)
|
||||
if cur == "sdfn":
|
||||
change_nl = {"ip-src", "ip-dst"}
|
||||
change_ic = "sd"
|
||||
else:
|
||||
change_nl = {"l4-b-0-1", "l4-b-2-3", "ip-src", "ip-dst"}
|
||||
change_ic = "sdfn"
|
||||
|
||||
cfg.ethnl.rss_set({
|
||||
"header": {"dev-index": cfg.ifindex},
|
||||
"flow-hash": {fl_type: change_nl}
|
||||
})
|
||||
reset = defer(ethtool, f"--disable-netlink -N {cfg.ifname} "
|
||||
f"rx-flow-hash {fl_type} {cur}")
|
||||
|
||||
cfg_nl = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
|
||||
ksft_eq(change_nl, cfg_nl["flow-hash"][fl_type],
|
||||
comment=f"Config for {fl_type} over Netlink")
|
||||
cfg_ic = _ethtool_get_cfg(cfg, fl_type)
|
||||
ksft_eq(change_ic, cfg_ic,
|
||||
comment=f"Config for {fl_type} over IOCTL")
|
||||
|
||||
reset.exec()
|
||||
cfg_nl = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
|
||||
ksft_eq(cfg_old["flow-hash"][fl_type], cfg_nl["flow-hash"][fl_type],
|
||||
comment=f"Un-config for {fl_type} over Netlink")
|
||||
cfg_ic = _ethtool_get_cfg(cfg, fl_type)
|
||||
ksft_eq(cur, cfg_ic, comment=f"Un-config for {fl_type} over IOCTL")
|
||||
|
||||
# Try to set multiple at once, the defer was already installed at the start
|
||||
change = {"ip-src"}
|
||||
if change == cfg_old["flow-hash"]["tcp4"]:
|
||||
change = {"ip-dst"}
|
||||
cfg.ethnl.rss_set({
|
||||
"header": {"dev-index": cfg.ifindex},
|
||||
"flow-hash": {x: change for x in flow_types}
|
||||
})
|
||||
|
||||
cfg_nl = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
|
||||
for fl_type in flow_types:
|
||||
ksft_eq(change, cfg_nl["flow-hash"][fl_type],
|
||||
comment=f"multi-config for {fl_type} over Netlink")
|
||||
|
||||
|
||||
def test_rxfh_fields_set_xfrm(cfg):
|
||||
""" Test changing Rx Flow Hash vs xfrm_input at once. """
|
||||
|
||||
def set_rss(cfg, xfrm, fh):
|
||||
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
|
||||
"input-xfrm": xfrm, "flow-hash": fh})
|
||||
|
||||
# Install the reset handler
|
||||
cfg_old = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
|
||||
# symmetric hashing is config-order-sensitive make sure we leave
|
||||
# symmetric mode, or make the flow-hash sym-compatible first
|
||||
changes = [{"flow-hash": cfg_old["flow-hash"],},
|
||||
{"input-xfrm": cfg_old.get("input-xfrm", {}),}]
|
||||
if cfg_old.get("input-xfrm"):
|
||||
changes = list(reversed(changes))
|
||||
for old in changes:
|
||||
defer(cfg.ethnl.rss_set, {"header": {"dev-index": cfg.ifindex},} | old)
|
||||
|
||||
# Make sure we start with input-xfrm off, and tcp4 config non-sym
|
||||
set_rss(cfg, {}, {})
|
||||
set_rss(cfg, {}, {"tcp4": {"ip-src"}})
|
||||
|
||||
# Setting sym and fixing tcp4 config not expected to pass right now
|
||||
with ksft_raises(NlError):
|
||||
set_rss(cfg, {"sym-xor"}, {"tcp4": {"ip-src", "ip-dst"}})
|
||||
# One at a time should work, hopefully
|
||||
set_rss(cfg, 0, {"tcp4": {"ip-src", "ip-dst"}})
|
||||
no_support = False
|
||||
try:
|
||||
set_rss(cfg, {"sym-xor"}, {})
|
||||
except NlError:
|
||||
try:
|
||||
set_rss(cfg, {"sym-or-xor"}, {})
|
||||
except NlError:
|
||||
no_support = True
|
||||
if no_support:
|
||||
raise KsftSkipEx("no input-xfrm supported")
|
||||
# Disabling two at once should not work either without kernel changes
|
||||
with ksft_raises(NlError):
|
||||
set_rss(cfg, {}, {"tcp4": {"ip-src"}})
|
||||
|
||||
|
||||
def test_rxfh_fields_ntf(cfg):
|
||||
""" Test Rx Flow Hash notifications. """
|
||||
|
||||
cur = _ethtool_get_cfg(cfg, "tcp4")
|
||||
if cur == "sdfn":
|
||||
change = {"ip-src", "ip-dst"}
|
||||
else:
|
||||
change = {"l4-b-0-1", "l4-b-2-3", "ip-src", "ip-dst"}
|
||||
|
||||
ethnl = EthtoolFamily()
|
||||
ethnl.ntf_subscribe("monitor")
|
||||
|
||||
ethnl.rss_set({
|
||||
"header": {"dev-index": cfg.ifindex},
|
||||
"flow-hash": {"tcp4": change}
|
||||
})
|
||||
reset = defer(ethtool,
|
||||
f"--disable-netlink -N {cfg.ifname} rx-flow-hash tcp4 {cur}")
|
||||
|
||||
ntf = next(ethnl.poll_ntf(duration=0.2), None)
|
||||
if ntf is None:
|
||||
raise KsftFailEx("No notification received after IOCTL change")
|
||||
ksft_eq(ntf["name"], "rss-ntf")
|
||||
ksft_eq(ntf["msg"]["flow-hash"]["tcp4"], change)
|
||||
ksft_eq(next(ethnl.poll_ntf(duration=0.01), None), None)
|
||||
|
||||
reset.exec()
|
||||
ntf = next(ethnl.poll_ntf(duration=0.2), None)
|
||||
if ntf is None:
|
||||
raise KsftFailEx("No notification received after Netlink change")
|
||||
ksft_eq(ntf["name"], "rss-ntf")
|
||||
ksft_ne(ntf["msg"]["flow-hash"]["tcp4"], change)
|
||||
ksft_eq(next(ethnl.poll_ntf(duration=0.01), None), None)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
""" Ksft boiler plate main """
|
||||
|
||||
with NetDrvEnv(__file__, nsim_test=False) as cfg:
|
||||
cfg.ethnl = EthtoolFamily()
|
||||
ksft_run(globs=globals(), case_pfx={"test_"}, args=(cfg, ))
|
||||
ksft_exit()
|
||||
|
||||
|
||||
@@ -37,11 +37,11 @@ def test_rss_input_xfrm(cfg, ipver):
|
||||
if not hasattr(socket, "SO_INCOMING_CPU"):
|
||||
raise KsftSkipEx("socket.SO_INCOMING_CPU was added in Python 3.11")
|
||||
|
||||
input_xfrm = cfg.ethnl.rss_get(
|
||||
{'header': {'dev-name': cfg.ifname}}).get('input-xfrm')
|
||||
rss = cfg.ethnl.rss_get({'header': {'dev-name': cfg.ifname}})
|
||||
input_xfrm = set(filter(lambda x: 'sym' in x, rss.get('input-xfrm', {})))
|
||||
|
||||
# Check for symmetric xor/or-xor
|
||||
if not input_xfrm or (input_xfrm != 1 and input_xfrm != 2):
|
||||
if not input_xfrm:
|
||||
raise KsftSkipEx("Symmetric RSS hash not requested")
|
||||
|
||||
cpus = set()
|
||||
|
||||
Reference in New Issue
Block a user