Merge branch 'ethtool-rss-driver-tweaks'
Jakub Kicinski says: ==================== ethtool: rss: driver tweaks and netlink context dumps This series is a semi-related collection of RSS patches. Main point is supporting dumping RSS contexts via ethtool netlink. At present additional RSS contexts can be queried one by one, and assuming user know the right IDs. This series uses the XArray added by Ed to provide netlink dump support for ETHTOOL_GET_RSS. Patch 1 is a trivial selftest debug patch. Patch 2 coverts mvpp2 for no real reason other than that I had a grand plan of converting all drivers at some stage. Patch 3 removes a now moot check from mlx5 so that all tests can pass. Patch 4 and 5 make a bit used for context support optional, for easier grepping of drivers which need converting if nothing else. Patch 6 OTOH adds a new cap bit; some devices don't support using a different key per context and currently act in surprising ways. Patch 7 and 8 update the RSS netlink code to use XArray. Patch 9 and 10 add support for dumping contexts. Patch 11 and 12 are small adjustments to spec and a new test. I'm getting distracted with other work, so probably won't have the time soon to complete next steps, but things which are missing are (and some of these may be bad ideas): - better discovery Some sort of API to tell the user who many contexts the device can create. Upper bound, devices often share contexts between ports etc. so it's hard to tell exactly and upfront number of contexts for a netdev. But order of magnitude (4 vs 10s) may be enough for container management system to know whether to bother. - create/modify/delete via netlink The only question here is how to handle all the tricky IOCTL legacy. "No change" maps trivially to attribute not present. "reset" (indir_size = 0) probably needs to be a new NLA_FLAG? - better table size handling The current API assumes the LUT has fixed size, which isn't true for modern devices. We should have better APIs for the drivers to resize the tables, and in user facing API - the ability to specify pattern and min size rather than exact table expected (sort of like ethtool CLI already does). - recounted / socket-bound contexts Support for contexts which get "cleaned up" when their parent netlink socket gets closed. The major catch is that ntuple filters (which we don't currently track) depend on the context, so we need auto-removal for both. v5: - fix build v4: https://lore.kernel.org/20240809031827.2373341-1-kuba@kernel.org - adjust to the meaning of max context from net v3: https://lore.kernel.org/20240806193317.1491822-1-kuba@kernel.org - quite a few code comments and commit message changes - mvpp2: fix interpretation of max_context_id (I'll take care of the net -> net-next merge as needed) - filter by ifindex in the selftest v2: https://lore.kernel.org/20240803042624.970352-1-kuba@kernel.org - fix bugs and build in mvpp2 v1: https://lore.kernel.org/20240802001801.565176-1-kuba@kernel.org ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
commit
fe1f433555
@ -1022,12 +1022,16 @@ attribute-sets:
|
||||
-
|
||||
name: indir
|
||||
type: binary
|
||||
sub-type: u32
|
||||
-
|
||||
name: hkey
|
||||
type: binary
|
||||
-
|
||||
name: input_xfrm
|
||||
type: u32
|
||||
-
|
||||
name: start-context
|
||||
type: u32
|
||||
-
|
||||
name: plca
|
||||
attributes:
|
||||
@ -1749,12 +1753,12 @@ operations:
|
||||
|
||||
attribute-set: rss
|
||||
|
||||
do: &rss-get-op
|
||||
do:
|
||||
request:
|
||||
attributes:
|
||||
- header
|
||||
- context
|
||||
reply:
|
||||
reply: &rss-reply
|
||||
attributes:
|
||||
- header
|
||||
- context
|
||||
@ -1762,6 +1766,12 @@ operations:
|
||||
- indir
|
||||
- hkey
|
||||
- input_xfrm
|
||||
dump:
|
||||
request:
|
||||
attributes:
|
||||
- header
|
||||
- start-context
|
||||
reply: *rss-reply
|
||||
-
|
||||
name: plca-get-cfg
|
||||
doc: Get PLCA params.
|
||||
|
@ -1866,10 +1866,18 @@ RSS context of an interface similar to ``ETHTOOL_GRSSH`` ioctl request.
|
||||
|
||||
Request contents:
|
||||
|
||||
===================================== ====== ==========================
|
||||
===================================== ====== ============================
|
||||
``ETHTOOL_A_RSS_HEADER`` nested request header
|
||||
``ETHTOOL_A_RSS_CONTEXT`` u32 context number
|
||||
===================================== ====== ==========================
|
||||
``ETHTOOL_A_RSS_START_CONTEXT`` u32 start context number (dumps)
|
||||
===================================== ====== ============================
|
||||
|
||||
``ETHTOOL_A_RSS_CONTEXT`` specifies which RSS context number to query,
|
||||
if not set context 0 (the main context) is queried. Dumps can be filtered
|
||||
by device (only listing contexts of a given netdev). Filtering single
|
||||
context number is not supported but ``ETHTOOL_A_RSS_START_CONTEXT``
|
||||
can be used to start dumping context from the given number (primarily
|
||||
used to ignore context 0s and only dump additional contexts).
|
||||
|
||||
Kernel response contents:
|
||||
|
||||
|
@ -5289,7 +5289,7 @@ void bnxt_ethtool_free(struct bnxt *bp)
|
||||
|
||||
const struct ethtool_ops bnxt_ethtool_ops = {
|
||||
.cap_link_lanes_supported = 1,
|
||||
.cap_rss_ctx_supported = 1,
|
||||
.rxfh_per_ctx_key = 1,
|
||||
.rxfh_max_num_contexts = BNXT_MAX_ETH_RSS_CTX + 1,
|
||||
.rxfh_indir_space = BNXT_MAX_RSS_TABLE_ENTRIES_P5,
|
||||
.rxfh_priv_size = sizeof(struct bnxt_rss_ctx),
|
||||
|
@ -4725,6 +4725,7 @@ static const struct ethtool_ops ice_ethtool_ops = {
|
||||
ETHTOOL_COALESCE_USE_ADAPTIVE |
|
||||
ETHTOOL_COALESCE_RX_USECS_HIGH,
|
||||
.cap_rss_sym_xor_supported = true,
|
||||
.rxfh_per_ctx_key = true,
|
||||
.get_link_ksettings = ice_get_link_ksettings,
|
||||
.set_link_ksettings = ice_set_link_ksettings,
|
||||
.get_fec_stats = ice_get_fec_stats,
|
||||
|
@ -1522,29 +1522,19 @@ static int mvpp22_rss_context_create(struct mvpp2_port *port, u32 *rss_ctx)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mvpp22_port_rss_ctx_create(struct mvpp2_port *port, u32 *port_ctx)
|
||||
int mvpp22_port_rss_ctx_create(struct mvpp2_port *port, u32 port_ctx)
|
||||
{
|
||||
u32 rss_ctx;
|
||||
int ret, i;
|
||||
int ret;
|
||||
|
||||
ret = mvpp22_rss_context_create(port, &rss_ctx);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Find the first available context number in the port, starting from 1.
|
||||
* Context 0 on each port is reserved for the default context.
|
||||
*/
|
||||
for (i = 1; i < MVPP22_N_RSS_TABLES; i++) {
|
||||
if (port->rss_ctx[i] < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == MVPP22_N_RSS_TABLES)
|
||||
if (WARN_ON_ONCE(port->rss_ctx[port_ctx] >= 0))
|
||||
return -EINVAL;
|
||||
|
||||
port->rss_ctx[i] = rss_ctx;
|
||||
*port_ctx = i;
|
||||
|
||||
port->rss_ctx[port_ctx] = rss_ctx;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -264,7 +264,7 @@ int mvpp22_port_rss_init(struct mvpp2_port *port);
|
||||
int mvpp22_port_rss_enable(struct mvpp2_port *port);
|
||||
int mvpp22_port_rss_disable(struct mvpp2_port *port);
|
||||
|
||||
int mvpp22_port_rss_ctx_create(struct mvpp2_port *port, u32 *rss_ctx);
|
||||
int mvpp22_port_rss_ctx_create(struct mvpp2_port *port, u32 rss_ctx);
|
||||
int mvpp22_port_rss_ctx_delete(struct mvpp2_port *port, u32 rss_ctx);
|
||||
|
||||
int mvpp22_port_rss_ctx_indir_set(struct mvpp2_port *port, u32 rss_ctx,
|
||||
|
@ -5696,38 +5696,80 @@ static int mvpp2_ethtool_get_rxfh(struct net_device *dev,
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool mvpp2_ethtool_rxfh_okay(struct mvpp2_port *port,
|
||||
const struct ethtool_rxfh_param *rxfh)
|
||||
{
|
||||
if (!mvpp22_rss_is_supported(port))
|
||||
return false;
|
||||
|
||||
if (rxfh->hfunc != ETH_RSS_HASH_NO_CHANGE &&
|
||||
rxfh->hfunc != ETH_RSS_HASH_CRC32)
|
||||
return false;
|
||||
|
||||
if (rxfh->key)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int mvpp2_create_rxfh_context(struct net_device *dev,
|
||||
struct ethtool_rxfh_context *ctx,
|
||||
const struct ethtool_rxfh_param *rxfh,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct mvpp2_port *port = netdev_priv(dev);
|
||||
int ret = 0;
|
||||
|
||||
if (!mvpp2_ethtool_rxfh_okay(port, rxfh))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
ctx->hfunc = ETH_RSS_HASH_CRC32;
|
||||
|
||||
ret = mvpp22_port_rss_ctx_create(port, rxfh->rss_context);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!rxfh->indir)
|
||||
ret = mvpp22_port_rss_ctx_indir_get(port, rxfh->rss_context,
|
||||
ethtool_rxfh_context_indir(ctx));
|
||||
else
|
||||
ret = mvpp22_port_rss_ctx_indir_set(port, rxfh->rss_context,
|
||||
rxfh->indir);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mvpp2_modify_rxfh_context(struct net_device *dev,
|
||||
struct ethtool_rxfh_context *ctx,
|
||||
const struct ethtool_rxfh_param *rxfh,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct mvpp2_port *port = netdev_priv(dev);
|
||||
int ret = 0;
|
||||
|
||||
if (!mvpp2_ethtool_rxfh_okay(port, rxfh))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (rxfh->indir)
|
||||
ret = mvpp22_port_rss_ctx_indir_set(port, rxfh->rss_context,
|
||||
rxfh->indir);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mvpp2_remove_rxfh_context(struct net_device *dev,
|
||||
struct ethtool_rxfh_context *ctx,
|
||||
u32 rss_context,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct mvpp2_port *port = netdev_priv(dev);
|
||||
|
||||
return mvpp22_port_rss_ctx_delete(port, rss_context);
|
||||
}
|
||||
|
||||
static int mvpp2_ethtool_set_rxfh(struct net_device *dev,
|
||||
struct ethtool_rxfh_param *rxfh,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct mvpp2_port *port = netdev_priv(dev);
|
||||
u32 *rss_context = &rxfh->rss_context;
|
||||
int ret = 0;
|
||||
|
||||
if (!mvpp22_rss_is_supported(port))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (rxfh->hfunc != ETH_RSS_HASH_NO_CHANGE &&
|
||||
rxfh->hfunc != ETH_RSS_HASH_CRC32)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (rxfh->key)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (*rss_context && rxfh->rss_delete)
|
||||
return mvpp22_port_rss_ctx_delete(port, *rss_context);
|
||||
|
||||
if (*rss_context == ETH_RXFH_CONTEXT_ALLOC) {
|
||||
ret = mvpp22_port_rss_ctx_create(port, rss_context);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (rxfh->indir)
|
||||
ret = mvpp22_port_rss_ctx_indir_set(port, *rss_context,
|
||||
rxfh->indir);
|
||||
|
||||
return ret;
|
||||
return mvpp2_modify_rxfh_context(dev, NULL, rxfh, extack);
|
||||
}
|
||||
|
||||
/* Device ops */
|
||||
@ -5749,7 +5791,7 @@ static const struct net_device_ops mvpp2_netdev_ops = {
|
||||
};
|
||||
|
||||
static const struct ethtool_ops mvpp2_eth_tool_ops = {
|
||||
.cap_rss_ctx_supported = true,
|
||||
.rxfh_max_num_contexts = MVPP22_N_RSS_TABLES,
|
||||
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
|
||||
ETHTOOL_COALESCE_MAX_FRAMES,
|
||||
.nway_reset = mvpp2_ethtool_nway_reset,
|
||||
@ -5772,6 +5814,9 @@ static const struct ethtool_ops mvpp2_eth_tool_ops = {
|
||||
.get_rxfh_indir_size = mvpp2_ethtool_get_rxfh_indir_size,
|
||||
.get_rxfh = mvpp2_ethtool_get_rxfh,
|
||||
.set_rxfh = mvpp2_ethtool_set_rxfh,
|
||||
.create_rxfh_context = mvpp2_create_rxfh_context,
|
||||
.modify_rxfh_context = mvpp2_modify_rxfh_context,
|
||||
.remove_rxfh_context = mvpp2_remove_rxfh_context,
|
||||
};
|
||||
|
||||
/* Used for PPv2.1, or PPv2.2 with the old Device Tree binding that
|
||||
|
@ -433,7 +433,6 @@ int mlx5e_ethtool_set_channels(struct mlx5e_priv *priv,
|
||||
unsigned int count = ch->combined_count;
|
||||
struct mlx5e_params new_params;
|
||||
bool arfs_enabled;
|
||||
int rss_cnt;
|
||||
bool opened;
|
||||
int err = 0;
|
||||
|
||||
@ -487,17 +486,6 @@ int mlx5e_ethtool_set_channels(struct mlx5e_priv *priv,
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Don't allow changing the number of channels if non-default RSS contexts exist,
|
||||
* the kernel doesn't protect against set_channels operations that break them.
|
||||
*/
|
||||
rss_cnt = mlx5e_rx_res_rss_cnt(priv->rx_res) - 1;
|
||||
if (rss_cnt) {
|
||||
err = -EINVAL;
|
||||
netdev_err(priv->netdev, "%s: Non-default RSS contexts exist (%d), cannot change the number of channels\n",
|
||||
__func__, rss_cnt);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Don't allow changing the number of channels if MQPRIO mode channel offload is active,
|
||||
* because it defines a partition over the channels queues.
|
||||
*/
|
||||
@ -2607,6 +2595,7 @@ static void mlx5e_get_ts_stats(struct net_device *netdev,
|
||||
|
||||
const struct ethtool_ops mlx5e_ethtool_ops = {
|
||||
.cap_rss_ctx_supported = true,
|
||||
.rxfh_per_ctx_key = true,
|
||||
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
|
||||
ETHTOOL_COALESCE_MAX_FRAMES |
|
||||
ETHTOOL_COALESCE_USE_ADAPTIVE |
|
||||
|
@ -37,7 +37,6 @@ ef100_ethtool_get_ringparam(struct net_device *net_dev,
|
||||
/* Ethtool options available
|
||||
*/
|
||||
const struct ethtool_ops ef100_ethtool_ops = {
|
||||
.cap_rss_ctx_supported = true,
|
||||
.get_drvinfo = efx_ethtool_get_drvinfo,
|
||||
.get_msglevel = efx_ethtool_get_msglevel,
|
||||
.set_msglevel = efx_ethtool_set_msglevel,
|
||||
@ -59,6 +58,7 @@ const struct ethtool_ops ef100_ethtool_ops = {
|
||||
|
||||
.get_rxfh_indir_size = efx_ethtool_get_rxfh_indir_size,
|
||||
.get_rxfh_key_size = efx_ethtool_get_rxfh_key_size,
|
||||
.rxfh_per_ctx_key = true,
|
||||
.rxfh_priv_size = sizeof(struct efx_rss_context_priv),
|
||||
.get_rxfh = efx_ethtool_get_rxfh,
|
||||
.set_rxfh = efx_ethtool_set_rxfh,
|
||||
|
@ -240,7 +240,6 @@ static int efx_ethtool_get_ts_info(struct net_device *net_dev,
|
||||
}
|
||||
|
||||
const struct ethtool_ops efx_ethtool_ops = {
|
||||
.cap_rss_ctx_supported = true,
|
||||
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
|
||||
ETHTOOL_COALESCE_USECS_IRQ |
|
||||
ETHTOOL_COALESCE_USE_ADAPTIVE_RX,
|
||||
@ -268,6 +267,7 @@ const struct ethtool_ops efx_ethtool_ops = {
|
||||
.set_rxnfc = efx_ethtool_set_rxnfc,
|
||||
.get_rxfh_indir_size = efx_ethtool_get_rxfh_indir_size,
|
||||
.get_rxfh_key_size = efx_ethtool_get_rxfh_key_size,
|
||||
.rxfh_per_ctx_key = true,
|
||||
.rxfh_priv_size = sizeof(struct efx_rss_context_priv),
|
||||
.get_rxfh = efx_ethtool_get_rxfh,
|
||||
.set_rxfh = efx_ethtool_set_rxfh,
|
||||
|
@ -727,9 +727,13 @@ struct kernel_ethtool_ts_info {
|
||||
* @cap_link_lanes_supported: indicates if the driver supports lanes
|
||||
* parameter.
|
||||
* @cap_rss_ctx_supported: indicates if the driver supports RSS
|
||||
* contexts.
|
||||
* contexts via legacy API, drivers implementing @create_rxfh_context
|
||||
* do not have to set this bit.
|
||||
* @cap_rss_sym_xor_supported: indicates if the driver supports symmetric-xor
|
||||
* RSS.
|
||||
* @rxfh_per_ctx_key: device supports setting different RSS key for each
|
||||
* additional context. Netlink API should report hfunc, key, and input_xfrm
|
||||
* for every context, not just context 0.
|
||||
* @rxfh_indir_space: max size of RSS indirection tables, if indirection table
|
||||
* size as returned by @get_rxfh_indir_size may change during lifetime
|
||||
* of the device. Leave as 0 if the table size is constant.
|
||||
@ -951,6 +955,7 @@ struct ethtool_ops {
|
||||
u32 cap_link_lanes_supported:1;
|
||||
u32 cap_rss_ctx_supported:1;
|
||||
u32 cap_rss_sym_xor_supported:1;
|
||||
u32 rxfh_per_ctx_key:1;
|
||||
u32 rxfh_indir_space;
|
||||
u16 rxfh_key_space;
|
||||
u16 rxfh_priv_size;
|
||||
|
@ -965,6 +965,7 @@ enum {
|
||||
ETHTOOL_A_RSS_INDIR, /* binary */
|
||||
ETHTOOL_A_RSS_HKEY, /* binary */
|
||||
ETHTOOL_A_RSS_INPUT_XFRM, /* u32 */
|
||||
ETHTOOL_A_RSS_START_CONTEXT, /* u32 */
|
||||
|
||||
__ETHTOOL_A_RSS_CNT,
|
||||
ETHTOOL_A_RSS_MAX = (__ETHTOOL_A_RSS_CNT - 1),
|
||||
|
@ -1227,7 +1227,8 @@ static noinline_for_stack int ethtool_get_rxfh(struct net_device *dev,
|
||||
if (rxfh.rsvd8[0] || rxfh.rsvd8[1] || rxfh.rsvd32)
|
||||
return -EINVAL;
|
||||
/* Most drivers don't handle rss_context, check it's 0 as well */
|
||||
if (rxfh.rss_context && !ops->cap_rss_ctx_supported)
|
||||
if (rxfh.rss_context && !(ops->cap_rss_ctx_supported ||
|
||||
ops->create_rxfh_context))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
rxfh.indir_size = rxfh_dev.indir_size;
|
||||
@ -1260,10 +1261,15 @@ static noinline_for_stack int ethtool_get_rxfh(struct net_device *dev,
|
||||
if (rxfh_dev.indir)
|
||||
memcpy(rxfh_dev.indir, ethtool_rxfh_context_indir(ctx),
|
||||
indir_bytes);
|
||||
if (rxfh_dev.key)
|
||||
memcpy(rxfh_dev.key, ethtool_rxfh_context_key(ctx),
|
||||
user_key_size);
|
||||
rxfh_dev.hfunc = ctx->hfunc;
|
||||
if (!ops->rxfh_per_ctx_key) {
|
||||
rxfh_dev.key_size = 0;
|
||||
} else {
|
||||
if (rxfh_dev.key)
|
||||
memcpy(rxfh_dev.key,
|
||||
ethtool_rxfh_context_key(ctx),
|
||||
user_key_size);
|
||||
rxfh_dev.hfunc = ctx->hfunc;
|
||||
}
|
||||
rxfh_dev.input_xfrm = ctx->input_xfrm;
|
||||
ret = 0;
|
||||
} else {
|
||||
@ -1280,6 +1286,11 @@ static noinline_for_stack int ethtool_get_rxfh(struct net_device *dev,
|
||||
&rxfh_dev.input_xfrm,
|
||||
sizeof(rxfh.input_xfrm))) {
|
||||
ret = -EFAULT;
|
||||
} else if (copy_to_user(useraddr +
|
||||
offsetof(struct ethtool_rxfh, key_size),
|
||||
&rxfh_dev.key_size,
|
||||
sizeof(rxfh.key_size))) {
|
||||
ret = -EFAULT;
|
||||
} else if (copy_to_user(useraddr +
|
||||
offsetof(struct ethtool_rxfh, rss_config[0]),
|
||||
rss_config, total_size)) {
|
||||
@ -1357,7 +1368,8 @@ static noinline_for_stack int ethtool_set_rxfh(struct net_device *dev,
|
||||
if (rxfh.rsvd8[0] || rxfh.rsvd8[1] || rxfh.rsvd32)
|
||||
return -EINVAL;
|
||||
/* Most drivers don't handle rss_context, check it's 0 as well */
|
||||
if (rxfh.rss_context && !ops->cap_rss_ctx_supported)
|
||||
if (rxfh.rss_context && !(ops->cap_rss_ctx_supported ||
|
||||
ops->create_rxfh_context))
|
||||
return -EOPNOTSUPP;
|
||||
/* Check input data transformation capabilities */
|
||||
if (rxfh.input_xfrm && rxfh.input_xfrm != RXH_XFRM_SYM_XOR &&
|
||||
@ -1387,6 +1399,13 @@ static noinline_for_stack int ethtool_set_rxfh(struct net_device *dev,
|
||||
|
||||
indir_bytes = dev_indir_size * sizeof(rxfh_dev.indir[0]);
|
||||
|
||||
/* Check settings which may be global rather than per RSS-context */
|
||||
if (rxfh.rss_context && !ops->rxfh_per_ctx_key)
|
||||
if (rxfh.key_size ||
|
||||
(rxfh.hfunc && rxfh.hfunc != ETH_RSS_HASH_NO_CHANGE) ||
|
||||
(rxfh.input_xfrm && rxfh.input_xfrm != RXH_XFRM_NO_CHANGE))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
rss_config = kzalloc(indir_bytes + dev_key_size, GFP_USER);
|
||||
if (!rss_config)
|
||||
return -ENOMEM;
|
||||
|
@ -1128,6 +1128,8 @@ static const struct genl_ops ethtool_genl_ops[] = {
|
||||
{
|
||||
.cmd = ETHTOOL_MSG_RSS_GET,
|
||||
.doit = ethnl_default_doit,
|
||||
.start = ethnl_rss_dump_start,
|
||||
.dumpit = ethnl_rss_dumpit,
|
||||
.policy = ethnl_rss_get_policy,
|
||||
.maxattr = ARRAY_SIZE(ethnl_rss_get_policy) - 1,
|
||||
},
|
||||
|
@ -449,7 +449,7 @@ extern const struct nla_policy ethnl_module_get_policy[ETHTOOL_A_MODULE_HEADER +
|
||||
extern const struct nla_policy ethnl_module_set_policy[ETHTOOL_A_MODULE_POWER_MODE_POLICY + 1];
|
||||
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_CONTEXT + 1];
|
||||
extern const struct nla_policy ethnl_rss_get_policy[ETHTOOL_A_RSS_START_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];
|
||||
@ -464,6 +464,8 @@ int ethnl_tunnel_info_doit(struct sk_buff *skb, struct genl_info *info);
|
||||
int ethnl_tunnel_info_start(struct netlink_callback *cb);
|
||||
int ethnl_tunnel_info_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
|
||||
int ethnl_act_module_fw_flash(struct sk_buff *skb, struct genl_info *info);
|
||||
int ethnl_rss_dump_start(struct netlink_callback *cb);
|
||||
int ethnl_rss_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
|
||||
|
||||
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];
|
||||
|
@ -10,6 +10,7 @@ struct rss_req_info {
|
||||
|
||||
struct rss_reply_data {
|
||||
struct ethnl_reply_data base;
|
||||
bool no_key_fields;
|
||||
u32 indir_size;
|
||||
u32 hkey_size;
|
||||
u32 hfunc;
|
||||
@ -27,6 +28,7 @@ struct rss_reply_data {
|
||||
const struct nla_policy ethnl_rss_get_policy[] = {
|
||||
[ETHTOOL_A_RSS_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
|
||||
[ETHTOOL_A_RSS_CONTEXT] = { .type = NLA_U32 },
|
||||
[ETHTOOL_A_RSS_START_CONTEXT] = { .type = NLA_U32 },
|
||||
};
|
||||
|
||||
static int
|
||||
@ -37,18 +39,18 @@ rss_parse_request(struct ethnl_req_info *req_info, struct nlattr **tb,
|
||||
|
||||
if (tb[ETHTOOL_A_RSS_CONTEXT])
|
||||
request->rss_context = nla_get_u32(tb[ETHTOOL_A_RSS_CONTEXT]);
|
||||
if (tb[ETHTOOL_A_RSS_START_CONTEXT]) {
|
||||
NL_SET_BAD_ATTR(extack, tb[ETHTOOL_A_RSS_START_CONTEXT]);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
rss_prepare_data(const struct ethnl_req_info *req_base,
|
||||
struct ethnl_reply_data *reply_base,
|
||||
const struct genl_info *info)
|
||||
rss_prepare_get(const struct rss_req_info *request, struct net_device *dev,
|
||||
struct rss_reply_data *data, const struct genl_info *info)
|
||||
{
|
||||
struct rss_reply_data *data = RSS_REPDATA(reply_base);
|
||||
struct rss_req_info *request = RSS_REQINFO(req_base);
|
||||
struct net_device *dev = reply_base->dev;
|
||||
struct ethtool_rxfh_param rxfh = {};
|
||||
const struct ethtool_ops *ops;
|
||||
u32 total_size, indir_bytes;
|
||||
@ -56,12 +58,6 @@ rss_prepare_data(const struct ethnl_req_info *req_base,
|
||||
int ret;
|
||||
|
||||
ops = dev->ethtool_ops;
|
||||
if (!ops->get_rxfh)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
/* Some drivers don't handle rss_context */
|
||||
if (request->rss_context && !ops->cap_rss_ctx_supported)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
ret = ethnl_ops_begin(dev);
|
||||
if (ret < 0)
|
||||
@ -91,7 +87,6 @@ rss_prepare_data(const struct ethnl_req_info *req_base,
|
||||
rxfh.indir = data->indir_table;
|
||||
rxfh.key_size = data->hkey_size;
|
||||
rxfh.key = data->hkey;
|
||||
rxfh.rss_context = request->rss_context;
|
||||
|
||||
ret = ops->get_rxfh(dev, &rxfh);
|
||||
if (ret)
|
||||
@ -104,6 +99,67 @@ out_ops:
|
||||
return ret;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
struct ethtool_rxfh_context *ctx;
|
||||
u32 total_size, indir_bytes;
|
||||
u8 *rss_config;
|
||||
|
||||
ctx = xa_load(&dev->ethtool->rss_ctx, request->rss_context);
|
||||
if (!ctx)
|
||||
return -ENOENT;
|
||||
|
||||
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;
|
||||
rss_config = kzalloc(total_size, GFP_KERNEL);
|
||||
if (!rss_config)
|
||||
return -ENOMEM;
|
||||
|
||||
data->indir_table = (u32 *)rss_config;
|
||||
memcpy(data->indir_table, ethtool_rxfh_context_indir(ctx), indir_bytes);
|
||||
|
||||
if (data->hkey_size) {
|
||||
data->hkey = rss_config + indir_bytes;
|
||||
memcpy(data->hkey, ethtool_rxfh_context_key(ctx),
|
||||
data->hkey_size);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
rss_prepare_data(const struct ethnl_req_info *req_base,
|
||||
struct ethnl_reply_data *reply_base,
|
||||
const struct genl_info *info)
|
||||
{
|
||||
struct rss_reply_data *data = RSS_REPDATA(reply_base);
|
||||
struct rss_req_info *request = RSS_REQINFO(req_base);
|
||||
struct net_device *dev = reply_base->dev;
|
||||
const struct ethtool_ops *ops;
|
||||
|
||||
ops = dev->ethtool_ops;
|
||||
if (!ops->get_rxfh)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
/* Some drivers don't handle rss_context */
|
||||
if (request->rss_context) {
|
||||
if (!ops->cap_rss_ctx_supported && !ops->create_rxfh_context)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
data->no_key_fields = !ops->rxfh_per_ctx_key;
|
||||
return rss_prepare_ctx(request, dev, data, info);
|
||||
}
|
||||
|
||||
return rss_prepare_get(request, dev, data, info);
|
||||
}
|
||||
|
||||
static int
|
||||
rss_reply_size(const struct ethnl_req_info *req_base,
|
||||
const struct ethnl_reply_data *reply_base)
|
||||
@ -131,13 +187,18 @@ rss_fill_reply(struct sk_buff *skb, const struct ethnl_req_info *req_base,
|
||||
nla_put_u32(skb, ETHTOOL_A_RSS_CONTEXT, request->rss_context))
|
||||
return -EMSGSIZE;
|
||||
|
||||
if ((data->indir_size &&
|
||||
nla_put(skb, ETHTOOL_A_RSS_INDIR,
|
||||
sizeof(u32) * data->indir_size, data->indir_table)))
|
||||
return -EMSGSIZE;
|
||||
|
||||
if (data->no_key_fields)
|
||||
return 0;
|
||||
|
||||
if ((data->hfunc &&
|
||||
nla_put_u32(skb, ETHTOOL_A_RSS_HFUNC, data->hfunc)) ||
|
||||
(data->input_xfrm &&
|
||||
nla_put_u32(skb, ETHTOOL_A_RSS_INPUT_XFRM, data->input_xfrm)) ||
|
||||
(data->indir_size &&
|
||||
nla_put(skb, ETHTOOL_A_RSS_INDIR,
|
||||
sizeof(u32) * data->indir_size, data->indir_table)) ||
|
||||
(data->hkey_size &&
|
||||
nla_put(skb, ETHTOOL_A_RSS_HKEY, data->hkey_size, data->hkey)))
|
||||
return -EMSGSIZE;
|
||||
@ -152,6 +213,146 @@ static void rss_cleanup_data(struct ethnl_reply_data *reply_base)
|
||||
kfree(data->indir_table);
|
||||
}
|
||||
|
||||
struct rss_nl_dump_ctx {
|
||||
unsigned long ifindex;
|
||||
unsigned long ctx_idx;
|
||||
|
||||
/* User wants to only dump contexts from given ifindex */
|
||||
unsigned int match_ifindex;
|
||||
unsigned int start_ctx;
|
||||
};
|
||||
|
||||
static struct rss_nl_dump_ctx *rss_dump_ctx(struct netlink_callback *cb)
|
||||
{
|
||||
NL_ASSERT_DUMP_CTX_FITS(struct rss_nl_dump_ctx);
|
||||
|
||||
return (struct rss_nl_dump_ctx *)cb->ctx;
|
||||
}
|
||||
|
||||
int ethnl_rss_dump_start(struct netlink_callback *cb)
|
||||
{
|
||||
const struct genl_info *info = genl_info_dump(cb);
|
||||
struct rss_nl_dump_ctx *ctx = rss_dump_ctx(cb);
|
||||
struct ethnl_req_info req_info = {};
|
||||
struct nlattr **tb = info->attrs;
|
||||
int ret;
|
||||
|
||||
/* Filtering by context not supported */
|
||||
if (tb[ETHTOOL_A_RSS_CONTEXT]) {
|
||||
NL_SET_BAD_ATTR(info->extack, tb[ETHTOOL_A_RSS_CONTEXT]);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (tb[ETHTOOL_A_RSS_START_CONTEXT]) {
|
||||
ctx->start_ctx = nla_get_u32(tb[ETHTOOL_A_RSS_START_CONTEXT]);
|
||||
ctx->ctx_idx = ctx->start_ctx;
|
||||
}
|
||||
|
||||
ret = ethnl_parse_header_dev_get(&req_info,
|
||||
tb[ETHTOOL_A_RSS_HEADER],
|
||||
sock_net(cb->skb->sk), cb->extack,
|
||||
false);
|
||||
if (req_info.dev) {
|
||||
ctx->match_ifindex = req_info.dev->ifindex;
|
||||
ctx->ifindex = ctx->match_ifindex;
|
||||
ethnl_parse_header_dev_put(&req_info);
|
||||
req_info.dev = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
rss_dump_one_ctx(struct sk_buff *skb, struct netlink_callback *cb,
|
||||
struct net_device *dev, u32 rss_context)
|
||||
{
|
||||
const struct genl_info *info = genl_info_dump(cb);
|
||||
struct rss_reply_data data = {};
|
||||
struct rss_req_info req = {};
|
||||
void *ehdr;
|
||||
int ret;
|
||||
|
||||
req.rss_context = rss_context;
|
||||
|
||||
ehdr = ethnl_dump_put(skb, cb, ETHTOOL_MSG_RSS_GET_REPLY);
|
||||
if (!ehdr)
|
||||
return -EMSGSIZE;
|
||||
|
||||
ret = ethnl_fill_reply_header(skb, dev, ETHTOOL_A_RSS_HEADER);
|
||||
if (ret < 0)
|
||||
goto err_cancel;
|
||||
|
||||
/* Context 0 is not currently storred or cached in the XArray */
|
||||
if (!rss_context)
|
||||
ret = rss_prepare_get(&req, dev, &data, info);
|
||||
else
|
||||
ret = rss_prepare_ctx(&req, dev, &data, info);
|
||||
if (ret)
|
||||
goto err_cancel;
|
||||
|
||||
ret = rss_fill_reply(skb, &req.base, &data.base);
|
||||
if (ret)
|
||||
goto err_cleanup;
|
||||
genlmsg_end(skb, ehdr);
|
||||
|
||||
rss_cleanup_data(&data.base);
|
||||
return 0;
|
||||
|
||||
err_cleanup:
|
||||
rss_cleanup_data(&data.base);
|
||||
err_cancel:
|
||||
genlmsg_cancel(skb, ehdr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
rss_dump_one_dev(struct sk_buff *skb, struct netlink_callback *cb,
|
||||
struct net_device *dev)
|
||||
{
|
||||
struct rss_nl_dump_ctx *ctx = rss_dump_ctx(cb);
|
||||
int ret;
|
||||
|
||||
if (!dev->ethtool_ops->get_rxfh)
|
||||
return 0;
|
||||
|
||||
if (!ctx->ctx_idx) {
|
||||
ret = rss_dump_one_ctx(skb, cb, dev, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
ctx->ctx_idx++;
|
||||
}
|
||||
|
||||
for (; xa_find(&dev->ethtool->rss_ctx, &ctx->ctx_idx,
|
||||
ULONG_MAX, XA_PRESENT); ctx->ctx_idx++) {
|
||||
ret = rss_dump_one_ctx(skb, cb, dev, ctx->ctx_idx);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
ctx->ctx_idx = ctx->start_ctx;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ethnl_rss_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
|
||||
{
|
||||
struct rss_nl_dump_ctx *ctx = rss_dump_ctx(cb);
|
||||
struct net *net = sock_net(skb->sk);
|
||||
struct net_device *dev;
|
||||
int ret = 0;
|
||||
|
||||
rtnl_lock();
|
||||
for_each_netdev_dump(net, dev, ctx->ifindex) {
|
||||
if (ctx->match_ifindex && ctx->match_ifindex != ctx->ifindex)
|
||||
break;
|
||||
|
||||
ret = rss_dump_one_dev(skb, cb, dev);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
rtnl_unlock();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
const struct ethnl_request_ops ethnl_rss_request_ops = {
|
||||
.request_cmd = ETHTOOL_MSG_RSS_GET,
|
||||
.reply_cmd = ETHTOOL_MSG_RSS_GET_REPLY,
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
import datetime
|
||||
import random
|
||||
from lib.py import ksft_run, ksft_pr, ksft_exit, ksft_eq, ksft_ge, ksft_lt
|
||||
from lib.py import ksft_run, ksft_pr, ksft_exit, ksft_eq, ksft_ne, ksft_ge, ksft_lt
|
||||
from lib.py import NetDrvEpEnv
|
||||
from lib.py import EthtoolFamily, NetdevFamily
|
||||
from lib.py import KsftSkipEx
|
||||
@ -90,10 +90,10 @@ def _send_traffic_check(cfg, port, name, params):
|
||||
ksft_ge(directed, 20000, f"traffic on {name}: " + str(cnts))
|
||||
if params.get('noise'):
|
||||
ksft_lt(sum(cnts[i] for i in params['noise']), directed / 2,
|
||||
"traffic on other queues:" + str(cnts))
|
||||
f"traffic on other queues ({name})':" + str(cnts))
|
||||
if params.get('empty'):
|
||||
ksft_eq(sum(cnts[i] for i in params['empty']), 0,
|
||||
"traffic on inactive queues: " + str(cnts))
|
||||
f"traffic on inactive queues ({name}): " + str(cnts))
|
||||
|
||||
|
||||
def test_rss_key_indir(cfg):
|
||||
@ -302,6 +302,78 @@ def test_hitless_key_update(cfg):
|
||||
ksft_eq(carrier1 - carrier0, 0)
|
||||
|
||||
|
||||
def test_rss_context_dump(cfg):
|
||||
"""
|
||||
Test dumping RSS contexts. This tests mostly exercises the kernel APIs.
|
||||
"""
|
||||
|
||||
# Get a random key of the right size
|
||||
data = get_rss(cfg)
|
||||
if 'rss-hash-key' in data:
|
||||
key_data = _rss_key_rand(len(data['rss-hash-key']))
|
||||
key = _rss_key_str(key_data)
|
||||
else:
|
||||
key_data = []
|
||||
key = "ba:ad"
|
||||
|
||||
ids = []
|
||||
try:
|
||||
ids.append(ethtool_create(cfg, "-X", f"context new"))
|
||||
defer(ethtool, f"-X {cfg.ifname} context {ids[-1]} delete")
|
||||
|
||||
ids.append(ethtool_create(cfg, "-X", f"context new weight 1 1"))
|
||||
defer(ethtool, f"-X {cfg.ifname} context {ids[-1]} delete")
|
||||
|
||||
ids.append(ethtool_create(cfg, "-X", f"context new hkey {key}"))
|
||||
defer(ethtool, f"-X {cfg.ifname} context {ids[-1]} delete")
|
||||
except CmdExitFailure:
|
||||
if not ids:
|
||||
raise KsftSkipEx("Unable to add any contexts")
|
||||
ksft_pr(f"Added only {len(ids)} out of 3 contexts")
|
||||
|
||||
expect_tuples = set([(cfg.ifname, -1)] + [(cfg.ifname, ctx_id) for ctx_id in ids])
|
||||
|
||||
# Dump all
|
||||
ctxs = cfg.ethnl.rss_get({}, dump=True)
|
||||
tuples = [(c['header']['dev-name'], c.get('context', -1)) for c in ctxs]
|
||||
ksft_eq(len(tuples), len(set(tuples)), "duplicates in context dump")
|
||||
ctx_tuples = set([ctx for ctx in tuples if ctx[0] == cfg.ifname])
|
||||
ksft_eq(expect_tuples, ctx_tuples)
|
||||
|
||||
# Sanity-check the results
|
||||
for data in ctxs:
|
||||
ksft_ne(set(data['indir']), {0}, "indir table is all zero")
|
||||
ksft_ne(set(data.get('hkey', [1])), {0}, "key is all zero")
|
||||
|
||||
# More specific checks
|
||||
if len(ids) > 1 and data.get('context') == ids[1]:
|
||||
ksft_eq(set(data['indir']), {0, 1},
|
||||
"ctx1 - indir table mismatch")
|
||||
if len(ids) > 2 and data.get('context') == ids[2]:
|
||||
ksft_eq(data['hkey'], bytes(key_data), "ctx2 - key mismatch")
|
||||
|
||||
# Ifindex filter
|
||||
ctxs = cfg.ethnl.rss_get({'header': {'dev-name': cfg.ifname}}, dump=True)
|
||||
tuples = [(c['header']['dev-name'], c.get('context', -1)) for c in ctxs]
|
||||
ctx_tuples = set(tuples)
|
||||
ksft_eq(len(tuples), len(ctx_tuples), "duplicates in context dump")
|
||||
ksft_eq(expect_tuples, ctx_tuples)
|
||||
|
||||
# Skip ctx 0
|
||||
expect_tuples.remove((cfg.ifname, -1))
|
||||
|
||||
ctxs = cfg.ethnl.rss_get({'start-context': 1}, dump=True)
|
||||
tuples = [(c['header']['dev-name'], c.get('context', -1)) for c in ctxs]
|
||||
ksft_eq(len(tuples), len(set(tuples)), "duplicates in context dump")
|
||||
ctx_tuples = set([ctx for ctx in tuples if ctx[0] == cfg.ifname])
|
||||
ksft_eq(expect_tuples, ctx_tuples)
|
||||
|
||||
# And finally both with ifindex and skip main
|
||||
ctxs = cfg.ethnl.rss_get({'header': {'dev-name': cfg.ifname}, 'start-context': 1}, dump=True)
|
||||
ctx_tuples = set([(c['header']['dev-name'], c.get('context', -1)) for c in ctxs])
|
||||
ksft_eq(expect_tuples, ctx_tuples)
|
||||
|
||||
|
||||
def test_rss_context(cfg, ctx_cnt=1, create_with_cfg=None):
|
||||
"""
|
||||
Test separating traffic into RSS contexts.
|
||||
@ -542,7 +614,7 @@ def main() -> None:
|
||||
ksft_run([test_rss_key_indir, test_rss_queue_reconfigure,
|
||||
test_rss_resize, test_hitless_key_update,
|
||||
test_rss_context, test_rss_context4, test_rss_context32,
|
||||
test_rss_context_queue_reconfigure,
|
||||
test_rss_context_dump, test_rss_context_queue_reconfigure,
|
||||
test_rss_context_overlap, test_rss_context_overlap2,
|
||||
test_rss_context_out_of_order, test_rss_context4_create_with_cfg],
|
||||
args=(cfg, ))
|
||||
|
@ -55,6 +55,12 @@ def ksft_eq(a, b, comment=""):
|
||||
_fail("Check failed", a, "!=", b, comment)
|
||||
|
||||
|
||||
def ksft_ne(a, b, comment=""):
|
||||
global KSFT_RESULT
|
||||
if a == b:
|
||||
_fail("Check failed", a, "==", b, comment)
|
||||
|
||||
|
||||
def ksft_true(a, comment=""):
|
||||
if not a:
|
||||
_fail("Check failed", a, "does not eval to True", comment)
|
||||
|
Loading…
Reference in New Issue
Block a user