Request the same information as ETHTOOL_GDRVINFO. This is read-only so that
corresponding SET_DRVINFO exists but is only used in kernel replies. Rip
the code to query the driver out of the legacy interface and move it to
a new file ethtool_common.c so that both interfaces can use it.

Signed-off-by: Michal Kubecek <mkube...@suse.cz>
---
 Documentation/networking/ethtool-netlink.txt | 30 ++++++++++-
 include/uapi/linux/ethtool_netlink.h         | 21 ++++++++
 net/core/Makefile                            |  2 +-
 net/core/ethtool.c                           | 42 +++-------------
 net/core/ethtool_common.c                    | 46 +++++++++++++++++
 net/core/ethtool_common.h                    | 11 ++++
 net/core/ethtool_netlink.c                   | 75 ++++++++++++++++++++++++++++
 7 files changed, 189 insertions(+), 38 deletions(-)
 create mode 100644 net/core/ethtool_common.c
 create mode 100644 net/core/ethtool_common.h

diff --git a/Documentation/networking/ethtool-netlink.txt 
b/Documentation/networking/ethtool-netlink.txt
index 893e5156f6a7..cb992180b211 100644
--- a/Documentation/networking/ethtool-netlink.txt
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -107,6 +107,9 @@ which the request applies.
 List of message types
 ---------------------
 
+    ETHTOOL_CMD_GET_DRVINFO
+    ETHTOOL_CMD_SET_DRVINFO            response only
+
 All constants use ETHTOOL_CMD_ prefix followed by "GET", "SET" or "ACT" to
 indicate the type.
 
@@ -119,6 +122,31 @@ messages marked as "response only" in the table above.
 Later sections describe the format and semantics of these request messages.
 
 
+GET_DRVINFO
+-----------
+
+GET_DRVINFO request corresponds to ETHTOOL_GDRVINFO ioctl command and provides
+basic driver information. The request doesn't use any attributes and flags,
+info_mask and index field in request header are ignored. Kernel response
+contents:
+
+    ETHA_DRVINFO_DRIVER                (string)        driver name
+    ETHA_DRVINFO_VERSION       (string)        driver version
+    ETHA_DRVINFO_FWVERSION     (string)        firmware version
+    ETHA_DRVINFO_BUSINFO       (string)        device bus address
+    ETHA_DRVINFO_EROM_VER      (string)        expansion ROM version
+    ETHA_DRVINFO_N_PRIV_FLAGS  (u32)           number of private flags
+    ETHA_DRVINFO_N_STATS       (u32)           number of device stats
+    ETHA_DRVINFO_TESTINFO_LEN  (u32)           number of test results
+    ETHA_DRVINFO_EEDUMP_LEN    (u32)           EEPROM dump size
+    ETHA_DRVINFO_REGDUMP_LEN   (u32)           register dump size
+
+The meaning of these follows the corresponding fields of ETHTOOL_GDRVINFO
+response.
+
+All information is read only, SET_DRVINFO request is not implemented.
+
+
 Request translation
 -------------------
 
@@ -130,7 +158,7 @@ ioctl command                       netlink command
 ---------------------------------------------------------------------
 ETHTOOL_GSET                   n/a
 ETHTOOL_SSET                   n/a
-ETHTOOL_GDRVINFO               n/a
+ETHTOOL_GDRVINFO               ETHTOOL_CMD_GET_DRVINFO
 ETHTOOL_GREGS                  n/a
 ETHTOOL_GWOL                   n/a
 ETHTOOL_SWOL                   n/a
diff --git a/include/uapi/linux/ethtool_netlink.h 
b/include/uapi/linux/ethtool_netlink.h
index 6db35f00ea32..d6ab1d73d494 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -21,6 +21,8 @@ struct ethnlmsghdr {
 
 enum {
        ETHTOOL_CMD_NOOP,
+       ETHTOOL_CMD_GET_DRVINFO,
+       ETHTOOL_CMD_SET_DRVINFO,        /* only for reply */
 
        __ETHTOOL_CMD_MAX,
        ETHTOOL_CMD_MAX = (__ETHTOOL_CMD_MAX - 1),
@@ -57,6 +59,25 @@ enum {
        ETHA_BITSET_MAX = (__ETHA_BITSET_MAX - 1),
 };
 
+/* GET_DRVINFO / SET_DRVINFO */
+
+enum {
+       ETHA_DRVINFO_UNSPEC,
+       ETHA_DRVINFO_DRIVER,                    /* string */
+       ETHA_DRVINFO_VERSION,                   /* string */
+       ETHA_DRVINFO_FWVERSION,                 /* string */
+       ETHA_DRVINFO_BUSINFO,                   /* string */
+       ETHA_DRVINFO_EROM_VER,                  /* string */
+       ETHA_DRVINFO_N_PRIV_FLAGS,              /* u32 */
+       ETHA_DRVINFO_N_STATS,                   /* u32 */
+       ETHA_DRVINFO_TESTINFO_LEN,              /* u32 */
+       ETHA_DRVINFO_EEDUMP_LEN,                /* u32 */
+       ETHA_DRVINFO_REGDUMP_LEN,               /* u32 */
+
+       __ETHA_DRVINFO_MAX,
+       ETHA_DRVINFO_MAX = (__ETHA_DRVINFO_MAX - 1),
+};
+
 /* generic netlink info */
 #define ETHTOOL_GENL_NAME "ethtool"
 #define ETHTOOL_GENL_VERSION 1
diff --git a/net/core/Makefile b/net/core/Makefile
index 617ab2abecdf..3196641c0e70 100644
--- a/net/core/Makefile
+++ b/net/core/Makefile
@@ -11,7 +11,7 @@ obj-$(CONFIG_SYSCTL) += sysctl_net_core.o
 obj-y               += dev.o ethtool.o dev_addr_lists.o dst.o netevent.o \
                        neighbour.o rtnetlink.o utils.o link_watch.o filter.o \
                        sock_diag.o dev_ioctl.o tso.o sock_reuseport.o \
-                       fib_notifier.o
+                       fib_notifier.o ethtool_common.o
 
 obj-y += net-sysfs.o
 obj-$(CONFIG_PROC_FS) += net-procfs.o
diff --git a/net/core/ethtool.c b/net/core/ethtool.c
index f8fcf450a36e..09e780a748f9 100644
--- a/net/core/ethtool.c
+++ b/net/core/ethtool.c
@@ -26,6 +26,7 @@
 #include <linux/rtnetlink.h>
 #include <linux/sched/signal.h>
 #include <linux/net.h>
+#include "ethtool_common.h"
 
 /*
  * Some useful ethtool_ops methods that're device independent.
@@ -885,45 +886,14 @@ static noinline_for_stack int ethtool_get_drvinfo(struct 
net_device *dev,
                                                  void __user *useraddr)
 {
        struct ethtool_drvinfo info;
-       const struct ethtool_ops *ops = dev->ethtool_ops;
-
-       memset(&info, 0, sizeof(info));
-       info.cmd = ETHTOOL_GDRVINFO;
-       if (ops->get_drvinfo) {
-               ops->get_drvinfo(dev, &info);
-       } else if (dev->dev.parent && dev->dev.parent->driver) {
-               strlcpy(info.bus_info, dev_name(dev->dev.parent),
-                       sizeof(info.bus_info));
-               strlcpy(info.driver, dev->dev.parent->driver->name,
-                       sizeof(info.driver));
-       } else {
-               return -EOPNOTSUPP;
-       }
-
-       /*
-        * this method of obtaining string set info is deprecated;
-        * Use ETHTOOL_GSSET_INFO instead.
-        */
-       if (ops->get_sset_count) {
-               int rc;
-
-               rc = ops->get_sset_count(dev, ETH_SS_TEST);
-               if (rc >= 0)
-                       info.testinfo_len = rc;
-               rc = ops->get_sset_count(dev, ETH_SS_STATS);
-               if (rc >= 0)
-                       info.n_stats = rc;
-               rc = ops->get_sset_count(dev, ETH_SS_PRIV_FLAGS);
-               if (rc >= 0)
-                       info.n_priv_flags = rc;
-       }
-       if (ops->get_regs_len)
-               info.regdump_len = ops->get_regs_len(dev);
-       if (ops->get_eeprom_len)
-               info.eedump_len = ops->get_eeprom_len(dev);
+       int rc;
 
+       rc = __ethtool_get_drvinfo(dev, &info);
+       if (rc < 0)
+               return rc;
        if (copy_to_user(useraddr, &info, sizeof(info)))
                return -EFAULT;
+
        return 0;
 }
 
diff --git a/net/core/ethtool_common.c b/net/core/ethtool_common.c
new file mode 100644
index 000000000000..2c0abab0e43c
--- /dev/null
+++ b/net/core/ethtool_common.c
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#include <linux/rtnetlink.h>
+#include "ethtool_common.h"
+
+int __ethtool_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info)
+{
+       const struct ethtool_ops *ops = dev->ethtool_ops;
+
+       memset(info, 0, sizeof(*info));
+       info->cmd = ETHTOOL_GDRVINFO;
+       if (ops->get_drvinfo) {
+               ops->get_drvinfo(dev, info);
+       } else if (dev->dev.parent && dev->dev.parent->driver) {
+               strlcpy(info->bus_info, dev_name(dev->dev.parent),
+                       sizeof(info->bus_info));
+               strlcpy(info->driver, dev->dev.parent->driver->name,
+                       sizeof(info->driver));
+       } else {
+               return -EOPNOTSUPP;
+       }
+
+       /* this method of obtaining string set info is deprecated;
+        * Use ETHTOOL_GSSET_INFO instead.
+        */
+       if (ops->get_sset_count) {
+               int rc;
+
+               rc = ops->get_sset_count(dev, ETH_SS_TEST);
+               if (rc >= 0)
+                       info->testinfo_len = rc;
+               rc = ops->get_sset_count(dev, ETH_SS_STATS);
+               if (rc >= 0)
+                       info->n_stats = rc;
+               rc = ops->get_sset_count(dev, ETH_SS_PRIV_FLAGS);
+               if (rc >= 0)
+                       info->n_priv_flags = rc;
+       }
+       if (ops->get_regs_len)
+               info->regdump_len = ops->get_regs_len(dev);
+       if (ops->get_eeprom_len)
+               info->eedump_len = ops->get_eeprom_len(dev);
+
+       return 0;
+}
+EXPORT_SYMBOL(__ethtool_get_drvinfo);
diff --git a/net/core/ethtool_common.h b/net/core/ethtool_common.h
new file mode 100644
index 000000000000..1f031c1d943a
--- /dev/null
+++ b/net/core/ethtool_common.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#ifndef _ETHTOOL_COMMON_H
+#define _ETHTOOL_COMMON_H
+
+#include <linux/netdevice.h>
+#include <linux/ethtool.h>
+
+int __ethtool_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo 
*info);
+
+#endif /* _ETHTOOL_COMMON_H */
diff --git a/net/core/ethtool_netlink.c b/net/core/ethtool_netlink.c
index 9f287e67f30b..077814fd36bd 100644
--- a/net/core/ethtool_netlink.c
+++ b/net/core/ethtool_netlink.c
@@ -531,9 +531,84 @@ static struct sk_buff *ethnl_reply_init(size_t payload, 
struct net_device *dev,
        return rskb;
 }
 
+/* GET_DRVINFO */
+
+static int ethnl_drvinfo_size(struct ethtool_drvinfo *drvinfo)
+{
+       int len = 0;
+
+       len += ethnl_str_ifne_size(drvinfo->driver);
+       len += ethnl_str_ifne_size(drvinfo->version);
+       len += ethnl_str_ifne_size(drvinfo->fw_version);
+       len += ethnl_str_ifne_size(drvinfo->bus_info);
+       len += ethnl_str_ifne_size(drvinfo->erom_version);
+       /* n_priv_flags, n_stats, testinfo_len, eedump_len, regdump_len */
+       len += 5 * nla_total_size(sizeof(u32));
+
+       return len;
+}
+
+static int ethnl_get_drvinfo(struct sk_buff *skb, struct genl_info *info)
+{
+       struct ethnlmsghdr *ehdr;
+       struct ethtool_drvinfo drvinfo = {};
+       struct net_device *dev;
+       struct sk_buff *rskb;
+       int reply_len;
+       int rc = 0;
+
+       dev = ethnl_dev_get(info);
+       if (IS_ERR(dev))
+               return PTR_ERR(dev);
+       rc = __ethtool_get_drvinfo(dev, &drvinfo);
+       if (rc < 0) {
+               dev_put(dev);
+               GENL_SET_ERR_MSG(info,
+                                "failed to retrieve driver info from driver");
+               return rc;
+       }
+
+       reply_len = ethnl_drvinfo_size(&drvinfo);
+       rskb = ethnl_reply_init(reply_len, dev, ETHTOOL_CMD_SET_DRVINFO, info,
+                               &ehdr);
+       dev_put(dev);
+       if (!rskb)
+               return -ENOMEM;
+
+       if (ethnl_put_str_ifne(rskb, ETHA_DRVINFO_DRIVER, drvinfo.driver) ||
+           ethnl_put_str_ifne(rskb, ETHA_DRVINFO_VERSION, drvinfo.version) ||
+           ethnl_put_str_ifne(rskb, ETHA_DRVINFO_FWVERSION,
+                              drvinfo.fw_version) ||
+           ethnl_put_str_ifne(rskb, ETHA_DRVINFO_BUSINFO, drvinfo.bus_info) ||
+           ethnl_put_str_ifne(rskb, ETHA_DRVINFO_EROM_VER,
+                              drvinfo.erom_version) ||
+           nla_put_u32(rskb, ETHA_DRVINFO_N_PRIV_FLAGS,
+                       drvinfo.n_priv_flags) ||
+           nla_put_u32(rskb, ETHA_DRVINFO_N_STATS, drvinfo.n_stats) ||
+           nla_put_u32(rskb, ETHA_DRVINFO_TESTINFO_LEN,
+                       drvinfo.testinfo_len) ||
+           nla_put_u32(rskb, ETHA_DRVINFO_EEDUMP_LEN, drvinfo.eedump_len) ||
+           nla_put_u32(rskb, ETHA_DRVINFO_REGDUMP_LEN, drvinfo.regdump_len))
+               goto err;
+
+       genlmsg_end(rskb, ehdr);
+       return genlmsg_reply(rskb, info);
+
+err:
+       nlmsg_free(rskb);
+       GENL_SET_ERR_MSG(info, "kernel error, see kernel log for details");
+       WARN_ONCE(1, "calculated message payload length (%d) not sufficient\n",
+                 reply_len);
+       return -EMSGSIZE;
+}
+
 /* genetlink paperwork */
 
 static const struct genl_ops ethtool_genl_ops[] = {
+       {
+               .cmd    = ETHTOOL_CMD_GET_DRVINFO,
+               .doit   = ethnl_get_drvinfo,
+       },
 };
 
 static struct genl_family ethtool_genl_family = {
-- 
2.15.1

Reply via email to