Implement GET_SETTINGS netlink request to get link settings and link mode
information provided by ETHTOOL_GLINKSETTINGS ioctl command.

The information is divided into two parts: supported, advertised and peer
advertised link modes when ETH_SETTINGS_IM_LINKMODES flag is set in the
request and other settings when ETH_SETTINGS_IM_LINKINFO is set.

Signed-off-by: Michal Kubecek <mkube...@suse.cz>
---
 Documentation/networking/ethtool-netlink.txt |  49 +++-
 include/linux/ethtool_netlink.h              |   3 +
 include/uapi/linux/ethtool_netlink.h         |  37 +++
 net/ethtool/Makefile                         |   2 +-
 net/ethtool/common.c                         |  48 ++++
 net/ethtool/common.h                         |   4 +
 net/ethtool/ioctl.c                          |  48 ----
 net/ethtool/netlink.c                        |   9 +
 net/ethtool/settings.c                       | 265 +++++++++++++++++++
 9 files changed, 414 insertions(+), 51 deletions(-)
 create mode 100644 net/ethtool/settings.c

diff --git a/Documentation/networking/ethtool-netlink.txt 
b/Documentation/networking/ethtool-netlink.txt
index c6c7475340e2..0ea7d89c6052 100644
--- a/Documentation/networking/ethtool-netlink.txt
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -130,6 +130,8 @@ List of message types
     ETHNL_CMD_SET_STRSET               response only
     ETHNL_CMD_GET_INFO
     ETHNL_CMD_SET_INFO                 response only
+    ETHNL_CMD_GET_SETTINGS
+    ETHNL_CMD_SET_SETTINGS             response only (for now)
 
 All constants use ETHNL_CMD_ prefix, usually followed by "GET", "SET" or "ACT"
 to indicate the type.
@@ -264,6 +266,49 @@ if no PHC is associated, the attribute is not present.
 GET_INFO requests allow dumps.
 
 
+GET_SETTINGS
+------------
+
+GET_SETTINGS request retrieves information provided by ETHTOOL_GLINKSETTINGS,
+ETHTOOL_GWOL, ETHTOOL_GMSGLVL and ETHTOOL_GLINK ioctl requests. The request
+doesn't use any attributes.
+
+Request attributes:
+
+    ETHA_SETTINGS_DEV          (nested)        device identification
+    ETHA_SETTINGS_INFOMASK     (u32)           info mask
+    ETHA_SETTINGS_COMPACT      (flag)          request compact bitsets
+
+Info mask bits meaning:
+
+    ETH_SETTINGS_IM_LINKINFO           link_ksettings except link modes
+    ETH_SETTINGS_IM_LINKMODES          link modes from link_ksettings
+
+Response contents:
+
+    ETHA_SETTINGS_DEV          (nested)        device identification
+    ETHA_SETTINGS_LINK_INFO    (nested)        link settings
+        ETHA_LINKINFO_SPEED            (u32)           link speed (Mb/s)
+        ETHA_LINKINFO_DUPLEX           (u8)            duplex mode
+        ETHA_LINKINFO_PORT             (u8)            physical port
+        ETHA_LINKINFO_PHYADDR          (u8)            MDIO address of phy
+        ETHA_LINKINFO_AUTONEG          (u8)            autoneotiation status
+        ETHA_LINKINFO_TP_MDIX          (u8)            MDI(-X) status
+        ETHA_LINKINFO_TP_MDIX_CTRL     (u8)            MDI(-X) control
+        ETHA_LINKINFO_TRANSCEIVER      (u8)            transceiver
+    ETHA_SETTINGS_LINK_MODES   (bitset)        device link modes
+    ETHA_SETTINGS_PEER_MODES   (bitset)        link partner link modes
+
+Most of the attributes and their values have the same meaning as matching
+members of the corresponding ioctl structures. For ETHA_SETTINGS_LINK_MODES,
+value represents advertised modes and mask represents supported modes.
+ETHA_SETTINGS_PEER_MODES in the reply is a bit list.
+
+GET_SETTINGS requests allow dumps and messages in the same format as response
+to them are broadcasted as notifications on change of these settings using
+netlink or ioctl ethtool interface.
+
+
 Request translation
 -------------------
 
@@ -273,7 +318,7 @@ have their netlink replacement yet.
 
 ioctl command                  netlink command
 ---------------------------------------------------------------------
-ETHTOOL_GSET                   n/a
+ETHTOOL_GSET                   ETHNL_CMD_GET_SETTINGS
 ETHTOOL_SSET                   n/a
 ETHTOOL_GDRVINFO               ETHNL_CMD_GET_INFO
 ETHTOOL_GREGS                  n/a
@@ -347,7 +392,7 @@ ETHTOOL_GTUNABLE            n/a
 ETHTOOL_STUNABLE               n/a
 ETHTOOL_GPHYSTATS              n/a
 ETHTOOL_PERQUEUE               n/a
-ETHTOOL_GLINKSETTINGS          n/a
+ETHTOOL_GLINKSETTINGS          ETHNL_CMD_GET_SETTINGS
 ETHTOOL_SLINKSETTINGS          n/a
 ETHTOOL_PHY_GTUNABLE           n/a
 ETHTOOL_PHY_STUNABLE           n/a
diff --git a/include/linux/ethtool_netlink.h b/include/linux/ethtool_netlink.h
index 2a15e64a16f3..e770e6e9acca 100644
--- a/include/linux/ethtool_netlink.h
+++ b/include/linux/ethtool_netlink.h
@@ -7,6 +7,9 @@
 #include <linux/ethtool.h>
 #include <linux/netdevice.h>
 
+#define __ETHTOOL_LINK_MODE_MASK_NWORDS \
+       DIV_ROUND_UP(__ETHTOOL_LINK_MODE_MASK_NBITS, 32)
+
 enum ethtool_multicast_groups {
        ETHNL_MCGRP_MONITOR,
 };
diff --git a/include/uapi/linux/ethtool_netlink.h 
b/include/uapi/linux/ethtool_netlink.h
index 8ab2b7454e81..4e1fa4a06aac 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -12,6 +12,8 @@ enum {
        ETHNL_CMD_SET_STRSET,           /* only for reply */
        ETHNL_CMD_GET_INFO,
        ETHNL_CMD_SET_INFO,             /* only for reply */
+       ETHNL_CMD_GET_SETTINGS,
+       ETHNL_CMD_SET_SETTINGS,
 
        __ETHNL_CMD_CNT,
        ETHNL_CMD_MAX = (__ETHNL_CMD_CNT - 1)
@@ -189,6 +191,41 @@ enum {
        ETHA_TSINFO_MAX = (__ETHA_TSINFO_CNT - 1)
 };
 
+/* GET_SETTINGS / SET_SETTINGS */
+
+enum {
+       ETHA_SETTINGS_UNSPEC,
+       ETHA_SETTINGS_DEV,                      /* nest - ETHA_DEV_* */
+       ETHA_SETTINGS_INFOMASK,                 /* u32 */
+       ETHA_SETTINGS_COMPACT,                  /* flag */
+       ETHA_SETTINGS_LINK_INFO,                /* nested */
+       ETHA_SETTINGS_LINK_MODES,               /* bitset */
+       ETHA_SETTINGS_PEER_MODES,               /* bitset */
+
+       __ETHA_SETTINGS_CNT,
+       ETHA_SETTINGS_MAX = (__ETHA_SETTINGS_CNT - 1)
+};
+
+#define ETH_SETTINGS_IM_LINKINFO               0x01
+#define ETH_SETTINGS_IM_LINKMODES              0x02
+
+#define ETH_SETTINGS_IM_ALL                    0x03
+
+enum {
+       ETHA_LINKINFO_UNSPEC,
+       ETHA_LINKINFO_SPEED,                    /* u32 */
+       ETHA_LINKINFO_DUPLEX,                   /* u8 */
+       ETHA_LINKINFO_PORT,                     /* u8 */
+       ETHA_LINKINFO_PHYADDR,                  /* u8 */
+       ETHA_LINKINFO_AUTONEG,                  /* u8 */
+       ETHA_LINKINFO_TP_MDIX,                  /* u8 */
+       ETHA_LINKINFO_TP_MDIX_CTRL,             /* u8 */
+       ETHA_LINKINFO_TRANSCEIVER,              /* u8 */
+
+       __ETHA_LINKINFO_CNT,
+       ETHA_LINKINFO_MAX = (__ETHA_LINKINFO_CNT - 1)
+};
+
 /* generic netlink info */
 #define ETHTOOL_GENL_NAME "ethtool"
 #define ETHTOOL_GENL_VERSION 1
diff --git a/net/ethtool/Makefile b/net/ethtool/Makefile
index 96d41dc45d4f..6a7a182e1568 100644
--- a/net/ethtool/Makefile
+++ b/net/ethtool/Makefile
@@ -4,4 +4,4 @@ obj-y                           += ioctl.o common.o
 
 obj-$(CONFIG_ETHTOOL_NETLINK)  += ethtool_nl.o
 
-ethtool_nl-y   := netlink.o bitset.o strset.o info.o
+ethtool_nl-y   := netlink.o bitset.o strset.o info.o settings.o
diff --git a/net/ethtool/common.c b/net/ethtool/common.c
index 4616816861cc..3316e9e403c9 100644
--- a/net/ethtool/common.c
+++ b/net/ethtool/common.c
@@ -159,3 +159,51 @@ int __ethtool_get_ts_info(struct net_device *dev, struct 
ethtool_ts_info *info)
 
        return err;
 }
+
+/* return false if legacy contained non-0 deprecated fields
+ * maxtxpkt/maxrxpkt. rest of ksettings always updated
+ */
+bool
+convert_legacy_settings_to_link_ksettings(
+       struct ethtool_link_ksettings *link_ksettings,
+       const struct ethtool_cmd *legacy_settings)
+{
+       bool retval = true;
+
+       memset(link_ksettings, 0, sizeof(*link_ksettings));
+
+       /* This is used to tell users that driver is still using these
+        * deprecated legacy fields, and they should not use
+        * %ETHTOOL_GLINKSETTINGS/%ETHTOOL_SLINKSETTINGS
+        */
+       if (legacy_settings->maxtxpkt ||
+           legacy_settings->maxrxpkt)
+               retval = false;
+
+       ethtool_convert_legacy_u32_to_link_mode(
+               link_ksettings->link_modes.supported,
+               legacy_settings->supported);
+       ethtool_convert_legacy_u32_to_link_mode(
+               link_ksettings->link_modes.advertising,
+               legacy_settings->advertising);
+       ethtool_convert_legacy_u32_to_link_mode(
+               link_ksettings->link_modes.lp_advertising,
+               legacy_settings->lp_advertising);
+       link_ksettings->base.speed
+               = ethtool_cmd_speed(legacy_settings);
+       link_ksettings->base.duplex
+               = legacy_settings->duplex;
+       link_ksettings->base.port
+               = legacy_settings->port;
+       link_ksettings->base.phy_address
+               = legacy_settings->phy_address;
+       link_ksettings->base.autoneg
+               = legacy_settings->autoneg;
+       link_ksettings->base.mdio_support
+               = legacy_settings->mdio_support;
+       link_ksettings->base.eth_tp_mdix
+               = legacy_settings->eth_tp_mdix;
+       link_ksettings->base.eth_tp_mdix_ctrl
+               = legacy_settings->eth_tp_mdix_ctrl;
+       return retval;
+}
diff --git a/net/ethtool/common.h b/net/ethtool/common.h
index 02cbee79da35..7a3e0b10e69a 100644
--- a/net/ethtool/common.h
+++ b/net/ethtool/common.h
@@ -18,4 +18,8 @@ 
phy_tunable_strings[__ETHTOOL_PHY_TUNABLE_COUNT][ETH_GSTRING_LEN];
 int __ethtool_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo 
*info);
 int __ethtool_get_ts_info(struct net_device *dev, struct ethtool_ts_info 
*info);
 
+bool convert_legacy_settings_to_link_ksettings(
+       struct ethtool_link_ksettings *link_ksettings,
+       const struct ethtool_cmd *legacy_settings);
+
 #endif /* _ETHTOOL_COMMON_H */
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index 0837849156d3..5e878cf6f418 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -356,54 +356,6 @@ bool ethtool_convert_link_mode_to_legacy_u32(u32 
*legacy_u32,
 }
 EXPORT_SYMBOL(ethtool_convert_link_mode_to_legacy_u32);
 
-/* return false if legacy contained non-0 deprecated fields
- * maxtxpkt/maxrxpkt. rest of ksettings always updated
- */
-static bool
-convert_legacy_settings_to_link_ksettings(
-       struct ethtool_link_ksettings *link_ksettings,
-       const struct ethtool_cmd *legacy_settings)
-{
-       bool retval = true;
-
-       memset(link_ksettings, 0, sizeof(*link_ksettings));
-
-       /* This is used to tell users that driver is still using these
-        * deprecated legacy fields, and they should not use
-        * %ETHTOOL_GLINKSETTINGS/%ETHTOOL_SLINKSETTINGS
-        */
-       if (legacy_settings->maxtxpkt ||
-           legacy_settings->maxrxpkt)
-               retval = false;
-
-       ethtool_convert_legacy_u32_to_link_mode(
-               link_ksettings->link_modes.supported,
-               legacy_settings->supported);
-       ethtool_convert_legacy_u32_to_link_mode(
-               link_ksettings->link_modes.advertising,
-               legacy_settings->advertising);
-       ethtool_convert_legacy_u32_to_link_mode(
-               link_ksettings->link_modes.lp_advertising,
-               legacy_settings->lp_advertising);
-       link_ksettings->base.speed
-               = ethtool_cmd_speed(legacy_settings);
-       link_ksettings->base.duplex
-               = legacy_settings->duplex;
-       link_ksettings->base.port
-               = legacy_settings->port;
-       link_ksettings->base.phy_address
-               = legacy_settings->phy_address;
-       link_ksettings->base.autoneg
-               = legacy_settings->autoneg;
-       link_ksettings->base.mdio_support
-               = legacy_settings->mdio_support;
-       link_ksettings->base.eth_tp_mdix
-               = legacy_settings->eth_tp_mdix;
-       link_ksettings->base.eth_tp_mdix_ctrl
-               = legacy_settings->eth_tp_mdix_ctrl;
-       return retval;
-}
-
 /* return false if ksettings link modes had higher bits
  * set. legacy_settings always updated (best effort)
  */
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 1ff6696ad716..5166b0c28288 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -143,10 +143,12 @@ int ethnl_fill_dev(struct sk_buff *msg, struct net_device 
*dev, u16 attrtype)
 
 extern const struct get_request_ops strset_request_ops;
 extern const struct get_request_ops info_request_ops;
+extern const struct get_request_ops settings_request_ops;
 
 const struct get_request_ops *get_requests[__ETHNL_CMD_CNT] = {
        [ETHNL_CMD_GET_STRSET]          = &strset_request_ops,
        [ETHNL_CMD_GET_INFO]            = &info_request_ops,
+       [ETHNL_CMD_GET_SETTINGS]        = &settings_request_ops,
 };
 
 static struct common_req_info *alloc_get_data(const struct get_request_ops 
*ops)
@@ -572,6 +574,13 @@ static const struct genl_ops ethtool_genl_ops[] = {
                .dumpit = ethnl_get_dumpit,
                .done   = ethnl_get_done,
        },
+       {
+               .cmd    = ETHNL_CMD_GET_SETTINGS,
+               .doit   = ethnl_get_doit,
+               .start  = ethnl_get_start,
+               .dumpit = ethnl_get_dumpit,
+               .done   = ethnl_get_done,
+       },
 };
 
 static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
diff --git a/net/ethtool/settings.c b/net/ethtool/settings.c
new file mode 100644
index 000000000000..4f2858fe1a7d
--- /dev/null
+++ b/net/ethtool/settings.c
@@ -0,0 +1,265 @@
+// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
+
+#include "netlink.h"
+#include "common.h"
+#include "bitset.h"
+
+struct settings_data {
+       struct common_req_info          reqinfo_base;
+
+       /* everything below here will be reset for each device in dumps */
+       struct common_reply_data        repdata_base;
+       struct ethtool_link_ksettings   ksettings;
+       struct ethtool_link_settings    *lsettings;
+       bool                            lpm_empty;
+};
+
+static const struct nla_policy get_settings_policy[ETHA_SETTINGS_MAX + 1] = {
+       [ETHA_SETTINGS_UNSPEC]          = { .type = NLA_REJECT },
+       [ETHA_SETTINGS_DEV]             = { .type = NLA_NESTED },
+       [ETHA_SETTINGS_INFOMASK]        = { .type = NLA_U32 },
+       [ETHA_SETTINGS_COMPACT]         = { .type = NLA_FLAG },
+       [ETHA_SETTINGS_LINK_INFO]       = { .type = NLA_REJECT },
+       [ETHA_SETTINGS_LINK_MODES]      = { .type = NLA_REJECT },
+       [ETHA_SETTINGS_PEER_MODES]      = { .type = NLA_REJECT },
+};
+
+static int parse_settings(struct common_req_info *req_info,
+                         struct sk_buff *skb, struct genl_info *info,
+                         const struct nlmsghdr *nlhdr)
+{
+       struct nlattr *tb[ETHA_SETTINGS_MAX + 1];
+       int ret;
+
+       ret = genlmsg_parse(nlhdr, &ethtool_genl_family, tb,
+                           ETHA_SETTINGS_MAX, get_settings_policy,
+                           info ? info->extack : NULL);
+       if (ret < 0)
+               return ret;
+
+       if (tb[ETHA_SETTINGS_DEV]) {
+               req_info->dev = ethnl_dev_get(info, tb[ETHA_SETTINGS_DEV]);
+               if (IS_ERR(req_info->dev)) {
+                       ret = PTR_ERR(req_info->dev);
+                       req_info->dev = NULL;
+                       return ret;
+               }
+       }
+       if (tb[ETHA_SETTINGS_INFOMASK])
+               req_info->req_mask = nla_get_u32(tb[ETHA_SETTINGS_INFOMASK]);
+       if (tb[ETHA_SETTINGS_COMPACT])
+               req_info->compact = true;
+       if (req_info->req_mask == 0)
+               req_info->req_mask = ETH_SETTINGS_IM_ALL;
+
+       return 0;
+}
+
+static int ethnl_get_link_ksettings(struct genl_info *info,
+                                   struct net_device *dev,
+                                   struct ethtool_link_ksettings *ksettings)
+{
+       int ret;
+
+       ret = __ethtool_get_link_ksettings(dev, ksettings);
+
+       if (ret < 0)
+               ETHNL_SET_ERRMSG(info, "failed to retrieve link settings");
+       return ret;
+}
+
+static int prepare_settings(struct common_req_info *req_info,
+                           struct genl_info *info)
+{
+       struct settings_data *data =
+               container_of(req_info, struct settings_data, reqinfo_base);
+       struct net_device *dev = data->repdata_base.dev;
+       u32 req_mask = req_info->req_mask;
+       int ret;
+
+       data->lsettings = &data->ksettings.base;
+       data->lpm_empty = true;
+
+       ret = ethnl_before_ops(dev);
+       if (ret < 0)
+               return ret;
+       if (req_mask & (ETH_SETTINGS_IM_LINKINFO | ETH_SETTINGS_IM_LINKMODES)) {
+               ret = ethnl_get_link_ksettings(info, dev, &data->ksettings);
+               if (ret < 0)
+                       req_mask &= ~(ETH_SETTINGS_IM_LINKINFO |
+                                     ETH_SETTINGS_IM_LINKMODES);
+       }
+       if (req_mask & ETH_SETTINGS_IM_LINKMODES) {
+               data->lpm_empty =
+                       bitmap_empty(data->ksettings.link_modes.lp_advertising,
+                                    __ETHTOOL_LINK_MODE_MASK_NBITS);
+               ethnl_bitmap_to_u32(data->ksettings.link_modes.supported,
+                                   __ETHTOOL_LINK_MODE_MASK_NWORDS);
+               ethnl_bitmap_to_u32(data->ksettings.link_modes.advertising,
+                                   __ETHTOOL_LINK_MODE_MASK_NWORDS);
+               ethnl_bitmap_to_u32(data->ksettings.link_modes.lp_advertising,
+                                   __ETHTOOL_LINK_MODE_MASK_NWORDS);
+       }
+       ethnl_after_ops(dev);
+
+       data->repdata_base.info_mask = req_mask;
+       if (req_info->req_mask & ~req_mask)
+               warn_partial_info(info);
+       return 0;
+}
+
+static int link_info_size(void)
+{
+       int len = 0;
+
+       /* speed */
+       len += nla_total_size(sizeof(u32));
+       /* duplex, autoneg, port, phyaddr, mdix, mdixctrl, transcvr */
+       len += 7 * nla_total_size(sizeof(u8));
+       /* mdio_support */
+       len += nla_total_size(sizeof(struct nla_bitfield32));
+
+       /* nest */
+       return nla_total_size(len);
+}
+
+static int link_modes_size(const struct ethtool_link_ksettings *ksettings,
+                          bool compact)
+{
+       unsigned int flags = compact ? ETHNL_BITSET_COMPACT : 0;
+       u32 *supported = (u32 *)ksettings->link_modes.supported;
+       u32 *advertising = (u32 *)ksettings->link_modes.advertising;
+       u32 *lp_advertising = (u32 *)ksettings->link_modes.lp_advertising;
+       int len = 0, ret;
+
+       ret = ethnl_bitset32_size(__ETHTOOL_LINK_MODE_MASK_NBITS, advertising,
+                                 supported, link_mode_names, flags);
+       if (ret < 0)
+               return ret;
+       len += ret;
+       ret = ethnl_bitset32_size(__ETHTOOL_LINK_MODE_MASK_NBITS,
+                                 lp_advertising, NULL, link_mode_names,
+                                 flags & ETHNL_BITSET_LIST);
+       if (ret < 0)
+               return ret;
+       len += ret;
+
+       return len;
+}
+
+/* To keep things simple, reserve space for some attributes which may not
+ * be added to the message (e.g. ETHA_SETTINGS_SOPASS); therefore the length
+ * returned may be bigger than the actual length of the message sent
+ */
+static int settings_size(const struct common_req_info *req_info)
+{
+       struct settings_data *data =
+               container_of(req_info, struct settings_data, reqinfo_base);
+       u32 info_mask = data->repdata_base.info_mask;
+       bool compact = req_info->compact;
+       int len = 0, ret;
+
+       len += dev_ident_size();
+       if (info_mask & ETH_SETTINGS_IM_LINKINFO)
+               len += link_info_size();
+       if (info_mask & ETH_SETTINGS_IM_LINKMODES) {
+               ret = link_modes_size(&data->ksettings, compact);
+               if (ret < 0)
+                       return ret;
+               len += ret;
+       }
+
+       return len;
+}
+
+static int fill_link_info(struct sk_buff *skb,
+                         const struct ethtool_link_settings *lsettings)
+{
+       struct nlattr *nest = ethnl_nest_start(skb, ETHA_SETTINGS_LINK_INFO);
+
+       if (!nest)
+               return -EMSGSIZE;
+       if (nla_put_u32(skb, ETHA_LINKINFO_SPEED, lsettings->speed) ||
+           nla_put_u8(skb, ETHA_LINKINFO_DUPLEX, lsettings->duplex) ||
+           nla_put_u8(skb, ETHA_LINKINFO_PORT, lsettings->port) ||
+           nla_put_u8(skb, ETHA_LINKINFO_PHYADDR,
+                      lsettings->phy_address) ||
+           nla_put_u8(skb, ETHA_LINKINFO_AUTONEG,
+                      lsettings->autoneg) ||
+           nla_put_u8(skb, ETHA_LINKINFO_TP_MDIX,
+                      lsettings->eth_tp_mdix) ||
+           nla_put_u8(skb, ETHA_LINKINFO_TP_MDIX_CTRL,
+                      lsettings->eth_tp_mdix_ctrl) ||
+           nla_put_u8(skb, ETHA_LINKINFO_TRANSCEIVER,
+                      lsettings->transceiver)) {
+               nla_nest_cancel(skb, nest);
+               return -EMSGSIZE;
+       }
+
+       nla_nest_end(skb, nest);
+       return 0;
+}
+
+static int fill_link_modes(struct sk_buff *skb,
+                          const struct ethtool_link_ksettings *ksettings,
+                          bool lpm_empty, bool compact)
+{
+       const u32 *supported = (const u32 *)ksettings->link_modes.supported;
+       const u32 *advertising = (const u32 *)ksettings->link_modes.advertising;
+       const u32 *lp_adv = (const u32 *)ksettings->link_modes.lp_advertising;
+       const unsigned int flags = compact ? ETHNL_BITSET_COMPACT : 0;
+       int ret;
+
+       ret = ethnl_put_bitset32(skb, ETHA_SETTINGS_LINK_MODES,
+                                __ETHTOOL_LINK_MODE_MASK_NBITS, advertising,
+                                supported, link_mode_names, flags);
+       if (ret < 0)
+               return ret;
+       if (!lpm_empty) {
+               ret = ethnl_put_bitset32(skb, ETHA_SETTINGS_PEER_MODES,
+                                        __ETHTOOL_LINK_MODE_MASK_NBITS,
+                                        lp_adv, NULL, link_mode_names,
+                                        flags | ETHNL_BITSET_LIST);
+               if (ret < 0)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static int fill_settings(struct sk_buff *skb,
+                        const struct common_req_info *req_info)
+{
+       const struct settings_data *data =
+               container_of(req_info, struct settings_data, reqinfo_base);
+       u32 info_mask = data->repdata_base.info_mask;
+       bool compact = req_info->compact;
+       int ret;
+
+       if (info_mask & ETH_SETTINGS_IM_LINKINFO) {
+               ret = fill_link_info(skb, data->lsettings);
+               if (ret < 0)
+                       return ret;
+       }
+       if (info_mask & ETH_SETTINGS_IM_LINKMODES) {
+               ret = fill_link_modes(skb, &data->ksettings, data->lpm_empty,
+                                     compact);
+               if (ret < 0)
+                       return ret;
+       }
+
+       return 0;
+}
+
+const struct get_request_ops settings_request_ops = {
+       .request_cmd            = ETHNL_CMD_GET_SETTINGS,
+       .reply_cmd              = ETHNL_CMD_SET_SETTINGS,
+       .dev_attrtype           = ETHA_SETTINGS_DEV,
+       .data_size              = sizeof(struct settings_data),
+       .repdata_offset         = offsetof(struct settings_data, repdata_base),
+
+       .parse_request          = parse_settings,
+       .prepare_data           = prepare_settings,
+       .reply_size             = settings_size,
+       .fill_reply             = fill_settings,
+};
-- 
2.20.1

Reply via email to