diff mbox series

[RFC,net-next,v3,11/21] ethtool: provide driver/device information in GET_INFO request

Message ID 7f16d345d4e65239b9ec450ae0239d7dafa67cbb.1550513384.git.mkubecek@suse.cz
State RFC
Delegated to: David Miller
Headers show
Series ethtool netlink interface, part 1 | expand

Commit Message

Michal Kubecek Feb. 18, 2019, 6:22 p.m. UTC
Implement GET_INFO request to get basic driver and device information as
provided by ETHTOOL_GDRVINFO ioct command. The information is read only so
that the corresponding SET_INFO message is only used in kernel replies.

Move most of ethtool_get_drvinfo() int common.c so that the code can be
shared by both ioctl and netlink interface.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Documentation/networking/ethtool-netlink.txt |  43 ++++-
 include/uapi/linux/ethtool_netlink.h         |  32 ++++
 net/ethtool/Makefile                         |   2 +-
 net/ethtool/common.c                         |  54 +++++++
 net/ethtool/common.h                         |   2 +
 net/ethtool/info.c                           | 155 +++++++++++++++++++
 net/ethtool/ioctl.c                          |  52 +------
 net/ethtool/netlink.c                        |   9 ++
 8 files changed, 300 insertions(+), 49 deletions(-)
 create mode 100644 net/ethtool/info.c
diff mbox series

Patch

diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt
index f0fe4f50db9f..b6999a2167e8 100644
--- a/Documentation/networking/ethtool-netlink.txt
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -128,6 +128,8 @@  List of message types
     ETHNL_CMD_EVENT			notification only
     ETHNL_CMD_GET_STRSET
     ETHNL_CMD_SET_STRSET		response only
+    ETHNL_CMD_GET_INFO
+    ETHNL_CMD_SET_INFO			response only
 
 All constants use ETHNL_CMD_ prefix, usually followed by "GET", "SET" or "ACT"
 to indicate the type.
@@ -208,6 +210,45 @@  ETHA_STRSET_COUNTS tells kernel to only return string counts of the sets, not
 the actual strings.
 
 
+GET_INFO
+--------
+
+GET_INFO requests information provided by ioctl commands ETHTOOL_GDRVINFO,
+ETHTOOL_GPERMADDR and ETHTOOL_GET_TS_INFO to provide basic device information.
+Common pattern is that all information is read only so that SET_INFO message
+exists but is only used by kernel for replies to GET_INFO requests. There is
+also no corresponding notification.
+
+Request contents:
+
+    ETHA_INFO_DEV		(nested)	device identification
+    ETHA_INFO_INFOMASK		(u32)		info mask
+    ETHA_INFO_COMPACT		(flag)		request compact bitsets
+
+Info mask bits meaning:
+
+    ETH_INFO_IM_DRVINFO			driver info (GDRVINFO)
+    ETH_INFO_IM_PERMADDR		permanent HW address (GPERMADDR)
+    ETH_INFO_IM_TSINFO			timestamping info (GET_TS_INFO)
+
+Kernel response contents:
+
+    ETHA_INFO_DEV		(nested)	device identification
+    ETHA_INFO_DRVINFO		(nested)	driver information
+        ETHA_DRVINFO_DRIVER		(string)	driver name
+        ETHA_DRVINFO_FWVERSION		(string)	firmware version
+        ETHA_DRVINFO_BUSINFO		(string)	device bus address
+        ETHA_DRVINFO_EROM_VER		(string)	expansion ROM version
+
+The meaning of DRVINFO attributes follows the corresponding fields of
+ETHTOOL_GDRVINFO response. Second part with various counts and sizes is
+omitted as these are not really needed (and if they are, they can be easily
+found by different means). Driver version is also omitted as it is rather
+misleading in most cases.
+
+GET_INFO requests allow dumps.
+
+
 Request translation
 -------------------
 
@@ -219,7 +260,7 @@  ioctl command			netlink command
 ---------------------------------------------------------------------
 ETHTOOL_GSET			n/a
 ETHTOOL_SSET			n/a
-ETHTOOL_GDRVINFO		n/a
+ETHTOOL_GDRVINFO		ETHNL_CMD_GET_INFO
 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 630b66732dc9..fdae12b6c6b6 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -10,6 +10,8 @@  enum {
 	ETHNL_CMD_EVENT,		/* only for notifications */
 	ETHNL_CMD_GET_STRSET,
 	ETHNL_CMD_SET_STRSET,		/* only for reply */
+	ETHNL_CMD_GET_INFO,
+	ETHNL_CMD_SET_INFO,		/* only for reply */
 
 	__ETHNL_CMD_CNT,
 	ETHNL_CMD_MAX = (__ETHNL_CMD_CNT - 1)
@@ -135,6 +137,36 @@  enum {
 	ETHA_STRSET_MAX = (__ETHA_STRSET_CNT - 1)
 };
 
+/* GET_INFO / SET_INFO */
+
+enum {
+	ETHA_INFO_UNSPEC,
+	ETHA_INFO_DEV,				/* nest - ETHA_DEV_* */
+	ETHA_INFO_INFOMASK,			/* u32 */
+	ETHA_INFO_COMPACT,			/* flag */
+	ETHA_INFO_DRVINFO,			/* nest - ETHA_DRVINFO_* */
+	ETHA_INFO_PERMADDR,			/* nest - ETHA_PERMADDR_* */
+	ETHA_INFO_TSINFO,			/* nest - ETHA_TSINFO_* */
+
+	__ETHA_INFO_CNT,
+	ETHA_INFO_MAX = (__ETHA_INFO_CNT - 1)
+};
+
+#define ETH_INFO_IM_DRVINFO			0x01
+
+#define ETH_INFO_IM_ALL				0x01
+
+enum {
+	ETHA_DRVINFO_UNSPEC,
+	ETHA_DRVINFO_DRIVER,			/* string */
+	ETHA_DRVINFO_FWVERSION,			/* string */
+	ETHA_DRVINFO_BUSINFO,			/* string */
+	ETHA_DRVINFO_EROM_VER,			/* string */
+
+	__ETHA_DRVINFO_CNT,
+	ETHA_DRVINFO_MAX = (__ETHA_DRVINFO_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 11ceb00821b3..96d41dc45d4f 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
+ethtool_nl-y	:= netlink.o bitset.o strset.o info.o
diff --git a/net/ethtool/common.c b/net/ethtool/common.c
index 73f721a1c557..e0bd7c6c5874 100644
--- a/net/ethtool/common.c
+++ b/net/ethtool/common.c
@@ -1,5 +1,7 @@ 
 // SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
 
+#include <linux/rtnetlink.h>
+#include <net/devlink.h>
 #include "common.h"
 
 const char netdev_features_strings[NETDEV_FEATURE_COUNT][ETH_GSTRING_LEN] = {
@@ -81,3 +83,55 @@  phy_tunable_strings[__ETHTOOL_PHY_TUNABLE_COUNT][ETH_GSTRING_LEN] = {
 	[ETHTOOL_ID_UNSPEC]     = "Unspec",
 	[ETHTOOL_PHY_DOWNSHIFT]	= "phy-downshift",
 };
+
+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) {
+		int ret = ops->get_regs_len(dev);
+
+		if (ret > 0)
+			info->regdump_len = ret;
+	}
+
+	if (ops->get_eeprom_len)
+		info->eedump_len = ops->get_eeprom_len(dev);
+
+	rtnl_unlock();
+	if (!info->fw_version[0])
+		devlink_compat_running_version(dev, info->fw_version,
+					       sizeof(info->fw_version));
+	rtnl_lock();
+
+	return 0;
+}
diff --git a/net/ethtool/common.h b/net/ethtool/common.h
index 41b2efc1e4e1..e87e58b3a274 100644
--- a/net/ethtool/common.h
+++ b/net/ethtool/common.h
@@ -3,6 +3,7 @@ 
 #ifndef _ETHTOOL_COMMON_H
 #define _ETHTOOL_COMMON_H
 
+#include <linux/netdevice.h>
 #include <linux/ethtool.h>
 
 extern const char
@@ -14,4 +15,5 @@  tunable_strings[__ETHTOOL_TUNABLE_COUNT][ETH_GSTRING_LEN];
 extern const char
 phy_tunable_strings[__ETHTOOL_PHY_TUNABLE_COUNT][ETH_GSTRING_LEN];
 
+int __ethtool_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info);
 #endif /* _ETHTOOL_COMMON_H */
diff --git a/net/ethtool/info.c b/net/ethtool/info.c
new file mode 100644
index 000000000000..1a2e78d238e3
--- /dev/null
+++ b/net/ethtool/info.c
@@ -0,0 +1,155 @@ 
+// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
+
+#include "netlink.h"
+#include "common.h"
+#include "bitset.h"
+
+struct info_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_drvinfo		drvinfo;
+};
+
+static const struct nla_policy get_info_policy[ETHA_INFO_MAX + 1] = {
+	[ETHA_INFO_UNSPEC]		= { .type = NLA_REJECT },
+	[ETHA_INFO_DEV]			= { .type = NLA_NESTED },
+	[ETHA_INFO_INFOMASK]		= { .type = NLA_U32 },
+	[ETHA_INFO_COMPACT]		= { .type = NLA_FLAG },
+	[ETHA_INFO_DRVINFO]		= { .type = NLA_REJECT },
+};
+
+static int parse_info(struct common_req_info *req_info, struct sk_buff *skb,
+		      struct genl_info *info, const struct nlmsghdr *nlhdr)
+{
+	struct nlattr *tb[ETHA_INFO_MAX + 1];
+	int ret;
+
+	ret = genlmsg_parse(nlhdr, &ethtool_genl_family, tb, ETHA_INFO_MAX,
+			    get_info_policy, info ? info->extack : NULL);
+	if (ret < 0)
+		return ret;
+
+	if (tb[ETHA_INFO_DEV]) {
+		req_info->dev = ethnl_dev_get(info, tb[ETHA_INFO_DEV]);
+		if (IS_ERR(req_info->dev)) {
+			ret = PTR_ERR(req_info->dev);
+			req_info->dev = NULL;
+			return ret;
+		}
+	}
+	if (tb[ETHA_INFO_INFOMASK])
+		req_info->req_mask = nla_get_u32(tb[ETHA_INFO_INFOMASK]);
+	if (tb[ETHA_INFO_COMPACT])
+		req_info->compact = true;
+	if (req_info->req_mask == 0)
+		req_info->req_mask = ETH_INFO_IM_ALL;
+
+	return 0;
+}
+
+static int prepare_info(struct common_req_info *req_info,
+			struct genl_info *info)
+{
+	struct info_data *data =
+		container_of(req_info, struct info_data, reqinfo_base);
+	struct net_device *dev = data->repdata_base.dev;
+	u32 req_mask = req_info->req_mask & ETH_INFO_IM_ALL;
+	int ret;
+
+	ret = ethnl_before_ops(dev);
+	if (ret < 0)
+		return ret;
+	if (req_mask & ETH_INFO_IM_DRVINFO) {
+		ret = __ethtool_get_drvinfo(dev, &data->drvinfo);
+		if (ret < 0)
+			req_mask &= ~ETH_INFO_IM_DRVINFO;
+	}
+	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 drvinfo_size(const struct ethtool_drvinfo *drvinfo)
+{
+	int len = 0;
+
+	len += ethnl_str_ifne_size(drvinfo->driver);
+	len += ethnl_str_ifne_size(drvinfo->fw_version);
+	len += ethnl_str_ifne_size(drvinfo->bus_info);
+	len += ethnl_str_ifne_size(drvinfo->erom_version);
+
+	return nla_total_size(len);
+}
+
+static int info_size(const struct common_req_info *req_info)
+{
+	const struct info_data *data =
+		container_of(req_info, struct info_data, reqinfo_base);
+	u32 info_mask = data->repdata_base.info_mask;
+	int len = 0;
+
+	len += dev_ident_size();
+	if (info_mask & ETH_INFO_IM_DRVINFO)
+		len += drvinfo_size(&data->drvinfo);
+
+	return len;
+}
+
+static int fill_drvinfo(struct sk_buff *skb,
+			const struct ethtool_drvinfo *drvinfo)
+{
+	struct nlattr *nest = ethnl_nest_start(skb, ETHA_INFO_DRVINFO);
+	int ret;
+
+	if (!nest)
+		return -EMSGSIZE;
+	ret = -EMSGSIZE;
+	if (ethnl_put_str_ifne(skb, ETHA_DRVINFO_DRIVER, drvinfo->driver) ||
+	    ethnl_put_str_ifne(skb, ETHA_DRVINFO_FWVERSION,
+			       drvinfo->fw_version) ||
+	    ethnl_put_str_ifne(skb, ETHA_DRVINFO_BUSINFO, drvinfo->bus_info) ||
+	    ethnl_put_str_ifne(skb, ETHA_DRVINFO_EROM_VER,
+			       drvinfo->erom_version))
+		goto err;
+
+	nla_nest_end(skb, nest);
+	return 0;
+err:
+	nla_nest_cancel(skb, nest);
+	return ret;
+}
+
+static int fill_info(struct sk_buff *skb,
+		     const struct common_req_info *req_info)
+{
+	const struct info_data *data =
+		container_of(req_info, struct info_data, reqinfo_base);
+	u32 info_mask = data->repdata_base.info_mask;
+	int ret;
+
+	if (info_mask & ETH_INFO_IM_DRVINFO) {
+		ret = fill_drvinfo(skb, &data->drvinfo);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+const struct get_request_ops info_request_ops = {
+	.request_cmd		= ETHNL_CMD_GET_INFO,
+	.reply_cmd		= ETHNL_CMD_SET_INFO,
+	.dev_attrtype		= ETHA_INFO_DEV,
+	.data_size		= sizeof(struct info_data),
+	.repdata_offset		= offsetof(struct info_data, repdata_base),
+
+	.parse_request		= parse_info,
+	.prepare_data		= prepare_info,
+	.reply_size		= info_size,
+	.fill_reply		= fill_info,
+};
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index 71a1643adb2b..c883239001a4 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -685,56 +685,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) {
-		int ret = ops->get_regs_len(dev);
-
-		if (ret > 0)
-			info.regdump_len = ret;
-	}
-
-	if (ops->get_eeprom_len)
-		info.eedump_len = ops->get_eeprom_len(dev);
-
-	rtnl_unlock();
-	if (!info.fw_version[0])
-		devlink_compat_running_version(dev, info.fw_version,
-					       sizeof(info.fw_version));
-	rtnl_lock();
+	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/ethtool/netlink.c b/net/ethtool/netlink.c
index 082a9f2aa0a7..e27dec427414 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -87,9 +87,11 @@  int ethnl_fill_dev(struct sk_buff *msg, struct net_device *dev, u16 attrtype)
 /* GET request helpers */
 
 extern const struct get_request_ops strset_request_ops;
+extern const struct get_request_ops info_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,
 };
 
 static struct common_req_info *alloc_get_data(const struct get_request_ops *ops)
@@ -508,6 +510,13 @@  static const struct genl_ops ethtool_genl_ops[] = {
 		.dumpit	= ethnl_get_dumpit,
 		.done	= ethnl_get_done,
 	},
+	{
+		.cmd	= ETHNL_CMD_GET_INFO,
+		.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[] = {