Skip to content

Commit

Permalink
ethtool: set device channel counts with CHANNELS_SET request
Browse files Browse the repository at this point in the history
Implement CHANNELS_SET netlink request to set channel counts of a network
device. These are traditionally set with ETHTOOL_SCHANNELS ioctl request.

Like the ioctl implementation, the generic ethtool code checks if supplied
values do not exceed driver defined limits; if they do, first offending
attribute is reported using extack. Checks preventing removing channels
used for RX indirection table or zerocopy AF_XDP socket are also
implemented.

Move ethtool_get_max_rxfh_channel() helper into common.c so that it can be
used by both ioctl and netlink code.

v2:
  - fix netdev reference leak in error path (found by Jakub Kicinsky)

Signed-off-by: Michal Kubecek <[email protected]>
Reviewed-by: Jakub Kicinski <[email protected]>
Signed-off-by: David S. Miller <[email protected]>
  • Loading branch information
mkubecek authored and davem330 committed Mar 12, 2020
1 parent 0c84979 commit e19c591
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 32 deletions.
23 changes: 22 additions & 1 deletion Documentation/networking/ethtool-netlink.rst
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ Userspace to kernel:
``ETHTOOL_MSG_RINGS_GET`` get ring sizes
``ETHTOOL_MSG_RINGS_SET`` set ring sizes
``ETHTOOL_MSG_CHANNELS_GET`` get channel counts
``ETHTOOL_MSG_CHANNELS_SET`` set channel counts
===================================== ================================

Kernel to userspace:
Expand Down Expand Up @@ -723,6 +724,26 @@ Kernel response contents:
===================================== ====== ==========================


CHANNELS_SET
============

Sets channel counts like ``ETHTOOL_SCHANNELS`` ioctl request.

Request contents:

===================================== ====== ==========================
``ETHTOOL_A_CHANNELS_HEADER`` nested request header
``ETHTOOL_A_CHANNELS_RX_COUNT`` u32 receive channel count
``ETHTOOL_A_CHANNELS_TX_COUNT`` u32 transmit channel count
``ETHTOOL_A_CHANNELS_OTHER_COUNT`` u32 other channel count
``ETHTOOL_A_CHANNELS_COMBINED_COUNT`` u32 combined channel count
===================================== ====== ==========================

Kernel checks that requested channel counts do not exceed limits reported by
driver. Driver may impose additional constraints and may not suspport all
attributes.


Request translation
===================

Expand Down Expand Up @@ -794,7 +815,7 @@ have their netlink replacement yet.
``ETHTOOL_GFEATURES`` ``ETHTOOL_MSG_FEATURES_GET``
``ETHTOOL_SFEATURES`` ``ETHTOOL_MSG_FEATURES_SET``
``ETHTOOL_GCHANNELS`` ``ETHTOOL_MSG_CHANNELS_GET``
``ETHTOOL_SCHANNELS`` n/a
``ETHTOOL_SCHANNELS`` ``ETHTOOL_MSG_CHANNELS_SET``
``ETHTOOL_SET_DUMP`` n/a
``ETHTOOL_GET_DUMP_FLAG`` n/a
``ETHTOOL_GET_DUMP_DATA`` n/a
Expand Down
1 change: 1 addition & 0 deletions include/uapi/linux/ethtool_netlink.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ enum {
ETHTOOL_MSG_RINGS_GET,
ETHTOOL_MSG_RINGS_SET,
ETHTOOL_MSG_CHANNELS_GET,
ETHTOOL_MSG_CHANNELS_SET,

/* add new constants above here */
__ETHTOOL_MSG_USER_CNT,
Expand Down
116 changes: 116 additions & 0 deletions net/ethtool/channels.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only

#include <net/xdp_sock.h>

#include "netlink.h"
#include "common.h"

Expand Down Expand Up @@ -106,3 +108,117 @@ const struct ethnl_request_ops ethnl_channels_request_ops = {
.reply_size = channels_reply_size,
.fill_reply = channels_fill_reply,
};

/* CHANNELS_SET */

static const struct nla_policy
channels_set_policy[ETHTOOL_A_CHANNELS_MAX + 1] = {
[ETHTOOL_A_CHANNELS_UNSPEC] = { .type = NLA_REJECT },
[ETHTOOL_A_CHANNELS_HEADER] = { .type = NLA_NESTED },
[ETHTOOL_A_CHANNELS_RX_MAX] = { .type = NLA_REJECT },
[ETHTOOL_A_CHANNELS_TX_MAX] = { .type = NLA_REJECT },
[ETHTOOL_A_CHANNELS_OTHER_MAX] = { .type = NLA_REJECT },
[ETHTOOL_A_CHANNELS_COMBINED_MAX] = { .type = NLA_REJECT },
[ETHTOOL_A_CHANNELS_RX_COUNT] = { .type = NLA_U32 },
[ETHTOOL_A_CHANNELS_TX_COUNT] = { .type = NLA_U32 },
[ETHTOOL_A_CHANNELS_OTHER_COUNT] = { .type = NLA_U32 },
[ETHTOOL_A_CHANNELS_COMBINED_COUNT] = { .type = NLA_U32 },
};

int ethnl_set_channels(struct sk_buff *skb, struct genl_info *info)
{
struct nlattr *tb[ETHTOOL_A_CHANNELS_MAX + 1];
unsigned int from_channel, old_total, i;
struct ethtool_channels channels = {};
struct ethnl_req_info req_info = {};
const struct nlattr *err_attr;
const struct ethtool_ops *ops;
struct net_device *dev;
u32 max_rx_in_use = 0;
bool mod = false;
int ret;

ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb,
ETHTOOL_A_CHANNELS_MAX, channels_set_policy,
info->extack);
if (ret < 0)
return ret;
ret = ethnl_parse_header_dev_get(&req_info,
tb[ETHTOOL_A_CHANNELS_HEADER],
genl_info_net(info), info->extack,
true);
if (ret < 0)
return ret;
dev = req_info.dev;
ops = dev->ethtool_ops;
ret = -EOPNOTSUPP;
if (!ops->get_channels || !ops->set_channels)
goto out_dev;

rtnl_lock();
ret = ethnl_ops_begin(dev);
if (ret < 0)
goto out_rtnl;
ops->get_channels(dev, &channels);
old_total = channels.combined_count +
max(channels.rx_count, channels.tx_count);

ethnl_update_u32(&channels.rx_count, tb[ETHTOOL_A_CHANNELS_RX_COUNT],
&mod);
ethnl_update_u32(&channels.tx_count, tb[ETHTOOL_A_CHANNELS_TX_COUNT],
&mod);
ethnl_update_u32(&channels.other_count,
tb[ETHTOOL_A_CHANNELS_OTHER_COUNT], &mod);
ethnl_update_u32(&channels.combined_count,
tb[ETHTOOL_A_CHANNELS_COMBINED_COUNT], &mod);
ret = 0;
if (!mod)
goto out_ops;

/* ensure new channel counts are within limits */
if (channels.rx_count > channels.max_rx)
err_attr = tb[ETHTOOL_A_CHANNELS_RX_COUNT];
else if (channels.tx_count > channels.max_tx)
err_attr = tb[ETHTOOL_A_CHANNELS_TX_COUNT];
else if (channels.other_count > channels.max_other)
err_attr = tb[ETHTOOL_A_CHANNELS_OTHER_COUNT];
else if (channels.combined_count > channels.max_combined)
err_attr = tb[ETHTOOL_A_CHANNELS_COMBINED_COUNT];
else
err_attr = NULL;
if (err_attr) {
ret = -EINVAL;
NL_SET_ERR_MSG_ATTR(info->extack, err_attr,
"requested channel count exceeeds maximum");
goto out_ops;
}

/* ensure the new Rx count fits within the configured Rx flow
* indirection table settings
*/
if (netif_is_rxfh_configured(dev) &&
!ethtool_get_max_rxfh_channel(dev, &max_rx_in_use) &&
(channels.combined_count + channels.rx_count) <= max_rx_in_use) {
GENL_SET_ERR_MSG(info, "requested channel counts are too low for existing indirection table settings");
return -EINVAL;
}

/* Disabling channels, query zero-copy AF_XDP sockets */
from_channel = channels.combined_count +
min(channels.rx_count, channels.tx_count);
for (i = from_channel; i < old_total; i++)
if (xdp_get_umem_from_qid(dev, i)) {
GENL_SET_ERR_MSG(info, "requested channel counts are too low for existing zerocopy AF_XDP sockets");
return -EINVAL;
}

ret = dev->ethtool_ops->set_channels(dev, &channels);

out_ops:
ethnl_ops_complete(dev);
out_rtnl:
rtnl_unlock();
out_dev:
dev_put(dev);
return ret;
}
31 changes: 31 additions & 0 deletions net/ethtool/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -258,3 +258,34 @@ int __ethtool_get_link(struct net_device *dev)

return netif_running(dev) && dev->ethtool_ops->get_link(dev);
}

int ethtool_get_max_rxfh_channel(struct net_device *dev, u32 *max)
{
u32 dev_size, current_max = 0;
u32 *indir;
int ret;

if (!dev->ethtool_ops->get_rxfh_indir_size ||
!dev->ethtool_ops->get_rxfh)
return -EOPNOTSUPP;
dev_size = dev->ethtool_ops->get_rxfh_indir_size(dev);
if (dev_size == 0)
return -EOPNOTSUPP;

indir = kcalloc(dev_size, sizeof(indir[0]), GFP_USER);
if (!indir)
return -ENOMEM;

ret = dev->ethtool_ops->get_rxfh(dev, indir, NULL, NULL);
if (ret)
goto out;

while (dev_size--)
current_max = max(current_max, indir[dev_size]);

*max = current_max;

out:
kfree(indir);
return ret;
}
1 change: 1 addition & 0 deletions net/ethtool/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@ int __ethtool_get_link(struct net_device *dev);
bool convert_legacy_settings_to_link_ksettings(
struct ethtool_link_ksettings *link_ksettings,
const struct ethtool_cmd *legacy_settings);
int ethtool_get_max_rxfh_channel(struct net_device *dev, u32 *max);

#endif /* _ETHTOOL_COMMON_H */
31 changes: 0 additions & 31 deletions net/ethtool/ioctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -929,37 +929,6 @@ void netdev_rss_key_fill(void *buffer, size_t len)
}
EXPORT_SYMBOL(netdev_rss_key_fill);

static int ethtool_get_max_rxfh_channel(struct net_device *dev, u32 *max)
{
u32 dev_size, current_max = 0;
u32 *indir;
int ret;

if (!dev->ethtool_ops->get_rxfh_indir_size ||
!dev->ethtool_ops->get_rxfh)
return -EOPNOTSUPP;
dev_size = dev->ethtool_ops->get_rxfh_indir_size(dev);
if (dev_size == 0)
return -EOPNOTSUPP;

indir = kcalloc(dev_size, sizeof(indir[0]), GFP_USER);
if (!indir)
return -ENOMEM;

ret = dev->ethtool_ops->get_rxfh(dev, indir, NULL, NULL);
if (ret)
goto out;

while (dev_size--)
current_max = max(current_max, indir[dev_size]);

*max = current_max;

out:
kfree(indir);
return ret;
}

static noinline_for_stack int ethtool_get_rxfh_indir(struct net_device *dev,
void __user *useraddr)
{
Expand Down
5 changes: 5 additions & 0 deletions net/ethtool/netlink.c
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,11 @@ static const struct genl_ops ethtool_genl_ops[] = {
.dumpit = ethnl_default_dumpit,
.done = ethnl_default_done,
},
{
.cmd = ETHTOOL_MSG_CHANNELS_SET,
.flags = GENL_UNS_ADMIN_PERM,
.doit = ethnl_set_channels,
},
};

static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
Expand Down
1 change: 1 addition & 0 deletions net/ethtool/netlink.h
Original file line number Diff line number Diff line change
Expand Up @@ -349,5 +349,6 @@ int ethnl_set_wol(struct sk_buff *skb, struct genl_info *info);
int ethnl_set_features(struct sk_buff *skb, struct genl_info *info);
int ethnl_set_privflags(struct sk_buff *skb, struct genl_info *info);
int ethnl_set_rings(struct sk_buff *skb, struct genl_info *info);
int ethnl_set_channels(struct sk_buff *skb, struct genl_info *info);

#endif /* _NET_ETHTOOL_NETLINK_H */

0 comments on commit e19c591

Please sign in to comment.