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

Reply via email to