``ETHTOOL_MSG_WOL_SET``               set wake-on-lan settings
   ``ETHTOOL_MSG_FEATURES_GET``          get device features
   ``ETHTOOL_MSG_FEATURES_SET``          set device features
+  ``ETHTOOL_MSG_PRIVFLAGS_GET``         get private flags
   ===================================== ================================
 
 Kernel to userspace:
   ``ETHTOOL_MSG_FEATURES_GET_REPLY``    device features
   ``ETHTOOL_MSG_FEATURES_SET_REPLY``    optional reply to FEATURES_SET
   ``ETHTOOL_MSG_FEATURES_NTF``          netdev features notification
+  ``ETHTOOL_MSG_PRIVFLAGS_GET_REPLY``   private flags
   ===================================== =================================
 
 ``GET`` requests are sent by userspace applications to retrieve device
 or netdev_change_features().
 
 
+PRIVFLAGS_GET
+=============
+
+Gets private flags like ``ETHTOOL_GPFLAGS`` ioctl request.
+
+Request contents:
+
+  ====================================  ======  ==========================
+  ``ETHTOOL_A_PRIVFLAGS_HEADER``        nested  request header
+  ====================================  ======  ==========================
+
+Kernel response contents:
+
+  ====================================  ======  ==========================
+  ``ETHTOOL_A_PRIVFLAGS_HEADER``        nested  reply header
+  ``ETHTOOL_A_PRIVFLAGS_FLAGS``         bitset  private flags
+  ====================================  ======  ==========================
+
+``ETHTOOL_A_PRIVFLAGS_FLAGS`` is a bitset with values of device private flags.
+These flags are defined by driver, their number and names (and also meaning)
+are device dependent. For compact bitset format, names can be retrieved as
+``ETH_SS_PRIV_FLAGS`` string set. If verbose bitset format is requested,
+response uses all private flags supported by the device as mask so that client
+gets the full information without having to fetch the string set with names.
+
+
 Request translation
 ===================
 
   ``ETHTOOL_SGSO``                    ``ETHTOOL_MSG_FEATURES_SET``
   ``ETHTOOL_GFLAGS``                  ``ETHTOOL_MSG_FEATURES_GET``
   ``ETHTOOL_SFLAGS``                  ``ETHTOOL_MSG_FEATURES_SET``
-  ``ETHTOOL_GPFLAGS``                 n/a
+  ``ETHTOOL_GPFLAGS``                 ``ETHTOOL_MSG_PRIVFLAGS_GET``
   ``ETHTOOL_SPFLAGS``                 n/a
   ``ETHTOOL_GRXFH``                   n/a
   ``ETHTOOL_SRXFH``                   n/a
 
        ETHTOOL_MSG_WOL_SET,
        ETHTOOL_MSG_FEATURES_GET,
        ETHTOOL_MSG_FEATURES_SET,
+       ETHTOOL_MSG_PRIVFLAGS_GET,
 
        /* add new constants above here */
        __ETHTOOL_MSG_USER_CNT,
        ETHTOOL_MSG_FEATURES_GET_REPLY,
        ETHTOOL_MSG_FEATURES_SET_REPLY,
        ETHTOOL_MSG_FEATURES_NTF,
+       ETHTOOL_MSG_PRIVFLAGS_GET_REPLY,
 
        /* add new constants above here */
        __ETHTOOL_MSG_KERNEL_CNT,
        ETHTOOL_A_FEATURES_MAX = __ETHTOOL_A_FEATURES_CNT - 1
 };
 
+/* PRIVFLAGS */
+
+enum {
+       ETHTOOL_A_PRIVFLAGS_UNSPEC,
+       ETHTOOL_A_PRIVFLAGS_HEADER,                     /* nest - _A_HEADER_* */
+       ETHTOOL_A_PRIVFLAGS_FLAGS,                      /* bitset */
+
+       /* add new constants above here */
+       __ETHTOOL_A_PRIVFLAGS_CNT,
+       ETHTOOL_A_PRIVFLAGS_MAX = __ETHTOOL_A_PRIVFLAGS_CNT - 1
+};
+
 /* generic netlink info */
 #define ETHTOOL_GENL_NAME "ethtool"
 #define ETHTOOL_GENL_VERSION 1
 
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include "netlink.h"
+#include "common.h"
+#include "bitset.h"
+
+struct privflags_req_info {
+       struct ethnl_req_info           base;
+};
+
+struct privflags_reply_data {
+       struct ethnl_reply_data         base;
+       const char                      (*priv_flag_names)[ETH_GSTRING_LEN];
+       unsigned int                    n_priv_flags;
+       u32                             priv_flags;
+};
+
+#define PRIVFLAGS_REPDATA(__reply_base) \
+       container_of(__reply_base, struct privflags_reply_data, base)
+
+static const struct nla_policy
+privflags_get_policy[ETHTOOL_A_PRIVFLAGS_MAX + 1] = {
+       [ETHTOOL_A_PRIVFLAGS_UNSPEC]            = { .type = NLA_REJECT },
+       [ETHTOOL_A_PRIVFLAGS_HEADER]            = { .type = NLA_NESTED },
+       [ETHTOOL_A_PRIVFLAGS_FLAGS]             = { .type = NLA_REJECT },
+};
+
+static int ethnl_get_priv_flags_info(struct net_device *dev,
+                                    unsigned int *count,
+                                    const char (**names)[ETH_GSTRING_LEN])
+{
+       const struct ethtool_ops *ops = dev->ethtool_ops;
+       int nflags;
+
+       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, (u8 *)*names);
+       }
+
+       /* We can 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 privflags_prepare_data(const struct ethnl_req_info *req_base,
+                                 struct ethnl_reply_data *reply_base,
+                                 struct genl_info *info)
+{
+       struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_base);
+       struct net_device *dev = reply_base->dev;
+       const char (*names)[ETH_GSTRING_LEN];
+       const struct ethtool_ops *ops;
+       unsigned int nflags;
+       int ret;
+
+       ops = dev->ethtool_ops;
+       if (!ops->get_priv_flags || !ops->get_sset_count || !ops->get_strings)
+               return -EOPNOTSUPP;
+       ret = ethnl_ops_begin(dev);
+       if (ret < 0)
+               return ret;
+
+       ret = ethnl_get_priv_flags_info(dev, &nflags, &names);
+       if (ret < 0)
+               goto out_ops;
+       data->priv_flags = ops->get_priv_flags(dev);
+       data->priv_flag_names = names;
+       data->n_priv_flags = nflags;
+
+out_ops:
+       ethnl_ops_complete(dev);
+       return ret;
+}
+
+static int privflags_reply_size(const struct ethnl_req_info *req_base,
+                               const struct ethnl_reply_data *reply_base)
+{
+       const struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_base);
+       bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
+       const u32 all_flags = ~(u32)0 >> (32 - data->n_priv_flags);
+
+       return ethnl_bitset32_size(&data->priv_flags, &all_flags,
+                                  data->n_priv_flags,
+                                  data->priv_flag_names, compact);
+}
+
+static int privflags_fill_reply(struct sk_buff *skb,
+                               const struct ethnl_req_info *req_base,
+                               const struct ethnl_reply_data *reply_base)
+{
+       const struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_base);
+       bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
+       const u32 all_flags = ~(u32)0 >> (32 - data->n_priv_flags);
+
+       return ethnl_put_bitset32(skb, ETHTOOL_A_PRIVFLAGS_FLAGS,
+                                 &data->priv_flags, &all_flags,
+                                 data->n_priv_flags, data->priv_flag_names,
+                                 compact);
+}
+
+static void privflags_cleanup_data(struct ethnl_reply_data *reply_data)
+{
+       struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_data);
+
+       kfree(data->priv_flag_names);
+}
+
+const struct ethnl_request_ops ethnl_privflags_request_ops = {
+       .request_cmd            = ETHTOOL_MSG_PRIVFLAGS_GET,
+       .reply_cmd              = ETHTOOL_MSG_PRIVFLAGS_GET_REPLY,
+       .hdr_attr               = ETHTOOL_A_PRIVFLAGS_HEADER,
+       .max_attr               = ETHTOOL_A_PRIVFLAGS_MAX,
+       .req_info_size          = sizeof(struct privflags_req_info),
+       .reply_data_size        = sizeof(struct privflags_reply_data),
+       .request_policy         = privflags_get_policy,
+
+       .prepare_data           = privflags_prepare_data,
+       .reply_size             = privflags_reply_size,
+       .fill_reply             = privflags_fill_reply,
+       .cleanup_data           = privflags_cleanup_data,
+};