Requests the information provided by ETHTOOL_GLINKSETTINGS, ETHTOOL_GWOL
and ETHTOOL_GMSGLVL. The info_mask header field can be used to request only
part of the information. Flag ETH_SETTINGS_RF_COMPACT_BITSETS switches
between flag-by-flag list and compact bitmaps for link modes in the reply.

Signed-off-by: Michal Kubecek <mkube...@suse.cz>
---
 Documentation/networking/ethtool-netlink.txt |  62 +++++-
 include/linux/ethtool_netlink.h              |   3 +
 include/linux/netdevice.h                    |   2 +
 include/uapi/linux/ethtool.h                 |   3 +
 include/uapi/linux/ethtool_netlink.h         |  36 ++++
 net/core/ethtool.c                           | 108 +---------
 net/core/ethtool_common.c                    | 112 ++++++++++
 net/core/ethtool_common.h                    |   8 +
 net/core/ethtool_netlink.c                   | 299 +++++++++++++++++++++++++++
 9 files changed, 528 insertions(+), 105 deletions(-)

diff --git a/Documentation/networking/ethtool-netlink.txt 
b/Documentation/networking/ethtool-netlink.txt
index cb992180b211..7aabc87c9f09 100644
--- a/Documentation/networking/ethtool-netlink.txt
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -109,6 +109,8 @@ List of message types
 
     ETHTOOL_CMD_GET_DRVINFO
     ETHTOOL_CMD_SET_DRVINFO            response only
+    ETHTOOL_CMD_GET_SETTINGS
+    ETHTOOL_CMD_SET_SETTINGS           response only (for now)
 
 All constants use ETHTOOL_CMD_ prefix followed by "GET", "SET" or "ACT" to
 indicate the type.
@@ -147,6 +149,56 @@ response.
 All information is read only, SET_DRVINFO request is not implemented.
 
 
+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.
+
+Header flags:
+    ETH_SETTINGS_RF_COMPACT_BITSETS    bitset form in response (1 is compact)
+
+Header info_mask bits:
+    ETH_SETTINGS_IM_LINKINFO           link_ksettings except link modes
+    ETH_SETTINGS_IM_LINKMODES          link modes from link_ksettings
+    ETH_SETTINGS_IM_MSGLEVEL           msglevel
+    ETH_SETTINGS_IM_WOLINFO            struct ethtool_wolinfo
+    ETH_SETTINGS_IM_LINK               link state
+
+Zero info_mask
+
+Response contents:
+
+    ETHA_SETTINGS_SPEED                (u32)           link speed (Mb/s)
+    ETHA_SETTINGS_DUPLEX       (u8)            duplex mode
+    ETHA_SETTINGS_PORT         (u8)            physical port
+    ETHA_SETTINGS_PHYADDR      (u8)            MDIO address of phy
+    ETHA_SETTINGS_AUTONEG      (u8)            autoneotiation status
+    ETHA_SETTINGS_MDIO_SUPPORT (bitfield32)    MDIO support flags
+    ETHA_SETTINGS_TP_MDIX      (u8)            MDI(-X) status
+    ETHA_SETTINGS_TP_MDIX_CTRL (u8)            MDI(-X) control
+    ETHA_SETTINGS_TRANSCEIVER  (u8)            transceiver
+    ETHA_SETTINGS_WOL_MODES    (bitfield32)    wake-on-lan modes
+    ETHA_SETTINGS_SOPASS       (binary)        SecureOn(tm) password
+    ETHA_SETTINGS_MSGLVL       (bitfield32)    debug level
+    ETHA_SETTINGS_LINK_MODES   (bitset)        device link modes
+    ETHA_SETTINGS_PEER_MODES   (bitset)        link partner link modes
+    ETHA_SETTINGS_LINK         (u32)           link state
+
+Most of the attributes have the same meaning (including values) as
+corresponding members of ioctl structures. For ETHA_SETTINGS_MDIO_SUPPORT and
+ETHA_SETTINGS_MSGLVL, selector reports flags supported by kernel. For
+ETHA_SETTINGS_WOL_MODES it reports flags supported by the device. For
+ETHA_SETTINGS_LINK_MODES, value represent advertised modes and mask represents
+supported modes. For ETHA_SETTINGS_PEER_MODES, both value and mask represent
+partner advertised link modes.
+
+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.
+
+
 Request translation
 -------------------
 
@@ -156,16 +208,16 @@ have their netlink replacement yet.
 
 ioctl command                  netlink command
 ---------------------------------------------------------------------
-ETHTOOL_GSET                   n/a
+ETHTOOL_GSET                   ETHTOOL_CMD_GET_SETTINGS
 ETHTOOL_SSET                   n/a
 ETHTOOL_GDRVINFO               ETHTOOL_CMD_GET_DRVINFO
 ETHTOOL_GREGS                  n/a
-ETHTOOL_GWOL                   n/a
+ETHTOOL_GWOL                   ETHTOOL_CMD_GET_SETTINGS
 ETHTOOL_SWOL                   n/a
-ETHTOOL_GMSGLVL                        n/a
+ETHTOOL_GMSGLVL                        ETHTOOL_CMD_GET_SETTINGS
 ETHTOOL_SMSGLVL                        n/a
 ETHTOOL_NWAY_RST               n/a
-ETHTOOL_GLINK                  n/a
+ETHTOOL_GLINK                  ETHTOOL_CMD_GET_SETTINGS
 ETHTOOL_GEEPROM                        n/a
 ETHTOOL_SEEPROM                        n/a
 ETHTOOL_GCOALESCE              n/a
@@ -230,7 +282,7 @@ ETHTOOL_GTUNABLE            n/a
 ETHTOOL_STUNABLE               n/a
 ETHTOOL_GPHYSTATS              n/a
 ETHTOOL_PERQUEUE               n/a
-ETHTOOL_GLINKSETTINGS          n/a
+ETHTOOL_GLINKSETTINGS          ETHTOOL_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 0412adb4f42f..1787359f9e5d 100644
--- a/include/linux/ethtool_netlink.h
+++ b/include/linux/ethtool_netlink.h
@@ -6,4 +6,7 @@
 #include <uapi/linux/ethtool_netlink.h>
 #include <linux/ethtool.h>
 
+#define __ETHTOOL_LINK_MODE_MASK_NWORDS \
+       ((__ETHTOOL_LINK_MODE_MASK_NBITS + 31) / 32)
+
 #endif /* _LINUX_ETHTOOL_NETLINK_H_ */
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index cc4ce7456e38..0e1d0a04c3cc 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -3506,6 +3506,8 @@ enum {
        NETIF_MSG_PKTDATA       = 0x1000,
        NETIF_MSG_HW            = 0x2000,
        NETIF_MSG_WOL           = 0x4000,
+
+       NETIF_MSG_ALL           = 0x7fff,
 };
 
 #define netif_msg_drv(p)       ((p)->msg_enable & NETIF_MSG_DRV)
diff --git a/include/uapi/linux/ethtool.h b/include/uapi/linux/ethtool.h
index 44a0b675a6bc..a9076a76cdb4 100644
--- a/include/uapi/linux/ethtool.h
+++ b/include/uapi/linux/ethtool.h
@@ -143,6 +143,9 @@ static inline __u32 ethtool_cmd_speed(const struct 
ethtool_cmd *ep)
  */
 #define ETH_MDIO_SUPPORTS_C45  2
 
+/* All defined ETH_MDIO_SUPPORTS_* flags */
+#define ETH_MDIO_SUPPORTS_ALL (ETH_MDIO_SUPPORTS_C22 | ETH_MDIO_SUPPORTS_C45)
+
 #define ETHTOOL_FWVERS_LEN     32
 #define ETHTOOL_BUSINFO_LEN    32
 #define ETHTOOL_EROMVERS_LEN   32
diff --git a/include/uapi/linux/ethtool_netlink.h 
b/include/uapi/linux/ethtool_netlink.h
index d6ab1d73d494..9520d13fc9ab 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -23,6 +23,8 @@ enum {
        ETHTOOL_CMD_NOOP,
        ETHTOOL_CMD_GET_DRVINFO,
        ETHTOOL_CMD_SET_DRVINFO,        /* only for reply */
+       ETHTOOL_CMD_GET_SETTINGS,
+       ETHTOOL_CMD_SET_SETTINGS,
 
        __ETHTOOL_CMD_MAX,
        ETHTOOL_CMD_MAX = (__ETHTOOL_CMD_MAX - 1),
@@ -78,6 +80,40 @@ enum {
        ETHA_DRVINFO_MAX = (__ETHA_DRVINFO_MAX - 1),
 };
 
+/* GET_SETTINGS / SET_SETTINGS */
+
+enum {
+       ETHA_SETTINGS_UNSPEC,
+       ETHA_SETTINGS_SPEED,                    /* u32 */
+       ETHA_SETTINGS_DUPLEX,                   /* u8 */
+       ETHA_SETTINGS_PORT,                     /* u8 */
+       ETHA_SETTINGS_PHYADDR,                  /* u8 */
+       ETHA_SETTINGS_AUTONEG,                  /* u8 */
+       ETHA_SETTINGS_MDIO_SUPPORT,             /* bitfield32 */
+       ETHA_SETTINGS_TP_MDIX,                  /* u8 */
+       ETHA_SETTINGS_TP_MDIX_CTRL,             /* u8 */
+       ETHA_SETTINGS_TRANSCEIVER,              /* u8 */
+       ETHA_SETTINGS_WOL_MODES,                /* bitfield32 */
+       ETHA_SETTINGS_SOPASS,                   /* binary */
+       ETHA_SETTINGS_MSGLVL,                   /* bitfield32 */
+       ETHA_SETTINGS_LINK_MODES,               /* bitset */
+       ETHA_SETTINGS_PEER_MODES,               /* bitset */
+       ETHA_SETTINGS_LINK,                     /* u32 */
+
+       __ETHA_SETTINGS_MAX,
+       ETHA_SETTINGS_MAX = (__ETHA_SETTINGS_MAX - 1),
+};
+
+#define ETH_SETTINGS_RF_COMPACT_BITSETS                0x1
+
+#define ETH_SETTINGS_IM_LINKINFO               0x01
+#define ETH_SETTINGS_IM_LINKMODES              0x02
+#define ETH_SETTINGS_IM_MSGLEVEL               0x04
+#define ETH_SETTINGS_IM_WOLINFO                        0x08
+#define ETH_SETTINGS_IM_LINK                   0x10
+
+#define ETH_SETTINGS_IM_DEFAULT                        0x1f
+
 /* generic netlink info */
 #define ETHTOOL_GENL_NAME "ethtool"
 #define ETHTOOL_GENL_VERSION 1
diff --git a/net/core/ethtool.c b/net/core/ethtool.c
index 09e780a748f9..b08a2efa2e89 100644
--- a/net/core/ethtool.c
+++ b/net/core/ethtool.c
@@ -452,54 +452,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)
  */
@@ -560,50 +512,6 @@ struct ethtool_link_usettings {
        } link_modes;
 };
 
-/* Internal kernel helper to query a device ethtool_link_settings.
- *
- * Backward compatibility note: for compatibility with legacy drivers
- * that implement only the ethtool_cmd API, this has to work with both
- * drivers implementing get_link_ksettings API and drivers
- * implementing get_settings API. When drivers implement get_settings
- * and report ethtool_cmd deprecated fields
- * (transceiver/maxrxpkt/maxtxpkt), these fields are silently ignored
- * because the resulting struct ethtool_link_settings does not report them.
- */
-int __ethtool_get_link_ksettings(struct net_device *dev,
-                                struct ethtool_link_ksettings *link_ksettings)
-{
-       int err;
-       struct ethtool_cmd cmd;
-
-       ASSERT_RTNL();
-
-       if (dev->ethtool_ops->get_link_ksettings) {
-               memset(link_ksettings, 0, sizeof(*link_ksettings));
-               return dev->ethtool_ops->get_link_ksettings(dev,
-                                                           link_ksettings);
-       }
-
-       /* driver doesn't support %ethtool_link_ksettings API. revert to
-        * legacy %ethtool_cmd API, unless it's not supported either.
-        * TODO: remove when ethtool_ops::get_settings disappears internally
-        */
-       if (!dev->ethtool_ops->get_settings)
-               return -EOPNOTSUPP;
-
-       memset(&cmd, 0, sizeof(cmd));
-       cmd.cmd = ETHTOOL_GSET;
-       err = dev->ethtool_ops->get_settings(dev, &cmd);
-       if (err < 0)
-               return err;
-
-       /* we ignore deprecated fields transceiver/maxrxpkt/maxtxpkt
-        */
-       convert_legacy_settings_to_link_ksettings(link_ksettings, &cmd);
-       return err;
-}
-EXPORT_SYMBOL(__ethtool_get_link_ksettings);
-
 /* convert ethtool_link_usettings in user space to a kernel internal
  * ethtool_link_ksettings. return 0 on success, errno on error.
  */
@@ -1437,11 +1345,11 @@ static int ethtool_reset(struct net_device *dev, char 
__user *useraddr)
 static int ethtool_get_wol(struct net_device *dev, char __user *useraddr)
 {
        struct ethtool_wolinfo wol = { .cmd = ETHTOOL_GWOL };
+       int rc;
 
-       if (!dev->ethtool_ops->get_wol)
-               return -EOPNOTSUPP;
-
-       dev->ethtool_ops->get_wol(dev, &wol);
+       rc = __ethtool_get_wol(dev, &wol);
+       if (rc < 0)
+               return rc;
 
        if (copy_to_user(useraddr, &wol, sizeof(wol)))
                return -EFAULT;
@@ -1506,12 +1414,12 @@ static int ethtool_nway_reset(struct net_device *dev)
 static int ethtool_get_link(struct net_device *dev, char __user *useraddr)
 {
        struct ethtool_value edata = { .cmd = ETHTOOL_GLINK };
+       int link = __ethtool_get_link(dev);
 
-       if (!dev->ethtool_ops->get_link)
-               return -EOPNOTSUPP;
-
-       edata.data = netif_running(dev) && dev->ethtool_ops->get_link(dev);
+       if (link < 0)
+               return link;
 
+       edata.data = link;
        if (copy_to_user(useraddr, &edata, sizeof(edata)))
                return -EFAULT;
        return 0;
diff --git a/net/core/ethtool_common.c b/net/core/ethtool_common.c
index 2c0abab0e43c..30bc2b14cf2a 100644
--- a/net/core/ethtool_common.c
+++ b/net/core/ethtool_common.c
@@ -44,3 +44,115 @@ int __ethtool_get_drvinfo(struct net_device *dev, struct 
ethtool_drvinfo *info)
        return 0;
 }
 EXPORT_SYMBOL(__ethtool_get_drvinfo);
+
+/* 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;
+}
+
+/* Internal kernel helper to query a device ethtool_link_settings.
+ *
+ * Backward compatibility note: for compatibility with legacy drivers
+ * that implement only the ethtool_cmd API, this has to work with both
+ * drivers implementing get_link_ksettings API and drivers
+ * implementing get_settings API. When drivers implement get_settings
+ * and report ethtool_cmd deprecated fields
+ * (transceiver/maxrxpkt/maxtxpkt), these fields are silently ignored
+ * because the resulting struct ethtool_link_settings does not report them.
+ */
+int __ethtool_get_link_ksettings(struct net_device *dev,
+                                struct ethtool_link_ksettings *link_ksettings)
+{
+       int err;
+       struct ethtool_cmd cmd;
+
+       ASSERT_RTNL();
+
+       if (dev->ethtool_ops->get_link_ksettings) {
+               memset(link_ksettings, 0, sizeof(*link_ksettings));
+               return dev->ethtool_ops->get_link_ksettings(dev,
+                                                           link_ksettings);
+       }
+
+       /* driver doesn't support %ethtool_link_ksettings API. revert to
+        * legacy %ethtool_cmd API, unless it's not supported either.
+        * TODO: remove when ethtool_ops::get_settings disappears internally
+        */
+       if (!dev->ethtool_ops->get_settings)
+               return -EOPNOTSUPP;
+
+       memset(&cmd, 0, sizeof(cmd));
+       cmd.cmd = ETHTOOL_GSET;
+       err = dev->ethtool_ops->get_settings(dev, &cmd);
+       if (err < 0)
+               return err;
+
+       /* we ignore deprecated fields transceiver/maxrxpkt/maxtxpkt
+        */
+       convert_legacy_settings_to_link_ksettings(link_ksettings, &cmd);
+       return err;
+}
+EXPORT_SYMBOL(__ethtool_get_link_ksettings);
+
+int __ethtool_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
+{
+       if (!dev->ethtool_ops->get_wol)
+               return -EOPNOTSUPP;
+
+       dev->ethtool_ops->get_wol(dev, wol);
+
+       return 0;
+}
+EXPORT_SYMBOL(__ethtool_get_wol);
+
+int __ethtool_get_link(struct net_device *dev)
+{
+       if (!dev->ethtool_ops->get_link)
+               return -EOPNOTSUPP;
+
+       return netif_running(dev) && dev->ethtool_ops->get_link(dev);
+}
+EXPORT_SYMBOL(__ethtool_get_link);
diff --git a/net/core/ethtool_common.h b/net/core/ethtool_common.h
index 1f031c1d943a..92e236952f18 100644
--- a/net/core/ethtool_common.h
+++ b/net/core/ethtool_common.h
@@ -7,5 +7,13 @@
 #include <linux/ethtool.h>
 
 int __ethtool_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo 
*info);
+int __ethtool_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol);
+int __ethtool_get_link_ksettings(struct net_device *dev,
+                                struct ethtool_link_ksettings *link_ksettings);
+int __ethtool_get_link(struct net_device *dev);
+
+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/core/ethtool_netlink.c b/net/core/ethtool_netlink.c
index 077814fd36bd..0c2bed7850bc 100644
--- a/net/core/ethtool_netlink.c
+++ b/net/core/ethtool_netlink.c
@@ -4,11 +4,69 @@
 #include <linux/ethtool_netlink.h>
 #include <linux/netdevice.h>
 #include <linux/bitmap.h>
+#include <linux/rtnetlink.h>
 #include <net/genetlink.h>
 #include "ethtool_common.h"
 
 static struct genl_family ethtool_genl_family;
 
+/* dictionary */
+
+static const char *const link_mode_names[] = {
+       [ETHTOOL_LINK_MODE_10baseT_Half_BIT]            = "10baseT/Half",
+       [ETHTOOL_LINK_MODE_10baseT_Full_BIT]            = "10baseT/Full",
+       [ETHTOOL_LINK_MODE_100baseT_Half_BIT]           = "100baseT/Half",
+       [ETHTOOL_LINK_MODE_100baseT_Full_BIT]           = "100baseT/Full",
+       [ETHTOOL_LINK_MODE_1000baseT_Half_BIT]          = "1000baseT/Half",
+       [ETHTOOL_LINK_MODE_1000baseT_Full_BIT]          = "1000baseT/Full",
+       [ETHTOOL_LINK_MODE_Autoneg_BIT]                 = "Autoneg",
+       [ETHTOOL_LINK_MODE_TP_BIT]                      = "TP",
+       [ETHTOOL_LINK_MODE_AUI_BIT]                     = "AUI",
+       [ETHTOOL_LINK_MODE_MII_BIT]                     = "MII",
+       [ETHTOOL_LINK_MODE_FIBRE_BIT]                   = "FIBRE",
+       [ETHTOOL_LINK_MODE_BNC_BIT]                     = "BNC",
+       [ETHTOOL_LINK_MODE_10000baseT_Full_BIT]         = "10000baseT/Full",
+       [ETHTOOL_LINK_MODE_Pause_BIT]                   = "Pause",
+       [ETHTOOL_LINK_MODE_Asym_Pause_BIT]              = "Asym_Pause",
+       [ETHTOOL_LINK_MODE_2500baseX_Full_BIT]          = "2500baseX/Full",
+       [ETHTOOL_LINK_MODE_Backplane_BIT]               = "Backplane",
+       [ETHTOOL_LINK_MODE_1000baseKX_Full_BIT]         = "1000baseKX/Full",
+       [ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT]       = "10000baseKX4/Full",
+       [ETHTOOL_LINK_MODE_10000baseKR_Full_BIT]        = "10000baseKR/Full",
+       [ETHTOOL_LINK_MODE_10000baseR_FEC_BIT]          = "10000baseR/FEC",
+       [ETHTOOL_LINK_MODE_20000baseMLD2_Full_BIT]      = "20000baseMLD2/Full",
+       [ETHTOOL_LINK_MODE_20000baseKR2_Full_BIT]       = "20000baseKR2/Full",
+       [ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT]       = "40000baseKR4/Full",
+       [ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT]       = "40000baseCR4/Full",
+       [ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT]       = "40000baseSR4/Full",
+       [ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT]       = "40000baseLR4/Full",
+       [ETHTOOL_LINK_MODE_56000baseKR4_Full_BIT]       = "56000baseKR4/Full",
+       [ETHTOOL_LINK_MODE_56000baseCR4_Full_BIT]       = "56000baseCR4/Full",
+       [ETHTOOL_LINK_MODE_56000baseSR4_Full_BIT]       = "56000baseSR4/Full",
+       [ETHTOOL_LINK_MODE_56000baseLR4_Full_BIT]       = "56000baseLR4/Full",
+       [ETHTOOL_LINK_MODE_25000baseCR_Full_BIT]        = "25000baseCR/Full",
+       [ETHTOOL_LINK_MODE_25000baseKR_Full_BIT]        = "25000baseKR/Full",
+       [ETHTOOL_LINK_MODE_25000baseSR_Full_BIT]        = "25000baseSR/Full",
+       [ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT]       = "50000baseCR2/Full",
+       [ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT]       = "50000baseKR2/Full",
+       [ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT]      = "100000baseKR4/Full",
+       [ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT]      = "100000baseSR4/Full",
+       [ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT]      = "100000baseCR4/Full",
+       [ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT]  = 
"100000baseLR4/ER4_Full",
+       [ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT]       = "50000baseSR2/Full",
+       [ETHTOOL_LINK_MODE_1000baseX_Full_BIT]          = "1000baseX/Full",
+       [ETHTOOL_LINK_MODE_10000baseCR_Full_BIT]        = "10000baseCR/Full",
+       [ETHTOOL_LINK_MODE_10000baseSR_Full_BIT]        = "10000baseSR/Full",
+       [ETHTOOL_LINK_MODE_10000baseLR_Full_BIT]        = "10000baseLR/Full",
+       [ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT]       = "10000baseLRM/Full",
+       [ETHTOOL_LINK_MODE_10000baseER_Full_BIT]        = "10000baseER/Full",
+       [ETHTOOL_LINK_MODE_2500baseT_Full_BIT]          = "2500baseT/Full",
+       [ETHTOOL_LINK_MODE_5000baseT_Full_BIT]          = "5000baseT/Full",
+       [ETHTOOL_LINK_MODE_FEC_NONE_BIT]                = "None",
+       [ETHTOOL_LINK_MODE_FEC_RS_BIT]                  = "RS",
+       [ETHTOOL_LINK_MODE_FEC_BASER_BIT]               = "BASER",
+};
+
 /* misc helper functions */
 
 static int ethnl_str_size(const char *s)
@@ -602,6 +660,243 @@ static int ethnl_get_drvinfo(struct sk_buff *skb, struct 
genl_info *info)
        return -EMSGSIZE;
 }
 
+/* GET_SETTINGS */
+
+/* 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 ethnl_settings_size(struct ethnlmsghdr *ehdr,
+                              struct ethtool_link_ksettings *ksettings,
+                              struct net_device *dev, u16 req_mask)
+{
+       size_t len = 0;
+       int rc = 0;
+
+       if (req_mask & ETH_SETTINGS_IM_LINKINFO) {
+               /* 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));
+       }
+       if (req_mask & ETH_SETTINGS_IM_LINKMODES) {
+               u32 *supported = (u32 *)ksettings->link_modes.supported;
+               u32 *advertising = (u32 *)ksettings->link_modes.advertising;
+               u32 *lp_advertising =
+                       (u32 *)ksettings->link_modes.lp_advertising;
+               bool compact = ehdr->flags & ETH_SETTINGS_RF_COMPACT_BITSETS;
+
+               rc = ethnl_bitset_size(compact, __ETHTOOL_LINK_MODE_MASK_NBITS,
+                                      advertising, supported, link_mode_names);
+               if (rc < 0)
+                       return rc;
+               len += rc;
+               rc = ethnl_bitset_size(compact, __ETHTOOL_LINK_MODE_MASK_NBITS,
+                                      lp_advertising, lp_advertising,
+                                      link_mode_names);
+               if (rc < 0)
+                       return rc;
+               len += rc;
+       }
+       if (req_mask & ETH_SETTINGS_IM_MSGLEVEL)
+               len += nla_total_size(sizeof(struct nla_bitfield32));
+       if (req_mask & ETH_SETTINGS_IM_WOLINFO) {
+               /* wolopts / wol_supported */
+               len += nla_total_size(sizeof(struct nla_bitfield32));
+               /* sopass */
+               len += nla_total_size(SOPASS_MAX);
+       }
+       if (req_mask & ETH_SETTINGS_IM_LINK)
+               len += nla_total_size(sizeof(u32));
+
+       return len;
+}
+
+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)
+               GENL_SET_ERR_MSG(info, "failed to retrieve link settings");
+       return ret;
+}
+
+static int ethnl_get_wol(struct genl_info *info, struct net_device *dev,
+                        struct ethtool_wolinfo *wolinfo)
+{
+       int ret = __ethtool_get_wol(dev, wolinfo);
+
+       if (ret < 0)
+               GENL_SET_ERR_MSG(info, "failed to retrieve wol info");
+       return ret;
+}
+
+static int ethnl_get_settings(struct sk_buff *skb, struct genl_info *info)
+{
+       struct ethtool_link_ksettings ksettings = {};
+       struct ethtool_link_settings *lsettings;
+       struct ethnlmsghdr *ehdr;
+       struct ethtool_wolinfo wolinfo = {};
+       struct net_device *dev;
+       struct sk_buff *rskb;
+       unsigned int reply_len;
+       bool lpm_empty = true;
+       u16 req_flags;
+       u16 req_mask;
+       u32 msglevel;
+       int ret = 0;
+       int link = -EOPNOTSUPP;
+
+       lsettings = &ksettings.base;
+       ehdr = info->userhdr;
+       if (!ehdr->info_mask)
+               ehdr->info_mask = ETH_SETTINGS_IM_DEFAULT;
+       req_mask = ehdr->info_mask;
+       req_flags = ehdr->flags;
+
+       dev = ethnl_dev_get(info);
+       if (IS_ERR(dev))
+               return PTR_ERR(dev);
+       if (req_mask & (ETH_SETTINGS_IM_LINKINFO | ETH_SETTINGS_IM_LINKMODES)) {
+               rtnl_lock();
+               ret = ethnl_get_link_ksettings(info, dev, &ksettings);
+               rtnl_unlock();
+               if (ret < 0) {
+                       warn_partial_info(info);
+                       req_mask &= ~(ETH_SETTINGS_IM_LINKINFO |
+                                     ETH_SETTINGS_IM_LINKMODES);
+               }
+       }
+       if (req_mask & ETH_SETTINGS_IM_LINKMODES) {
+               lpm_empty = bitmap_empty(ksettings.link_modes.lp_advertising,
+                                        __ETHTOOL_LINK_MODE_MASK_NBITS);
+               ethnl_bitmap_to_u32(ksettings.link_modes.supported,
+                                   __ETHTOOL_LINK_MODE_MASK_NWORDS);
+               ethnl_bitmap_to_u32(ksettings.link_modes.advertising,
+                                   __ETHTOOL_LINK_MODE_MASK_NWORDS);
+               ethnl_bitmap_to_u32(ksettings.link_modes.lp_advertising,
+                                   __ETHTOOL_LINK_MODE_MASK_NWORDS);
+       }
+       if (req_mask & ETH_SETTINGS_IM_MSGLEVEL) {
+               if (dev->ethtool_ops->get_msglevel) {
+                       msglevel = dev->ethtool_ops->get_msglevel(dev);
+               } else {
+                       warn_partial_info(info);
+                       req_mask &= ~ETH_SETTINGS_IM_MSGLEVEL;
+               }
+       }
+       if (req_mask & ETH_SETTINGS_IM_WOLINFO) {
+               ret = ethnl_get_wol(info, dev, &wolinfo);
+               if (ret < 0) {
+                       warn_partial_info(info);
+                       req_mask &= ~ETH_SETTINGS_IM_WOLINFO;
+               }
+       }
+       if (req_mask & ETH_SETTINGS_IM_LINK)
+               link = __ethtool_get_link(dev);
+
+       ret = ethnl_settings_size(ehdr, &ksettings, dev, req_mask);
+       if (ret < 0)
+               goto err_putdev;
+       else
+               reply_len = ret;
+       rskb = ethnl_reply_init(reply_len, dev, ETHTOOL_CMD_SET_SETTINGS, info,
+                               &ehdr);
+       ret = -ENOMEM;
+       if (!rskb)
+               goto err_putdev;
+       if (req_mask != ETH_SETTINGS_IM_DEFAULT)
+               ehdr->info_mask = req_mask;
+
+       ret = -EMSGSIZE;
+       if (req_mask & ETH_SETTINGS_IM_LINKINFO) {
+               if (nla_put_u32(rskb, ETHA_SETTINGS_SPEED, lsettings->speed) ||
+                   nla_put_u8(rskb, ETHA_SETTINGS_DUPLEX, lsettings->duplex) ||
+                   nla_put_u8(rskb, ETHA_SETTINGS_PORT, lsettings->port) ||
+                   nla_put_u8(rskb, ETHA_SETTINGS_PHYADDR,
+                              lsettings->phy_address) ||
+                   nla_put_u8(rskb, ETHA_SETTINGS_AUTONEG,
+                              lsettings->autoneg) ||
+                   nla_put_bitfield32(rskb, ETHA_SETTINGS_MDIO_SUPPORT,
+                                      lsettings->mdio_support,
+                                      ETH_MDIO_SUPPORTS_ALL) ||
+                   nla_put_u8(rskb, ETHA_SETTINGS_TP_MDIX,
+                              lsettings->eth_tp_mdix) ||
+                   nla_put_u8(rskb, ETHA_SETTINGS_TP_MDIX_CTRL,
+                              lsettings->eth_tp_mdix_ctrl) ||
+                   nla_put_u8(rskb, ETHA_SETTINGS_TRANSCEIVER,
+                              lsettings->transceiver))
+                       goto err;
+       }
+       if (req_mask & ETH_SETTINGS_IM_LINKMODES) {
+               u32 *supported = (u32 *)ksettings.link_modes.supported;
+               u32 *advertising = (u32 *)ksettings.link_modes.advertising;
+               u32 *lp_advertising =
+                       (u32 *)ksettings.link_modes.lp_advertising;
+               bool compact = req_flags & ETH_SETTINGS_RF_COMPACT_BITSETS;
+
+               ret = ethnl_put_bitset(rskb, ETHA_SETTINGS_LINK_MODES, compact,
+                                      __ETHTOOL_LINK_MODE_MASK_NBITS,
+                                      advertising, supported, link_mode_names);
+               if (ret < 0)
+                       goto err;
+               if (!lpm_empty) {
+                       ret = ethnl_put_bitset(rskb, ETHA_SETTINGS_PEER_MODES,
+                                              compact,
+                                              __ETHTOOL_LINK_MODE_MASK_NBITS,
+                                              lp_advertising, lp_advertising,
+                                              link_mode_names);
+                       if (ret < 0)
+                               goto err;
+               }
+               ret = -EMSGSIZE;
+       }
+       if (req_mask & ETH_SETTINGS_IM_MSGLEVEL) {
+               if (nla_put_bitfield32(rskb, ETHA_SETTINGS_MSGLVL, msglevel,
+                                      NETIF_MSG_ALL))
+                       goto err;
+       }
+       if (req_mask & ETH_SETTINGS_IM_WOLINFO) {
+               /* ioctl() restricts read access to wolinfo but the actual
+                * reason is to hide sopass from unprivileged users; netlink
+                * can show wol modes without sopass
+                */
+               if (nla_put_bitfield32(rskb, ETHA_SETTINGS_WOL_MODES,
+                                      wolinfo.wolopts, wolinfo.supported))
+                       goto err;
+               if (ethnl_is_privileged(skb, info) &&
+                   nla_put(rskb, ETHA_SETTINGS_SOPASS, sizeof(wolinfo.sopass),
+                           wolinfo.sopass))
+                       goto err;
+       }
+       if (req_mask & ETH_SETTINGS_IM_LINK && link >= 0) {
+               if (nla_put_u32(rskb, ETHA_SETTINGS_LINK, link))
+                       goto err;
+       }
+
+       dev_put(dev);
+       genlmsg_end(rskb, ehdr);
+       return genlmsg_reply(rskb, info);
+
+err:
+       nlmsg_free(rskb);
+err_putdev:
+       dev_put(dev);
+       if (ret == -EMSGSIZE)
+               GENL_SET_ERR_MSG(info,
+                                "kernel error, see kernel log for details");
+       WARN_ONCE(ret == -EMSGSIZE,
+                 "calculated message payload length (%d) not sufficient\n",
+                 reply_len);
+       return ret;
+}
+
 /* genetlink paperwork */
 
 static const struct genl_ops ethtool_genl_ops[] = {
@@ -609,6 +904,10 @@ static const struct genl_ops ethtool_genl_ops[] = {
                .cmd    = ETHTOOL_CMD_GET_DRVINFO,
                .doit   = ethnl_get_drvinfo,
        },
+       {
+               .cmd    = ETHTOOL_CMD_GET_SETTINGS,
+               .doit   = ethnl_get_settings,
+       },
 };
 
 static struct genl_family ethtool_genl_family = {
-- 
2.15.1

Reply via email to