[RFC PATCH net-next v2 14/17] ethtool: implement SET_SETTINGS request for features

From: Michal Kubecek
Date: Mon Jul 30 2018 - 08:54:00 EST


Using ETHNL_SETTINGS_FEATURES attribute, userspace can modify device
features. Actual change is subject to netdev_change_features() sanity
checks so that it can differ from what was requested. Unlike with most
other requests, kernel can reply (if ETHA_FEATURES_WANT_DIFF flag is used)
with a message in the same format but with different semantics: information
about difference between user request and actual result and difference
between old and new state of dev->features.

Signed-off-by: Michal Kubecek <mkubecek@xxxxxxx>
---
Documentation/networking/ethtool-netlink.txt | 30 +++--
include/uapi/linux/ethtool_netlink.h | 1 +
net/ethtool/settings.c | 127 +++++++++++++++++++
3 files changed, 150 insertions(+), 8 deletions(-)

diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt
index c7fe4f518972..307d8c6c6c85 100644
--- a/Documentation/networking/ethtool-netlink.txt
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -285,6 +285,9 @@ to be passed with SET_SETTINGS request:
ETHA_SETTINGS_SOPASS (binary) SecureOn(tm) password
ETHA_SETTINGS_MSGLVL (bitfield32) debug level
ETHA_SETTINGS_LINK_MODES (bitset) device link modes
+ ETHA_SETTINGS_FEATURES (nested) device features
+ ETHA_FEATURES_WANTED (bitset) wanted features
+ ETHA_FEATURES_WANT_DIFF (flag) actual diff

For both bitfield32 types, value and selector work the usual way, i.e. bits
set in selector are set to corresponding bits from value and the rest is
@@ -299,6 +302,17 @@ autoselection is done on ethtool side with ioctl interface, netlink interface
is supposed to allow requesting changes without knowing what exactly kernel
supports.

+When changing device features, only ETHA_FEATURES_WANTED is passed. As usual,
+mask defines which bits are to be set and value their values. If the request
+has ETHA_FEATURES_WANT_DIFF flag set, reply will contain a message in the same
+format as response to GET request, except only two bitsets are provided.
+ETHA_FEATURES_WANTED shows difference between requested features and actual
+result (dev->features after the operation); mask shows bits which differ and
+value their values from the original request (new values are negated). Value
+shows changes between old dev->features (before the operation) and new (after
+the operation); mask shows bits which have been changed and value their new
+values.
+

Request translation
-------------------
@@ -328,30 +342,30 @@ ETHTOOL_SRINGPARAM n/a
ETHTOOL_GPAUSEPARAM n/a
ETHTOOL_SPAUSEPARAM n/a
ETHTOOL_GRXCSUM ETHNL_CMD_GET_SETTINGS
-ETHTOOL_SRXCSUM n/a
+ETHTOOL_SRXCSUM ETHNL_CMD_SET_SETTINGS
ETHTOOL_GTXCSUM ETHNL_CMD_GET_SETTINGS
-ETHTOOL_STXCSUM n/a
+ETHTOOL_STXCSUM ETHNL_CMD_SET_SETTINGS
ETHTOOL_GSG ETHNL_CMD_GET_SETTINGS
-ETHTOOL_SSG n/a
+ETHTOOL_SSG ETHNL_CMD_SET_SETTINGS
ETHTOOL_TEST n/a
ETHTOOL_GSTRINGS ETHNL_CMD_GET_STRSET
ETHTOOL_PHYS_ID n/a
ETHTOOL_GSTATS n/a
ETHTOOL_GTSO ETHNL_CMD_GET_SETTINGS
-ETHTOOL_STSO n/a
+ETHTOOL_STSO ETHNL_CMD_SET_SETTINGS
ETHTOOL_GPERMADDR n/a
ETHTOOL_GUFO ETHNL_CMD_GET_SETTINGS
-ETHTOOL_SUFO n/a
+ETHTOOL_SUFO ETHNL_CMD_SET_SETTINGS
ETHTOOL_GGSO ETHNL_CMD_GET_SETTINGS
-ETHTOOL_SGSO n/a
+ETHTOOL_SGSO ETHNL_CMD_SET_SETTINGS
ETHTOOL_GFLAGS ETHNL_CMD_GET_SETTINGS
-ETHTOOL_SFLAGS n/a
+ETHTOOL_SFLAGS ETHNL_CMD_SET_SETTINGS
ETHTOOL_GPFLAGS n/a
ETHTOOL_SPFLAGS n/a
ETHTOOL_GRXFH n/a
ETHTOOL_SRXFH n/a
ETHTOOL_GGRO ETHNL_CMD_GET_SETTINGS
-ETHTOOL_SGRO n/a
+ETHTOOL_SGRO ETHNL_CMD_SET_SETTINGS
ETHTOOL_GRXRINGS n/a
ETHTOOL_GRXCLSRLCNT n/a
ETHTOOL_GRXCLSRULE n/a
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index 06c78b281275..8dfcb9ef4009 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -191,6 +191,7 @@ enum {
ETHA_FEATURES_WANTED, /* bitset */
ETHA_FEATURES_ACTIVE, /* bitset */
ETHA_FEATURES_NOCHANGE, /* bitset */
+ ETHA_FEATURES_WANT_DIFF, /* flag */

__ETHA_FEATURES_MAX,
ETHA_FEATURES_MAX = (__ETHA_FEATURES_MAX - 1)
diff --git a/net/ethtool/settings.c b/net/ethtool/settings.c
index 678199e621a2..475582b7950c 100644
--- a/net/ethtool/settings.c
+++ b/net/ethtool/settings.c
@@ -1016,6 +1016,122 @@ static int ethnl_update_lsettings(struct genl_info *info, struct nlattr **tb,
return 0;
}

+static const struct nla_policy features_policy[ETHA_FEATURES_MAX + 1] = {
+ [ETHA_FEATURES_UNSPEC] = { .type = NLA_UNSPEC },
+ [ETHA_FEATURES_HW] = { .type = NLA_NESTED },
+ [ETHA_FEATURES_WANTED] = { .type = NLA_NESTED },
+ [ETHA_FEATURES_ACTIVE] = { .type = NLA_NESTED },
+ [ETHA_FEATURES_NOCHANGE] = { .type = NLA_NESTED },
+ [ETHA_FEATURES_WANT_DIFF] = { .type = NLA_FLAG },
+};
+
+static void bitmap_from_features(unsigned long *bitmap, netdev_features_t val)
+{
+ const unsigned int words = BITS_TO_LONGS(NETDEV_FEATURE_COUNT);
+ unsigned int i;
+
+ bitmap_zero(bitmap, NETDEV_FEATURE_COUNT);
+ for (i = 0; i < words; i++)
+ bitmap[i] = (unsigned long)(val >> (i * BITS_PER_LONG));
+}
+
+static netdev_features_t features_from_bitmap(unsigned long *bitmap)
+{
+ const unsigned int words = BITS_TO_LONGS(NETDEV_FEATURE_COUNT);
+ netdev_features_t ret = 0;
+ unsigned int i;
+
+ for (i = 0; i < words; i++)
+ ret |= (netdev_features_t)(bitmap[i]) << (i * BITS_PER_LONG);
+ return ret;
+}
+
+int update_features(struct genl_info *info, struct net_device *dev,
+ const struct nlattr *nest, bool compact)
+{
+ struct nlattr *tb[ETHA_FEATURES_MAX + 1];
+ DECLARE_BITMAP(old_active, NETDEV_FEATURE_COUNT);
+ DECLARE_BITMAP(req_wanted, NETDEV_FEATURE_COUNT);
+ DECLARE_BITMAP(req_mask, NETDEV_FEATURE_COUNT);
+ DECLARE_BITMAP(new_active, NETDEV_FEATURE_COUNT);
+ DECLARE_BITMAP(wanted_diff_mask, NETDEV_FEATURE_COUNT);
+ DECLARE_BITMAP(active_diff_mask, NETDEV_FEATURE_COUNT);
+ struct nlattr *feat_attr;
+ unsigned int reply_len;
+ struct sk_buff *rskb;
+ bool mod = false;
+ void *ehdr;
+ int ret;
+
+ ret = nla_parse_nested(tb, ETHA_FEATURES_MAX, nest, features_policy,
+ info->extack);
+ if (ret < 0)
+ return ret;
+ if (tb[ETHA_FEATURES_HW] || !tb[ETHA_FEATURES_WANTED] ||
+ tb[ETHA_FEATURES_ACTIVE] || tb[ETHA_FEATURES_NOCHANGE])
+ return -EINVAL;
+
+ bitmap_from_features(old_active, dev->features);
+ bitmap_copy(req_wanted, old_active, NETDEV_FEATURE_COUNT);
+ bitmap_zero(req_mask, NETDEV_FEATURE_COUNT);
+ mod = ethnl_update_bitset(req_wanted, req_mask, NETDEV_FEATURE_COUNT,
+ tb[ETHA_FEATURES_WANTED], &ret, feature_names,
+ info);
+ if (ret < 0 || !mod)
+ return ret;
+
+ dev->wanted_features = features_from_bitmap(req_wanted);
+ netdev_update_features(dev);
+ if (!tb[ETHA_FEATURES_WANT_DIFF])
+ return 0;
+ bitmap_from_features(new_active, dev->features);
+ bitmap_xor(wanted_diff_mask, req_wanted, new_active,
+ NETDEV_FEATURE_COUNT);
+ bitmap_xor(active_diff_mask, old_active, new_active,
+ NETDEV_FEATURE_COUNT);
+ bitmap_and(wanted_diff_mask, wanted_diff_mask, req_mask,
+ NETDEV_FEATURE_COUNT);
+ bitmap_and(req_wanted, req_wanted, wanted_diff_mask,
+ NETDEV_FEATURE_COUNT);
+ bitmap_and(new_active, new_active, active_diff_mask,
+ NETDEV_FEATURE_COUNT);
+
+ reply_len = ethnl_bitset_size(compact, NETDEV_FEATURE_COUNT, req_wanted,
+ wanted_diff_mask, feature_names) +
+ ethnl_bitset_size(compact, NETDEV_FEATURE_COUNT, new_active,
+ active_diff_mask, feature_names);
+ reply_len = nla_total_size(reply_len);
+ rskb = ethnl_reply_init(reply_len, dev, ETHNL_CMD_SET_SETTINGS,
+ ETHA_SETTINGS_DEV, info, &ehdr);
+ if (!rskb)
+ goto err;
+ ret = -EMSGSIZE;
+
+ feat_attr = ethnl_nest_start(rskb, ETHA_SETTINGS_FEATURES);
+ if (!feat_attr)
+ goto err;
+ ret = ethnl_put_bitset(rskb, ETHA_FEATURES_WANTED, compact,
+ NETDEV_FEATURE_COUNT, req_wanted,
+ wanted_diff_mask, feature_names);
+ if (ret < 0)
+ goto err;
+ ret = ethnl_put_bitset(rskb, ETHA_FEATURES_ACTIVE, compact,
+ NETDEV_FEATURE_COUNT, new_active,
+ active_diff_mask, feature_names);
+ if (ret < 0)
+ goto err;
+ nla_nest_end(rskb, feat_attr);
+
+ genlmsg_end(rskb, ehdr);
+ return genlmsg_reply(rskb, info);
+err:
+ WARN_ONCE(ret == -EMSGSIZE,
+ "calculated message payload length (%d) not sufficient\n",
+ reply_len);
+ nlmsg_free(rskb);
+ return ret;
+}
+
int ethnl_set_settings(struct sk_buff *skb, struct genl_info *info)
{
struct nlattr *tb[ETHA_SETTINGS_MAX + 1];
@@ -1092,6 +1208,17 @@ int ethnl_set_settings(struct sk_buff *skb, struct genl_info *info)
req_mask |= ETH_SETTINGS_IM_MSGLEVEL;
}
}
+ if (tb[ETHA_SETTINGS_FEATURES]) {
+ bool compact = tb[ETHA_SETTINGS_COMPACT];
+
+ ret = update_features(info, dev, tb[ETHA_SETTINGS_FEATURES],
+ compact);
+ if (ret > 0) {
+ req_mask |= ETH_SETTINGS_IM_FEATURES;
+ ret = 0;
+ }
+
+ }
ret = 0;

out_unlock:
--
2.18.0