[RFC PATCH 5/9] ethtool: implement GET_DRVINFO message

From: Michal Kubecek
Date: Mon Dec 11 2017 - 08:54:14 EST


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 <mkubecek@xxxxxxx>
---
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