Add information about device private flags (as provided by ETHTOOL_GPFLAGS ioctl command) in GET_SETTINGS reply when ETH_SETTINGS_IM_PRIVFLAGS flag is set in the request.
Signed-off-by: Michal Kubecek <mkube...@suse.cz> --- Documentation/networking/ethtool-netlink.txt | 9 +- include/uapi/linux/ethtool_netlink.h | 4 +- net/ethtool/settings.c | 98 ++++++++++++++++++++ 3 files changed, 109 insertions(+), 2 deletions(-) diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt index 664c922a05eb..290008aaed0a 100644 --- a/Documentation/networking/ethtool-netlink.txt +++ b/Documentation/networking/ethtool-netlink.txt @@ -287,6 +287,7 @@ Info mask bits meaning: ETH_SETTINGS_IM_MSGLEVEL msglevel ETH_SETTINGS_IM_LINK link state ETH_SETTINGS_IM_FEATURES features + ETH_SETTINGS_IM_PRIVFLAGS device private flags Response contents: @@ -312,6 +313,7 @@ Response contents: ETHA_FEATURES_WANTED (bitset) dev->wanted_features ETHA_FEATURES_ACTIVE (bitset) dev->features ETHA_FEATURES_NOCHANGE (bitset) NETIF_F_NEVER_CHANGE + ETHA_SETTINGS_PRIV_FLAGS (bitset) device private flags Most of the attributes and their values have the same meaning as matching members of the corresponding ioctl structures. For ETHA_SETTINGS_LINK_MODES, @@ -333,6 +335,11 @@ itself. ETHA_FEATURES_HW uses mask consisting of all features recognized by kernel (to provide all names when using verbose bitmap format), remaining three use mask equal to value (to save space). +ETHA_SETTINGS_PRIV_FLAGS is a bitset with values of device private flags. +These flags are defined by driver, their number and names (as well as meaning) +are device dependent. For compact bitset format, names can be retrieved as +ETH_SS_PRIV_FLAGS string set. + GET_SETTINGS request is allowed for unprivileged user but ETHA_SETTINGS_SOPASS is only provided by kernel in response to privileged (netns CAP_NET_ADMIN) requests. @@ -389,7 +396,7 @@ ETHTOOL_GGSO ETHNL_CMD_GET_SETTINGS ETHTOOL_SGSO n/a ETHTOOL_GFLAGS ETHNL_CMD_GET_SETTINGS ETHTOOL_SFLAGS n/a -ETHTOOL_GPFLAGS n/a +ETHTOOL_GPFLAGS ETHNL_CMD_GET_SETTINGS ETHTOOL_SPFLAGS n/a ETHTOOL_GRXFH n/a ETHTOOL_SRXFH n/a diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h index 154d7e6a59dd..afd61d36bcf8 100644 --- a/include/uapi/linux/ethtool_netlink.h +++ b/include/uapi/linux/ethtool_netlink.h @@ -205,6 +205,7 @@ enum { ETHA_SETTINGS_MSGLEVEL, /* bitfield32 */ ETHA_SETTINGS_LINK, /* u8 */ ETHA_SETTINGS_FEATURES, /* nest - ETHA_FEATURES_* */ + ETHA_SETTINGS_PRIV_FLAGS, /* nest - ETHA_BITSET_* */ __ETHA_SETTINGS_CNT, ETHA_SETTINGS_MAX = (__ETHA_SETTINGS_CNT - 1) @@ -216,8 +217,9 @@ enum { #define ETH_SETTINGS_IM_MSGLEVEL 0x08 #define ETH_SETTINGS_IM_LINK 0x10 #define ETH_SETTINGS_IM_FEATURES 0x20 +#define ETH_SETTINGS_IM_PRIVFLAGS 0x40 -#define ETH_SETTINGS_IM_ALL 0x3f +#define ETH_SETTINGS_IM_ALL 0x7f enum { ETHA_FEATURES_UNSPEC, diff --git a/net/ethtool/settings.c b/net/ethtool/settings.c index 32ee273de879..7f72f4250306 100644 --- a/net/ethtool/settings.c +++ b/net/ethtool/settings.c @@ -22,6 +22,9 @@ struct settings_data { u32 active[ETHTOOL_DEV_FEATURE_WORDS]; u32 nochange[ETHTOOL_DEV_FEATURE_WORDS]; } features; + char (*priv_flag_names)[ETH_GSTRING_LEN]; + u32 priv_flags; + unsigned int n_priv_flags; }; static const struct nla_policy get_settings_policy[ETHA_SETTINGS_MAX + 1] = { @@ -36,6 +39,7 @@ static const struct nla_policy get_settings_policy[ETHA_SETTINGS_MAX + 1] = { [ETHA_SETTINGS_MSGLEVEL] = { .type = NLA_REJECT }, [ETHA_SETTINGS_LINK] = { .type = NLA_REJECT }, [ETHA_SETTINGS_FEATURES] = { .type = NLA_REJECT }, + [ETHA_SETTINGS_PRIV_FLAGS] = { .type = NLA_REJECT }, }; static int parse_settings(struct common_req_info *req_info, @@ -114,6 +118,58 @@ static int ethnl_get_features(struct net_device *dev, return 0; } +static int get_priv_flags_info(struct net_device *dev, unsigned int *count, + void **names) +{ + const struct ethtool_ops *ops = dev->ethtool_ops; + int nflags; + + if (!ops->get_priv_flags || !ops->get_sset_count || !ops->get_strings) + return -EOPNOTSUPP; + nflags = ops->get_sset_count(dev, ETH_SS_PRIV_FLAGS); + if (nflags < 0) + return nflags; + + if (names) { + *names = kcalloc(nflags, ETH_GSTRING_LEN, GFP_KERNEL); + if (!*names) + return -ENOMEM; + ops->get_strings(dev, ETH_SS_PRIV_FLAGS, *names); + } + + /* We can easily pass more than 32 private flags to userspace via + * netlink but we cannot get more with ethtool_ops::get_priv_flags(). + * Note that we must not adjust nflags before allocating the space + * for flag names as the buffer must be large enough for all flags. + */ + if (WARN_ONCE(nflags > 32, + "device %s reports more than 32 private flags (%d)\n", + netdev_name(dev), nflags)) + nflags = 32; + + *count = nflags; + return 0; +} + +static int ethnl_get_priv_flags(struct genl_info *info, + struct settings_data *data) +{ + struct net_device *dev = data->repdata_base.dev; + const struct ethtool_ops *ops = dev->ethtool_ops; + unsigned int nflags; + void *names; + int ret; + + ret = get_priv_flags_info(dev, &nflags, &names); + if (ret < 0) + return ret; + + data->priv_flags = ops->get_priv_flags(dev); + data->priv_flag_names = names; + data->n_priv_flags = nflags; + return 0; +} + static int prepare_settings(struct common_req_info *req_info, struct genl_info *info) { @@ -163,6 +219,11 @@ static int prepare_settings(struct common_req_info *req_info, data->link = __ethtool_get_link(dev); if (req_mask & ETH_SETTINGS_IM_FEATURES) ethnl_get_features(dev, data); + if (req_mask & ETH_SETTINGS_IM_PRIVFLAGS) { + ret = ethnl_get_priv_flags(info, data); + if (ret < 0) + req_mask &= ~ETH_SETTINGS_IM_PRIVFLAGS; + } ethnl_after_ops(dev); data->repdata_base.info_mask = req_mask; @@ -281,6 +342,17 @@ static int settings_size(const struct common_req_info *req_info) return ret; len += ret; } + if (info_mask & ETH_SETTINGS_IM_PRIVFLAGS) { + const unsigned int flags = + (compact ? ETHNL_BITSET_COMPACT : 0) | + ETHNL_BITSET_LEGACY_NAMES; + + ret = ethnl_bitset32_size(data->n_priv_flags, &data->priv_flags, + NULL, data->priv_flag_names, flags); + if (ret < 0) + return ret; + len += ret; + } return len; } @@ -404,6 +476,18 @@ static int fill_features(struct sk_buff *skb, const struct settings_data *data) return 0; } +static int fill_priv_flags(struct sk_buff *skb, + const struct settings_data *data) +{ + const unsigned int bitset_flags = + (data->reqinfo_base.compact ? ETHNL_BITSET_COMPACT : 0) | + ETHNL_BITSET_LEGACY_NAMES; + + return ethnl_put_bitset32(skb, ETHA_SETTINGS_PRIV_FLAGS, + data->n_priv_flags, &data->priv_flags, NULL, + data->priv_flag_names, bitset_flags); +} + static int fill_settings(struct sk_buff *skb, const struct common_req_info *req_info) { @@ -443,10 +527,23 @@ static int fill_settings(struct sk_buff *skb, if (ret < 0) return ret; } + if (info_mask & ETH_SETTINGS_IM_PRIVFLAGS) { + ret = fill_priv_flags(skb, data); + if (ret < 0) + return ret; + } return 0; } +static void settings_cleanup(struct common_req_info *req_info) +{ + const struct settings_data *data = + container_of(req_info, struct settings_data, reqinfo_base); + + kfree(data->priv_flag_names); +} + const struct get_request_ops settings_request_ops = { .request_cmd = ETHNL_CMD_GET_SETTINGS, .reply_cmd = ETHNL_CMD_SET_SETTINGS, @@ -458,4 +555,5 @@ const struct get_request_ops settings_request_ops = { .prepare_data = prepare_settings, .reply_size = settings_size, .fill_reply = fill_settings, + .cleanup = settings_cleanup, }; -- 2.20.1