A netlink based interface for ethtool is a recurring discussion theme;
such discussion happens from time to time but never seems to reach any
clear conclusion except that netlink interface is desperately needed.
I'm sending this hoping that having a proposal (even if partial) on the
table could help move the discussion further.
The interface used for communication between ethtool and kernel is based on
ioctl() and suffers from many problems. The most pressing seems the be the
lack of extensibility. While some of the newer commands use structures
designed to allow future extensions (e.g. GFEATURES or TEST), most either
allow no extension at all (GPAUSEPARAM, GCOALESCE) or only limited set of
reserved fields (GDRVINFO, GEEE). Even most of those which support future
extensions limit the data types that can be used.
This series aims to provide an alternative interface based on netlink which
is what other network configuration utilities use. In particular, it uses
generic netlink (family "ethtool"). The goal is to provide an interface
which would be extensible, flexible and practical both for ethtool and for
other network configuration tools (e.g. wicked, systemd-networkd or
NetworkManager).
The interface is documented in Documentation/networking/ethtool-netlink.txt
A series for ethtool utility will follow shortly.
Basic concepts:
- the interface is based on generic netlink (family name "ethtool")
- the goal is to provide all features of ioctl interface but allow
easier future extensions
- inextensibility of ioctl interface resulted in way too many commands,
many of them obsoleted by newer ones; reduce the number by ignoring the
obsolete commands and grouping some together
- for "set" type commands, netlink allows providing only the attributes to
be changed; therefore we don't need a get-modify-set cycle (which is
inherently racy), userspace can simply say what it wants to change
- provide notifications to multicast group "monitor" like rtnetlink
does, i.e. in the form of messages close to replies to "get" requests
- allow dump requests to get some information about all network defices
providing it
- be less dependent on ethtool and kernel being in sync; allow e.g. saying
"ethtool -s eth0 advertise foo off" without ethtool knowing what "foo"
means; it's kernel's job to know what mode "xyz" is and if it exists
and is supported
Main changes again RFC v1:
- support dumps for all "get" requests
- provide notifications for changes related to supported request types
- support getting string sets (both global and per device)
- support getting/setting device features
- get rid of family specific header, everything passed as attributes
- split netlink code into multiple files in net/ethtool/ directory
ToDo / open questions:
- as some comments in discussion on v1 pointed out, some features of
ethtool would rather belong to devlink; phy_tunables and phy_stats
seem to be candidates, maybe part of drvinfo; are there more?
- another question is where to do the split; should ethtool use devlink
API for these or can we provide them in ethtool API as well but with
devlink backend (for communication with NIC)
- currently, all communication with drivers via ethtool_ops is done
under RTNL as this is what ioctl interface does and I suspect many
ethtool_ops rely on that; can we do without RTNL?
- notifications are sent whenever a change is done via netlink API or
ioctl API and for netdev features also whenever they are updated using
netdev_change_features(); it would be desirable to notify also about
link state and negotiation result (speed/duplex and partner link
modes) but it would be more tricky
- find reasonable format for data transfers (e.g. eeprom dump or flash);
I don't have clear idea how big these can get and if 64 KB limit on
attribute size (including nested ones) is a problem; if so, dumps can
be an answer for dumps, some kind of multi-message requests would be
needed for flashes
- while the netlink interface allows easy future extensions, ethtool_ops
interface does not; some settings could be implemented using tunables and
accessed via relevant netlink messages (as well as tunables) from
userspace but in the long term, something better will be needed
- it would be nice if driver could provide useful error/warning messages to
be passed to userspace via extended ACK; example: while testing, I found
a driver which only allows values 0, 1, 3 and 10000 for certain parameter
but the only way poor user can find out is either by trying all values or
by checking driver source
- some of the functions for GET_SETTINGS and GET_PARAMS are quite
similar (e.g. ethtool_get_*); it might be beneficial to introduce some
"ops", leave only "parse", "prepare", "size" and "fill" handlers and
make the rest generic (like ethnl_dumpit()).
- the counts and sizes in GET_DRVINFO reply seem to be a relic of the
past and if userspace needs them, there are (or will be) other ways to
get them; they should most likely go
Michal Kubecek (17):
netlink: introduce nla_put_bitfield32()
ethtool: move to its own directory
ethtool: introduce ethtool netlink interface
ethtool: helper functions for netlink interface
ethtool: netlink bitset handling
ethtool: support for netlink notifications
ethtool: implement EVENT notifications
ethtool: implement GET_STRSET message
ethtool: implement GET_DRVINFO message
ethtool: implement GET_SETTINGS message
ethtool: implement GET_SETTINGS request for features
ethtool: implement SET_SETTINGS notification
ethtool: implement SET_SETTINGS message
ethtool: implement SET_SETTINGS request for features
ethtool: implement GET_PARAMS message
ethtool: implement SET_PARAMS notification
ethtool: implement SET_PARAMS message
Documentation/networking/ethtool-netlink.txt | 558 ++++++++
include/linux/ethtool_netlink.h | 17 +
include/linux/netdevice.h | 25 +
include/net/netlink.h | 15 +
include/uapi/linux/ethtool.h | 7 +
include/uapi/linux/ethtool_netlink.h | 325 +++++
net/Kconfig | 7 +
net/Makefile | 2 +-
net/core/Makefile | 2 +-
net/core/dev.c | 27 +-
net/ethtool/Makefile | 7 +
net/ethtool/common.c | 242 ++++
net/ethtool/common.h | 26 +
net/ethtool/drvinfo.c | 131 ++
net/{core/ethtool.c => ethtool/ioctl.c} | 310 ++---
net/ethtool/netlink.c | 840 ++++++++++++
net/ethtool/netlink.h | 169 +++
net/ethtool/params.c | 1008 ++++++++++++++
net/ethtool/settings.c | 1230 ++++++++++++++++++
net/ethtool/strset.c | 552 ++++++++
20 files changed, 5269 insertions(+), 231 deletions(-)
create mode 100644 Documentation/networking/ethtool-netlink.txt
create mode 100644 include/linux/ethtool_netlink.h
create mode 100644 include/uapi/linux/ethtool_netlink.h
create mode 100644 net/ethtool/Makefile
create mode 100644 net/ethtool/common.c
create mode 100644 net/ethtool/common.h
create mode 100644 net/ethtool/drvinfo.c
rename net/{core/ethtool.c => ethtool/ioctl.c} (88%)
create mode 100644 net/ethtool/netlink.c
create mode 100644 net/ethtool/netlink.h
create mode 100644 net/ethtool/params.c
create mode 100644 net/ethtool/settings.c
create mode 100644 net/ethtool/strset.c
--
2.18.0
With ethtool netlink interface, there is going to be a lot of new files
so that it seem more convenient to put them into a separate directory
net/ethtool. Start with moving current file with ioctl implementation
there and renaming it to ioctl.c.
Signed-off-by: Michal Kubecek <[email protected]>
---
net/Makefile | 2 +-
net/core/Makefile | 2 +-
net/ethtool/Makefile | 3 +++
net/{core/ethtool.c => ethtool/ioctl.c} | 0
4 files changed, 5 insertions(+), 2 deletions(-)
create mode 100644 net/ethtool/Makefile
rename net/{core/ethtool.c => ethtool/ioctl.c} (100%)
diff --git a/net/Makefile b/net/Makefile
index bdaf53925acd..6c68520e5eb9 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -13,7 +13,7 @@ obj-$(CONFIG_NET) += $(tmp-y)
# LLC has to be linked before the files in net/802/
obj-$(CONFIG_LLC) += llc/
-obj-$(CONFIG_NET) += ethernet/ 802/ sched/ netlink/ bpf/
+obj-$(CONFIG_NET) += ethernet/ 802/ sched/ netlink/ bpf/ ethtool/
obj-$(CONFIG_NETFILTER) += netfilter/
obj-$(CONFIG_INET) += ipv4/
obj-$(CONFIG_TLS) += tls/
diff --git a/net/core/Makefile b/net/core/Makefile
index 80175e6a2eb8..55734d0a1603 100644
--- a/net/core/Makefile
+++ b/net/core/Makefile
@@ -8,7 +8,7 @@ obj-y := sock.o request_sock.o skbuff.o datagram.o stream.o scm.o \
obj-$(CONFIG_SYSCTL) += sysctl_net_core.o
-obj-y += dev.o ethtool.o dev_addr_lists.o dst.o netevent.o \
+obj-y += dev.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 xdp.o
diff --git a/net/ethtool/Makefile b/net/ethtool/Makefile
new file mode 100644
index 000000000000..3ebfab2bca66
--- /dev/null
+++ b/net/ethtool/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-y += ioctl.o
diff --git a/net/core/ethtool.c b/net/ethtool/ioctl.c
similarity index 100%
rename from net/core/ethtool.c
rename to net/ethtool/ioctl.c
--
2.18.0
No function implemented yet, only genetlink and module infrastructure.
Register/unregister genetlink family "ethtool" and allow the module to be
autoloaded by genetlink code (if built as a module, distributions would
probably prefer "y").
Signed-off-by: Michal Kubecek <[email protected]>
---
Documentation/networking/ethtool-netlink.txt | 168 +++++++++++++++++++
include/linux/ethtool_netlink.h | 9 +
include/uapi/linux/ethtool_netlink.h | 19 +++
net/Kconfig | 7 +
net/ethtool/Makefile | 6 +-
net/ethtool/netlink.c | 41 +++++
net/ethtool/netlink.h | 12 ++
7 files changed, 261 insertions(+), 1 deletion(-)
create mode 100644 Documentation/networking/ethtool-netlink.txt
create mode 100644 include/linux/ethtool_netlink.h
create mode 100644 include/uapi/linux/ethtool_netlink.h
create mode 100644 net/ethtool/netlink.c
create mode 100644 net/ethtool/netlink.h
diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt
new file mode 100644
index 000000000000..120376e0f91b
--- /dev/null
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -0,0 +1,168 @@
+ Netlink interface for ethtool
+ =============================
+
+
+Basic information
+-----------------
+
+Netlink interface for ethtool uses generic netlink family "ethtool" (userspace
+application should use macros ETHTOOL_GENL_NAME and ETHTOOL_GENL_VERSION
+defined in <linux/ethtool_netlink.h> uapi header). This family does not use
+a specific header, all information in requests and replies is passed using
+netlink attributes.
+
+In requests, device can be identified by ifindex or by name; if both are used,
+they must match. In replies, kernel fills both. The meaning of flags,
+info_mask and index fields depends on request type.
+
+The ethtool netlink interface uses extended ACK for error and warning
+reporting, userspace application developers are encouraged to make these
+messages available to user in a suitable way.
+
+Requests can be divided into three categories: "get" (retrieving information),
+"set" (setting parameters) and "action" (invoking an action).
+
+All "set" and "action" type requests require admin privileges (CAP_NET_ADMIN
+in the namespace). Most "get" type request are allowed for anyone but there
+are exceptions (where the response contains sensitive information). In some
+cases, the request as such is allowed for anyone but unprivileged users have
+attributes with sensitive information (e.g. wake-on-lan password) omitted.
+
+
+Conventions
+-----------
+
+Attributes which represent a boolean value usually use u8 type so that we can
+distinguish three states: "on", "off" and "not present" (meaning the
+information is not available in "get" requests or value is not to be changed
+in "set" requests). For these attributes, the "true" value should be passed as
+number 1 but any non-zero value should be understood as "true" by recipient.
+
+Some request types allow passing an attribute named ETHA_*_INFOMASK with
+a bitmask telling kernel that we are only interested in some parts of the
+information. If info mask is omitted, all available information is returned.
+Meaning of info mask bits depends on request type and is listed below.
+
+
+Device identification
+---------------------
+
+When appropriate, network device is identified by a nested attribute named
+ETHA_*_DEV. This attribute can contain
+
+ ETHA_DEV_INDEX (u32) device ifindex
+ ETHA_DEV_NAME (string) device name
+
+In device related requests, one of these is sufficient; if both are used, they
+must match (i.e. identify the same device). In device related replies both are
+provided by kernel. In dump requests, device is not specified and kernel
+replies with one message per network device (only those for which the request
+is supported).
+
+List of message types
+---------------------
+
+All constants use ETHNL_CMD_ prefix, usually followed by "GET", "SET" or "ACT"
+to indicate the type.
+
+Messages of type "get" are used by userspace to request information and
+usually do not contain any attributes (that may be added later for dump
+filtering). Kernel response is in the form of corresponding "set" message;
+the same message can be also used to set (some of) the parameters, except for
+messages marked as "response only" in the table above. "Get" messages with
+NLM_F_DUMP flags and no device identification dump the information for all
+devices supporting the request.
+
+Later sections describe the format and semantics of these request messages.
+
+
+Request translation
+-------------------
+
+The following table maps iosctl commands to netlink commands providing their
+functionality. Entries with "n/a" in right column are commands which do not
+have their netlink replacement yet.
+
+ioctl command netlink command
+---------------------------------------------------------------------
+ETHTOOL_GSET n/a
+ETHTOOL_SSET n/a
+ETHTOOL_GDRVINFO n/a
+ETHTOOL_GREGS n/a
+ETHTOOL_GWOL n/a
+ETHTOOL_SWOL n/a
+ETHTOOL_GMSGLVL n/a
+ETHTOOL_SMSGLVL n/a
+ETHTOOL_NWAY_RST n/a
+ETHTOOL_GLINK n/a
+ETHTOOL_GEEPROM n/a
+ETHTOOL_SEEPROM n/a
+ETHTOOL_GCOALESCE n/a
+ETHTOOL_SCOALESCE n/a
+ETHTOOL_GRINGPARAM n/a
+ETHTOOL_SRINGPARAM n/a
+ETHTOOL_GPAUSEPARAM n/a
+ETHTOOL_SPAUSEPARAM n/a
+ETHTOOL_GRXCSUM n/a
+ETHTOOL_SRXCSUM n/a
+ETHTOOL_GTXCSUM n/a
+ETHTOOL_STXCSUM n/a
+ETHTOOL_GSG n/a
+ETHTOOL_SSG n/a
+ETHTOOL_TEST n/a
+ETHTOOL_GSTRINGS n/a
+ETHTOOL_PHYS_ID n/a
+ETHTOOL_GSTATS n/a
+ETHTOOL_GTSO n/a
+ETHTOOL_STSO n/a
+ETHTOOL_GPERMADDR n/a
+ETHTOOL_GUFO n/a
+ETHTOOL_SUFO n/a
+ETHTOOL_GGSO n/a
+ETHTOOL_SGSO n/a
+ETHTOOL_GFLAGS n/a
+ETHTOOL_SFLAGS n/a
+ETHTOOL_GPFLAGS n/a
+ETHTOOL_SPFLAGS n/a
+ETHTOOL_GRXFH n/a
+ETHTOOL_SRXFH n/a
+ETHTOOL_GGRO n/a
+ETHTOOL_SGRO n/a
+ETHTOOL_GRXRINGS n/a
+ETHTOOL_GRXCLSRLCNT n/a
+ETHTOOL_GRXCLSRULE n/a
+ETHTOOL_GRXCLSRLALL n/a
+ETHTOOL_SRXCLSRLDEL n/a
+ETHTOOL_SRXCLSRLINS n/a
+ETHTOOL_FLASHDEV n/a
+ETHTOOL_RESET n/a
+ETHTOOL_SRXNTUPLE n/a
+ETHTOOL_GRXNTUPLE n/a
+ETHTOOL_GSSET_INFO n/a
+ETHTOOL_GRXFHINDIR n/a
+ETHTOOL_SRXFHINDIR n/a
+ETHTOOL_GFEATURES n/a
+ETHTOOL_SFEATURES n/a
+ETHTOOL_GCHANNELS n/a
+ETHTOOL_SCHANNELS n/a
+ETHTOOL_SET_DUMP n/a
+ETHTOOL_GET_DUMP_FLAG n/a
+ETHTOOL_GET_DUMP_DATA n/a
+ETHTOOL_GET_TS_INFO n/a
+ETHTOOL_GMODULEINFO n/a
+ETHTOOL_GMODULEEEPROM n/a
+ETHTOOL_GEEE n/a
+ETHTOOL_SEEE n/a
+ETHTOOL_GRSSH n/a
+ETHTOOL_SRSSH n/a
+ETHTOOL_GTUNABLE n/a
+ETHTOOL_STUNABLE n/a
+ETHTOOL_GPHYSTATS n/a
+ETHTOOL_PERQUEUE n/a
+ETHTOOL_GLINKSETTINGS n/a
+ETHTOOL_SLINKSETTINGS n/a
+ETHTOOL_PHY_GTUNABLE n/a
+ETHTOOL_PHY_STUNABLE n/a
+ETHTOOL_GFECPARAM n/a
+ETHTOOL_SFECPARAM n/a
+
diff --git a/include/linux/ethtool_netlink.h b/include/linux/ethtool_netlink.h
new file mode 100644
index 000000000000..0412adb4f42f
--- /dev/null
+++ b/include/linux/ethtool_netlink.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#ifndef _LINUX_ETHTOOL_NETLINK_H_
+#define _LINUX_ETHTOOL_NETLINK_H_
+
+#include <uapi/linux/ethtool_netlink.h>
+#include <linux/ethtool.h>
+
+#endif /* _LINUX_ETHTOOL_NETLINK_H_ */
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
new file mode 100644
index 000000000000..3c0c5c70692b
--- /dev/null
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#ifndef _UAPI_LINUX_ETHTOOL_NETLINK_H_
+#define _UAPI_LINUX_ETHTOOL_NETLINK_H_
+
+#include <linux/ethtool.h>
+
+enum {
+ ETHNL_CMD_NOOP,
+
+ __ETHNL_CMD_MAX,
+ ETHNL_CMD_MAX = (__ETHNL_CMD_MAX - 1)
+};
+
+/* generic netlink info */
+#define ETHTOOL_GENL_NAME "ethtool"
+#define ETHTOOL_GENL_VERSION 1
+
+#endif /* _UAPI_LINUX_ETHTOOL_NETLINK_H_ */
diff --git a/net/Kconfig b/net/Kconfig
index 228dfa382eec..12e763449525 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -445,6 +445,13 @@ config FAILOVER
migration of VMs with direct attached VFs by failing over to the
paravirtual datapath when the VF is unplugged.
+config ETHTOOL_NETLINK
+ tristate "Netlink interface for ethtool"
+ default m
+ help
+ New netlink based interface for ethtool which is going to obsolete
+ the old ioctl based one once it provides all features.
+
endif # if NET
# Used by archs to tell that they support BPF JIT compiler plus which flavour.
diff --git a/net/ethtool/Makefile b/net/ethtool/Makefile
index 3ebfab2bca66..f30e0da88be5 100644
--- a/net/ethtool/Makefile
+++ b/net/ethtool/Makefile
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
-obj-y += ioctl.o
+obj-y += ioctl.o
+
+obj-$(CONFIG_ETHTOOL_NETLINK) += ethtool_nl.o
+
+ethtool_nl-y := netlink.o
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
new file mode 100644
index 000000000000..1c6efb9b2eae
--- /dev/null
+++ b/net/ethtool/netlink.c
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#include <linux/module.h>
+#include <linux/ethtool_netlink.h>
+#include "netlink.h"
+
+/* genetlink setup */
+
+static const struct genl_ops ethtool_genl_ops[] = {
+};
+
+struct genl_family ethtool_genl_family = {
+ .hdrsize = 0,
+ .name = ETHTOOL_GENL_NAME,
+ .version = ETHTOOL_GENL_VERSION,
+ .netnsok = true,
+ .parallel_ops = true,
+ .ops = ethtool_genl_ops,
+ .n_ops = ARRAY_SIZE(ethtool_genl_ops),
+};
+
+/* module setup */
+
+static int __init ethtool_nl_init(void)
+{
+ return genl_register_family(ðtool_genl_family);
+}
+
+static void __exit ethtool_nl_exit(void)
+{
+ genl_unregister_family(ðtool_genl_family);
+}
+
+module_init(ethtool_nl_init);
+module_exit(ethtool_nl_exit);
+
+/* alias for module autoload */
+MODULE_ALIAS_GENL_FAMILY(ETHTOOL_GENL_NAME);
+MODULE_AUTHOR("Michal Kubecek <[email protected]>");
+MODULE_DESCRIPTION("Netlink interface for ethtool");
+MODULE_LICENSE("GPL");
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
new file mode 100644
index 000000000000..63063b582ca2
--- /dev/null
+++ b/net/ethtool/netlink.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#ifndef _NET_ETHTOOL_NETLINK_H
+#define _NET_ETHTOOL_NETLINK_H
+
+#include <linux/ethtool_netlink.h>
+#include <linux/netdevice.h>
+#include <net/genetlink.h>
+
+extern struct genl_family ethtool_genl_family;
+
+#endif /* _NET_ETHTOOL_NETLINK_H */
--
2.18.0
Similar to other data types, this helper puts NLA_BITFIELD32 attribute into
a netlink message. It takes separate value and selector arguments, if you
already have struct nla_bitfield32, you can use nla_put().
Signed-off-by: Michal Kubecek <[email protected]>
---
include/net/netlink.h | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/include/net/netlink.h b/include/net/netlink.h
index 0c154f98e987..6d4eb6bd9235 100644
--- a/include/net/netlink.h
+++ b/include/net/netlink.h
@@ -1064,6 +1064,21 @@ static inline int nla_put_in6_addr(struct sk_buff *skb, int attrtype,
return nla_put(skb, attrtype, sizeof(*addr), addr);
}
+/**
+ * nla_put_bitfield32 - Add a bitfield32 value/selector attribute to
+ * a socket buffer
+ * @skb: socket buffer to add attribute to
+ * @value: 32-bit value bitmap
+ * @selector: 32-bit selector bitmap
+ */
+static inline int nla_put_bitfield32(struct sk_buff *skb, int attrtype,
+ u32 value, u32 selector)
+{
+ struct nla_bitfield32 tmp = { .value = value, .selector = selector };
+
+ return nla_put(skb, attrtype, sizeof(tmp), &tmp);
+}
+
/**
* nla_get_u32 - return payload of u32 attribute
* @nla: u32 netlink attribute
--
2.18.0
Declare attribute type constants and add helper functions to handle
arbitrary length bit sets.
Signed-off-by: Michal Kubecek <[email protected]>
---
Documentation/networking/ethtool-netlink.txt | 56 +++
include/uapi/linux/ethtool_netlink.h | 31 ++
net/ethtool/netlink.c | 395 +++++++++++++++++++
net/ethtool/netlink.h | 26 ++
4 files changed, 508 insertions(+)
diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt
index 120376e0f91b..a49dfe3ef4bb 100644
--- a/Documentation/networking/ethtool-netlink.txt
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -59,6 +59,62 @@ provided by kernel. In dump requests, device is not specified and kernel
replies with one message per network device (only those for which the request
is supported).
+Bit sets
+--------
+
+For short bitmaps of (reasonably) fixed length, standard NLA_BITFIELD32 type
+is used. For arbitrary length bitmaps, ethtool netlink uses a nested attribute
+with contents of one of two forms: compact (two binary bitmaps representing
+bit values and mask of affected bits) and bit-by-bit (list of bits identified
+by either index or name).
+
+Compact form: nested (bitset) atrribute contents:
+
+ ETHA_BITSET_SIZE (u32) number of significant bits
+ ETHA_BITSET_VALUE (binary) bitmap of bit values
+ ETHA_BITSET_MASK (binary) bitmap of valid bits
+
+Value and mask must have length at least ETHA_BITSET_SIZE bits rounded up to
+a multiple of 32 bits. They consist of 32-bit words in host byt order, words
+ordered from least significant to most significant (i.e. the same way as
+bitmaps are passed with ioctl interface).
+
+For compact form, ETHA_BITSET_SIZE and ETHA_BITSET_VALUE are mandatory.
+Similar to BITFIELD32, a compact form bit set requests to set bits in the mask
+to 1 (if set in value) or 0 (if not) and preserve the rest. If the mask is
+omitted, it is supposed to be "all ones", i.e. set all bits according to
+value.
+
+Kernel bit set length may differ from userspace length if older application is
+used on newer kernel or vice versa. If userspace bitmaps are longer, error is
+only issued if request actually tries to set bits not implemented in kernel.
+
+Bit-by-bit form: nested (bitset) attribute contents:
+
+ ETHA_BITSET_SIZE (u32) number of significant bits (optional)
+ ETHA_BITSET_BITS (nested) array of bits
+ ETHA_BITSET_BIT
+ ETHA_BIT_INDEX (u32) bit index (0 for LSB)
+ ETHA_BIT_NAME (string) bit name
+ ETHA_BIT_VALUE (flag) present if bit is set
+ ETHA_BITSET_BIT
+ ...
+
+Bit size is optional for bit-by-bit form. ETHA_BITSET_BITS nest can only
+contain ETHA_BITS_BIT attributes but there can be an arbitrary number of them.
+A bit may be identified by its index or by its name. When used in requests,
+listed bits are set to 0 or 1 according to ETHA_BIT_VALUE, the rest is
+preserved. A request fails if index exceeds kernel bit length or if name is
+not recognized.
+
+In requests, application can use either form. Form used by kernel in reply is
+determined by a flag in flags field of request header. Semantics of value and
+mask depends on the attribute. General idea is that flags control request
+processing, info_mask control which parts of the information are returned in
+"get" request and index identifies a particular subcommand or an object to
+which the request applies.
+
+
List of message types
---------------------
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index 1df263d9feb7..98d6fae315f3 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -23,6 +23,37 @@ enum {
ETHA_DEV_MAX = (__ETHA_DEV_MAX - 1)
};
+/* bit sets */
+
+enum {
+ ETHA_BIT_UNSPEC,
+ ETHA_BIT_INDEX, /* u32 */
+ ETHA_BIT_NAME, /* string */
+ ETHA_BIT_VALUE, /* flag */
+
+ __ETHA_BIT_MAX,
+ ETHA_BIT_MAX = (__ETHA_BIT_MAX - 1)
+};
+
+enum {
+ ETHA_BITS_UNSPEC,
+ ETHA_BITS_BIT,
+
+ __ETHA_BITS_MAX,
+ ETHA_BITS_MAX = (__ETHA_BITS_MAX - 1)
+};
+
+enum {
+ ETHA_BITSET_UNSPEC,
+ ETHA_BITSET_SIZE, /* u32 */
+ ETHA_BITSET_BITS, /* nest - ETHA_BITS_* */
+ ETHA_BITSET_VALUE, /* binary */
+ ETHA_BITSET_MASK, /* binary */
+
+ __ETHA_BITSET_MAX,
+ ETHA_BITSET_MAX = (__ETHA_BITSET_MAX - 1)
+};
+
/* generic netlink info */
#define ETHTOOL_GENL_NAME "ethtool"
#define ETHTOOL_GENL_VERSION 1
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index bf234e5bc660..df065fd3dc80 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#include <linux/module.h>
+#include <linux/bitmap.h>
#include <net/sock.h>
#include <linux/ethtool_netlink.h>
#include "netlink.h"
@@ -67,6 +68,400 @@ int ethnl_fill_dev(struct sk_buff *msg, struct net_device *dev, u16 attrtype)
return ret;
}
+/* bitset helper functions */
+
+static bool ethnl_test_bit(u32 *val, unsigned int index)
+{
+ if (val)
+ return val[index / 32] & (1 << (index % 32));
+ else
+ return true;
+}
+
+static void ethnl_copy_bitmap(u32 *dst, u32 *src, unsigned int size)
+{
+ unsigned int full_words = size / 32;
+
+ memcpy(dst, src, full_words * sizeof(u32));
+ if (size % 32 != 0)
+ dst[full_words] = src[full_words] & ((1U << (size % 32)) - 1);
+}
+
+static void ethnl_fill_bitmap(u32 *dst, unsigned int size)
+{
+ unsigned int full_words = size / 32;
+
+ memset(dst, 0xff, full_words * sizeof(u32));
+ if (size % 32 != 0)
+ dst[full_words] = (1U << (size % 32)) - 1;
+}
+
+/* convert standard kernel bitmap (long sized words) to ethtool one (u32 words)
+ * bitmap_to_arr32() is not guaranteed to do "in place" conversion correctly;
+ * moreover, we can use the fact that the conversion is no-op except for 64-bit
+ * big endian architectures
+ */
+#if BITS_PER_LONG == 64 && defined(__BIG_ENDIAN)
+void ethnl_bitmap_to_u32(unsigned long *bitmap, unsigned int nwords)
+{
+ u32 *dst = (u32 *)bitmap;
+ unsigned int i;
+
+ for (i = 0; i < nwords; i++) {
+ unsigned long tmp = READ_ONCE(bitmap[i]);
+
+ dst[2 * i] = tmp & 0xffffffff;
+ dst[2 * i + 1] = tmp >> 32;
+ }
+}
+#endif
+
+/* calculate size for a bitset attribute
+ * see ethnl_put_bitset() for arguments
+ */
+int ethnl_bitset32_size(bool compact, unsigned int size, u32 *val, u32 *mask,
+ const char *const *names)
+{
+ unsigned int nwords = (size + 31) / 32;
+ unsigned int len;
+
+ if (WARN_ON(!compact && !names))
+ return -EINVAL;
+ /* size */
+ len = nla_total_size(sizeof(u32));
+
+ if (compact) {
+ /* values, mask */
+ len += 2 * nla_total_size(nwords * sizeof(u32));
+ } else {
+ unsigned int bits_len = 0;
+ unsigned int bit_len, i;
+
+ for (i = 0; i < size; i++) {
+ if (!ethnl_test_bit(mask, i))
+ continue;
+ /* index */
+ bit_len = nla_total_size(sizeof(u32));
+ /* name */
+ bit_len += ethnl_str_size(names[i] ?: "");
+ /* value */
+ if (ethnl_test_bit(val, i))
+ bit_len += nla_total_size(0);
+
+ /* bit nest */
+ bits_len += nla_total_size(bit_len);
+ }
+ len += nla_total_size(bits_len);
+ }
+
+ /* outermost nest */
+ return nla_total_size(len);
+}
+
+int ethnl_bitset_size(bool compact, unsigned int size, unsigned long *val,
+ unsigned long *mask, const char *const *names)
+{
+ const unsigned int words = (size + 31) / 32;
+ u32 mask32[words];
+ u32 val32[words];
+ int ret;
+
+ bitmap_to_arr32(val32, val, size);
+ if (mask)
+ bitmap_to_arr32(mask32, mask, size);
+ ret = ethnl_bitset32_size(compact, size, val32, mask ? mask32 : NULL,
+ names);
+ bitmap_from_arr32(val, val32, size);
+ if (mask)
+ bitmap_from_arr32(mask, mask32, size);
+
+ return ret;
+}
+
+/* put bitset into a message
+ * skb: skb with the message
+ * attrtype: attribute type for the bitset
+ * compact: compact (bitmaps) or verbose (bit-by-bit with names) format
+ * size: size of the set in bits
+ * val: bitset values
+ * mask: mask of valid bits
+ * names: bit names (only used for verbose format)
+ */
+int ethnl_put_bitset32(struct sk_buff *skb, int attrtype, bool compact,
+ unsigned int size, u32 *val, u32 *mask,
+ const char *const *names)
+{
+ struct nlattr *nest;
+ struct nlattr *attr;
+ int ret;
+
+ if (WARN_ON(!compact && !names))
+ return -EINVAL;
+ nest = ethnl_nest_start(skb, attrtype);
+ if (!nest)
+ return -EMSGSIZE;
+
+ ret = -EMSGSIZE;
+ if (nla_put_u32(skb, ETHA_BITSET_SIZE, size))
+ goto err;
+ if (compact) {
+ unsigned int bytesize = ((size + 31) / 32) * sizeof(u32);
+
+ ret = -EMSGSIZE;
+ attr = nla_reserve(skb, ETHA_BITSET_VALUE, bytesize);
+ if (!attr)
+ goto err;
+ ethnl_copy_bitmap(nla_data(attr), val, size);
+ attr = nla_reserve(skb, ETHA_BITSET_MASK, bytesize);
+ if (!attr)
+ goto err;
+ if (mask)
+ ethnl_copy_bitmap(nla_data(attr), mask, size);
+ else
+ ethnl_fill_bitmap(nla_data(attr), size);
+ } else {
+ struct nlattr *bits;
+ unsigned int i;
+
+ bits = ethnl_nest_start(skb, ETHA_BITSET_BITS);
+ if (!bits)
+ goto err;
+ for (i = 0; i < size; i++) {
+ if (!ethnl_test_bit(mask, i))
+ continue;
+ attr = ethnl_nest_start(skb, ETHA_BITS_BIT);
+ if (!attr ||
+ nla_put_u32(skb, ETHA_BIT_INDEX, i) ||
+ nla_put_string(skb, ETHA_BIT_NAME, names[i] ?: ""))
+ goto err;
+ if (ethnl_test_bit(val, i))
+ if (nla_put_flag(skb, ETHA_BIT_VALUE))
+ goto err;
+ nla_nest_end(skb, attr);
+ }
+ nla_nest_end(skb, bits);
+ }
+
+ nla_nest_end(skb, nest);
+ return 0;
+err:
+ nla_nest_cancel(skb, nest);
+ return ret;
+}
+
+int ethnl_put_bitset(struct sk_buff *skb, int attrtype, bool compact,
+ unsigned int size, unsigned long *val, unsigned long *mask,
+ const char *const *names)
+{
+ const unsigned int words = (size + 31) / 32;
+ u32 mask32[words];
+ u32 val32[words];
+ int ret;
+
+ bitmap_to_arr32(val32, val, size);
+ if (mask)
+ bitmap_to_arr32(mask32, mask, size);
+ ret = ethnl_put_bitset32(skb, attrtype, compact, size, val32,
+ mask ? mask32 : NULL, names);
+ bitmap_from_arr32(val, val32, size);
+ if (mask)
+ bitmap_from_arr32(mask, mask32, size);
+
+ return ret;
+}
+
+static const struct nla_policy bitset_policy[ETHA_BITSET_MAX + 1] = {
+ [ETHA_BITSET_UNSPEC] = { .type = NLA_UNSPEC },
+ [ETHA_BITSET_SIZE] = { .type = NLA_U32 },
+ [ETHA_BITSET_BITS] = { .type = NLA_NESTED },
+ [ETHA_BITSET_VALUE] = { .type = NLA_BINARY },
+ [ETHA_BITSET_MASK] = { .type = NLA_BINARY },
+};
+
+static const struct nla_policy bit_policy[ETHA_BIT_MAX + 1] = {
+ [ETHA_BIT_UNSPEC] = { .type = NLA_UNSPEC },
+ [ETHA_BIT_INDEX] = { .type = NLA_U32 },
+ [ETHA_BIT_NAME] = { .type = NLA_STRING },
+ [ETHA_BIT_VALUE] = { .type = NLA_FLAG },
+};
+
+static int ethnl_name_to_idx(const char *const *names, unsigned int n_names,
+ const char *name, unsigned int name_len)
+{
+ unsigned int i;
+
+ for (i = 0; i < n_names; i++)
+ if (names[i] && !strncmp(names[i], name, name_len))
+ return i;
+
+ return n_names;
+}
+
+static int ethnl_update_bit(unsigned long *bitmap, unsigned long *bitmask,
+ unsigned int nbits, struct nlattr *bit_attr,
+ const char *const *names, struct genl_info *info)
+{
+ struct nlattr *tb[ETHA_BIT_MAX + 1];
+ int ret, idx;
+
+ if (nla_type(bit_attr) != ETHA_BITS_BIT) {
+ ETHNL_SET_ERRMSG(info,
+ "ETHA_BITSET_BITS can contain only ETHA_BITS_BIT");
+ return genl_err_attr(info, -EINVAL, bit_attr);
+ }
+ ret = nla_parse_nested(tb, ETHA_BIT_MAX, bit_attr, bit_policy,
+ info->extack);
+ if (ret < 0)
+ return ret;
+
+ if (tb[ETHA_BIT_INDEX]) {
+ idx = nla_get_u32(tb[ETHA_BIT_INDEX]);
+ if (idx >= nbits) {
+ ETHNL_SET_ERRMSG(info, "bit index too high");
+ return genl_err_attr(info, -EOPNOTSUPP,
+ tb[ETHA_BIT_INDEX]);
+ }
+ if (tb[ETHA_BIT_NAME] && names[idx] &&
+ strncmp(nla_data(tb[ETHA_BIT_NAME]), names[idx],
+ nla_len(tb[ETHA_BIT_NAME]))) {
+ ETHNL_SET_ERRMSG(info, "bit index and name mismatch");
+ return genl_err_attr(info, -EINVAL, bit_attr);
+ }
+ } else if (tb[ETHA_BIT_NAME]) {
+ idx = ethnl_name_to_idx(names, nbits,
+ nla_data(tb[ETHA_BIT_NAME]),
+ nla_len(tb[ETHA_BIT_NAME]));
+ if (idx >= nbits) {
+ ETHNL_SET_ERRMSG(info, "bit name not found");
+ return genl_err_attr(info, -EOPNOTSUPP,
+ tb[ETHA_BIT_NAME]);
+ }
+ } else {
+ ETHNL_SET_ERRMSG(info, "neither bit index nor name specified");
+ return genl_err_attr(info, -EINVAL, bit_attr);
+ }
+
+ if (tb[ETHA_BIT_VALUE])
+ set_bit(idx, bitmap);
+ else
+ clear_bit(idx, bitmap);
+ if (bitmask)
+ set_bit(idx, bitmask);
+ return 0;
+}
+
+bool ethnl_update_bitset(unsigned long *bitmap, unsigned long *bitmask,
+ unsigned int nbits, struct nlattr *attr, int *err,
+ const char *const *names, struct genl_info *info)
+{
+ struct nlattr *tb[ETHA_BITSET_MAX + 1];
+ unsigned int change_bits = 0;
+ bool mod = false;
+
+ if (!attr)
+ return false;
+ *err = nla_parse_nested(tb, ETHA_BITSET_MAX, attr, bitset_policy,
+ info->extack);
+ if (*err < 0)
+ return mod;
+
+ /* To let new userspace to work with old kernel, we allow bitmaps
+ * from userspace to be longer than kernel ones and only issue an
+ * error if userspace actually tries to change a bit not existing
+ * in kernel.
+ */
+ if (tb[ETHA_BITSET_SIZE])
+ change_bits = nla_get_u32(tb[ETHA_BITSET_SIZE]);
+
+ if (tb[ETHA_BITSET_BITS]) {
+ DECLARE_BITMAP(val, nbits);
+ DECLARE_BITMAP(mask, nbits);
+ struct nlattr *bit_attr;
+ int rem;
+
+ *err = -EINVAL;
+ if (tb[ETHA_BITSET_VALUE] || tb[ETHA_BITSET_MASK])
+ return mod;
+ bitmap_copy(val, bitmap, nbits);
+ bitmap_zero(mask, nbits);
+ nla_for_each_nested(bit_attr, tb[ETHA_BITSET_BITS], rem) {
+ *err = ethnl_update_bit(val, mask, nbits, bit_attr,
+ names, info);
+ if (*err < 0)
+ return mod;
+ }
+ mod = !bitmap_equal(val, bitmap, nbits);
+ if (mod)
+ bitmap_copy(bitmap, val, nbits);
+ if (bitmask)
+ bitmap_copy(bitmask, mask, nbits);
+ } else {
+ const unsigned int max_bits =
+ max_t(unsigned int, nbits, change_bits);
+ unsigned int nwords = (change_bits + 31) / 32;
+ DECLARE_BITMAP(mask, max_bits);
+ DECLARE_BITMAP(val, max_bits);
+
+ *err = -EINVAL;
+ if (!change_bits || !tb[ETHA_BITSET_VALUE])
+ return mod;
+ if (nla_len(tb[ETHA_BITSET_VALUE]) < nwords * sizeof(u32))
+ return mod;
+ if (tb[ETHA_BITSET_MASK] &&
+ nla_len(tb[ETHA_BITSET_VALUE]) < nwords * sizeof(u32))
+ return mod;
+
+ bitmap_zero(val, max_bits);
+ bitmap_from_arr32(val, nla_data(tb[ETHA_BITSET_VALUE]),
+ change_bits);
+ bitmap_zero(mask, max_bits);
+ if (tb[ETHA_BITSET_MASK])
+ bitmap_from_arr32(mask, nla_data(tb[ETHA_BITSET_MASK]),
+ change_bits);
+ else
+ bitmap_fill(mask, change_bits);
+
+ if (nbits < change_bits) {
+ unsigned int idx = find_next_bit(mask, max_bits, nbits);
+
+ *err = -EINVAL;
+ if (idx >= nbits)
+ return mod;
+ }
+
+ if (bitmask)
+ bitmap_copy(bitmask, mask, nbits);
+ bitmap_and(val, val, mask, nbits);
+ bitmap_complement(mask, mask, nbits);
+ bitmap_and(mask, mask, bitmap, nbits);
+ bitmap_or(val, val, mask, nbits);
+ mod = !bitmap_equal(val, bitmap, nbits);
+ if (mod)
+ bitmap_copy(bitmap, val, nbits);
+ }
+
+ *err = 0;
+ return mod;
+}
+
+bool ethnl_update_bitset32(u32 *bitmap, u32 *bitmask, unsigned int nbits,
+ struct nlattr *attr, int *err,
+ const char *const *names, struct genl_info *info)
+{
+ DECLARE_BITMAP(tmp, nbits);
+ DECLARE_BITMAP(tmp_mask, nbits);
+ bool mod;
+
+ bitmap_from_arr32(tmp, bitmap, nbits);
+ bitmap_zero(tmp_mask, nbits);
+ mod = ethnl_update_bitset(tmp, tmp_mask, nbits, attr, err, names, info);
+ if (!*err && mod)
+ bitmap_to_arr32(bitmap, tmp, nbits);
+ if (bitmask)
+ bitmap_to_arr32(bitmask, tmp_mask, nbits);
+ return (!*err && mod);
+}
+
/* create skb for a reply
* payload: payload length (without netlink, genetlink and ethnl headers)
* dev: device the reply is about
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index acfed5ba6b54..6e9e854eec5d 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -20,6 +20,32 @@ struct sk_buff *ethnl_reply_init(size_t payload, struct net_device *dev, u8 cmd,
u16 dev_attrtype, struct genl_info *info,
void **ehdrp);
+#if BITS_PER_LONG == 64 && defined(__BIG_ENDIAN)
+void ethnl_bitmap_to_u32(unsigned long *bitmap, unsigned int nwords);
+#else
+static inline void ethnl_bitmap_to_u32(unsigned long *bitmap,
+ unsigned int nwords)
+{
+}
+#endif
+
+int ethnl_bitset_size(bool compact, unsigned int size, unsigned long *val,
+ unsigned long *mask, const char *const *names);
+int ethnl_bitset32_size(bool compact, unsigned int size, u32 *val, u32 *mask,
+ const char *const *names);
+int ethnl_put_bitset(struct sk_buff *skb, int attrtype, bool compact,
+ unsigned int size, unsigned long *val, unsigned long *mask,
+ const char *const *names);
+int ethnl_put_bitset32(struct sk_buff *skb, int attrtype, bool compact,
+ unsigned int size, u32 *val, u32 *mask,
+ const char *const *names);
+bool ethnl_update_bitset(unsigned long *bitmap, unsigned long *bitmask,
+ unsigned int nbits, struct nlattr *attr, int *err,
+ const char *const *names, struct genl_info *info);
+bool ethnl_update_bitset32(u32 *bitmap, u32 *bitmask, unsigned int nbits,
+ struct nlattr *attr, int *err,
+ const char *const *names, struct genl_info *info);
+
static inline int ethnl_str_size(const char *s)
{
return nla_total_size(strlen(s) + 1);
--
2.18.0
Two types of netlink notifications are introduced: ETHA_EVENT_NEWDEV to
notify about newly registered network devices and ETHA_EVENT_DELDEV to
notify about unregistered network devices. These are triggered by
NETDEV_REGISTER and NETDEV_UNREGISTER notifiers.
These are intended for applications and daemons monitoring ethtool events
to allow updating the list of existing devices without having to open
another socket for rtnetlink.
Signed-off-by: Michal Kubecek <[email protected]>
---
Documentation/networking/ethtool-netlink.txt | 22 ++++++++++
include/uapi/linux/ethtool_netlink.h | 28 +++++++++++++
net/ethtool/netlink.c | 44 ++++++++++++++++++++
3 files changed, 94 insertions(+)
diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt
index a49dfe3ef4bb..0e83397f2975 100644
--- a/Documentation/networking/ethtool-netlink.txt
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -118,6 +118,8 @@ which the request applies.
List of message types
---------------------
+ ETHNL_CMD_EVENT notification only
+
All constants use ETHNL_CMD_ prefix, usually followed by "GET", "SET" or "ACT"
to indicate the type.
@@ -129,9 +131,29 @@ messages marked as "response only" in the table above. "Get" messages with
NLM_F_DUMP flags and no device identification dump the information for all
devices supporting the request.
+Type ETHNL_CMD_EVENT is special, these messages are never used in userspace
+requests or kernel replies. They are only sent by kernel to sockets listening
+to "monitor" multicast group to inform userspace about certain events.
+
Later sections describe the format and semantics of these request messages.
+EVENT
+-----
+
+EVENT messages are only used in kernel multicast notifications. Atributes
+correspond to specific event types, the same type can appear multiple times.
+
+ ETHA_EVENT_NEWDEV (nested) new device was registered
+ ETHA_NEWDEV_DEV (nested) new device
+ ETHA_EVENT_DELDEV (nested) device was unregistered
+ ETHA_DELDEV_DEV (nested) removed device
+
+Userspace application must expect multiple events to be present in one message
+and also multiple events of the same type (e.g. two or more newly registered
+devices).
+
+
Request translation
-------------------
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index 444a668e4a08..f162cd6f80d4 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -7,6 +7,7 @@
enum {
ETHNL_CMD_NOOP,
+ ETHNL_CMD_EVENT, /* only for notifications */
__ETHNL_CMD_MAX,
ETHNL_CMD_MAX = (__ETHNL_CMD_MAX - 1)
@@ -54,6 +55,33 @@ enum {
ETHA_BITSET_MAX = (__ETHA_BITSET_MAX - 1)
};
+/* events */
+
+enum {
+ ETHA_NEWDEV_UNSPEC,
+ ETHA_NEWDEV_DEV, /* nest - ETHA_DEV_* */
+
+ __ETHA_NEWDEV_MAX,
+ ETHA_NEWDEV_MAX = (__ETHA_NEWDEV_MAX - 1)
+};
+
+enum {
+ ETHA_DELDEV_UNSPEC,
+ ETHA_DELDEV_DEV, /* nest - ETHA_DEV_* */
+
+ __ETHA_DELDEV_MAX,
+ ETHA_DELDEV_MAX = (__ETHA_DELDEV_MAX - 1)
+};
+
+enum {
+ ETHA_EVENT_UNSPEC,
+ ETHA_EVENT_NEWDEV, /* nest - ETHA_NEWDEV_* */
+ ETHA_EVENT_DELDEV, /* nest - ETHA_DELDEV_* */
+
+ __ETHA_EVENT_MAX,
+ ETHA_EVENT_MAX = (__ETHA_EVENT_MAX - 1)
+};
+
/* generic netlink info */
#define ETHTOOL_GENL_NAME "ethtool"
#define ETHTOOL_GENL_VERSION 1
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index e4a20bb6c1d4..543560778c80 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -607,6 +607,44 @@ void ethnl_notify(struct net_device *dev, struct netlink_ext_ack *extack,
return __ethnl_notify(ðtool_info);
}
+static unsigned int dev_notify_size(void)
+{
+ return nla_total_size(nla_total_size(nla_total_size(sizeof(u32)) +
+ nla_total_size(IFNAMSIZ)));
+}
+
+static void ethnl_notify_devlist(struct netdev_notifier_info *info,
+ u16 ev_type, u16 dev_attr)
+{
+ struct net_device *dev = netdev_notifier_info_to_dev(info);
+ struct sk_buff *skb;
+ struct nlattr *nest;
+ void *ehdr;
+ int ret;
+
+ skb = genlmsg_new(dev_notify_size(), GFP_KERNEL);
+ if (!skb)
+ return;
+ ehdr = genlmsg_put(skb, 0, ++ethnl_bcast_seq, ðtool_genl_family, 0,
+ ETHNL_CMD_EVENT);
+ if (!ehdr)
+ goto out_skb;
+ nest = ethnl_nest_start(skb, ev_type);
+ if (!nest)
+ goto out_skb;
+ ret = ethnl_fill_dev(skb, dev, dev_attr);
+ if (ret < 0)
+ goto out_skb;
+ nla_nest_end(skb, nest);
+ genlmsg_end(skb, ehdr);
+
+ genlmsg_multicast(ðtool_genl_family, skb, 0, ETHNL_MCGRP_MONITOR,
+ GFP_KERNEL);
+ return;
+out_skb:
+ nlmsg_free(skb);
+}
+
static int ethnl_netdev_event(struct notifier_block *this, unsigned long event,
void *ptr)
{
@@ -614,6 +652,12 @@ static int ethnl_netdev_event(struct notifier_block *this, unsigned long event,
case NETDEV_ETHTOOL:
__ethnl_notify(ptr);
break;
+ case NETDEV_REGISTER:
+ ethnl_notify_devlist(ptr, ETHA_EVENT_NEWDEV, ETHA_NEWDEV_DEV);
+ break;
+ case NETDEV_UNREGISTER:
+ ethnl_notify_devlist(ptr, ETHA_EVENT_DELDEV, ETHA_DELDEV_DEV);
+ break;
}
return NOTIFY_DONE;
--
2.18.0
Misc helpers used by ethtool netlink code.
Signed-off-by: Michal Kubecek <[email protected]>
---
include/uapi/linux/ethtool_netlink.h | 11 ++
net/ethtool/netlink.c | 163 +++++++++++++++++++++++++++
net/ethtool/netlink.h | 124 ++++++++++++++++++++
3 files changed, 298 insertions(+)
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index 3c0c5c70692b..1df263d9feb7 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -12,6 +12,17 @@ enum {
ETHNL_CMD_MAX = (__ETHNL_CMD_MAX - 1)
};
+/* device specification */
+
+enum {
+ ETHA_DEV_UNSPEC,
+ ETHA_DEV_INDEX, /* u32 */
+ ETHA_DEV_NAME, /* string */
+
+ __ETHA_DEV_MAX,
+ ETHA_DEV_MAX = (__ETHA_DEV_MAX - 1)
+};
+
/* generic netlink info */
#define ETHTOOL_GENL_NAME "ethtool"
#define ETHTOOL_GENL_VERSION 1
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 1c6efb9b2eae..bf234e5bc660 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -1,9 +1,172 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#include <linux/module.h>
+#include <net/sock.h>
#include <linux/ethtool_netlink.h>
#include "netlink.h"
+static const struct nla_policy dev_policy[ETHA_DEV_MAX + 1] = {
+ [ETHA_DEV_UNSPEC] = { .type = NLA_UNSPEC },
+ [ETHA_DEV_INDEX] = { .type = NLA_U32 },
+ [ETHA_DEV_NAME] = { .type = NLA_STRING, .len = IFNAMSIZ - 1 },
+};
+
+struct net_device *ethnl_dev_get(struct genl_info *info, struct nlattr *nest)
+{
+ struct net *net = genl_info_net(info);
+ struct nlattr *tb[ETHA_DEV_MAX + 1];
+ struct net_device *dev;
+ int ret;
+
+ ret = nla_parse_nested(tb, ETHA_DEV_MAX, nest, dev_policy,
+ info->extack);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ if (tb[ETHA_DEV_INDEX]) {
+ dev = dev_get_by_index(net, nla_get_u32(tb[ETHA_BIT_INDEX]));
+ if (!dev)
+ return ERR_PTR(-ENODEV);
+ /* if both ifindex and ifname are passed, they must match */
+ if (tb[ETHA_DEV_NAME]) {
+ const char *nl_name = nla_data(tb[ETHA_DEV_NAME]);
+
+ if (strncmp(dev->name, nl_name, IFNAMSIZ)) {
+ ETHNL_SET_ERRMSG(info,
+ "ifindex and ifname do not match");
+ return ERR_PTR(-ENODEV);
+ }
+ }
+ return dev;
+ } else if (tb[ETHA_DEV_NAME]) {
+ dev = dev_get_by_name(net, nla_data(tb[ETHA_DEV_NAME]));
+ return dev ?: ERR_PTR(-ENODEV);
+ }
+
+ return ERR_PTR(-EINVAL);
+}
+
+int ethnl_fill_dev(struct sk_buff *msg, struct net_device *dev, u16 attrtype)
+{
+ struct nlattr *nest;
+ int ret = -EMSGSIZE;
+
+ nest = ethnl_nest_start(msg, attrtype);
+ if (!nest)
+ return -EMSGSIZE;
+
+ if (nla_put_u32(msg, ETHA_DEV_INDEX, (u32)dev->ifindex))
+ goto err;
+ if (nla_put_string(msg, ETHA_DEV_NAME, dev->name))
+ goto err;
+
+ nla_nest_end(msg, nest);
+ return 0;
+err:
+ nla_nest_cancel(msg, nest);
+ return ret;
+}
+
+/* create skb for a reply
+ * payload: payload length (without netlink, genetlink and ethnl headers)
+ * dev: device the reply is about
+ * cmd: ETHNL_CMD_* command for reply
+ * info: info for the received packet we respond to
+ * ehdrp: place to store payload pointer returned by genlmsg_new()
+ * returns: skb or null on error
+ */
+struct sk_buff *ethnl_reply_init(size_t payload, struct net_device *dev, u8 cmd,
+ u16 dev_attrtype, struct genl_info *info,
+ void **ehdrp)
+{
+ void *ehdr;
+ struct sk_buff *rskb;
+
+ if (dev)
+ payload += nla_total_size(nla_total_size(sizeof(u32)) +
+ nla_total_size(IFNAMSIZ));
+ rskb = genlmsg_new(payload, GFP_KERNEL);
+ if (!rskb) {
+ ETHNL_SET_ERRMSG(info,
+ "failed to allocate reply message");
+ return NULL;
+ }
+
+ ehdr = genlmsg_put_reply(rskb, info, ðtool_genl_family, 0, cmd);
+ if (!ehdr)
+ goto err;
+ if (ehdrp)
+ *ehdrp = ehdr;
+ if (dev) {
+ int ret = ethnl_fill_dev(rskb, dev, dev_attrtype);
+
+ if (ret < 0)
+ goto err;
+ }
+
+ return rskb;
+err:
+ nlmsg_free(rskb);
+ return NULL;
+}
+
+/* device iteration copied from rtnl_dump_ifinfo()
+ * cb->args[0]: command specific dump function
+ * cb->args[1]: ethnl command to use in reply
+ * cb->args[2]: position - hashbucket
+ * cb->args[3]: position - ifindex
+ */
+int ethnl_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ int (*dumpfn)(struct sk_buff *skb, struct netlink_callback *cb,
+ struct net_device *dev);
+ struct net *net = sock_net(skb->sk);
+ struct hlist_head *head;
+ struct net_device *dev;
+ int h, s_h, idx = 0, s_idx;
+ int ret = 0;
+ void *ehdr;
+ u8 rcmd;
+
+ dumpfn = (void *)cb->args[0];
+ rcmd = cb->args[1];
+ s_h = cb->args[2];
+ s_idx = cb->args[3];
+
+ for (h = s_h; h < NETDEV_HASHENTRIES; h++, s_idx = 0) {
+ idx = 0;
+ head = &net->dev_index_head[h];
+ hlist_for_each_entry(dev, head, index_hlist) {
+ if (idx < s_idx)
+ goto cont;
+ ehdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid,
+ cb->nlh->nlmsg_seq,
+ ðtool_genl_family, 0, rcmd);
+ ret = dumpfn(skb, cb, dev);
+ if (ret < 0) {
+ genlmsg_cancel(skb, ehdr);
+ if (ret == -EOPNOTSUPP)
+ goto cont;
+ if (likely(skb->len))
+ goto out;
+ goto out_err;
+ }
+ genlmsg_end(skb, ehdr);
+cont:
+ idx++;
+ }
+ }
+out:
+ ret = skb->len;
+out_err:
+ cb->args[2] = h;
+ cb->args[3] = idx;
+ cb->seq = net->dev_base_seq;
+ nl_dump_check_consistent(cb, nlmsg_hdr(skb));
+
+ return ret;
+}
+
/* genetlink setup */
static const struct genl_ops ethtool_genl_ops[] = {
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 63063b582ca2..acfed5ba6b54 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -6,7 +6,131 @@
#include <linux/ethtool_netlink.h>
#include <linux/netdevice.h>
#include <net/genetlink.h>
+#include <net/sock.h>
+
+#define ETHNL_SET_ERRMSG(info, msg) \
+ do { if (info) GENL_SET_ERR_MSG(info, msg); } while (0)
extern struct genl_family ethtool_genl_family;
+struct net_device *ethnl_dev_get(struct genl_info *info, struct nlattr *nest);
+int ethnl_fill_dev(struct sk_buff *msg, struct net_device *dev, u16 attrtype);
+
+struct sk_buff *ethnl_reply_init(size_t payload, struct net_device *dev, u8 cmd,
+ u16 dev_attrtype, struct genl_info *info,
+ void **ehdrp);
+
+static inline int ethnl_str_size(const char *s)
+{
+ return nla_total_size(strlen(s) + 1);
+}
+
+static inline int ethnl_str_ifne_size(const char *s)
+{
+ return s[0] ? ethnl_str_size(s) : 0;
+}
+
+static inline int ethnl_put_str_ifne(struct sk_buff *skb, int attrtype,
+ const char *s)
+{
+ if (!s[0])
+ return 0;
+ return nla_put_string(skb, attrtype, s);
+}
+
+static inline struct nlattr *ethnl_nest_start(struct sk_buff *skb,
+ int attrtype)
+{
+ return nla_nest_start(skb, attrtype | NLA_F_NESTED);
+}
+
+/* ethnl_update_* return true if the value is changed */
+static inline bool ethnl_update_u32(u32 *dst, struct nlattr *attr)
+{
+ u32 val;
+
+ if (!attr)
+ return false;
+ val = nla_get_u32(attr);
+ if (*dst == val)
+ return false;
+
+ *dst = val;
+ return true;
+}
+
+static inline bool ethnl_update_u8(u8 *dst, struct nlattr *attr)
+{
+ u8 val;
+
+ if (!attr)
+ return false;
+ val = nla_get_u8(attr);
+ if (*dst == val)
+ return false;
+
+ *dst = val;
+ return true;
+}
+
+/* update u32 value used as bool from NLA_U8 */
+static inline bool ethnl_update_bool32(u32 *dst, struct nlattr *attr)
+{
+ u8 val;
+
+ if (!attr)
+ return false;
+ val = !!nla_get_u8(attr);
+ if (!!*dst == val)
+ return false;
+
+ *dst = val;
+ return true;
+}
+
+static inline bool ethnl_update_binary(u8 *dst, unsigned int len, struct nlattr *attr)
+{
+ if (!attr)
+ return false;
+ if (nla_len(attr) < len)
+ len = nla_len(attr);
+ if (!memcmp(dst, nla_data(attr), len))
+ return false;
+
+ memcpy(dst, nla_data(attr), len);
+ return true;
+}
+
+static inline bool ethnl_update_bitfield32(u32 *dst, struct nlattr *attr)
+{
+ struct nla_bitfield32 change;
+ u32 newval;
+
+ if (!attr)
+ return false;
+ change = nla_get_bitfield32(attr);
+ newval = (*dst & ~change.selector) | (change.value & change.selector);
+ if (*dst == newval)
+ return false;
+
+ *dst = newval;
+ return true;
+}
+
+static inline void warn_partial_info(struct genl_info *info)
+{
+ ETHNL_SET_ERRMSG(info, "not all requested data could be retrieved");
+}
+
+/* Check user privileges explicitly to allow finer access control based on
+ * context of the request or hiding part of the information from unprivileged
+ * users
+ */
+static inline bool ethnl_is_privileged(struct sk_buff *skb)
+{
+ struct net *net = sock_net(skb->sk);
+
+ return netlink_ns_capable(skb, net->user_ns, CAP_NET_ADMIN);
+}
+
#endif /* _NET_ETHTOOL_NETLINK_H */
--
2.18.0
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 <[email protected]>
---
Documentation/networking/ethtool-netlink.txt | 69 ++-
include/linux/ethtool_netlink.h | 3 +
include/linux/netdevice.h | 2 +
include/uapi/linux/ethtool.h | 3 +
include/uapi/linux/ethtool_netlink.h | 37 ++
net/ethtool/Makefile | 2 +-
net/ethtool/common.c | 112 +++++
net/ethtool/common.h | 8 +
net/ethtool/ioctl.c | 108 +----
net/ethtool/netlink.c | 69 +++
net/ethtool/netlink.h | 2 +
net/ethtool/settings.c | 416 +++++++++++++++++++
12 files changed, 725 insertions(+), 106 deletions(-)
create mode 100644 net/ethtool/settings.c
diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt
index 1e3d5ffc97ab..60d426cf6dad 100644
--- a/Documentation/networking/ethtool-netlink.txt
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -123,6 +123,8 @@ List of message types
ETHNL_CMD_SET_STRSET response only
ETHNL_CMD_GET_DRVINFO
ETHNL_CMD_SET_DRVINFO 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.
@@ -192,6 +194,63 @@ GET_DRVINFO requests).
GET_DRVINFO 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
+ ETH_SETTINGS_IM_MSGLEVEL msglevel
+ ETH_SETTINGS_IM_WOLINFO struct ethtool_wolinfo
+ ETH_SETTINGS_IM_LINK link state
+
+Response contents:
+
+ ETHA_SETTINGS_DEV (nested) device identification
+ 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.
+
+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
-------------------
@@ -201,16 +260,16 @@ 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_DRVINFO
ETHTOOL_GREGS n/a
-ETHTOOL_GWOL n/a
+ETHTOOL_GWOL ETHNL_CMD_GET_SETTINGS
ETHTOOL_SWOL n/a
-ETHTOOL_GMSGLVL n/a
+ETHTOOL_GMSGLVL ETHNL_CMD_GET_SETTINGS
ETHTOOL_SMSGLVL n/a
ETHTOOL_NWAY_RST n/a
-ETHTOOL_GLINK n/a
+ETHTOOL_GLINK ETHNL_CMD_GET_SETTINGS
ETHTOOL_GEEPROM n/a
ETHTOOL_SEEPROM n/a
ETHTOOL_GCOALESCE n/a
@@ -275,7 +334,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..fba8ff961887 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 \
+ ((__ETHTOOL_LINK_MODE_MASK_NBITS + 31) / 32)
+
enum ethtool_multicast_groups {
ETHNL_MCGRP_MONITOR,
};
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index c4b0c575d57e..93071380558c 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -3738,6 +3738,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 2ae393963704..98f645a38168 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 df4de61fac48..66df44aa7226 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_DRVINFO,
ETHNL_CMD_SET_DRVINFO, /* only for reply */
+ ETHNL_CMD_GET_SETTINGS,
+ ETHNL_CMD_SET_SETTINGS,
__ETHNL_CMD_MAX,
ETHNL_CMD_MAX = (__ETHNL_CMD_MAX - 1)
@@ -146,6 +148,41 @@ enum {
ETHA_DRVINFO_MAX = (__ETHA_DRVINFO_MAX - 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_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_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/ethtool/Makefile b/net/ethtool/Makefile
index 2e840ae0ba1e..8dd98310ed6f 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 strset.o drvinfo.o
+ethtool_nl-y := netlink.o strset.o drvinfo.o settings.o
diff --git a/net/ethtool/common.c b/net/ethtool/common.c
index 1dc4a6515996..986e82664447 100644
--- a/net/ethtool/common.c
+++ b/net/ethtool/common.c
@@ -128,3 +128,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/ethtool/common.h b/net/ethtool/common.h
index 0f768c1be527..ec90d4ccddf7 100644
--- a/net/ethtool/common.h
+++ b/net/ethtool/common.h
@@ -12,5 +12,13 @@ extern const char 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);
+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/ethtool/ioctl.c b/net/ethtool/ioctl.c
index 7b5831d35bca..8613434b6fc0 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -352,54 +352,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)
*/
@@ -460,50 +412,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.
*/
@@ -1359,11 +1267,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;
@@ -1428,12 +1336,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/ethtool/netlink.c b/net/ethtool/netlink.c
index 305baa02ff70..6c14185a6466 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -2,12 +2,68 @@
#include <linux/module.h>
#include <linux/bitmap.h>
+#include <linux/rtnetlink.h>
#include <net/sock.h>
#include <linux/ethtool_netlink.h>
#include "netlink.h"
u32 ethnl_bcast_seq;
+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",
+};
+
static const struct nla_policy dev_policy[ETHA_DEV_MAX + 1] = {
[ETHA_DEV_UNSPEC] = { .type = NLA_UNSPEC },
[ETHA_DEV_INDEX] = { .type = NLA_U32 },
@@ -671,11 +727,14 @@ static struct notifier_block ethnl_netdev_notifier = {
int ethnl_get_strset(struct sk_buff *skb, struct genl_info *info);
int ethnl_get_drvinfo(struct sk_buff *skb, struct genl_info *info);
+int ethnl_get_settings(struct sk_buff *skb, struct genl_info *info);
int ethnl_strset_start(struct netlink_callback *cb);
int ethnl_drvinfo_start(struct netlink_callback *cb);
+int ethnl_settings_start(struct netlink_callback *cb);
int ethnl_strset_done(struct netlink_callback *cb);
+int ethnl_settings_done(struct netlink_callback *cb);
static const struct genl_ops ethtool_genl_ops[] = {
{
@@ -691,6 +750,13 @@ static const struct genl_ops ethtool_genl_ops[] = {
.start = ethnl_drvinfo_start,
.dumpit = ethnl_dumpit,
},
+ {
+ .cmd = ETHNL_CMD_GET_SETTINGS,
+ .doit = ethnl_get_settings,
+ .start = ethnl_settings_start,
+ .dumpit = ethnl_dumpit,
+ .done = ethnl_settings_done,
+ },
};
static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
@@ -715,6 +781,9 @@ static int __init ethtool_nl_init(void)
{
int ret;
+ BUILD_BUG_ON(ARRAY_SIZE(link_mode_names) <
+ __ETHTOOL_LINK_MODE_MASK_NBITS);
+
ret = genl_register_family(ðtool_genl_family);
if (ret < 0)
return ret;
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 94c14ec2c3fc..789aceae9f5e 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -15,6 +15,8 @@ extern u32 ethnl_bcast_seq;
extern struct genl_family ethtool_genl_family;
+extern const char *const link_mode_names[];
+
struct net_device *ethnl_dev_get(struct genl_info *info, struct nlattr *nest);
int ethnl_fill_dev(struct sk_buff *msg, struct net_device *dev, u16 attrtype);
diff --git a/net/ethtool/settings.c b/net/ethtool/settings.c
new file mode 100644
index 000000000000..dd76599f311f
--- /dev/null
+++ b/net/ethtool/settings.c
@@ -0,0 +1,416 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#include "netlink.h"
+#include "common.h"
+#include <linux/rtnetlink.h>
+
+struct settings_data {
+ struct ethtool_link_ksettings ksettings;
+ struct ethtool_link_settings *lsettings;
+ struct ethtool_wolinfo wolinfo;
+ int link;
+ u32 msglevel;
+ bool lpm_empty;
+ u32 req_mask;
+};
+
+struct settings_reqinfo {
+ struct net_device *dev;
+ u32 req_mask;
+ bool compact;
+ bool is_privileged;
+ bool have_rtnl;
+};
+
+/* We want to allow ~0 as selector for backward compatibility (to just set
+ * given set of modes, whatever kernel supports) so that we allow all bits
+ * on validation and do our own sanity check later.
+ */
+static u32 all_bits = ~(u32)0;
+
+static const struct nla_policy settings_policy[ETHA_SETTINGS_MAX + 1] = {
+ [ETHA_SETTINGS_UNSPEC] = { .type = NLA_UNSPEC },
+ [ETHA_SETTINGS_DEV] = { .type = NLA_NESTED },
+ [ETHA_SETTINGS_INFOMASK] = { .type = NLA_U32 },
+ [ETHA_SETTINGS_COMPACT] = { .type = NLA_FLAG },
+ [ETHA_SETTINGS_SPEED] = { .type = NLA_U32 },
+ [ETHA_SETTINGS_DUPLEX] = { .type = NLA_U8 },
+ [ETHA_SETTINGS_PORT] = { .type = NLA_U8 },
+ [ETHA_SETTINGS_PHYADDR] = { .type = NLA_U8 },
+ [ETHA_SETTINGS_AUTONEG] = { .type = NLA_U8 },
+ [ETHA_SETTINGS_MDIO_SUPPORT] = { .type = NLA_BITFIELD32 },
+ [ETHA_SETTINGS_TP_MDIX] = { .type = NLA_U8 },
+ [ETHA_SETTINGS_TP_MDIX_CTRL] = { .type = NLA_U8 },
+ [ETHA_SETTINGS_TRANSCEIVER] = { .type = NLA_U8 },
+ [ETHA_SETTINGS_WOL_MODES] = { .type = NLA_BITFIELD32,
+ .validation_data = &all_bits },
+ [ETHA_SETTINGS_SOPASS] = { .type = NLA_BINARY,
+ .len = SOPASS_MAX },
+ [ETHA_SETTINGS_MSGLVL] = { .type = NLA_BITFIELD32,
+ .validation_data = &all_bits },
+ [ETHA_SETTINGS_LINK_MODES] = { .type = NLA_NESTED },
+ [ETHA_SETTINGS_PEER_MODES] = { .type = NLA_NESTED },
+ [ETHA_SETTINGS_LINK] = { .type = NLA_FLAG },
+};
+
+/* 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(struct settings_data *data,
+ struct settings_reqinfo *req_info)
+{
+ struct ethtool_link_ksettings *ksettings = &data->ksettings;
+ u32 req_mask = req_info->req_mask;
+ bool compact = req_info->compact;
+ size_t len = 0;
+ int ret = 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;
+
+ ret = ethnl_bitset32_size(compact,
+ __ETHTOOL_LINK_MODE_MASK_NBITS,
+ advertising, supported,
+ link_mode_names);
+ if (ret < 0)
+ return ret;
+ len += ret;
+ ret = ethnl_bitset32_size(compact,
+ __ETHTOOL_LINK_MODE_MASK_NBITS,
+ lp_advertising, lp_advertising,
+ link_mode_names);
+ if (ret < 0)
+ return ret;
+ len += ret;
+ }
+ 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)
+ ETHNL_SET_ERRMSG(info, "failed to retrieve link settings");
+ return ret;
+}
+
+static int ethnl_get_legacy_settings(struct genl_info *info,
+ struct net_device *dev,
+ struct ethtool_cmd *cmd)
+{
+ int ret;
+
+ if (!dev->ethtool_ops->get_settings) {
+ /* we already tried ->get_link_ksettings */
+ ETHNL_SET_ERRMSG(info, "link settings retrieval unsupported");
+ return -EOPNOTSUPP;
+ }
+ ret = dev->ethtool_ops->get_settings(dev, cmd);
+ if (ret < 0)
+ ETHNL_SET_ERRMSG(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)
+ ETHNL_SET_ERRMSG(info, "failed to retrieve wol info");
+ return ret;
+}
+
+static int parse_settings_req(struct settings_reqinfo *req_info,
+ struct genl_info *info, struct sk_buff *skb,
+ const struct nlmsghdr *nlhdr)
+{
+ struct nlattr *tb[ETHA_SETTINGS_MAX + 1];
+ int ret;
+
+ memset(req_info, '\0', sizeof(*req_info));
+ req_info->is_privileged = ethnl_is_privileged(skb);
+
+ ret = genlmsg_parse(nlhdr, ðtool_genl_family, tb,
+ ETHA_SETTINGS_MAX, 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_DEFAULT;
+
+ return 0;
+}
+
+static int prepare_settings(struct settings_data *data,
+ struct settings_reqinfo *req_info,
+ struct genl_info *info, struct net_device *dev)
+{
+ u32 req_mask = req_info->req_mask;
+ int ret;
+
+ memset(data, '\0', sizeof(*data));
+ data->lsettings = &data->ksettings.base;
+ data->lpm_empty = true;
+ data->link = -EOPNOTSUPP;
+
+ if (!req_info->have_rtnl)
+ rtnl_lock();
+ if (req_mask & (ETH_SETTINGS_IM_LINKINFO | ETH_SETTINGS_IM_LINKMODES)) {
+ ret = ethnl_get_link_ksettings(info, dev, &data->ksettings);
+ if (ret < 0) {
+ warn_partial_info(info);
+ 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);
+ }
+ if (req_mask & ETH_SETTINGS_IM_MSGLEVEL) {
+ if (dev->ethtool_ops->get_msglevel) {
+ data->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, &data->wolinfo);
+ if (ret < 0) {
+ warn_partial_info(info);
+ req_mask &= ~ETH_SETTINGS_IM_WOLINFO;
+ }
+ }
+ if (req_mask & ETH_SETTINGS_IM_LINK)
+ data->link = __ethtool_get_link(dev);
+ if (!req_info->have_rtnl)
+ rtnl_unlock();
+
+ data->req_mask = req_mask;
+ return 0;
+}
+
+static int fill_settings(struct sk_buff *rskb, struct settings_data *data,
+ struct settings_reqinfo *req_info)
+{
+ struct ethtool_link_settings *lsettings = data->lsettings;
+ u32 req_mask = data->req_mask;
+ bool compact = req_info->compact;
+ int ret;
+
+ 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))
+ return ret;
+ }
+ if (req_mask & ETH_SETTINGS_IM_LINKMODES) {
+ u32 *supported = (u32 *)data->ksettings.link_modes.supported;
+ u32 *advertising =
+ (u32 *)data->ksettings.link_modes.advertising;
+ u32 *lp_advertising =
+ (u32 *)data->ksettings.link_modes.lp_advertising;
+
+ ret = ethnl_put_bitset32(rskb, ETHA_SETTINGS_LINK_MODES,
+ compact,
+ __ETHTOOL_LINK_MODE_MASK_NBITS,
+ advertising, supported,
+ link_mode_names);
+ if (ret < 0)
+ return ret;
+ if (!data->lpm_empty) {
+ ret = ethnl_put_bitset32(rskb, ETHA_SETTINGS_PEER_MODES,
+ compact,
+ __ETHTOOL_LINK_MODE_MASK_NBITS,
+ lp_advertising, lp_advertising,
+ link_mode_names);
+ if (ret < 0)
+ return ret;
+ }
+ ret = -EMSGSIZE;
+ }
+ if (req_mask & ETH_SETTINGS_IM_MSGLEVEL) {
+ if (nla_put_bitfield32(rskb, ETHA_SETTINGS_MSGLVL,
+ data->msglevel, NETIF_MSG_ALL))
+ return ret;
+ }
+ 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,
+ data->wolinfo.wolopts,
+ data->wolinfo.supported))
+ return ret;
+ if (req_info->is_privileged &&
+ nla_put(rskb, ETHA_SETTINGS_SOPASS,
+ sizeof(data->wolinfo.sopass), data->wolinfo.sopass))
+ return ret;
+ }
+ if (req_mask & ETH_SETTINGS_IM_LINK && data->link >= 0) {
+ if (nla_put_u32(rskb, ETHA_SETTINGS_LINK, data->link))
+ return ret;
+ }
+
+ return 0;
+}
+
+int ethnl_get_settings(struct sk_buff *skb, struct genl_info *info)
+{
+ struct settings_data data;
+ struct settings_reqinfo req_info;
+ struct sk_buff *rskb;
+ int reply_len;
+ void *ehdr;
+ int ret;
+
+ ret = parse_settings_req(&req_info, info, skb, info->nlhdr);
+ if (ret < 0)
+ goto err_dev;
+ ret = prepare_settings(&data, &req_info, info, req_info.dev);
+ if (ret < 0)
+ goto err_dev;
+ reply_len = settings_size(&data, &req_info);
+ if (ret < 0)
+ goto err_dev;
+ ret = -ENOMEM;
+ rskb = ethnl_reply_init(reply_len, req_info.dev, ETHNL_CMD_SET_SETTINGS,
+ ETHA_SETTINGS_DEV, info, &ehdr);
+ if (!rskb)
+ goto err_dev;
+ ret = fill_settings(rskb, &data, &req_info);
+ if (ret < 0)
+ goto err;
+
+ genlmsg_end(rskb, ehdr);
+ dev_put(req_info.dev);
+ return genlmsg_reply(rskb, info);
+
+err:
+ WARN_ONCE(ret == -EMSGSIZE,
+ "calculated message payload length (%d) not sufficient\n",
+ reply_len);
+ nlmsg_free(rskb);
+err_dev:
+ if (req_info.dev)
+ dev_put(req_info.dev);
+ return ret;
+}
+
+static int settings_dump(struct sk_buff *skb, struct netlink_callback *cb,
+ struct net_device *dev)
+{
+ struct settings_data data;
+ struct settings_reqinfo *req_info;
+ int ret;
+
+ req_info = (struct settings_reqinfo *)cb->args[4];
+ ret = prepare_settings(&data, req_info, NULL, dev);
+ if (ret < 0)
+ return ret;
+ ret = ethnl_fill_dev(skb, dev, ETHA_SETTINGS_DEV);
+ if (ret < 0)
+ return ret;
+ ret = fill_settings(skb, &data, req_info);
+ return ret;
+}
+
+int ethnl_settings_start(struct netlink_callback *cb)
+{
+ struct settings_reqinfo *req_info;
+ int ret;
+
+ req_info = kmalloc(sizeof(*req_info), GFP_KERNEL);
+ if (!req_info)
+ return -ENOMEM;
+ ret = parse_settings_req(req_info, NULL, cb->skb, cb->nlh);
+ if (ret < 0) {
+ if (req_info->dev)
+ dev_put(req_info->dev);
+ req_info->dev = NULL;
+ return ret;
+ }
+
+ cb->args[0] = (long)settings_dump;
+ cb->args[1] = ETHNL_CMD_SET_SETTINGS;
+ cb->args[4] = (long)req_info;
+
+ return 0;
+}
+
+int ethnl_settings_done(struct netlink_callback *cb)
+{
+ struct settings_reqinfo *req_info;
+
+ req_info = (struct settings_reqinfo *)cb->args[4];
+ if (req_info->dev)
+ dev_put(req_info->dev);
+ kfree(req_info);
+
+ return 0;
+}
--
2.18.0
Sets the information provided by ETHTOOL_SLINKSETTINGS, ETHTOOL_SWOL and
ETHTOOL_SMSGLVL. Unlike with ioctl(), userspace can send only some
attributes so that we only need to call ethtool_ops callbacks which we
really need (and the "set" callback is only called when we actually changed
some setting).
Signed-off-by: Michal Kubecek <[email protected]>
---
Documentation/networking/ethtool-netlink.txt | 43 +-
net/ethtool/netlink.c | 6 +
net/ethtool/settings.c | 495 +++++++++++++++++++
3 files changed, 539 insertions(+), 5 deletions(-)
diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt
index 3993652f81a9..c7fe4f518972 100644
--- a/Documentation/networking/ethtool-netlink.txt
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -124,7 +124,7 @@ List of message types
ETHNL_CMD_GET_DRVINFO
ETHNL_CMD_SET_DRVINFO response only
ETHNL_CMD_GET_SETTINGS
- ETHNL_CMD_SET_SETTINGS response only (for now)
+ ETHNL_CMD_SET_SETTINGS
All constants use ETHNL_CMD_ prefix, usually followed by "GET", "SET" or "ACT"
to indicate the type.
@@ -267,6 +267,39 @@ netlink or ioctl ethtool interface; feature notifications are also sent
whenever netdev_update_features() or netdev_change_features() is called.
+SET_SETTINGS
+------------
+
+SET_SETTINGS request allows setting some of the data reported by GET_SETTINGS.
+Request flags, info_mask and index are ignored. These attributes are allowed
+to be passed with SET_SETTINGS request:
+
+ ETHA_SETTINGS_DEV (nested) device identification
+ 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_TP_MDIX_CTRL (u8) MDI(-X) control
+ 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
+
+For both bitfield32 types, value and selector work the usual way, i.e. bits
+set in selector are set to corresponding bits from value and the rest is
+preserved. In a similar fashion, ETHA_SETTINGS_LINK_MODES allows setting
+advertised link modes.
+
+If autonegotiation is on (either set now or kept from before), advertised
+modes are not changed (no ETHA_SETTINGS_LINK_MODES attribute) and at least one
+of speed and duplex is specified, kernel adjusts advertised modes to all
+supported modes matching speed, duplex or both (whatever is specified). This
+autoselection is done on ethtool side with ioctl interface, netlink interface
+is supposed to allow requesting changes without knowing what exactly kernel
+supports.
+
+
Request translation
-------------------
@@ -277,13 +310,13 @@ have their netlink replacement yet.
ioctl command netlink command
---------------------------------------------------------------------
ETHTOOL_GSET ETHNL_CMD_GET_SETTINGS
-ETHTOOL_SSET n/a
+ETHTOOL_SSET ETHNL_CMD_SET_SETTINGS
ETHTOOL_GDRVINFO ETHNL_CMD_GET_DRVINFO
ETHTOOL_GREGS n/a
ETHTOOL_GWOL ETHNL_CMD_GET_SETTINGS
-ETHTOOL_SWOL n/a
+ETHTOOL_SWOL ETHNL_CMD_SET_SETTINGS
ETHTOOL_GMSGLVL ETHNL_CMD_GET_SETTINGS
-ETHTOOL_SMSGLVL n/a
+ETHTOOL_SMSGLVL ETHNL_CMD_SET_SETTINGS
ETHTOOL_NWAY_RST n/a
ETHTOOL_GLINK ETHNL_CMD_GET_SETTINGS
ETHTOOL_GEEPROM n/a
@@ -351,7 +384,7 @@ ETHTOOL_STUNABLE n/a
ETHTOOL_GPHYSTATS n/a
ETHTOOL_PERQUEUE n/a
ETHTOOL_GLINKSETTINGS ETHNL_CMD_GET_SETTINGS
-ETHTOOL_SLINKSETTINGS n/a
+ETHTOOL_SLINKSETTINGS ETHNL_CMD_SET_SETTINGS
ETHTOOL_PHY_GTUNABLE n/a
ETHTOOL_PHY_STUNABLE n/a
ETHTOOL_GFECPARAM n/a
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 0e2158092a44..6e183caca01f 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -730,6 +730,7 @@ static struct notifier_block ethnl_netdev_notifier = {
int ethnl_get_strset(struct sk_buff *skb, struct genl_info *info);
int ethnl_get_drvinfo(struct sk_buff *skb, struct genl_info *info);
int ethnl_get_settings(struct sk_buff *skb, struct genl_info *info);
+int ethnl_set_settings(struct sk_buff *skb, struct genl_info *info);
int ethnl_strset_start(struct netlink_callback *cb);
int ethnl_drvinfo_start(struct netlink_callback *cb);
@@ -759,6 +760,11 @@ static const struct genl_ops ethtool_genl_ops[] = {
.dumpit = ethnl_dumpit,
.done = ethnl_settings_done,
},
+ {
+ .cmd = ETHNL_CMD_SET_SETTINGS,
+ .flags = GENL_UNS_ADMIN_PERM,
+ .doit = ethnl_set_settings,
+ },
};
static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
diff --git a/net/ethtool/settings.c b/net/ethtool/settings.c
index 739bd006c924..678199e621a2 100644
--- a/net/ethtool/settings.c
+++ b/net/ethtool/settings.c
@@ -88,6 +88,222 @@ struct settings_reqinfo {
bool have_rtnl;
};
+struct link_mode_info {
+ int speed;
+ u8 duplex;
+};
+
+static const struct link_mode_info link_mode_params[] = {
+ [ETHTOOL_LINK_MODE_10baseT_Half_BIT] = {
+ .speed = 10,
+ .duplex = DUPLEX_HALF,
+ },
+ [ETHTOOL_LINK_MODE_10baseT_Full_BIT] = {
+ .speed = 10,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_100baseT_Half_BIT] = {
+ .speed = 100,
+ .duplex = DUPLEX_HALF,
+ },
+ [ETHTOOL_LINK_MODE_100baseT_Full_BIT] = {
+ .speed = 100,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_1000baseT_Half_BIT] = {
+ .speed = 1000,
+ .duplex = DUPLEX_HALF,
+ },
+ [ETHTOOL_LINK_MODE_1000baseT_Full_BIT] = {
+ .speed = 1000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_Autoneg_BIT] = {
+ .speed = SPEED_UNKNOWN,
+ .duplex = DUPLEX_UNKNOWN,
+ },
+ [ETHTOOL_LINK_MODE_TP_BIT] = {
+ .speed = SPEED_UNKNOWN,
+ .duplex = DUPLEX_UNKNOWN,
+ },
+ [ETHTOOL_LINK_MODE_AUI_BIT] = {
+ .speed = SPEED_UNKNOWN,
+ .duplex = DUPLEX_UNKNOWN,
+ },
+ [ETHTOOL_LINK_MODE_MII_BIT] = {
+ .speed = SPEED_UNKNOWN,
+ .duplex = DUPLEX_UNKNOWN,
+ },
+ [ETHTOOL_LINK_MODE_FIBRE_BIT] = {
+ .speed = SPEED_UNKNOWN,
+ .duplex = DUPLEX_UNKNOWN,
+ },
+ [ETHTOOL_LINK_MODE_BNC_BIT] = {
+ .speed = SPEED_UNKNOWN,
+ .duplex = DUPLEX_UNKNOWN,
+ },
+ [ETHTOOL_LINK_MODE_10000baseT_Full_BIT] = {
+ .speed = 10000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_Pause_BIT] = {
+ .speed = SPEED_UNKNOWN,
+ .duplex = DUPLEX_UNKNOWN,
+ },
+ [ETHTOOL_LINK_MODE_Asym_Pause_BIT] = {
+ .speed = SPEED_UNKNOWN,
+ .duplex = DUPLEX_UNKNOWN,
+ },
+ [ETHTOOL_LINK_MODE_2500baseX_Full_BIT] = {
+ .speed = 2500,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_Backplane_BIT] = {
+ .speed = SPEED_UNKNOWN,
+ .duplex = DUPLEX_UNKNOWN,
+ },
+ [ETHTOOL_LINK_MODE_1000baseKX_Full_BIT] = {
+ .speed = 1000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT] = {
+ .speed = 10000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_10000baseKR_Full_BIT] = {
+ .speed = 10000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_10000baseR_FEC_BIT] = {
+ .speed = 10000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_20000baseMLD2_Full_BIT] = {
+ .speed = 20000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_20000baseKR2_Full_BIT] = {
+ .speed = 20000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT] = {
+ .speed = 40000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT] = {
+ .speed = 40000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT] = {
+ .speed = 40000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT] = {
+ .speed = 40000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_56000baseKR4_Full_BIT] = {
+ .speed = 56000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_56000baseCR4_Full_BIT] = {
+ .speed = 56000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_56000baseSR4_Full_BIT] = {
+ .speed = 56000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_56000baseLR4_Full_BIT] = {
+ .speed = 56000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_25000baseCR_Full_BIT] = {
+ .speed = 25000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_25000baseKR_Full_BIT] = {
+ .speed = 25000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_25000baseSR_Full_BIT] = {
+ .speed = 25000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT] = {
+ .speed = 50000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT] = {
+ .speed = 50000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT] = {
+ .speed = 100000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT] = {
+ .speed = 100000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT] = {
+ .speed = 100000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT] = {
+ .speed = 100000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT] = {
+ .speed = 50000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_1000baseX_Full_BIT] = {
+ .speed = 1000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_10000baseCR_Full_BIT] = {
+ .speed = 10000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_10000baseSR_Full_BIT] = {
+ .speed = 10000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_10000baseLR_Full_BIT] = {
+ .speed = 10000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT] = {
+ .speed = 10000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_10000baseER_Full_BIT] = {
+ .speed = 10000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_2500baseT_Full_BIT] = {
+ .speed = 2500,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_5000baseT_Full_BIT] = {
+ .speed = 5000,
+ .duplex = DUPLEX_FULL,
+ },
+ [ETHTOOL_LINK_MODE_FEC_NONE_BIT] = {
+ .speed = SPEED_UNKNOWN,
+ .duplex = DUPLEX_UNKNOWN,
+ },
+ [ETHTOOL_LINK_MODE_FEC_RS_BIT] = {
+ .speed = SPEED_UNKNOWN,
+ .duplex = DUPLEX_UNKNOWN,
+ },
+ [ETHTOOL_LINK_MODE_FEC_BASER_BIT] = {
+ .speed = SPEED_UNKNOWN,
+ .duplex = DUPLEX_UNKNOWN,
+ },
+};
+
/* We want to allow ~0 as selector for backward compatibility (to just set
* given set of modes, whatever kernel supports) so that we allow all bits
* on validation and do our own sanity check later.
@@ -606,3 +822,282 @@ void ethnl_settings_notify(struct netdev_notifier_ethtool_info *info)
err_skb:
nlmsg_free(skb);
}
+
+/* SET_SETTINGS */
+
+static int ethnl_set_link_ksettings(struct genl_info *info,
+ struct net_device *dev,
+ struct ethtool_link_ksettings *ksettings)
+{
+ int ret = dev->ethtool_ops->set_link_ksettings(dev, ksettings);
+
+ if (ret < 0)
+ ETHNL_SET_ERRMSG(info, "link settings update failed");
+ return ret;
+}
+
+static int ethnl_set_legacy_settings(struct genl_info *info,
+ struct net_device *dev,
+ struct ethtool_cmd *cmd)
+{
+ int ret;
+
+ if (!dev->ethtool_ops->set_settings) {
+ /* we already tried ->set_link_ksettings */
+ ETHNL_SET_ERRMSG(info, "link settings update unsupported");
+ return -EOPNOTSUPP;
+ }
+ ret = dev->ethtool_ops->set_settings(dev, cmd);
+ if (ret < 0)
+ ETHNL_SET_ERRMSG(info, "link settings update failed");
+
+ return ret;
+}
+
+static int ethnl_set_wol(struct genl_info *info, struct net_device *dev,
+ struct ethtool_wolinfo *wolinfo)
+{
+ int ret = dev->ethtool_ops->set_wol(dev, wolinfo);
+
+ if (ret < 0)
+ ETHNL_SET_ERRMSG(info, "wol info update failed");
+ return ret;
+}
+
+/* Set advertised link modes to all supported modes matching requested speed
+ * and duplex values. Called when autonegotiation is on, speed or duplex is
+ * requested but no link mode change. This is done in userspace with ioctl()
+ * interface, move it into kernel for netlink.
+ * Returns true if advertised modes bitmap was modified.
+ */
+static bool auto_link_modes(unsigned long *supported,
+ unsigned long *advertising, unsigned int nbits,
+ struct nlattr *speed_attr,
+ struct nlattr *duplex_attr)
+{
+ u8 duplex = duplex_attr ? nla_get_u8(duplex_attr) : DUPLEX_UNKNOWN;
+ u32 speed = speed_attr ? nla_get_u32(speed_attr) : SPEED_UNKNOWN;
+ DECLARE_BITMAP(old_adv, nbits);
+ unsigned int i;
+
+ bitmap_copy(old_adv, advertising, nbits);
+
+ for (i = 0; i < nbits; i++) {
+ const struct link_mode_info *info = &link_mode_params[i];
+
+ if (info->speed == SPEED_UNKNOWN)
+ continue;
+ if (test_bit(i, supported) &&
+ (!speed_attr || info->speed == speed) &&
+ (!duplex_attr || info->duplex == duplex))
+ set_bit(i, advertising);
+ else
+ clear_bit(i, advertising);
+ }
+
+ return !bitmap_equal(old_adv, advertising, nbits);
+}
+
+/* Update device settings using ->set_link_ksettings() callback */
+static int ethnl_update_ksettings(struct genl_info *info, struct nlattr **tb,
+ struct net_device *dev)
+{
+ struct ethtool_link_ksettings ksettings = {};
+ struct ethtool_link_settings *lsettings;
+ bool mod = false;
+ int ret;
+
+ ret = ethnl_get_link_ksettings(info, dev, &ksettings);
+ if (ret < 0)
+ return ret;
+ lsettings = &ksettings.base;
+
+ mod = false;
+ if (ethnl_update_u32(&lsettings->speed, tb[ETHA_SETTINGS_SPEED]))
+ mod = true;
+ if (ethnl_update_u8(&lsettings->duplex, tb[ETHA_SETTINGS_DUPLEX]))
+ mod = true;
+ if (ethnl_update_u8(&lsettings->port, tb[ETHA_SETTINGS_PORT]))
+ mod = true;
+ if (ethnl_update_u8(&lsettings->phy_address, tb[ETHA_SETTINGS_PHYADDR]))
+ mod = true;
+ if (ethnl_update_u8(&lsettings->autoneg, tb[ETHA_SETTINGS_AUTONEG]))
+ mod = true;
+ if (ethnl_update_u8(&lsettings->eth_tp_mdix_ctrl,
+ tb[ETHA_SETTINGS_TP_MDIX_CTRL]))
+ mod = true;
+ if (ethnl_update_bitset(ksettings.link_modes.advertising, NULL,
+ __ETHTOOL_LINK_MODE_MASK_NBITS,
+ tb[ETHA_SETTINGS_LINK_MODES],
+ &ret, link_mode_names, info))
+ mod = true;
+ if (ret < 0)
+ return ret;
+
+ if (!tb[ETHA_SETTINGS_LINK_MODES] && lsettings->autoneg &&
+ (tb[ETHA_SETTINGS_SPEED] || tb[ETHA_SETTINGS_DUPLEX])) {
+ if (auto_link_modes(ksettings.link_modes.supported,
+ ksettings.link_modes.advertising,
+ __ETHTOOL_LINK_MODE_MASK_NBITS,
+ tb[ETHA_SETTINGS_SPEED],
+ tb[ETHA_SETTINGS_DUPLEX]))
+ mod = true;
+ }
+
+ if (mod) {
+ ret = ethnl_set_link_ksettings(info, dev, &ksettings);
+ if (ret < 0)
+ return ret;
+ }
+
+ return mod ? 1 : 0;
+}
+
+/* Update legacy settings using ->set_settings() callback. */
+static int ethnl_update_lsettings(struct genl_info *info, struct nlattr **tb,
+ struct net_device *dev)
+{
+ struct ethtool_cmd cmd = {};
+ const unsigned int nbits = sizeof(cmd.advertising) * BITS_PER_BYTE;
+ DECLARE_BITMAP(advertising, nbits);
+ DECLARE_BITMAP(supported, nbits);
+ bool mod = false;
+ u32 speed;
+ int ret;
+
+ ret = ethnl_get_legacy_settings(info, dev, &cmd);
+ if (ret < 0)
+ return ret;
+ bitmap_from_arr32(supported, &cmd.supported, nbits);
+ bitmap_from_arr32(advertising, &cmd.advertising, nbits);
+
+ mod = false;
+ speed = ethtool_cmd_speed(&cmd);
+ if (ethnl_update_u32(&speed, tb[ETHA_SETTINGS_SPEED])) {
+ ethtool_cmd_speed_set(&cmd, speed);
+ mod = true;
+ }
+ if (ethnl_update_u8(&cmd.duplex, tb[ETHA_SETTINGS_DUPLEX]))
+ mod = true;
+ if (ethnl_update_u8(&cmd.port, tb[ETHA_SETTINGS_PORT]))
+ mod = true;
+ if (ethnl_update_u8(&cmd.phy_address, tb[ETHA_SETTINGS_PHYADDR]))
+ mod = true;
+ if (ethnl_update_u8(&cmd.autoneg, tb[ETHA_SETTINGS_AUTONEG]))
+ mod = true;
+ if (ethnl_update_u8(&cmd.eth_tp_mdix_ctrl,
+ tb[ETHA_SETTINGS_TP_MDIX_CTRL]))
+ mod = true;
+ if (ethnl_update_bitset(advertising, NULL, nbits,
+ tb[ETHA_SETTINGS_LINK_MODES],
+ &ret, link_mode_names, info)) {
+ bitmap_to_arr32(&cmd.advertising, advertising, nbits);
+ mod = true;
+ }
+ if (ret < 0)
+ return ret;
+
+ if (!tb[ETHA_SETTINGS_LINK_MODES] && cmd.autoneg &&
+ (tb[ETHA_SETTINGS_SPEED] || tb[ETHA_SETTINGS_DUPLEX])) {
+ if (auto_link_modes(supported, advertising, nbits,
+ tb[ETHA_SETTINGS_SPEED],
+ tb[ETHA_SETTINGS_DUPLEX])) {
+ bitmap_to_arr32(&cmd.advertising, advertising, nbits);
+ mod = true;
+ }
+ }
+
+ if (mod) {
+ ret = ethnl_set_legacy_settings(info, dev, &cmd);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+int ethnl_set_settings(struct sk_buff *skb, struct genl_info *info)
+{
+ struct nlattr *tb[ETHA_SETTINGS_MAX + 1];
+ struct ethtool_wolinfo wolinfo = {};
+ struct net_device *dev;
+ u32 req_mask = 0;
+ bool mod;
+ int ret;
+
+ ret = genlmsg_parse(info->nlhdr, ðtool_genl_family, tb,
+ ETHA_SETTINGS_MAX, settings_policy, info->extack);
+ if (ret < 0)
+ return ret;
+ dev = ethnl_dev_get(info, tb[ETHA_SETTINGS_DEV]);
+ if (IS_ERR(dev))
+ return PTR_ERR(dev);
+ rtnl_lock();
+
+ /* read only attributes */
+ ret = -EINVAL;
+ if (tb[ETHA_SETTINGS_MDIO_SUPPORT] || tb[ETHA_SETTINGS_TP_MDIX] ||
+ tb[ETHA_SETTINGS_TRANSCEIVER] || tb[ETHA_SETTINGS_PEER_MODES] ||
+ tb[ETHA_SETTINGS_LINK]) {
+ ETHNL_SET_ERRMSG(info, "attempt to set a read only attribute");
+ goto out_unlock;
+ }
+
+ if (tb[ETHA_SETTINGS_SPEED] || tb[ETHA_SETTINGS_DUPLEX] ||
+ tb[ETHA_SETTINGS_PORT] || tb[ETHA_SETTINGS_PHYADDR] ||
+ tb[ETHA_SETTINGS_AUTONEG] || tb[ETHA_SETTINGS_TP_MDIX_CTRL] ||
+ tb[ETHA_SETTINGS_LINK_MODES]) {
+ if (dev->ethtool_ops->get_link_ksettings)
+ ret = ethnl_update_ksettings(info, tb, dev);
+ else
+ ret = ethnl_update_lsettings(info, tb, dev);
+ if (ret < 0)
+ goto out_unlock;
+ req_mask |= ETH_SETTINGS_IM_LINKINFO |
+ ETH_SETTINGS_IM_LINKMODES;
+ }
+ if (tb[ETHA_SETTINGS_WOL_MODES] || tb[ETHA_SETTINGS_SOPASS]) {
+ ret = ethnl_get_wol(info, dev, &wolinfo);
+ if (ret < 0)
+ goto out_unlock;
+
+ mod = false;
+ if (ethnl_update_bitfield32(&wolinfo.wolopts,
+ tb[ETHA_SETTINGS_WOL_MODES]))
+ mod = true;
+ if (ethnl_update_binary(wolinfo.sopass, SOPASS_MAX,
+ tb[ETHA_SETTINGS_SOPASS]))
+ mod = true;
+ if (mod) {
+ ret = ethnl_set_wol(info, dev, &wolinfo);
+ if (ret < 0)
+ goto out_unlock;
+ req_mask |= ETH_SETTINGS_IM_WOLINFO;
+ }
+ }
+ if (tb[ETHA_SETTINGS_MSGLVL]) {
+ u32 msglvl;
+
+ ret = -EOPNOTSUPP;
+ if (!dev->ethtool_ops->get_msglevel ||
+ !dev->ethtool_ops->set_msglevel) {
+ ETHNL_SET_ERRMSG(info,
+ "device does not provide msglvl access");
+ goto out_unlock;
+ }
+ msglvl = dev->ethtool_ops->get_msglevel(dev);
+ if (ethnl_update_bitfield32(&msglvl,
+ tb[ETHA_SETTINGS_MSGLVL])) {
+ dev->ethtool_ops->set_msglevel(dev, msglvl);
+ req_mask |= ETH_SETTINGS_IM_MSGLEVEL;
+ }
+ }
+ ret = 0;
+
+out_unlock:
+ if (req_mask)
+ ethnl_notify(dev, NULL, ETHNL_CMD_SET_SETTINGS, req_mask);
+ rtnl_unlock();
+ dev_put(dev);
+ return ret;
+}
--
2.18.0
Requests a contents of a string set, i.e. indexed array of strings; this
information is provided by ETHTOOL_GSSET_INFO and ETHTOOL_GSTRINGS commands
of ioctl interface. There are three types of requests
- no NLM_F_DUMP, no device: get all "global" stringsets
- no NLM_F_DUMP, with device: get all string sets related to the device
- NLM_F_DUMP, no device: get all device related string sets for all
devices
It's also possible to request only specific string sets.
In addition to string sets recognized by ioctl interface, GET_STRSET
request can also retrieve list of link modes.
Signed-off-by: Michal Kubecek <[email protected]>
---
Documentation/networking/ethtool-netlink.txt | 6 +-
include/uapi/linux/ethtool.h | 4 +
include/uapi/linux/ethtool_netlink.h | 42 ++
net/ethtool/Makefile | 2 +-
net/ethtool/common.c | 87 +++
net/ethtool/common.h | 13 +
net/ethtool/ioctl.c | 80 ---
net/ethtool/netlink.c | 13 +
net/ethtool/strset.c | 552 +++++++++++++++++++
9 files changed, 716 insertions(+), 83 deletions(-)
create mode 100644 net/ethtool/common.c
create mode 100644 net/ethtool/common.h
create mode 100644 net/ethtool/strset.c
diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt
index 0e83397f2975..8b43f41a8140 100644
--- a/Documentation/networking/ethtool-netlink.txt
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -119,6 +119,8 @@ List of message types
---------------------
ETHNL_CMD_EVENT notification only
+ ETHNL_CMD_GET_STRSET
+ ETHNL_CMD_SET_STRSET response only
All constants use ETHNL_CMD_ prefix, usually followed by "GET", "SET" or "ACT"
to indicate the type.
@@ -188,7 +190,7 @@ ETHTOOL_STXCSUM n/a
ETHTOOL_GSG n/a
ETHTOOL_SSG n/a
ETHTOOL_TEST n/a
-ETHTOOL_GSTRINGS n/a
+ETHTOOL_GSTRINGS ETHNL_CMD_GET_STRSET
ETHTOOL_PHYS_ID n/a
ETHTOOL_GSTATS n/a
ETHTOOL_GTSO n/a
@@ -216,7 +218,7 @@ ETHTOOL_FLASHDEV n/a
ETHTOOL_RESET n/a
ETHTOOL_SRXNTUPLE n/a
ETHTOOL_GRXNTUPLE n/a
-ETHTOOL_GSSET_INFO n/a
+ETHTOOL_GSSET_INFO ETHNL_CMD_GET_STRSET
ETHTOOL_GRXFHINDIR n/a
ETHTOOL_SRXFHINDIR n/a
ETHTOOL_GFEATURES n/a
diff --git a/include/uapi/linux/ethtool.h b/include/uapi/linux/ethtool.h
index 7363f18e65a5..2ae393963704 100644
--- a/include/uapi/linux/ethtool.h
+++ b/include/uapi/linux/ethtool.h
@@ -578,6 +578,10 @@ enum ethtool_stringset {
ETH_SS_TUNABLES,
ETH_SS_PHY_STATS,
ETH_SS_PHY_TUNABLES,
+ ETH_SS_LINK_MODES,
+
+ __ETH_SS_MAX,
+ ETH_SS_MAX = (__ETH_SS_MAX - 1)
};
/**
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index f162cd6f80d4..5177c1940c2b 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -8,6 +8,8 @@
enum {
ETHNL_CMD_NOOP,
ETHNL_CMD_EVENT, /* only for notifications */
+ ETHNL_CMD_GET_STRSET,
+ ETHNL_CMD_SET_STRSET, /* only for reply */
__ETHNL_CMD_MAX,
ETHNL_CMD_MAX = (__ETHNL_CMD_MAX - 1)
@@ -82,6 +84,46 @@ enum {
ETHA_EVENT_MAX = (__ETHA_EVENT_MAX - 1)
};
+/* string sets */
+
+enum {
+ ETHA_STRING_UNSPEC,
+ ETHA_STRING_INDEX, /* u32 */
+ ETHA_STRING_VALUE, /* string */
+
+ __ETHA_STRING_MAX,
+ ETHA_STRING_MAX = (__ETHA_STRING_MAX - 1)
+};
+
+enum {
+ ETHA_STRINGS_UNSPEC,
+ ETHA_STRINGS_STRING, /* nest - ETHA_STRINGS_* */
+
+ __ETHA_STRINGS_MAX,
+ ETHA_STRINGS_MAX = (__ETHA_STRINGS_MAX - 1)
+};
+
+enum {
+ ETHA_STRINGSET_UNSPEC,
+ ETHA_STRINGSET_ID, /* u32 */
+ ETHA_STRINGSET_COUNT, /* u32 */
+ ETHA_STRINGSET_STRINGS, /* nest - ETHA_STRINGS_* */
+
+ __ETHA_STRINGSET_MAX,
+ ETHA_STRINGSET_MAX = (__ETHA_STRINGSET_MAX - 1)
+};
+
+/* GET_STRINGSET / SET_STRINGSET */
+
+enum {
+ ETHA_STRSET_UNSPEC,
+ ETHA_STRSET_DEV, /* nest - ETHA_DEV_* */
+ ETHA_STRSET_STRINGSET, /* nest - ETHA_STRSET_* */
+
+ __ETHA_STRSET_MAX,
+ ETHA_STRSET_MAX = (__ETHA_STRSET_MAX - 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 f30e0da88be5..ba260d5b53b2 100644
--- a/net/ethtool/Makefile
+++ b/net/ethtool/Makefile
@@ -4,4 +4,4 @@ obj-y += ioctl.o
obj-$(CONFIG_ETHTOOL_NETLINK) += ethtool_nl.o
-ethtool_nl-y := netlink.o
+ethtool_nl-y := netlink.o strset.o
diff --git a/net/ethtool/common.c b/net/ethtool/common.c
new file mode 100644
index 000000000000..208259c51b73
--- /dev/null
+++ b/net/ethtool/common.c
@@ -0,0 +1,87 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#include "common.h"
+
+const char netdev_features_strings[NETDEV_FEATURE_COUNT][ETH_GSTRING_LEN] = {
+ [NETIF_F_SG_BIT] = "tx-scatter-gather",
+ [NETIF_F_IP_CSUM_BIT] = "tx-checksum-ipv4",
+ [NETIF_F_HW_CSUM_BIT] = "tx-checksum-ip-generic",
+ [NETIF_F_IPV6_CSUM_BIT] = "tx-checksum-ipv6",
+ [NETIF_F_HIGHDMA_BIT] = "highdma",
+ [NETIF_F_FRAGLIST_BIT] = "tx-scatter-gather-fraglist",
+ [NETIF_F_HW_VLAN_CTAG_TX_BIT] = "tx-vlan-hw-insert",
+
+ [NETIF_F_HW_VLAN_CTAG_RX_BIT] = "rx-vlan-hw-parse",
+ [NETIF_F_HW_VLAN_CTAG_FILTER_BIT] = "rx-vlan-filter",
+ [NETIF_F_HW_VLAN_STAG_TX_BIT] = "tx-vlan-stag-hw-insert",
+ [NETIF_F_HW_VLAN_STAG_RX_BIT] = "rx-vlan-stag-hw-parse",
+ [NETIF_F_HW_VLAN_STAG_FILTER_BIT] = "rx-vlan-stag-filter",
+ [NETIF_F_VLAN_CHALLENGED_BIT] = "vlan-challenged",
+ [NETIF_F_GSO_BIT] = "tx-generic-segmentation",
+ [NETIF_F_LLTX_BIT] = "tx-lockless",
+ [NETIF_F_NETNS_LOCAL_BIT] = "netns-local",
+ [NETIF_F_GRO_BIT] = "rx-gro",
+ [NETIF_F_GRO_HW_BIT] = "rx-gro-hw",
+ [NETIF_F_LRO_BIT] = "rx-lro",
+
+ [NETIF_F_TSO_BIT] = "tx-tcp-segmentation",
+ [NETIF_F_GSO_ROBUST_BIT] = "tx-gso-robust",
+ [NETIF_F_TSO_ECN_BIT] = "tx-tcp-ecn-segmentation",
+ [NETIF_F_TSO_MANGLEID_BIT] = "tx-tcp-mangleid-segmentation",
+ [NETIF_F_TSO6_BIT] = "tx-tcp6-segmentation",
+ [NETIF_F_FSO_BIT] = "tx-fcoe-segmentation",
+ [NETIF_F_GSO_GRE_BIT] = "tx-gre-segmentation",
+ [NETIF_F_GSO_GRE_CSUM_BIT] = "tx-gre-csum-segmentation",
+ [NETIF_F_GSO_IPXIP4_BIT] = "tx-ipxip4-segmentation",
+ [NETIF_F_GSO_IPXIP6_BIT] = "tx-ipxip6-segmentation",
+ [NETIF_F_GSO_UDP_TUNNEL_BIT] = "tx-udp_tnl-segmentation",
+ [NETIF_F_GSO_UDP_TUNNEL_CSUM_BIT] = "tx-udp_tnl-csum-segmentation",
+ [NETIF_F_GSO_PARTIAL_BIT] = "tx-gso-partial",
+ [NETIF_F_GSO_SCTP_BIT] = "tx-sctp-segmentation",
+ [NETIF_F_GSO_ESP_BIT] = "tx-esp-segmentation",
+ [NETIF_F_GSO_UDP_L4_BIT] = "tx-udp-segmentation",
+
+ [NETIF_F_FCOE_CRC_BIT] = "tx-checksum-fcoe-crc",
+ [NETIF_F_SCTP_CRC_BIT] = "tx-checksum-sctp",
+ [NETIF_F_FCOE_MTU_BIT] = "fcoe-mtu",
+ [NETIF_F_NTUPLE_BIT] = "rx-ntuple-filter",
+ [NETIF_F_RXHASH_BIT] = "rx-hashing",
+ [NETIF_F_RXCSUM_BIT] = "rx-checksum",
+ [NETIF_F_NOCACHE_COPY_BIT] = "tx-nocache-copy",
+ [NETIF_F_LOOPBACK_BIT] = "loopback",
+ [NETIF_F_RXFCS_BIT] = "rx-fcs",
+ [NETIF_F_RXALL_BIT] = "rx-all",
+ [NETIF_F_HW_L2FW_DOFFLOAD_BIT] = "l2-fwd-offload",
+ [NETIF_F_HW_TC_BIT] = "hw-tc-offload",
+ [NETIF_F_HW_ESP_BIT] = "esp-hw-offload",
+ [NETIF_F_HW_ESP_TX_CSUM_BIT] = "esp-tx-csum-hw-offload",
+ [NETIF_F_RX_UDP_TUNNEL_PORT_BIT] = "rx-udp_tunnel-port-offload",
+ [NETIF_F_HW_TLS_RECORD_BIT] = "tls-hw-record",
+ [NETIF_F_HW_TLS_TX_BIT] = "tls-hw-tx-offload",
+ [NETIF_F_HW_TLS_RX_BIT] = "tls-hw-rx-offload",
+};
+EXPORT_SYMBOL(netdev_features_strings);
+
+const char
+rss_hash_func_strings[ETH_RSS_HASH_FUNCS_COUNT][ETH_GSTRING_LEN] = {
+ [ETH_RSS_HASH_TOP_BIT] = "toeplitz",
+ [ETH_RSS_HASH_XOR_BIT] = "xor",
+ [ETH_RSS_HASH_CRC32_BIT] = "crc32",
+};
+EXPORT_SYMBOL(rss_hash_func_strings);
+
+const char
+tunable_strings[__ETHTOOL_TUNABLE_COUNT][ETH_GSTRING_LEN] = {
+ [ETHTOOL_ID_UNSPEC] = "Unspec",
+ [ETHTOOL_RX_COPYBREAK] = "rx-copybreak",
+ [ETHTOOL_TX_COPYBREAK] = "tx-copybreak",
+ [ETHTOOL_PFC_PREVENTION_TOUT] = "pfc-prevention-tout",
+};
+EXPORT_SYMBOL(tunable_strings);
+
+const char
+phy_tunable_strings[__ETHTOOL_PHY_TUNABLE_COUNT][ETH_GSTRING_LEN] = {
+ [ETHTOOL_ID_UNSPEC] = "Unspec",
+ [ETHTOOL_PHY_DOWNSHIFT] = "phy-downshift",
+};
+EXPORT_SYMBOL(phy_tunable_strings);
diff --git a/net/ethtool/common.h b/net/ethtool/common.h
new file mode 100644
index 000000000000..45c6492e4aee
--- /dev/null
+++ b/net/ethtool/common.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#ifndef _ETHTOOL_COMMON_H
+#define _ETHTOOL_COMMON_H
+
+#include <linux/ethtool.h>
+
+extern const char netdev_features_strings[NETDEV_FEATURE_COUNT][ETH_GSTRING_LEN];
+extern const char rss_hash_func_strings[ETH_RSS_HASH_FUNCS_COUNT][ETH_GSTRING_LEN];
+extern const char tunable_strings[__ETHTOOL_TUNABLE_COUNT][ETH_GSTRING_LEN];
+extern const char phy_tunable_strings[__ETHTOOL_PHY_TUNABLE_COUNT][ETH_GSTRING_LEN];
+
+#endif /* _ETHTOOL_COMMON_H */
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index c9993c6c2fd4..a91b597073f8 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -55,86 +55,6 @@ EXPORT_SYMBOL(ethtool_op_get_ts_info);
#define ETHTOOL_DEV_FEATURE_WORDS ((NETDEV_FEATURE_COUNT + 31) / 32)
-static const char netdev_features_strings[NETDEV_FEATURE_COUNT][ETH_GSTRING_LEN] = {
- [NETIF_F_SG_BIT] = "tx-scatter-gather",
- [NETIF_F_IP_CSUM_BIT] = "tx-checksum-ipv4",
- [NETIF_F_HW_CSUM_BIT] = "tx-checksum-ip-generic",
- [NETIF_F_IPV6_CSUM_BIT] = "tx-checksum-ipv6",
- [NETIF_F_HIGHDMA_BIT] = "highdma",
- [NETIF_F_FRAGLIST_BIT] = "tx-scatter-gather-fraglist",
- [NETIF_F_HW_VLAN_CTAG_TX_BIT] = "tx-vlan-hw-insert",
-
- [NETIF_F_HW_VLAN_CTAG_RX_BIT] = "rx-vlan-hw-parse",
- [NETIF_F_HW_VLAN_CTAG_FILTER_BIT] = "rx-vlan-filter",
- [NETIF_F_HW_VLAN_STAG_TX_BIT] = "tx-vlan-stag-hw-insert",
- [NETIF_F_HW_VLAN_STAG_RX_BIT] = "rx-vlan-stag-hw-parse",
- [NETIF_F_HW_VLAN_STAG_FILTER_BIT] = "rx-vlan-stag-filter",
- [NETIF_F_VLAN_CHALLENGED_BIT] = "vlan-challenged",
- [NETIF_F_GSO_BIT] = "tx-generic-segmentation",
- [NETIF_F_LLTX_BIT] = "tx-lockless",
- [NETIF_F_NETNS_LOCAL_BIT] = "netns-local",
- [NETIF_F_GRO_BIT] = "rx-gro",
- [NETIF_F_GRO_HW_BIT] = "rx-gro-hw",
- [NETIF_F_LRO_BIT] = "rx-lro",
-
- [NETIF_F_TSO_BIT] = "tx-tcp-segmentation",
- [NETIF_F_GSO_ROBUST_BIT] = "tx-gso-robust",
- [NETIF_F_TSO_ECN_BIT] = "tx-tcp-ecn-segmentation",
- [NETIF_F_TSO_MANGLEID_BIT] = "tx-tcp-mangleid-segmentation",
- [NETIF_F_TSO6_BIT] = "tx-tcp6-segmentation",
- [NETIF_F_FSO_BIT] = "tx-fcoe-segmentation",
- [NETIF_F_GSO_GRE_BIT] = "tx-gre-segmentation",
- [NETIF_F_GSO_GRE_CSUM_BIT] = "tx-gre-csum-segmentation",
- [NETIF_F_GSO_IPXIP4_BIT] = "tx-ipxip4-segmentation",
- [NETIF_F_GSO_IPXIP6_BIT] = "tx-ipxip6-segmentation",
- [NETIF_F_GSO_UDP_TUNNEL_BIT] = "tx-udp_tnl-segmentation",
- [NETIF_F_GSO_UDP_TUNNEL_CSUM_BIT] = "tx-udp_tnl-csum-segmentation",
- [NETIF_F_GSO_PARTIAL_BIT] = "tx-gso-partial",
- [NETIF_F_GSO_SCTP_BIT] = "tx-sctp-segmentation",
- [NETIF_F_GSO_ESP_BIT] = "tx-esp-segmentation",
- [NETIF_F_GSO_UDP_L4_BIT] = "tx-udp-segmentation",
-
- [NETIF_F_FCOE_CRC_BIT] = "tx-checksum-fcoe-crc",
- [NETIF_F_SCTP_CRC_BIT] = "tx-checksum-sctp",
- [NETIF_F_FCOE_MTU_BIT] = "fcoe-mtu",
- [NETIF_F_NTUPLE_BIT] = "rx-ntuple-filter",
- [NETIF_F_RXHASH_BIT] = "rx-hashing",
- [NETIF_F_RXCSUM_BIT] = "rx-checksum",
- [NETIF_F_NOCACHE_COPY_BIT] = "tx-nocache-copy",
- [NETIF_F_LOOPBACK_BIT] = "loopback",
- [NETIF_F_RXFCS_BIT] = "rx-fcs",
- [NETIF_F_RXALL_BIT] = "rx-all",
- [NETIF_F_HW_L2FW_DOFFLOAD_BIT] = "l2-fwd-offload",
- [NETIF_F_HW_TC_BIT] = "hw-tc-offload",
- [NETIF_F_HW_ESP_BIT] = "esp-hw-offload",
- [NETIF_F_HW_ESP_TX_CSUM_BIT] = "esp-tx-csum-hw-offload",
- [NETIF_F_RX_UDP_TUNNEL_PORT_BIT] = "rx-udp_tunnel-port-offload",
- [NETIF_F_HW_TLS_RECORD_BIT] = "tls-hw-record",
- [NETIF_F_HW_TLS_TX_BIT] = "tls-hw-tx-offload",
- [NETIF_F_HW_TLS_RX_BIT] = "tls-hw-rx-offload",
-};
-
-static const char
-rss_hash_func_strings[ETH_RSS_HASH_FUNCS_COUNT][ETH_GSTRING_LEN] = {
- [ETH_RSS_HASH_TOP_BIT] = "toeplitz",
- [ETH_RSS_HASH_XOR_BIT] = "xor",
- [ETH_RSS_HASH_CRC32_BIT] = "crc32",
-};
-
-static const char
-tunable_strings[__ETHTOOL_TUNABLE_COUNT][ETH_GSTRING_LEN] = {
- [ETHTOOL_ID_UNSPEC] = "Unspec",
- [ETHTOOL_RX_COPYBREAK] = "rx-copybreak",
- [ETHTOOL_TX_COPYBREAK] = "tx-copybreak",
- [ETHTOOL_PFC_PREVENTION_TOUT] = "pfc-prevention-tout",
-};
-
-static const char
-phy_tunable_strings[__ETHTOOL_PHY_TUNABLE_COUNT][ETH_GSTRING_LEN] = {
- [ETHTOOL_ID_UNSPEC] = "Unspec",
- [ETHTOOL_PHY_DOWNSHIFT] = "phy-downshift",
-};
-
static int ethtool_get_features(struct net_device *dev, void __user *useraddr)
{
struct ethtool_gfeatures cmd = {
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 543560778c80..237a2cb40be4 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -669,7 +669,20 @@ static struct notifier_block ethnl_netdev_notifier = {
/* genetlink setup */
+int ethnl_get_strset(struct sk_buff *skb, struct genl_info *info);
+
+int ethnl_strset_start(struct netlink_callback *cb);
+
+int ethnl_strset_done(struct netlink_callback *cb);
+
static const struct genl_ops ethtool_genl_ops[] = {
+ {
+ .cmd = ETHNL_CMD_GET_STRSET,
+ .doit = ethnl_get_strset,
+ .start = ethnl_strset_start,
+ .dumpit = ethnl_dumpit,
+ .done = ethnl_strset_done,
+ },
};
static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
diff --git a/net/ethtool/strset.c b/net/ethtool/strset.c
new file mode 100644
index 000000000000..32543c68fae8
--- /dev/null
+++ b/net/ethtool/strset.c
@@ -0,0 +1,552 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+#include "netlink.h"
+#include "common.h"
+
+enum strset_type {
+ ETH_SS_TYPE_NONE,
+ ETH_SS_TYPE_LEGACY,
+ ETH_SS_TYPE_SIMPLE,
+};
+
+struct strset_info {
+ enum strset_type type;
+ bool per_dev;
+ bool free_data;
+ unsigned int count;
+ union {
+ const char (*legacy)[ETH_GSTRING_LEN];
+ const char * const *simple;
+ void *ptr;
+ } data;
+};
+
+static const struct strset_info info_template[] = {
+ [ETH_SS_TEST] = {
+ .type = ETH_SS_TYPE_LEGACY,
+ .per_dev = true,
+ },
+ [ETH_SS_STATS] = {
+ .type = ETH_SS_TYPE_LEGACY,
+ .per_dev = true,
+ },
+ [ETH_SS_PRIV_FLAGS] = {
+ .type = ETH_SS_TYPE_LEGACY,
+ .per_dev = true,
+ },
+ [ETH_SS_NTUPLE_FILTERS] = {
+ .type = ETH_SS_TYPE_NONE,
+ },
+ [ETH_SS_FEATURES] = {
+ .type = ETH_SS_TYPE_LEGACY,
+ .per_dev = false,
+ .count = ARRAY_SIZE(netdev_features_strings),
+ .data = { .legacy = netdev_features_strings },
+ },
+ [ETH_SS_RSS_HASH_FUNCS] = {
+ .type = ETH_SS_TYPE_LEGACY,
+ .per_dev = false,
+ .count = ARRAY_SIZE(rss_hash_func_strings),
+ .data = { .legacy = rss_hash_func_strings },
+ },
+ [ETH_SS_TUNABLES] = {
+ .type = ETH_SS_TYPE_LEGACY,
+ .per_dev = false,
+ .count = ARRAY_SIZE(tunable_strings),
+ .data = { .legacy = tunable_strings },
+ },
+ [ETH_SS_PHY_STATS] = {
+ .type = ETH_SS_TYPE_LEGACY,
+ .per_dev = true,
+ },
+ [ETH_SS_PHY_TUNABLES] = {
+ .type = ETH_SS_TYPE_LEGACY,
+ .per_dev = false,
+ .count = ARRAY_SIZE(phy_tunable_strings),
+ .data = { .legacy = phy_tunable_strings },
+ },
+ [ETH_SS_LINK_MODES] = {
+ .type = ETH_SS_TYPE_SIMPLE,
+ .per_dev = false,
+ .count = __ETHTOOL_LINK_MODE_MASK_NBITS,
+ .data = { .simple = link_mode_names },
+ },
+};
+
+struct strset_data {
+ struct net_device *dev;
+ struct strset_info info[ETH_SS_MAX + 1];
+};
+
+struct strset_reqinfo {
+ struct net_device *dev;
+ u32 req_ids;
+ bool have_rtnl;
+};
+
+static const struct nla_policy get_strset_policy[ETHA_STRSET_MAX + 1] = {
+ [ETHA_STRSET_DEV] = { .type = NLA_NESTED },
+ [ETHA_STRSET_STRINGSET] = { .type = NLA_NESTED },
+};
+
+static const struct nla_policy stringset_policy[ETHA_STRINGSET_MAX + 1] = {
+ [ETHA_STRINGSET_ID] = { .type = NLA_U32 },
+ [ETHA_STRINGSET_COUNT] = { .type = NLA_U32 },
+ [ETHA_STRINGSET_STRINGS] = { .type = NLA_NESTED },
+};
+
+static int legacy_set_size(const char (*set)[ETH_GSTRING_LEN], unsigned count)
+{
+ unsigned len = 0;
+ unsigned int i;
+
+ for (i = 0; i < count; i++)
+ len += nla_total_size(nla_total_size(sizeof(u32)) +
+ ethnl_str_size(set[i]));
+ len = 2 * nla_total_size(sizeof(u32)) + nla_total_size(len);
+
+ return nla_total_size(len);
+}
+
+static int simple_set_size(const char * const *set, unsigned count)
+{
+ unsigned len = 0;
+ unsigned int i;
+
+ for (i = 0; i < count; i++)
+ len += nla_total_size(nla_total_size(sizeof(u32)) +
+ ethnl_str_size(set[i]));
+ len = 2 * nla_total_size(sizeof(u32)) + nla_total_size(len);
+
+ return nla_total_size(len);
+}
+
+static int set_size(const struct strset_info *info)
+{
+ if (info->count == 0)
+ return 0;
+
+ switch(info->type) {
+ case ETH_SS_TYPE_LEGACY:
+ return legacy_set_size(info->data.legacy, info->count);
+ case ETH_SS_TYPE_SIMPLE:
+ return simple_set_size(info->data.simple, info->count);
+ default:
+ return -EINVAL;
+ };
+}
+
+static bool id_requested(const struct strset_reqinfo *req_info, u32 id)
+{
+ return req_info->req_ids & (1U << id);
+}
+
+static bool include_set(struct strset_data *data,
+ struct strset_reqinfo *req_info, u32 id)
+{
+ /* once ETH_SS_MAX reaches 32, we will need to change
+ * strset_info::req_ids to u64; one day we might even need to use
+ * generic bitmap but let's not complicate the code prematurely
+ */
+ BUILD_BUG_ON(ETH_SS_MAX >= BITS_PER_BYTE * sizeof(req_info->req_ids));
+
+ if (req_info->req_ids)
+ return id_requested(req_info, id);
+ else {
+ bool per_dev = data->info[id].per_dev;
+
+ if (data->info[id].type == ETH_SS_TYPE_NONE)
+ return false;
+ return data->dev ? per_dev : !per_dev;
+ }
+}
+
+static int strset_size(struct strset_data *data,
+ struct strset_reqinfo *req_info)
+{
+ unsigned int i;
+ int len = 0;
+ int ret;
+ for (i = 0; i <= ETH_SS_MAX; i++) {
+ const struct strset_info *info = &data->info[i];
+
+ if (!include_set(data, req_info, i) ||
+ (info->type == ETH_SS_TYPE_NONE))
+ continue;
+
+ ret = set_size(info);
+ if (ret < 0)
+ return ret;
+ else
+ len += ret;
+ }
+
+ return len;
+}
+
+const char *str_value(struct strset_info *info, unsigned int i)
+{
+ switch(info->type) {
+ case ETH_SS_TYPE_LEGACY:
+ return info->data.legacy[i];
+ case ETH_SS_TYPE_SIMPLE:
+ return info->data.simple[i];
+ default:
+ WARN_ONCE(1, "unexpected string set type");
+ return "";
+ }
+}
+
+static int get_strset_id(const struct nlattr *nest, u32 *val,
+ struct genl_info *info)
+{
+ struct nlattr *tb[ETHA_STRINGSET_MAX + 1];
+ int ret;
+
+ ret = nla_parse_nested(tb, ETHA_STRINGSET_MAX, nest, stringset_policy,
+ info ? info->extack : NULL);
+ if (ret < 0)
+ return ret;
+ if (!tb[ETHA_STRINGSET_ID])
+ return -EINVAL;
+
+ *val = nla_get_u32(tb[ETHA_STRINGSET_ID]);
+ return 0;
+}
+
+static int parse_strset_req(struct strset_reqinfo *req_info,
+ struct genl_info *info, struct sk_buff *skb,
+ const struct nlmsghdr *nlhdr)
+{
+ struct nlattr *tb[ETHA_STRSET_MAX + 1];
+ int ret;
+
+ memset(req_info, '\0', sizeof(*req_info));
+
+ ret = genlmsg_parse(nlhdr, ðtool_genl_family, tb,
+ ETHA_STRSET_MAX, get_strset_policy,
+ info ? info->extack : NULL);
+ if (ret < 0)
+ return ret;
+
+ if (tb[ETHA_STRSET_DEV]) {
+ req_info->dev = ethnl_dev_get(info, tb[ETHA_STRSET_DEV]);
+ if (IS_ERR(req_info->dev)) {
+ ret = PTR_ERR(req_info->dev);
+ req_info->dev = NULL;
+ return ret;
+ }
+ }
+ if (tb[ETHA_STRSET_STRINGSET]) {
+ struct nlattr *set;
+ int rem;
+
+ nlmsg_for_each_attr(set, nlhdr, GENL_HDRLEN, rem) {
+ u32 id;
+
+ ret = get_strset_id(set, &id, info);
+ if (ret < 0)
+ return ret;
+ if (ret > ETH_SS_MAX)
+ return -EOPNOTSUPP;
+ req_info->req_ids |= (1U << id);
+ }
+ }
+
+ return 0;
+}
+
+static void free_strset(struct strset_data *data)
+{
+ unsigned int i;
+
+ for (i = 0; i <= ETH_SS_MAX; i++)
+ if (data->info[i].free_data) {
+ kfree(data->info[i].data.ptr);
+ data->info[i].data.ptr = NULL;
+ data->info[i].free_data = false;
+ }
+}
+
+static int prepare_one_stringset(struct strset_info *info,
+ struct net_device *dev, unsigned int id)
+{
+ const struct ethtool_ops *ops = dev->ethtool_ops;
+ void *strings;
+ int count, ret;
+
+
+ if (id == ETH_SS_PHY_STATS && dev->phydev &&
+ !ops->get_ethtool_phy_stats)
+ ret = phy_ethtool_get_sset_count(dev->phydev);
+ else if (ops->get_sset_count && ops->get_strings)
+ ret = ops->get_sset_count(dev, id);
+ else
+ ret = -EOPNOTSUPP;
+ if (ret <= 0) {
+ info->count = 0;
+ return 0;
+ }
+
+ count = ret;
+ strings = kcalloc(count, ETH_GSTRING_LEN, GFP_KERNEL);
+ if (!strings)
+ return -ENOMEM;
+ if (id == ETH_SS_PHY_STATS && dev->phydev &&
+ !ops->get_ethtool_phy_stats)
+ phy_ethtool_get_strings(dev->phydev, strings);
+ else
+ ops->get_strings(dev, id, strings);
+ info->count = count;
+ info->data.legacy = strings;
+ info->free_data = true;
+
+ return 0;
+}
+
+static int prepare_strset(struct strset_data *data,
+ struct strset_reqinfo *req_info,
+ struct genl_info *info, struct net_device *dev)
+{
+ unsigned int i;
+ int ret;
+
+ memset(data, '\0', sizeof(*data));
+ memcpy(&data->info, &info_template, sizeof(data->info));
+ data->dev = dev;
+
+ if (!dev)
+ for (i = 0; i <= ETH_SS_MAX; i++)
+ if (id_requested(req_info, i) &&
+ data->info[i].per_dev) {
+ ETHNL_SET_ERRMSG(info,
+ "requested per device strings without dev");
+ return -EINVAL;
+ }
+
+ if (!req_info->have_rtnl)
+ rtnl_lock();
+ for (i = 0; i <= ETH_SS_MAX; i++) {
+ if (!include_set(data, req_info, i) || !data->info[i].per_dev)
+ continue;
+ if (WARN_ONCE(data->info[i].type != ETH_SS_TYPE_LEGACY,
+ "unexpected string set type %u",
+ data->info[i].type))
+ goto err_free;
+
+ ret = prepare_one_stringset(&data->info[i], dev, i);
+ if (ret < 0)
+ goto err_free;
+ }
+ if (!req_info->have_rtnl)
+ rtnl_unlock();
+
+ return 0;
+err_free:
+ if (!req_info->have_rtnl)
+ rtnl_unlock();
+ free_strset(data);
+ return ret;
+}
+
+static int fill_set(struct sk_buff *skb, struct strset_data *data, u32 id)
+{
+ struct strset_info *info = &data->info[id];
+ struct nlattr *strings;
+ struct nlattr *nest;
+ unsigned int i = (unsigned int)(-1);
+
+ if (info->type == ETH_SS_TYPE_NONE)
+ return -EOPNOTSUPP;
+ if (info->count == 0)
+ return 0;
+ nest = ethnl_nest_start(skb, ETHA_STRSET_STRINGSET);
+ if (!nest)
+ return -EMSGSIZE;
+
+ if (nla_put_u32(skb, ETHA_STRINGSET_ID, id) ||
+ nla_put_u32(skb, ETHA_STRINGSET_COUNT, info->count))
+ goto err;
+
+ strings = ethnl_nest_start(skb, ETHA_STRINGSET_STRINGS);
+ if (!strings)
+ goto err;
+ for (i = 0; i < info->count; i++) {
+ struct nlattr *string = ethnl_nest_start(skb,
+ ETHA_STRINGS_STRING);
+
+ if (!string)
+ goto err;
+ if (nla_put_u32(skb, ETHA_STRING_INDEX, i) ||
+ nla_put_string(skb, ETHA_STRING_VALUE, str_value(info, i)))
+ goto err;
+ nla_nest_end(skb, string);
+ }
+ nla_nest_end(skb, strings);
+
+ nla_nest_end(skb, nest);
+ return 0;
+
+err:
+ nla_nest_cancel(skb, nest);
+ return -EMSGSIZE;
+}
+
+static int fill_strset(struct sk_buff *skb, struct strset_data *data,
+ struct strset_reqinfo *req_info)
+{
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i <= ETH_SS_MAX; i++)
+ if (include_set(data, req_info, i)) {
+ ret = fill_set(skb, data, i);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+int ethnl_get_strset(struct sk_buff *skb, struct genl_info *info)
+{
+ struct strset_data data;
+ struct strset_reqinfo req_info;
+ struct sk_buff *rskb;
+ int reply_len;
+ void *ehdr;
+ int ret;
+
+ ret = parse_strset_req(&req_info, info, skb, info->nlhdr);
+ if (ret < 0)
+ goto err_dev;
+ ret = prepare_strset(&data, &req_info, info, req_info.dev);
+ if (ret < 0)
+ goto err_dev;
+ ret = strset_size(&data, &req_info);
+ if (ret < 0)
+ goto err_data;
+ reply_len = ret;
+ ret = -ENOMEM;
+ rskb = ethnl_reply_init(reply_len, req_info.dev, ETHNL_CMD_SET_STRSET,
+ ETHA_STRSET_DEV, info, &ehdr);
+ if (!rskb)
+ goto err_data;
+ ret = fill_strset(rskb, &data, &req_info);
+ if (ret < 0)
+ goto err;
+
+ genlmsg_end(rskb, ehdr);
+ free_strset(&data);
+ if (req_info.dev)
+ dev_put(req_info.dev);
+ return genlmsg_reply(rskb, info);
+
+err:
+ WARN_ONCE(ret == -EMSGSIZE,
+ "calculated message payload length (%d) not sufficient\n",
+ reply_len);
+ nlmsg_free(rskb);
+err_data:
+ free_strset(&data);
+err_dev:
+ if (req_info.dev)
+ dev_put(req_info.dev);
+ return ret;
+}
+
+static int strset_dump(struct sk_buff *skb, struct netlink_callback *cb,
+ struct net_device *dev)
+{
+ struct strset_data data;
+ struct strset_reqinfo *req_info;
+ int ret;
+
+ req_info = (struct strset_reqinfo *)cb->args[4];
+ ret = prepare_strset(&data, req_info, NULL, dev);
+ if (ret < 0)
+ return ret;
+ ret = ethnl_fill_dev(skb, dev, ETHA_STRSET_DEV);
+ if (ret < 0)
+ goto out;
+ ret = fill_strset(skb, &data, req_info);
+out:
+ free_strset(&data);
+ return ret;
+}
+
+int ethnl_strset_start(struct netlink_callback *cb)
+{
+ struct strset_reqinfo *req_info;
+ int ret;
+
+ req_info = kmalloc(sizeof(*req_info), GFP_KERNEL);
+ if (!req_info)
+ return -ENOMEM;
+ ret = parse_strset_req(req_info, NULL, cb->skb, cb->nlh);
+ if (ret < 0) {
+ if (req_info->dev)
+ dev_put(req_info->dev);
+ req_info->dev = NULL;
+ return ret;
+ }
+
+ cb->args[0] = (long)strset_dump;
+ cb->args[1] = ETHNL_CMD_SET_STRSET;
+ cb->args[4] = (long)req_info;
+ cb->min_dump_alloc = 65535;
+
+ return 0;
+}
+
+int ethnl_strset_done(struct netlink_callback *cb)
+{
+ struct strset_reqinfo *req_info;
+
+ req_info = (struct strset_reqinfo *)cb->args[4];
+ if (req_info->dev)
+ dev_put(req_info->dev);
+ kfree(req_info);
+
+ return 0;
+}
+
+void ethnl_strset_notify(struct netdev_notifier_ethtool_info *info)
+{
+ struct strset_reqinfo req_info = {
+ .dev = info->info.dev,
+ .have_rtnl = true,
+ };
+ struct strset_data data;
+ struct sk_buff *skb;
+ int reply_len;
+ void *ehdr;
+ int ret;
+
+ ret = prepare_strset(&data, &req_info, NULL, req_info.dev);
+ if (ret < 0)
+ return;
+ reply_len = strset_size(&data, &req_info);
+ if (reply_len < 0)
+ return;
+ skb = genlmsg_new(reply_len, GFP_KERNEL);
+ if (!skb)
+ return;
+ ehdr = genlmsg_put(skb, 0, ++ethnl_bcast_seq, ðtool_genl_family, 0,
+ ETHNL_CMD_SET_STRSET);
+ ret = ethnl_fill_dev(skb, req_info.dev, ETHA_STRSET_DEV);
+ if (ret < 0)
+ goto err_skb;
+ ret = fill_strset(skb, &data, &req_info);
+ if (ret < 0)
+ goto err_skb;
+ genlmsg_end(skb, ehdr);
+
+ genlmsg_multicast(ðtool_genl_family, skb, 0, ETHNL_MCGRP_MONITOR,
+ GFP_KERNEL);
+ return;
+err_skb:
+ nlmsg_free(skb);
+}
--
2.18.0
Requests the information provide by ETHTOOL_GCOALESCE, ETHTOOL_GRINGPARAM,
ETHTOOL_GPAUSEPARAM, ETHTOOL_GCHANNELS, ETHTOOL_GEEE and ETHTOOL_GFECPARAM.
Flags in info_mask allow selecting only some (or on) of these categories.
Signed-off-by: Michal Kubecek <[email protected]>
---
Documentation/networking/ethtool-netlink.txt | 107 +++-
include/uapi/linux/ethtool_netlink.h | 119 ++++
net/ethtool/Makefile | 2 +-
net/ethtool/netlink.c | 10 +
net/ethtool/params.c | 539 +++++++++++++++++++
5 files changed, 770 insertions(+), 7 deletions(-)
create mode 100644 net/ethtool/params.c
diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt
index 307d8c6c6c85..630eebd7e741 100644
--- a/Documentation/networking/ethtool-netlink.txt
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -125,6 +125,8 @@ List of message types
ETHNL_CMD_SET_DRVINFO response only
ETHNL_CMD_GET_SETTINGS
ETHNL_CMD_SET_SETTINGS
+ ETHNL_CMD_GET_PARAMS
+ ETHNL_CMD_SET_PARAMS response only (for now)
All constants use ETHNL_CMD_ prefix, usually followed by "GET", "SET" or "ACT"
to indicate the type.
@@ -314,6 +316,99 @@ the operation); mask shows bits which have been changed and value their new
values.
+GET_PARAMS
+----------
+
+GET_PARAMS request retrieves information provided by legacy comands
+ETHTOOL_GCOALESCE (coalescing setting), ETHTOOL_GRINGPARAM (ring parameters),
+ETHTOOL_GPAUSEPARAM (pause parameters), ETHTOOL_GCHANNELS (channel settings),
+ETHTOOL_GEEE (EEE settings) and ETHTOOL_GFECPARAM (FEC parameters). For each
+of these, there is a bit in header info_mask so that only one type of
+information can be requested.
+
+Request contents:
+
+ ETHA_PARAMS_DEV (nested) device identification
+ ETHA_PARAMS_INFOMASK (u32) info mask
+ ETHA_PARAMS_COMPACT (flag) request compact bitsets
+
+Info mask bits:
+
+ ETH_PARAMS_IM_COALESCE coalescing settings
+ ETH_PARAMS_IM_RING ring parameters
+ ETH_PARAMS_IM_PAUSE pause parameters
+ ETH_PARAMS_IM_CHANNELS channel settings
+ ETH_PARAMS_IM_EEE EEE settings
+ ETH_PARAMS_IM_FEC FEC parameters
+
+Response contents: On top level, there is one attribute for each of the
+information categories, the information is nested in it.
+
+ ETHA_PARAMS_DEV (nested) device identification
+ ETHA_PARAMS_COALESCE (nested) coalescing
+ ETHA_COALESCE_RX_USECS (u32)
+ ETHA_COALESCE_RX_MAXFRM (u32)
+ ETHA_COALESCE_RX_USECS_IRQ (u32)
+ ETHA_COALESCE_RX_MAXFRM_IRQ (u32)
+ ETHA_COALESCE_RX_USECS_LOW (u32)
+ ETHA_COALESCE_RX_MAXFRM_LOW (u32)
+ ETHA_COALESCE_RX_USECS_HIGH (u32)
+ ETHA_COALESCE_RX_MAXFRM_HIGH (u32)
+ ETHA_COALESCE_TX_USECS (u32)
+ ETHA_COALESCE_TX_MAXFRM (u32)
+ ETHA_COALESCE_TX_USECS_IRQ (u32)
+ ETHA_COALESCE_TX_MAXFRM_IRQ (u32)
+ ETHA_COALESCE_TX_USECS_LOW (u32)
+ ETHA_COALESCE_TX_MAXFRM_LOW (u32)
+ ETHA_COALESCE_TX_USECS_HIGH (u32)
+ ETHA_COALESCE_TX_MAXFRM_HIGH (u32)
+ ETHA_COALESCE_PKT_RATE_LOW (u32)
+ ETHA_COALESCE_PKT_RATE_HIGH (u32)
+ ETHA_COALESCE_RX_USE_ADAPTIVE (bool)
+ ETHA_COALESCE_TX_USE_ADAPTIVE (bool)
+ ETHA_COALESCE_RATE_SAMPLE_INTERVAL (u32)
+ ETHA_COALESCE_STATS_BLOCK_USECS (u32)
+ ETHA_PARAMS_RING (nested) ring parameters
+ ETHA_RING_RX_MAX_PENDING (u32)
+ ETHA_RING_RX_MINI_MAX_PENDING (u32)
+ ETHA_RING_RX_JUMBO_MAX_PENDING (u32)
+ ETHA_RING_TX_MAX_PENDING (u32)
+ ETHA_RING_RX_PENDING (u32)
+ ETHA_RING_RX_MINI_PENDING (u32)
+ ETHA_RING_RX_JUMBO_PENDING (u32)
+ ETHA_RING_TX_PENDING (u32)
+ ETHA_PARAMS_PAUSE (nested) pause parameters
+ ETHA_PAUSE_AUTONEG (bool)
+ ETHA_PAUSE_RX (bool)
+ ETHA_PAUSE_TX (bool)
+ ETHA_PARAMS_CHANNELS (nested) channel settings
+ ETHA_CHANNELS_MAX_RX (u32)
+ ETHA_CHANNELS_MAX_TX (u32)
+ ETHA_CHANNELS_MAX_OTHER (u32)
+ ETHA_CHANNELS_MAX_COMBINED (u32)
+ ETHA_CHANNELS_RX_COUNT (u32)
+ ETHA_CHANNELS_TX_COUNT (u32)
+ ETHA_CHANNELS_OTHER_COUNT (u32)
+ ETHA_CHANNELS_COMBINED_COUNT (u32)
+ ETHA_PARAMS_EEE (nested) EEE settings
+ ETHA_EEE_LINK_MODES (bitset)
+ - modes for which EEE is advertised (value) or supported (mask)
+ ETHA_EEE_PEER_MODES (bitset)
+ - modes for which link partner advertises EEE
+ ETHA_EEE_ACTIVE (bool)
+ ETHA_EEE_ENABLED (bool)
+ ETHA_EEE_TX_LPI_ENABLED (bool)
+ ETHA_EEE_TX_LPI_TIMER (u32)
+ ETHA_PARAMS_FEC (nested) FEC parameters
+ ETHA_FEC_MODES (bitfield32)
+ - active (value) and configured (selector) FEC encodings
+
+GET_PARAMS 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
-------------------
@@ -335,11 +430,11 @@ ETHTOOL_NWAY_RST n/a
ETHTOOL_GLINK ETHNL_CMD_GET_SETTINGS
ETHTOOL_GEEPROM n/a
ETHTOOL_SEEPROM n/a
-ETHTOOL_GCOALESCE n/a
+ETHTOOL_GCOALESCE ETHNL_CMD_GET_PARAMS
ETHTOOL_SCOALESCE n/a
-ETHTOOL_GRINGPARAM n/a
+ETHTOOL_GRINGPARAM ETHNL_CMD_GET_PARAMS
ETHTOOL_SRINGPARAM n/a
-ETHTOOL_GPAUSEPARAM n/a
+ETHTOOL_GPAUSEPARAM ETHNL_CMD_GET_PARAMS
ETHTOOL_SPAUSEPARAM n/a
ETHTOOL_GRXCSUM ETHNL_CMD_GET_SETTINGS
ETHTOOL_SRXCSUM ETHNL_CMD_SET_SETTINGS
@@ -381,7 +476,7 @@ ETHTOOL_GRXFHINDIR n/a
ETHTOOL_SRXFHINDIR n/a
ETHTOOL_GFEATURES ETHNL_CMD_GET_SETTINGS
ETHTOOL_SFEATURES n/a
-ETHTOOL_GCHANNELS n/a
+ETHTOOL_GCHANNELS ETHNL_CMD_GET_PARAMS
ETHTOOL_SCHANNELS n/a
ETHTOOL_SET_DUMP n/a
ETHTOOL_GET_DUMP_FLAG n/a
@@ -389,7 +484,7 @@ ETHTOOL_GET_DUMP_DATA n/a
ETHTOOL_GET_TS_INFO n/a
ETHTOOL_GMODULEINFO n/a
ETHTOOL_GMODULEEEPROM n/a
-ETHTOOL_GEEE n/a
+ETHTOOL_GEEE ETHNL_CMD_GET_PARAMS
ETHTOOL_SEEE n/a
ETHTOOL_GRSSH n/a
ETHTOOL_SRSSH n/a
@@ -401,6 +496,6 @@ ETHTOOL_GLINKSETTINGS ETHNL_CMD_GET_SETTINGS
ETHTOOL_SLINKSETTINGS ETHNL_CMD_SET_SETTINGS
ETHTOOL_PHY_GTUNABLE n/a
ETHTOOL_PHY_STUNABLE n/a
-ETHTOOL_GFECPARAM n/a
+ETHTOOL_GFECPARAM ETHNL_CMD_GET_PARAMS
ETHTOOL_SFECPARAM n/a
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index 8dfcb9ef4009..fec0f2000dc5 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -14,6 +14,8 @@ enum {
ETHNL_CMD_SET_DRVINFO, /* only for reply */
ETHNL_CMD_GET_SETTINGS,
ETHNL_CMD_SET_SETTINGS,
+ ETHNL_CMD_GET_PARAMS,
+ ETHNL_CMD_SET_PARAMS,
__ETHNL_CMD_MAX,
ETHNL_CMD_MAX = (__ETHNL_CMD_MAX - 1)
@@ -197,6 +199,123 @@ enum {
ETHA_FEATURES_MAX = (__ETHA_FEATURES_MAX - 1)
};
+/* GET_PARAMS / SET_PARAMS */
+
+enum {
+ ETHA_PARAMS_UNSPEC,
+ ETHA_PARAMS_DEV, /* nest - ETHA_DEV_* */
+ ETHA_PARAMS_INFOMASK, /* u32 */
+ ETHA_PARAMS_COMPACT, /* flag */
+ ETHA_PARAMS_COALESCE, /* nest - ETHA_COALESCE_* */
+ ETHA_PARAMS_RING, /* nest - ETHA_RING_* */
+ ETHA_PARAMS_PAUSE, /* nest - ETHA_PAUSE_* */
+ ETHA_PARAMS_CHANNELS, /* nest - ETHA_CHANNELS_* */
+ ETHA_PARAMS_EEE, /* nest - ETHA_EEE_* */
+ ETHA_PARAMS_FEC, /* nest - ETHA_FEC_* */
+
+ __ETHA_PARAMS_MAX,
+ ETHA_PARAMS_MAX = (__ETHA_PARAMS_MAX - 1)
+};
+
+#define ETH_PARAMS_IM_COALESCE 0x01
+#define ETH_PARAMS_IM_RING 0x02
+#define ETH_PARAMS_IM_PAUSE 0x04
+#define ETH_PARAMS_IM_CHANNELS 0x08
+#define ETH_PARAMS_IM_EEE 0x10
+#define ETH_PARAMS_IM_FEC 0x20
+
+#define ETH_PARAMS_IM_DEFAULT 0x3f
+
+enum {
+ ETHA_COALESCE_UNSPEC,
+ ETHA_COALESCE_RX_USECS, /* u32 */
+ ETHA_COALESCE_RX_MAXFRM, /* u32 */
+ ETHA_COALESCE_RX_USECS_IRQ, /* u32 */
+ ETHA_COALESCE_RX_MAXFRM_IRQ, /* u32 */
+ ETHA_COALESCE_RX_USECS_LOW, /* u32 */
+ ETHA_COALESCE_RX_MAXFRM_LOW, /* u32 */
+ ETHA_COALESCE_RX_USECS_HIGH, /* u32 */
+ ETHA_COALESCE_RX_MAXFRM_HIGH, /* u32 */
+ ETHA_COALESCE_TX_USECS, /* u32 */
+ ETHA_COALESCE_TX_MAXFRM, /* u32 */
+ ETHA_COALESCE_TX_USECS_IRQ, /* u32 */
+ ETHA_COALESCE_TX_MAXFRM_IRQ, /* u32 */
+ ETHA_COALESCE_TX_USECS_LOW, /* u32 */
+ ETHA_COALESCE_TX_MAXFRM_LOW, /* u32 */
+ ETHA_COALESCE_TX_USECS_HIGH, /* u32 */
+ ETHA_COALESCE_TX_MAXFRM_HIGH, /* u32 */
+ ETHA_COALESCE_PKT_RATE_LOW, /* u32 */
+ ETHA_COALESCE_PKT_RATE_HIGH, /* u32 */
+ ETHA_COALESCE_RX_USE_ADAPTIVE, /* u8 */
+ ETHA_COALESCE_TX_USE_ADAPTIVE, /* u8 */
+ ETHA_COALESCE_RATE_SAMPLE_INTERVAL, /* u32 */
+ ETHA_COALESCE_STATS_BLOCK_USECS, /* u32 */
+
+ __ETHA_COALESCE_MAX,
+ ETHA_COALESCE_MAX = (__ETHA_COALESCE_MAX - 1)
+};
+
+enum {
+ ETHA_RING_UNSPEC,
+ ETHA_RING_RX_MAX_PENDING, /* u32 */
+ ETHA_RING_RX_MINI_MAX_PENDING, /* u32 */
+ ETHA_RING_RX_JUMBO_MAX_PENDING, /* u32 */
+ ETHA_RING_TX_MAX_PENDING, /* u32 */
+ ETHA_RING_RX_PENDING, /* u32 */
+ ETHA_RING_RX_MINI_PENDING, /* u32 */
+ ETHA_RING_RX_JUMBO_PENDING, /* u32 */
+ ETHA_RING_TX_PENDING, /* u32 */
+
+ __ETHA_RING_MAX,
+ ETHA_RING_MAX = (__ETHA_RING_MAX - 1)
+};
+
+enum {
+ ETHA_PAUSE_UNSPEC,
+ ETHA_PAUSE_AUTONEG, /* u8 */
+ ETHA_PAUSE_RX, /* u8 */
+ ETHA_PAUSE_TX, /* u8 */
+
+ __ETHA_PAUSE_MAX,
+ ETHA_PAUSE_MAX = (__ETHA_PAUSE_MAX - 1)
+};
+
+enum {
+ ETHA_CHANNELS_UNSPEC,
+ ETHA_CHANNELS_MAX_RX, /* u32 */
+ ETHA_CHANNELS_MAX_TX, /* u32 */
+ ETHA_CHANNELS_MAX_OTHER, /* u32 */
+ ETHA_CHANNELS_MAX_COMBINED, /* u32 */
+ ETHA_CHANNELS_RX_COUNT, /* u32 */
+ ETHA_CHANNELS_TX_COUNT, /* u32 */
+ ETHA_CHANNELS_OTHER_COUNT, /* u32 */
+ ETHA_CHANNELS_COMBINED_COUNT, /* u32 */
+
+ __ETHA_CHANNELS_MAX,
+ ETHA_CHANNELS_MAX = (__ETHA_CHANNELS_MAX - 1)
+};
+
+enum {
+ ETHA_EEE_UNSPEC,
+ ETHA_EEE_LINK_MODES, /* bitset */
+ ETHA_EEE_PEER_MODES, /* bitset */
+ ETHA_EEE_ACTIVE, /* u8 */
+ ETHA_EEE_ENABLED, /* u8 */
+ ETHA_EEE_TX_LPI_ENABLED, /* u8 */
+ ETHA_EEE_TX_LPI_TIMER, /* u32 */
+
+ __ETHA_EEE_MAX,
+ ETHA_EEE_MAX = (__ETHA_EEE_MAX - 1)
+};
+
+enum {
+ ETHA_FEC_UNSPEC,
+ ETHA_FEC_MODES, /* bitfield32 */
+
+ __ETHA_FEC_MAX,
+ ETHA_FEC_MAX = (__ETHA_FEC_MAX - 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 8dd98310ed6f..10aeedcee336 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 strset.o drvinfo.o settings.o
+ethtool_nl-y := netlink.o strset.o drvinfo.o settings.o params.o
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 6e183caca01f..721101ed2ab6 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -731,13 +731,16 @@ int ethnl_get_strset(struct sk_buff *skb, struct genl_info *info);
int ethnl_get_drvinfo(struct sk_buff *skb, struct genl_info *info);
int ethnl_get_settings(struct sk_buff *skb, struct genl_info *info);
int ethnl_set_settings(struct sk_buff *skb, struct genl_info *info);
+int ethnl_get_params(struct sk_buff *skb, struct genl_info *info);
int ethnl_strset_start(struct netlink_callback *cb);
int ethnl_drvinfo_start(struct netlink_callback *cb);
int ethnl_settings_start(struct netlink_callback *cb);
+int ethnl_params_start(struct netlink_callback *cb);
int ethnl_strset_done(struct netlink_callback *cb);
int ethnl_settings_done(struct netlink_callback *cb);
+int ethnl_params_done(struct netlink_callback *cb);
static const struct genl_ops ethtool_genl_ops[] = {
{
@@ -765,6 +768,13 @@ static const struct genl_ops ethtool_genl_ops[] = {
.flags = GENL_UNS_ADMIN_PERM,
.doit = ethnl_set_settings,
},
+ {
+ .cmd = ETHNL_CMD_GET_PARAMS,
+ .doit = ethnl_get_params,
+ .start = ethnl_params_start,
+ .dumpit = ethnl_dumpit,
+ .done = ethnl_params_done,
+ },
};
static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
diff --git a/net/ethtool/params.c b/net/ethtool/params.c
new file mode 100644
index 000000000000..07d4c527abf2
--- /dev/null
+++ b/net/ethtool/params.c
@@ -0,0 +1,539 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#include "netlink.h"
+#include "common.h"
+
+static const struct nla_policy params_policy[ETHA_PARAMS_MAX + 1] = {
+ [ETHA_PARAMS_UNSPEC] = { .type = NLA_UNSPEC },
+ [ETHA_PARAMS_DEV] = { .type = NLA_NESTED },
+ [ETHA_PARAMS_INFOMASK] = { .type = NLA_U32 },
+ [ETHA_PARAMS_COMPACT] = { .type = NLA_FLAG },
+ [ETHA_PARAMS_COALESCE] = { .type = NLA_NESTED },
+ [ETHA_PARAMS_RING] = { .type = NLA_NESTED },
+ [ETHA_PARAMS_PAUSE] = { .type = NLA_NESTED },
+ [ETHA_PARAMS_CHANNELS] = { .type = NLA_NESTED },
+ [ETHA_PARAMS_EEE] = { .type = NLA_NESTED },
+ [ETHA_PARAMS_FEC] = { .type = NLA_NESTED },
+};
+
+struct params_data {
+ struct ethtool_coalesce coalesce;
+ struct ethtool_channels channels;
+ struct ethtool_pauseparam pause;
+ struct ethtool_ringparam ring;
+ struct ethtool_eee eee;
+ struct ethtool_fecparam fec;
+ u32 req_mask;
+};
+
+struct params_reqinfo {
+ struct net_device *dev;
+ u32 req_mask;
+ bool compact;
+ bool have_rtnl;
+};
+
+static int ethnl_get_coalesce(struct net_device *dev,
+ struct ethtool_coalesce *data)
+{
+ if (!dev->ethtool_ops->get_coalesce)
+ return -EOPNOTSUPP;
+ return dev->ethtool_ops->get_coalesce(dev, data);
+}
+
+static int ethnl_get_ring(struct net_device *dev,
+ struct ethtool_ringparam *data)
+{
+ if (!dev->ethtool_ops->get_ringparam)
+ return -EOPNOTSUPP;
+ dev->ethtool_ops->get_ringparam(dev, data);
+ return 0;
+}
+
+static int ethnl_get_pause(struct net_device *dev,
+ struct ethtool_pauseparam *data)
+{
+ if (!dev->ethtool_ops->get_pauseparam)
+ return -EOPNOTSUPP;
+ dev->ethtool_ops->get_pauseparam(dev, data);
+ return 0;
+}
+
+static int ethnl_get_channels(struct net_device *dev,
+ struct ethtool_channels *data)
+{
+ if (!dev->ethtool_ops->get_channels)
+ return -EOPNOTSUPP;
+ dev->ethtool_ops->get_channels(dev, data);
+ return 0;
+}
+
+static int ethnl_get_eee(struct net_device *dev, struct ethtool_eee *data)
+{
+ if (!dev->ethtool_ops->get_eee)
+ return -EOPNOTSUPP;
+ return dev->ethtool_ops->get_eee(dev, data);
+}
+
+static int ethnl_get_fec(struct net_device *dev, struct ethtool_fecparam *data)
+{
+ if (!dev->ethtool_ops->get_fecparam)
+ return -EOPNOTSUPP;
+ return dev->ethtool_ops->get_fecparam(dev, data);
+}
+
+static int params_size(struct params_data *data,
+ struct params_reqinfo *req_info)
+{
+ struct ethtool_eee *eee = &data->eee;
+ u32 req_mask = req_info->req_mask;
+ bool compact = req_info->compact;
+ int len = 0;
+
+ if (req_mask & ETH_PARAMS_IM_COALESCE)
+ len += nla_total_size(20 * nla_total_size(sizeof(u32)) +
+ 2 * nla_total_size(sizeof(u8)));
+ if (req_mask & ETH_PARAMS_IM_RING)
+ len += nla_total_size(8 * nla_total_size(sizeof(u32)));
+ if (req_mask & ETH_PARAMS_IM_PAUSE)
+ len += nla_total_size(3 * nla_total_size(sizeof(u8)));
+ if (req_mask & ETH_PARAMS_IM_CHANNELS)
+ len += nla_total_size(8 * nla_total_size(sizeof(u32)));
+ if (req_mask & ETH_PARAMS_IM_EEE) {
+ int nlen = 0;
+ int ret;
+
+ /* link_modes */
+ ret = ethnl_bitset32_size(compact, sizeof(u32) * 8,
+ &eee->advertised, &eee->supported,
+ link_mode_names);
+ if (ret < 0)
+ return ret;
+ nlen += ret;
+ /* peer_modes */
+ ret = ethnl_bitset32_size(compact, sizeof(u32) * 8,
+ &eee->lp_advertised,
+ &eee->lp_advertised, link_mode_names);
+ if (ret < 0)
+ return ret;
+ nlen += ret;
+ /* active, enabled, tx_lpi_enabled */
+ nlen += 3 * nla_total_size(sizeof(u8));
+ /* tx_lpi_timer */
+ nlen += nla_total_size(sizeof(u32));
+ /* nest */
+ len += nla_total_size(nlen);
+ }
+ if (req_mask & ETH_PARAMS_IM_FEC) {
+ int nlen = nla_total_size(sizeof(struct nla_bitfield32));
+
+ len += nla_total_size(nlen);
+ }
+
+ return len;
+}
+
+static int fill_coalesce(struct sk_buff *skb, struct ethtool_coalesce *data)
+{
+ struct nlattr *nest = ethnl_nest_start(skb, ETHA_PARAMS_COALESCE);
+
+ if (!nest)
+ return -EMSGSIZE;
+ if (nla_put_u32(skb, ETHA_COALESCE_RX_USECS,
+ data->rx_coalesce_usecs) ||
+ nla_put_u32(skb, ETHA_COALESCE_RX_MAXFRM,
+ data->rx_max_coalesced_frames) ||
+ nla_put_u32(skb, ETHA_COALESCE_RX_USECS_IRQ,
+ data->rx_coalesce_usecs_irq) ||
+ nla_put_u32(skb, ETHA_COALESCE_RX_MAXFRM_IRQ,
+ data->rx_max_coalesced_frames_irq) ||
+ nla_put_u32(skb, ETHA_COALESCE_RX_USECS_LOW,
+ data->rx_coalesce_usecs_low) ||
+ nla_put_u32(skb, ETHA_COALESCE_RX_MAXFRM_LOW,
+ data->rx_max_coalesced_frames_low) ||
+ nla_put_u32(skb, ETHA_COALESCE_RX_USECS_HIGH,
+ data->rx_coalesce_usecs_high) ||
+ nla_put_u32(skb, ETHA_COALESCE_RX_MAXFRM_HIGH,
+ data->rx_max_coalesced_frames_high) ||
+ nla_put_u32(skb, ETHA_COALESCE_TX_USECS,
+ data->tx_coalesce_usecs) ||
+ nla_put_u32(skb, ETHA_COALESCE_TX_MAXFRM,
+ data->tx_max_coalesced_frames) ||
+ nla_put_u32(skb, ETHA_COALESCE_TX_USECS_IRQ,
+ data->tx_coalesce_usecs_irq) ||
+ nla_put_u32(skb, ETHA_COALESCE_TX_MAXFRM_IRQ,
+ data->tx_max_coalesced_frames_irq) ||
+ nla_put_u32(skb, ETHA_COALESCE_TX_USECS_LOW,
+ data->tx_coalesce_usecs_low) ||
+ nla_put_u32(skb, ETHA_COALESCE_TX_MAXFRM_LOW,
+ data->tx_max_coalesced_frames_low) ||
+ nla_put_u32(skb, ETHA_COALESCE_TX_USECS_HIGH,
+ data->tx_coalesce_usecs_high) ||
+ nla_put_u32(skb, ETHA_COALESCE_TX_MAXFRM_HIGH,
+ data->tx_max_coalesced_frames_high) ||
+ nla_put_u32(skb, ETHA_COALESCE_PKT_RATE_LOW,
+ data->pkt_rate_low) ||
+ nla_put_u32(skb, ETHA_COALESCE_PKT_RATE_HIGH,
+ data->pkt_rate_high) ||
+ nla_put_u8(skb, ETHA_COALESCE_RX_USE_ADAPTIVE,
+ !!data->use_adaptive_rx_coalesce) ||
+ nla_put_u8(skb, ETHA_COALESCE_TX_USE_ADAPTIVE,
+ !!data->use_adaptive_tx_coalesce) ||
+ nla_put_u32(skb, ETHA_COALESCE_RATE_SAMPLE_INTERVAL,
+ data->rate_sample_interval) ||
+ nla_put_u32(skb, ETHA_COALESCE_STATS_BLOCK_USECS,
+ data->stats_block_coalesce_usecs)) {
+ nla_nest_cancel(skb, nest);
+ return -EMSGSIZE;
+ }
+
+ nla_nest_end(skb, nest);
+ return 0;
+}
+
+static int fill_ring(struct sk_buff *skb, struct ethtool_ringparam *data)
+{
+ struct nlattr *nest = ethnl_nest_start(skb, ETHA_PARAMS_RING);
+
+ if (!nest)
+ return -EMSGSIZE;
+ if (nla_put_u32(skb, ETHA_RING_RX_MAX_PENDING,
+ data->rx_max_pending) ||
+ nla_put_u32(skb, ETHA_RING_RX_MINI_MAX_PENDING,
+ data->rx_mini_max_pending) ||
+ nla_put_u32(skb, ETHA_RING_RX_JUMBO_MAX_PENDING,
+ data->rx_jumbo_max_pending) ||
+ nla_put_u32(skb, ETHA_RING_TX_MAX_PENDING,
+ data->tx_max_pending) ||
+ nla_put_u32(skb, ETHA_RING_RX_PENDING,
+ data->rx_pending) ||
+ nla_put_u32(skb, ETHA_RING_RX_MINI_PENDING,
+ data->rx_mini_pending) ||
+ nla_put_u32(skb, ETHA_RING_RX_JUMBO_PENDING,
+ data->rx_jumbo_pending) ||
+ nla_put_u32(skb, ETHA_RING_TX_PENDING,
+ data->tx_pending)) {
+ nla_nest_cancel(skb, nest);
+ return -EMSGSIZE;
+ }
+
+ nla_nest_end(skb, nest);
+ return 0;
+}
+
+static int fill_pause(struct sk_buff *skb, struct ethtool_pauseparam *data)
+{
+ struct nlattr *nest = ethnl_nest_start(skb, ETHA_PARAMS_PAUSE);
+
+ if (!nest)
+ return -EMSGSIZE;
+ if (nla_put_u8(skb, ETHA_PAUSE_AUTONEG, !!data->autoneg) ||
+ nla_put_u8(skb, ETHA_PAUSE_RX, !!data->rx_pause) ||
+ nla_put_u8(skb, ETHA_PAUSE_TX, !!data->tx_pause)) {
+ nla_nest_cancel(skb, nest);
+ return -EMSGSIZE;
+ }
+
+ nla_nest_end(skb, nest);
+ return 0;
+}
+
+static int fill_channels(struct sk_buff *skb, struct ethtool_channels *data)
+{
+ struct nlattr *nest = ethnl_nest_start(skb, ETHA_PARAMS_CHANNELS);
+
+ if (!nest)
+ return -EMSGSIZE;
+ if (nla_put_u32(skb, ETHA_CHANNELS_MAX_RX, data->max_rx) ||
+ nla_put_u32(skb, ETHA_CHANNELS_MAX_TX, data->max_tx) ||
+ nla_put_u32(skb, ETHA_CHANNELS_MAX_OTHER, data->max_other) ||
+ nla_put_u32(skb, ETHA_CHANNELS_MAX_COMBINED, data->max_combined) ||
+ nla_put_u32(skb, ETHA_CHANNELS_RX_COUNT, data->rx_count) ||
+ nla_put_u32(skb, ETHA_CHANNELS_TX_COUNT, data->tx_count) ||
+ nla_put_u32(skb, ETHA_CHANNELS_OTHER_COUNT, data->other_count) ||
+ nla_put_u32(skb, ETHA_CHANNELS_COMBINED_COUNT,
+ data->combined_count)) {
+ return -EMSGSIZE;
+ }
+
+ nla_nest_end(skb, nest);
+ return 0;
+}
+
+static int fill_eee(struct sk_buff *skb, struct ethtool_eee *data, bool compact)
+{
+ struct nlattr *nest = ethnl_nest_start(skb, ETHA_PARAMS_EEE);
+ int ret;
+
+ if (!nest)
+ return -EMSGSIZE;
+ ret = ethnl_put_bitset32(skb, ETHA_EEE_LINK_MODES, compact,
+ sizeof(data->advertised) * 8,
+ &data->advertised, &data->supported,
+ link_mode_names);
+ if (ret < 0)
+ goto err;
+ ret = ethnl_put_bitset32(skb, ETHA_EEE_PEER_MODES, compact,
+ sizeof(data->advertised) * 8,
+ &data->lp_advertised, &data->lp_advertised,
+ link_mode_names);
+ if (ret < 0)
+ goto err;
+
+ if (nla_put_u8(skb, ETHA_EEE_ACTIVE, !!data->eee_active) ||
+ nla_put_u8(skb, ETHA_EEE_ENABLED, !!data->eee_enabled) ||
+ nla_put_u8(skb, ETHA_EEE_TX_LPI_ENABLED, !!data->tx_lpi_enabled) ||
+ nla_put_u32(skb, ETHA_EEE_TX_LPI_TIMER, data->tx_lpi_timer)) {
+ ret = -EMSGSIZE;
+ goto err;
+ }
+
+ nla_nest_end(skb, nest);
+ return 0;
+err:
+ nla_nest_cancel(skb, nest);
+ return ret;
+}
+
+static int fill_fec(struct sk_buff *skb, struct ethtool_fecparam *data)
+{
+ struct nlattr *nest = ethnl_nest_start(skb, ETHA_PARAMS_FEC);
+
+ if (!nest)
+ return -EMSGSIZE;
+ if (nla_put_bitfield32(skb, ETHA_FEC_MODES, data->active_fec,
+ data->fec)) {
+ nla_nest_cancel(skb, nest);
+ return -EMSGSIZE;
+ }
+
+ nla_nest_end(skb, nest);
+ return 0;
+}
+
+static int parse_params_req(struct params_reqinfo *req_info,
+ struct genl_info *info, struct sk_buff *skb,
+ const struct nlmsghdr *nlhdr)
+{
+ struct nlattr *tb[ETHA_PARAMS_MAX + 1];
+ int ret;
+
+ memset(req_info, '\0', sizeof(*req_info));
+
+ ret = genlmsg_parse(nlhdr, ðtool_genl_family, tb,
+ ETHA_PARAMS_MAX, params_policy,
+ info ? info->extack : NULL);
+ if (ret < 0)
+ return ret;
+
+ if (tb[ETHA_PARAMS_DEV]) {
+ req_info->dev = ethnl_dev_get(info, tb[ETHA_PARAMS_DEV]);
+ if (IS_ERR(req_info->dev)) {
+ ret = PTR_ERR(req_info->dev);
+ req_info->dev = NULL;
+ return ret;
+ }
+ }
+ if (tb[ETHA_PARAMS_INFOMASK])
+ req_info->req_mask = nla_get_u32(tb[ETHA_PARAMS_INFOMASK]);
+ if (tb[ETHA_PARAMS_COMPACT])
+ req_info->compact = true;
+ if (req_info->req_mask == 0)
+ req_info->req_mask = ETH_PARAMS_IM_DEFAULT;
+
+ return 0;
+}
+
+static int prepare_params(struct params_data *data,
+ struct params_reqinfo *req_info,
+ struct genl_info *info, struct net_device *dev)
+{
+ u32 req_mask = req_info->req_mask;
+ int ret;
+
+ memset(data, '\0', sizeof(*data));
+ if (!req_info->have_rtnl)
+ rtnl_lock();
+ if (req_mask & ETH_PARAMS_IM_COALESCE) {
+ ret = ethnl_get_coalesce(dev, &data->coalesce);
+ if (ret < 0) {
+ warn_partial_info(info);
+ req_mask &= ~ETH_PARAMS_IM_COALESCE;
+ }
+ }
+ if (req_mask & ETH_PARAMS_IM_RING) {
+ ret = ethnl_get_ring(dev, &data->ring);
+ if (ret < 0) {
+ warn_partial_info(info);
+ req_mask &= ~ETH_PARAMS_IM_RING;
+ }
+ }
+ if (req_mask & ETH_PARAMS_IM_PAUSE) {
+ ret = ethnl_get_pause(dev, &data->pause);
+ if (ret < 0) {
+ warn_partial_info(info);
+ req_mask &= ~ETH_PARAMS_IM_PAUSE;
+ }
+ }
+ if (req_mask & ETH_PARAMS_IM_CHANNELS) {
+ ret = ethnl_get_channels(dev, &data->channels);
+ if (ret < 0) {
+ warn_partial_info(info);
+ req_mask &= ~ETH_PARAMS_IM_CHANNELS;
+ }
+ }
+ if (req_mask & ETH_PARAMS_IM_EEE) {
+ ret = ethnl_get_eee(dev, &data->eee);
+ if (ret < 0) {
+ warn_partial_info(info);
+ req_mask &= ~ETH_PARAMS_IM_EEE;
+ }
+ }
+ if (req_mask & ETH_PARAMS_IM_FEC) {
+ ret = ethnl_get_fec(dev, &data->fec);
+ if (ret < 0) {
+ warn_partial_info(info);
+ req_mask &= ~ETH_PARAMS_IM_FEC;
+ }
+ }
+ if (!req_info->have_rtnl)
+ rtnl_unlock();
+
+ data->req_mask = req_mask;
+ return 0;
+}
+
+static int fill_params(struct sk_buff *rskb, struct params_data *data,
+ struct params_reqinfo *req_info)
+{
+ u32 req_mask = data->req_mask;
+ int ret;
+
+ if (req_mask & ETH_PARAMS_IM_COALESCE) {
+ ret = fill_coalesce(rskb, &data->coalesce);
+ if (ret < 0)
+ return ret;
+ }
+ if (req_mask & ETH_PARAMS_IM_RING) {
+ ret = fill_ring(rskb, &data->ring);
+ if (ret < 0)
+ return ret;
+ }
+ if (req_mask & ETH_PARAMS_IM_PAUSE) {
+ ret = fill_pause(rskb, &data->pause);
+ if (ret < 0)
+ return ret;
+ }
+ if (req_mask & ETH_PARAMS_IM_CHANNELS) {
+ ret = fill_channels(rskb, &data->channels);
+ if (ret < 0)
+ return ret;
+ }
+ if (req_mask & ETH_PARAMS_IM_EEE) {
+ ret = fill_eee(rskb, &data->eee, req_info->compact);
+ if (ret < 0)
+ return ret;
+ }
+ if (req_mask & ETH_PARAMS_IM_FEC) {
+ ret = fill_fec(rskb, &data->fec);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+int ethnl_get_params(struct sk_buff *skb, struct genl_info *info)
+{
+ struct params_data data;
+ struct params_reqinfo req_info;
+ struct sk_buff *rskb;
+ int reply_len;
+ void *ehdr;
+ int ret;
+
+ ret = parse_params_req(&req_info, info, skb, info->nlhdr);
+ if (ret < 0)
+ goto err_dev;
+ ret = prepare_params(&data, &req_info, info, req_info.dev);
+ if (ret < 0)
+ goto err_dev;
+ reply_len = params_size(&data, &req_info);
+ if (ret < 0)
+ goto err_dev;
+ ret = -ENOMEM;
+ rskb = ethnl_reply_init(reply_len, req_info.dev, ETHNL_CMD_SET_PARAMS,
+ ETHA_PARAMS_DEV, info, &ehdr);
+ if (!rskb)
+ goto err_dev;
+ ret = fill_params(rskb, &data, &req_info);
+ if (ret < 0)
+ goto err;
+
+ genlmsg_end(rskb, ehdr);
+ dev_put(req_info.dev);
+ return genlmsg_reply(rskb, info);
+
+err:
+ WARN_ONCE(ret == -EMSGSIZE,
+ "calculated message payload length (%d) not sufficient\n",
+ reply_len);
+ nlmsg_free(rskb);
+err_dev:
+ if (req_info.dev)
+ dev_put(req_info.dev);
+ return ret;
+}
+
+static int params_dump(struct sk_buff *skb, struct netlink_callback *cb,
+ struct net_device *dev)
+{
+ struct params_data data;
+ struct params_reqinfo *req_info;
+ int ret;
+
+ req_info = (struct params_reqinfo *)cb->args[4];
+ ret = prepare_params(&data, req_info, NULL, dev);
+ if (ret < 0)
+ return ret;
+ ret = ethnl_fill_dev(skb, dev, ETHA_PARAMS_DEV);
+ if (ret < 0)
+ return ret;
+ ret = fill_params(skb, &data, req_info);
+ return ret;
+}
+
+int ethnl_params_start(struct netlink_callback *cb)
+{
+ struct params_reqinfo *req_info;
+ int ret;
+
+ req_info = kmalloc(sizeof(*req_info), GFP_KERNEL);
+ if (!req_info)
+ return -ENOMEM;
+ ret = parse_params_req(req_info, NULL, cb->skb, cb->nlh);
+ if (ret < 0) {
+ if (req_info->dev)
+ dev_put(req_info->dev);
+ req_info->dev = NULL;
+ return ret;
+ }
+
+ cb->args[0] = (long)params_dump;
+ cb->args[1] = ETHNL_CMD_SET_PARAMS;
+ cb->args[4] = (long)req_info;
+
+ return 0;
+}
+
+int ethnl_params_done(struct netlink_callback *cb)
+{
+ struct params_reqinfo *req_info;
+
+ req_info = (struct params_reqinfo *)cb->args[4];
+ if (req_info->dev)
+ dev_put(req_info->dev);
+ kfree(req_info);
+
+ return 0;
+}
--
2.18.0
Sets the information provided by ETHTOOL_SCOALESCE, ETHTOOL_SRINGPARAM,
ETHTOOL_SPAUSEPARAM, ETHTOOL_SCHANNELS, ETHTOOL_SEEE and ETHTOOL_SFECPARAM.
Each of these has corresponding nesting attribute containing attributes for
its settings. This way, userspace can request changes equivalent to one or
more of the legacy requests (and provide only part of the data to update).
Signed-off-by: Michal Kubecek <[email protected]>
---
Documentation/networking/ethtool-netlink.txt | 71 ++-
net/ethtool/netlink.c | 6 +
net/ethtool/params.c | 429 +++++++++++++++++++
3 files changed, 499 insertions(+), 7 deletions(-)
diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt
index 630eebd7e741..2b70736110fe 100644
--- a/Documentation/networking/ethtool-netlink.txt
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -126,7 +126,7 @@ List of message types
ETHNL_CMD_GET_SETTINGS
ETHNL_CMD_SET_SETTINGS
ETHNL_CMD_GET_PARAMS
- ETHNL_CMD_SET_PARAMS response only (for now)
+ ETHNL_CMD_SET_PARAMS
All constants use ETHNL_CMD_ prefix, usually followed by "GET", "SET" or "ACT"
to indicate the type.
@@ -408,6 +408,63 @@ to them are broadcasted as notifications on change of these settings using
netlink or ioctl ethtool interface.
+SET_PARAMS
+----------
+
+SET_PARAMS request modifies the settings retrieved by GET_PARAMS, i.e. it
+replaces ETHTOOL_SCOALESCE, ETHTOOL_SRINGPARAM, ETHTOOL_SPAUSEPARAM,
+ETHTOOL_SCHANNELS, ETHTOOL_SEEE and ETHTOOL_SFECPARAM legacy commands. For
+each of these, relevant data attributes are contained in a corresponding nest
+attribute. Some of the attributes provided by GET_SETPARAMS are read only and
+cannot be set by SET_PARAMS request.
+
+ ETHA_PARAMS_COALESCE (nest) coalescing
+ ETHA_COALESCE_RX_USECS (u32)
+ ETHA_COALESCE_RX_MAXFRM (u32)
+ ETHA_COALESCE_RX_USECS_IRQ (u32)
+ ETHA_COALESCE_RX_MAXFRM_IRQ (u32)
+ ETHA_COALESCE_RX_USECS_LOW (u32)
+ ETHA_COALESCE_RX_MAXFRM_LOW (u32)
+ ETHA_COALESCE_RX_USECS_HIGH (u32)
+ ETHA_COALESCE_RX_MAXFRM_HIGH (u32)
+ ETHA_COALESCE_TX_USECS (u32)
+ ETHA_COALESCE_TX_MAXFRM (u32)
+ ETHA_COALESCE_TX_USECS_IRQ (u32)
+ ETHA_COALESCE_TX_MAXFRM_IRQ (u32)
+ ETHA_COALESCE_TX_USECS_LOW (u32)
+ ETHA_COALESCE_TX_MAXFRM_LOW (u32)
+ ETHA_COALESCE_TX_USECS_HIGH (u32)
+ ETHA_COALESCE_TX_MAXFRM_HIGH (u32)
+ ETHA_COALESCE_PKT_RATE_LOW (u32)
+ ETHA_COALESCE_PKT_RATE_HIGH (u32)
+ ETHA_COALESCE_RX_USE_ADAPTIVE (bool)
+ ETHA_COALESCE_TX_USE_ADAPTIVE (bool)
+ ETHA_COALESCE_RATE_SAMPLE_INTERVAL (u32)
+ ETHA_COALESCE_STATS_BLOCK_USECS (u32)
+ ETHA_PARAMS_RING (nest) ring parameters
+ ETHA_RING_RX_PENDING (u32)
+ ETHA_RING_RX_MINI_PENDING (u32)
+ ETHA_RING_RX_JUMBO_PENDING (u32)
+ ETHA_RING_TX_PENDING (u32)
+ ETHA_PARAMS_PAUSE (nest) pause parameters
+ ETHA_PAUSE_AUTONEG (bool)
+ ETHA_PAUSE_RX (bool)
+ ETHA_PAUSE_TX (bool)
+ ETHA_PARAMS_CHANNELS (nest) channel settings
+ ETHA_CHANNELS_RX_COUNT (u32)
+ ETHA_CHANNELS_TX_COUNT (u32)
+ ETHA_CHANNELS_OTHER_COUNT (u32)
+ ETHA_CHANNELS_COMBINED_COUNT (u32)
+ ETHA_PARAMS_EEE (nest) EEE settings
+ ETHA_EEE_LINK_MODES (bitset)
+ - change modes for which EEE is advertised
+ ETHA_EEE_ENABLED (bool)
+ ETHA_EEE_TX_LPI_ENABLED (bool)
+ ETHA_EEE_TX_LPI_TIMER (u32)
+ ETHA_PARAMS_FEC (nest) FEC parameters
+ ETHA_FEC_MODES (bitfield32)
+ - change configured FEC encodings
+
Request translation
-------------------
@@ -431,11 +488,11 @@ ETHTOOL_GLINK ETHNL_CMD_GET_SETTINGS
ETHTOOL_GEEPROM n/a
ETHTOOL_SEEPROM n/a
ETHTOOL_GCOALESCE ETHNL_CMD_GET_PARAMS
-ETHTOOL_SCOALESCE n/a
+ETHTOOL_SCOALESCE ETHNL_CMD_SET_PARAMS
ETHTOOL_GRINGPARAM ETHNL_CMD_GET_PARAMS
-ETHTOOL_SRINGPARAM n/a
+ETHTOOL_SRINGPARAM ETHNL_CMD_SET_PARAMS
ETHTOOL_GPAUSEPARAM ETHNL_CMD_GET_PARAMS
-ETHTOOL_SPAUSEPARAM n/a
+ETHTOOL_SPAUSEPARAM ETHNL_CMD_SET_PARAMS
ETHTOOL_GRXCSUM ETHNL_CMD_GET_SETTINGS
ETHTOOL_SRXCSUM ETHNL_CMD_SET_SETTINGS
ETHTOOL_GTXCSUM ETHNL_CMD_GET_SETTINGS
@@ -477,7 +534,7 @@ ETHTOOL_SRXFHINDIR n/a
ETHTOOL_GFEATURES ETHNL_CMD_GET_SETTINGS
ETHTOOL_SFEATURES n/a
ETHTOOL_GCHANNELS ETHNL_CMD_GET_PARAMS
-ETHTOOL_SCHANNELS n/a
+ETHTOOL_SCHANNELS ETHNL_CMD_SET_PARAMS
ETHTOOL_SET_DUMP n/a
ETHTOOL_GET_DUMP_FLAG n/a
ETHTOOL_GET_DUMP_DATA n/a
@@ -485,7 +542,7 @@ ETHTOOL_GET_TS_INFO n/a
ETHTOOL_GMODULEINFO n/a
ETHTOOL_GMODULEEEPROM n/a
ETHTOOL_GEEE ETHNL_CMD_GET_PARAMS
-ETHTOOL_SEEE n/a
+ETHTOOL_SEEE ETHNL_CMD_SET_PARAMS
ETHTOOL_GRSSH n/a
ETHTOOL_SRSSH n/a
ETHTOOL_GTUNABLE n/a
@@ -497,5 +554,5 @@ ETHTOOL_SLINKSETTINGS ETHNL_CMD_SET_SETTINGS
ETHTOOL_PHY_GTUNABLE n/a
ETHTOOL_PHY_STUNABLE n/a
ETHTOOL_GFECPARAM ETHNL_CMD_GET_PARAMS
-ETHTOOL_SFECPARAM n/a
+ETHTOOL_SFECPARAM ETHNL_CMD_SET_PARAMS
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index b24a0e045ec0..17fee1ce077b 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -735,6 +735,7 @@ int ethnl_get_drvinfo(struct sk_buff *skb, struct genl_info *info);
int ethnl_get_settings(struct sk_buff *skb, struct genl_info *info);
int ethnl_set_settings(struct sk_buff *skb, struct genl_info *info);
int ethnl_get_params(struct sk_buff *skb, struct genl_info *info);
+int ethnl_set_params(struct sk_buff *skb, struct genl_info *info);
int ethnl_strset_start(struct netlink_callback *cb);
int ethnl_drvinfo_start(struct netlink_callback *cb);
@@ -778,6 +779,11 @@ static const struct genl_ops ethtool_genl_ops[] = {
.dumpit = ethnl_dumpit,
.done = ethnl_params_done,
},
+ {
+ .cmd = ETHNL_CMD_SET_PARAMS,
+ .flags = GENL_UNS_ADMIN_PERM,
+ .doit = ethnl_set_params,
+ },
};
static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
diff --git a/net/ethtool/params.c b/net/ethtool/params.c
index 773e13fc1ae6..8cbf33ad81b8 100644
--- a/net/ethtool/params.c
+++ b/net/ethtool/params.c
@@ -577,3 +577,432 @@ void ethnl_params_notify(struct netdev_notifier_ethtool_info *info)
err_skb:
nlmsg_free(skb);
}
+
+static const struct nla_policy coalesce_policy[ETHA_COALESCE_MAX + 1] = {
+ [ETHA_COALESCE_UNSPEC] = { .type = NLA_UNSPEC },
+ [ETHA_COALESCE_RX_USECS] = { .type = NLA_U32 },
+ [ETHA_COALESCE_RX_MAXFRM] = { .type = NLA_U32 },
+ [ETHA_COALESCE_RX_USECS_IRQ] = { .type = NLA_U32 },
+ [ETHA_COALESCE_RX_MAXFRM_IRQ] = { .type = NLA_U32 },
+ [ETHA_COALESCE_RX_USECS_LOW] = { .type = NLA_U32 },
+ [ETHA_COALESCE_RX_MAXFRM_LOW] = { .type = NLA_U32 },
+ [ETHA_COALESCE_RX_USECS_HIGH] = { .type = NLA_U32 },
+ [ETHA_COALESCE_RX_MAXFRM_HIGH] = { .type = NLA_U32 },
+ [ETHA_COALESCE_TX_USECS] = { .type = NLA_U32 },
+ [ETHA_COALESCE_TX_MAXFRM] = { .type = NLA_U32 },
+ [ETHA_COALESCE_TX_USECS_IRQ] = { .type = NLA_U32 },
+ [ETHA_COALESCE_TX_MAXFRM_IRQ] = { .type = NLA_U32 },
+ [ETHA_COALESCE_TX_USECS_LOW] = { .type = NLA_U32 },
+ [ETHA_COALESCE_TX_MAXFRM_LOW] = { .type = NLA_U32 },
+ [ETHA_COALESCE_TX_USECS_HIGH] = { .type = NLA_U32 },
+ [ETHA_COALESCE_TX_MAXFRM_HIGH] = { .type = NLA_U32 },
+ [ETHA_COALESCE_PKT_RATE_LOW] = { .type = NLA_U32 },
+ [ETHA_COALESCE_PKT_RATE_HIGH] = { .type = NLA_U32 },
+ [ETHA_COALESCE_RX_USE_ADAPTIVE] = { .type = NLA_U8 },
+ [ETHA_COALESCE_TX_USE_ADAPTIVE] = { .type = NLA_U8 },
+ [ETHA_COALESCE_RATE_SAMPLE_INTERVAL] = { .type = NLA_U32 },
+ [ETHA_COALESCE_STATS_BLOCK_USECS] = { .type = NLA_U32 },
+};
+
+static int update_coalesce(struct genl_info *info, struct net_device *dev,
+ struct nlattr *nest)
+{
+ struct nlattr *tb[ETHA_COALESCE_MAX + 1];
+ struct ethtool_coalesce data = {};
+ bool mod = false;
+ int ret;
+
+ if (!nest)
+ return 0;
+ if (!dev->ethtool_ops->get_coalesce || !dev->ethtool_ops->set_coalesce)
+ return -EOPNOTSUPP;
+ ret = dev->ethtool_ops->get_coalesce(dev, &data);
+ if (ret < 0)
+ return ret;
+
+ ret = nla_parse_nested(tb, ETHA_COALESCE_MAX, nest, coalesce_policy,
+ info->extack);
+ if (ret < 0)
+ return ret;
+
+ if (ethnl_update_u32(&data.rx_coalesce_usecs,
+ tb[ETHA_COALESCE_RX_USECS]))
+ mod = true;
+ if (ethnl_update_u32(&data.rx_max_coalesced_frames,
+ tb[ETHA_COALESCE_RX_MAXFRM]))
+ mod = true;
+ if (ethnl_update_u32(&data.rx_coalesce_usecs_irq,
+ tb[ETHA_COALESCE_RX_USECS_IRQ]))
+ mod = true;
+ if (ethnl_update_u32(&data.rx_max_coalesced_frames_irq,
+ tb[ETHA_COALESCE_RX_MAXFRM_IRQ]))
+ mod = true;
+ if (ethnl_update_u32(&data.rx_coalesce_usecs_low,
+ tb[ETHA_COALESCE_RX_USECS_LOW]))
+ mod = true;
+ if (ethnl_update_u32(&data.rx_max_coalesced_frames_low,
+ tb[ETHA_COALESCE_RX_MAXFRM_LOW]))
+ mod = true;
+ if (ethnl_update_u32(&data.rx_coalesce_usecs_high,
+ tb[ETHA_COALESCE_RX_USECS_HIGH]))
+ mod = true;
+ if (ethnl_update_u32(&data.rx_max_coalesced_frames_high,
+ tb[ETHA_COALESCE_RX_MAXFRM_HIGH]))
+ mod = true;
+ if (ethnl_update_u32(&data.tx_coalesce_usecs,
+ tb[ETHA_COALESCE_TX_USECS]))
+ mod = true;
+ if (ethnl_update_u32(&data.tx_max_coalesced_frames,
+ tb[ETHA_COALESCE_TX_MAXFRM]))
+ mod = true;
+ if (ethnl_update_u32(&data.tx_coalesce_usecs_irq,
+ tb[ETHA_COALESCE_TX_USECS_IRQ]))
+ mod = true;
+ if (ethnl_update_u32(&data.tx_max_coalesced_frames_irq,
+ tb[ETHA_COALESCE_TX_MAXFRM_IRQ]))
+ mod = true;
+ if (ethnl_update_u32(&data.tx_coalesce_usecs_low,
+ tb[ETHA_COALESCE_TX_USECS_LOW]))
+ mod = true;
+ if (ethnl_update_u32(&data.tx_max_coalesced_frames_low,
+ tb[ETHA_COALESCE_TX_MAXFRM_LOW]))
+ mod = true;
+ if (ethnl_update_u32(&data.tx_coalesce_usecs_high,
+ tb[ETHA_COALESCE_TX_USECS_HIGH]))
+ mod = true;
+ if (ethnl_update_u32(&data.tx_max_coalesced_frames_high,
+ tb[ETHA_COALESCE_TX_MAXFRM_HIGH]))
+ mod = true;
+ if (ethnl_update_u32(&data.pkt_rate_low,
+ tb[ETHA_COALESCE_PKT_RATE_LOW]))
+ mod = true;
+ if (ethnl_update_u32(&data.pkt_rate_high,
+ tb[ETHA_COALESCE_PKT_RATE_HIGH]))
+ mod = true;
+ if (ethnl_update_bool32(&data.use_adaptive_rx_coalesce,
+ tb[ETHA_COALESCE_RX_USE_ADAPTIVE]))
+ mod = true;
+ if (ethnl_update_bool32(&data.use_adaptive_tx_coalesce,
+ tb[ETHA_COALESCE_TX_USE_ADAPTIVE]))
+ mod = true;
+ if (ethnl_update_u32(&data.rate_sample_interval,
+ tb[ETHA_COALESCE_RATE_SAMPLE_INTERVAL]))
+ mod = true;
+ if (ethnl_update_u32(&data.stats_block_coalesce_usecs,
+ tb[ETHA_COALESCE_STATS_BLOCK_USECS]))
+ mod = true;
+
+ if (!mod)
+ return 0;
+ ret = dev->ethtool_ops->set_coalesce(dev, &data);
+ return (ret < 0) ? ret : 1;
+}
+
+static const struct nla_policy ring_policy[ETHA_RING_MAX + 1] = {
+ [ETHA_RING_UNSPEC] = { .type = NLA_UNSPEC },
+ [ETHA_RING_RX_MAX_PENDING] = { .type = NLA_U32 },
+ [ETHA_RING_RX_MINI_MAX_PENDING] = { .type = NLA_U32 },
+ [ETHA_RING_RX_JUMBO_MAX_PENDING] = { .type = NLA_U32 },
+ [ETHA_RING_TX_MAX_PENDING] = { .type = NLA_U32 },
+ [ETHA_RING_RX_PENDING] = { .type = NLA_U32 },
+ [ETHA_RING_RX_MINI_PENDING] = { .type = NLA_U32 },
+ [ETHA_RING_RX_JUMBO_PENDING] = { .type = NLA_U32 },
+ [ETHA_RING_TX_PENDING] = { .type = NLA_U32 },
+};
+
+static int update_ring(struct genl_info *info, struct net_device *dev,
+ struct nlattr *nest)
+{
+ struct nlattr *tb[ETHA_RING_MAX + 1];
+ struct ethtool_ringparam data = {};
+ bool mod = false;
+ int ret;
+
+ if (!nest)
+ return 0;
+ if (!dev->ethtool_ops->get_ringparam ||
+ !dev->ethtool_ops->set_ringparam)
+ return -EOPNOTSUPP;
+ dev->ethtool_ops->get_ringparam(dev, &data);
+
+ ret = nla_parse_nested(tb, ETHA_RING_MAX, nest, ring_policy,
+ info->extack);
+ if (ret < 0)
+ return ret;
+ /* read only attributes */
+ if (tb[ETHA_RING_RX_MAX_PENDING] || tb[ETHA_RING_RX_MINI_MAX_PENDING] ||
+ tb[ETHA_RING_RX_JUMBO_MAX_PENDING] ||
+ tb[ETHA_RING_TX_MAX_PENDING]) {
+ ETHNL_SET_ERRMSG(info, "attempt to set a read only attribute");
+ return -EINVAL;
+ }
+
+ if (ethnl_update_u32(&data.rx_pending, tb[ETHA_RING_RX_PENDING]))
+ mod = true;
+ if (ethnl_update_u32(&data.rx_mini_pending,
+ tb[ETHA_RING_RX_MINI_PENDING]))
+ mod = true;
+ if (ethnl_update_u32(&data.rx_jumbo_pending,
+ tb[ETHA_RING_RX_JUMBO_PENDING]))
+ mod = true;
+ if (ethnl_update_u32(&data.tx_pending, tb[ETHA_RING_TX_PENDING]))
+ mod = true;
+ if (!mod)
+ return 0;
+
+ /* ensure new ring parameters are within the maximums */
+ if (data.rx_pending > data.rx_max_pending ||
+ data.rx_mini_pending > data.rx_mini_max_pending ||
+ data.rx_jumbo_pending > data.rx_jumbo_max_pending ||
+ data.tx_pending > data.tx_max_pending) {
+ ETHNL_SET_ERRMSG(info,
+ "requested ring param value exceeeds maximum");
+ return -EINVAL;
+ }
+
+ ret = dev->ethtool_ops->set_ringparam(dev, &data);
+ return (ret < 0) ? ret : 1;
+}
+
+static const struct nla_policy pause_policy[ETHA_PAUSE_MAX + 1] = {
+ [ETHA_PAUSE_UNSPEC] = { .type = NLA_UNSPEC },
+ [ETHA_PAUSE_AUTONEG] = { .type = NLA_U8 },
+ [ETHA_PAUSE_RX] = { .type = NLA_U8 },
+ [ETHA_PAUSE_TX] = { .type = NLA_U8 },
+};
+
+static int update_pause(struct genl_info *info, struct net_device *dev,
+ struct nlattr *nest)
+{
+ struct nlattr *tb[ETHA_RING_MAX + 1];
+ struct ethtool_pauseparam data = {};
+ bool mod = false;
+ int ret;
+
+ if (!nest)
+ return 0;
+ if (!dev->ethtool_ops->get_pauseparam ||
+ !dev->ethtool_ops->set_pauseparam)
+ return -EOPNOTSUPP;
+ dev->ethtool_ops->get_pauseparam(dev, &data);
+
+ ret = nla_parse_nested(tb, ETHA_PAUSE_MAX, nest, pause_policy,
+ info->extack);
+ if (ret < 0)
+ return ret;
+
+ if (ethnl_update_u32(&data.autoneg, tb[ETHA_PAUSE_AUTONEG]))
+ mod = true;
+ if (ethnl_update_u32(&data.rx_pause, tb[ETHA_PAUSE_RX]))
+ mod = true;
+ if (ethnl_update_u32(&data.tx_pause, tb[ETHA_PAUSE_TX]))
+ mod = true;
+
+ if (!mod)
+ return 0;
+ ret = dev->ethtool_ops->set_pauseparam(dev, &data);
+ return (ret < 0) ? ret : 1;
+}
+
+static const struct nla_policy channels_policy[ETHA_CHANNELS_MAX + 1] = {
+ [ETHA_CHANNELS_UNSPEC] = { .type = NLA_UNSPEC },
+ [ETHA_CHANNELS_MAX_RX] = { .type = NLA_U32 },
+ [ETHA_CHANNELS_MAX_TX] = { .type = NLA_U32 },
+ [ETHA_CHANNELS_MAX_OTHER] = { .type = NLA_U32 },
+ [ETHA_CHANNELS_MAX_COMBINED] = { .type = NLA_U32 },
+ [ETHA_CHANNELS_RX_COUNT] = { .type = NLA_U32 },
+ [ETHA_CHANNELS_TX_COUNT] = { .type = NLA_U32 },
+ [ETHA_CHANNELS_OTHER_COUNT] = { .type = NLA_U32 },
+ [ETHA_CHANNELS_COMBINED_COUNT] = { .type = NLA_U32 },
+};
+
+static int update_channels(struct genl_info *info, struct net_device *dev,
+ struct nlattr *nest)
+{
+ struct nlattr *tb[ETHA_CHANNELS_MAX + 1];
+ struct ethtool_channels data = {};
+ bool mod = false;
+ int ret;
+
+ if (!nest)
+ return 0;
+ if (!dev->ethtool_ops->get_channels ||
+ !dev->ethtool_ops->set_channels)
+ return -EOPNOTSUPP;
+ dev->ethtool_ops->get_channels(dev, &data);
+
+ ret = nla_parse_nested(tb, ETHA_CHANNELS_MAX, nest, channels_policy,
+ info->extack);
+ if (ret < 0)
+ return ret;
+ /* read only attributes */
+ if (tb[ETHA_CHANNELS_MAX_RX] || tb[ETHA_CHANNELS_MAX_TX] ||
+ tb[ETHA_CHANNELS_MAX_OTHER] || tb[ETHA_CHANNELS_MAX_COMBINED]) {
+ ETHNL_SET_ERRMSG(info, "attempt to set a read only attribute");
+ return -EINVAL;
+ }
+
+ if (ethnl_update_u32(&data.rx_count, tb[ETHA_CHANNELS_RX_COUNT]))
+ mod = true;
+ if (ethnl_update_u32(&data.tx_count, tb[ETHA_CHANNELS_TX_COUNT]))
+ mod = true;
+ if (ethnl_update_u32(&data.other_count, tb[ETHA_CHANNELS_OTHER_COUNT]))
+ mod = true;
+ if (ethnl_update_u32(&data.combined_count,
+ tb[ETHA_CHANNELS_COMBINED_COUNT]))
+ mod = true;
+
+ if (!mod)
+ return 0;
+ ret = dev->ethtool_ops->set_channels(dev, &data);
+ return (ret < 0) ? ret : 1;
+}
+
+static const struct nla_policy eee_policy[ETHA_EEE_MAX + 1] = {
+ [ETHA_EEE_UNSPEC] = { .type = NLA_UNSPEC },
+ [ETHA_EEE_LINK_MODES] = { .type = NLA_NESTED },
+ [ETHA_EEE_PEER_MODES] = { .type = NLA_NESTED },
+ [ETHA_EEE_ACTIVE] = { .type = NLA_U8 },
+ [ETHA_EEE_ENABLED] = { .type = NLA_U8 },
+ [ETHA_EEE_TX_LPI_ENABLED] = { .type = NLA_U8 },
+ [ETHA_EEE_TX_LPI_TIMER] = { .type = NLA_U32 },
+};
+
+static int update_eee(struct genl_info *info, struct net_device *dev,
+ struct nlattr *nest)
+{
+ struct nlattr *tb[ETHA_EEE_MAX + 1];
+ struct ethtool_eee data = {};
+ bool mod = false;
+ int ret;
+
+ if (!nest)
+ return 0;
+ if (!dev->ethtool_ops->get_eee ||
+ !dev->ethtool_ops->set_eee)
+ return -EOPNOTSUPP;
+ ret = dev->ethtool_ops->get_eee(dev, &data);
+ if (ret < 0)
+ return ret;
+
+ ret = nla_parse_nested(tb, ETHA_EEE_MAX, nest, eee_policy,
+ info->extack);
+ if (ret < 0)
+ return ret;
+ /* read only attributes */
+ if (tb[ETHA_EEE_PEER_MODES] || tb[ETHA_EEE_ACTIVE]) {
+ ETHNL_SET_ERRMSG(info, "attempt to set a read only attribute");
+ return -EINVAL;
+ }
+
+ if (ethnl_update_bitset32(&data.advertised, NULL, 32,
+ tb[ETHA_EEE_LINK_MODES], &ret,
+ link_mode_names, info))
+ mod = true;
+ if (ret < 0)
+ return ret;
+ if (ethnl_update_bool32(&data.eee_enabled, tb[ETHA_EEE_ENABLED]))
+ mod = true;
+ if (ethnl_update_bool32(&data.tx_lpi_enabled,
+ tb[ETHA_EEE_TX_LPI_ENABLED]))
+ mod = true;
+ if (ethnl_update_u32(&data.tx_lpi_timer, tb[ETHA_EEE_TX_LPI_TIMER]))
+ mod = true;
+
+ if (!mod)
+ return 0;
+ ret = dev->ethtool_ops->set_eee(dev, &data);
+ return (ret < 0) ? ret : 1;
+}
+
+static const struct nla_policy fec_policy[ETHA_FEC_MAX + 1] = {
+ [ETHA_FEC_UNSPEC] = { .type = NLA_UNSPEC },
+ [ETHA_FEC_MODES] = { .type = NLA_U32 },
+};
+
+static int update_fec(struct genl_info *info, struct net_device *dev,
+ struct nlattr *nest)
+{
+ struct nlattr *tb[ETHA_FEC_MAX + 1];
+ struct ethtool_fecparam data = {};
+ bool mod = false;
+ int ret;
+
+ if (!nest)
+ return 0;
+ if (!dev->ethtool_ops->get_fecparam ||
+ !dev->ethtool_ops->set_fecparam)
+ return -EOPNOTSUPP;
+ ret = dev->ethtool_ops->get_fecparam(dev, &data);
+ if (ret < 0)
+ return ret;
+
+ ret = nla_parse_nested(tb, ETHA_FEC_MAX, nest, fec_policy,
+ info->extack);
+ if (ret < 0)
+ return ret;
+
+ if (ethnl_update_bitfield32(&data.fec, tb[ETHA_FEC_MODES]))
+ mod = true;
+
+ if (!mod)
+ return 0;
+ ret = dev->ethtool_ops->set_fecparam(dev, &data);
+ return (ret < 0) ? ret : 1;
+}
+
+int ethnl_set_params(struct sk_buff *skb, struct genl_info *info)
+{
+ struct nlattr *tb[ETHA_PARAMS_MAX + 1];
+ struct net_device *dev;
+ u32 req_mask = 0;
+ int ret;
+
+ ret = genlmsg_parse(info->nlhdr, ðtool_genl_family, tb,
+ ETHA_PARAMS_MAX, params_policy, info->extack);
+ if (ret < 0)
+ return ret;
+ dev = ethnl_dev_get(info, tb[ETHA_PARAMS_DEV]);
+ if (IS_ERR(dev))
+ return PTR_ERR(dev);
+ rtnl_lock();
+
+ ret = update_coalesce(info, dev, tb[ETHA_PARAMS_COALESCE]);
+ if (ret < 0)
+ goto out_unlock;
+ if (ret)
+ req_mask |= ETH_PARAMS_IM_COALESCE;
+ ret = update_ring(info, dev, tb[ETHA_PARAMS_RING]);
+ if (ret < 0)
+ goto out_unlock;
+ if (ret)
+ req_mask |= ETH_PARAMS_IM_RING;
+ ret = update_pause(info, dev, tb[ETHA_PARAMS_PAUSE]);
+ if (ret < 0)
+ goto out_unlock;
+ if (ret)
+ req_mask |= ETH_PARAMS_IM_PAUSE;
+ ret = update_channels(info, dev, tb[ETHA_PARAMS_CHANNELS]);
+ if (ret < 0)
+ goto out_unlock;
+ if (ret)
+ req_mask |= ETH_PARAMS_IM_CHANNELS;
+ ret = update_eee(info, dev, tb[ETHA_PARAMS_EEE]);
+ if (ret < 0)
+ goto out_unlock;
+ if (ret)
+ req_mask |= ETH_PARAMS_IM_EEE;
+ ret = update_fec(info, dev, tb[ETHA_PARAMS_FEC]);
+ if (ret < 0)
+ goto out_unlock;
+ if (ret)
+ req_mask |= ETH_PARAMS_IM_FEC;
+
+ ret = 0;
+out_unlock:
+ if (req_mask)
+ ethnl_notify(dev, NULL, ETHNL_CMD_SET_PARAMS, req_mask);
+ rtnl_unlock();
+ dev_put(dev);
+ return ret;
+}
--
2.18.0
With ETH_SETTINGS_IM_FEATURES info mask, ETHNL_CMD_GET_SETTINGS request
gets network device feature values. This is the same information as
provided via ETHTOOL_GFEATURES ioctl request.
Signed-off-by: Michal Kubecek <[email protected]>
---
Documentation/networking/ethtool-netlink.txt | 33 ++--
include/uapi/linux/ethtool_netlink.h | 15 +-
net/ethtool/common.h | 2 +
net/ethtool/ioctl.c | 2 -
net/ethtool/settings.c | 151 +++++++++++++++++++
5 files changed, 191 insertions(+), 12 deletions(-)
diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt
index 60d426cf6dad..82b839c03707 100644
--- a/Documentation/networking/ethtool-netlink.txt
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -214,6 +214,7 @@ Info mask bits meaning:
ETH_SETTINGS_IM_MSGLEVEL msglevel
ETH_SETTINGS_IM_WOLINFO struct ethtool_wolinfo
ETH_SETTINGS_IM_LINK link state
+ ETH_SETTINGS_IM_FEATURES features
Response contents:
@@ -233,6 +234,11 @@ Response contents:
ETHA_SETTINGS_LINK_MODES (bitset) device link modes
ETHA_SETTINGS_PEER_MODES (bitset) link partner link modes
ETHA_SETTINGS_LINK (u32) link state
+ ETHA_SETTINGS_FEATURES (nested) device features
+ ETHA_FEATURES_HW (bitset) dev->hw_features
+ ETHA_FEATURES_WANTED (bitset) dev->wanted_features
+ ETHA_FEATURES_ACTIVE (bitset) dev->features
+ ETHA_FEATURES_NOCHANGE (bitset) NETIF_F_NEVER_CHANGE
Most of the attributes have the same meaning (including values) as
corresponding members of ioctl structures. For ETHA_SETTINGS_MDIO_SUPPORT and
@@ -242,6 +248,15 @@ 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.
+Bitmaps contained in ETHA_SETTINGS_FEATURES have the same meaning as bitmaps
+used in ioctl interference but attribute names are different (they are based
+on corresponding members of struct net_device). Legacy "flags" are not
+provided, if userspace needs them (most likely only ethtool for backward
+compatibility), it can calculate their values from related feature bits
+itself. ETHA_FEATURES_HW uses mask consisting of all features recognized by
+kernel (to provide all names when using verbose bitmap format), remaining
+three use mask equal to value (to save space).
+
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.
@@ -278,30 +293,30 @@ ETHTOOL_GRINGPARAM n/a
ETHTOOL_SRINGPARAM n/a
ETHTOOL_GPAUSEPARAM n/a
ETHTOOL_SPAUSEPARAM n/a
-ETHTOOL_GRXCSUM n/a
+ETHTOOL_GRXCSUM ETHNL_CMD_GET_SETTINGS
ETHTOOL_SRXCSUM n/a
-ETHTOOL_GTXCSUM n/a
+ETHTOOL_GTXCSUM ETHNL_CMD_GET_SETTINGS
ETHTOOL_STXCSUM n/a
-ETHTOOL_GSG n/a
+ETHTOOL_GSG ETHNL_CMD_GET_SETTINGS
ETHTOOL_SSG n/a
ETHTOOL_TEST n/a
ETHTOOL_GSTRINGS ETHNL_CMD_GET_STRSET
ETHTOOL_PHYS_ID n/a
ETHTOOL_GSTATS n/a
-ETHTOOL_GTSO n/a
+ETHTOOL_GTSO ETHNL_CMD_GET_SETTINGS
ETHTOOL_STSO n/a
ETHTOOL_GPERMADDR n/a
-ETHTOOL_GUFO n/a
+ETHTOOL_GUFO ETHNL_CMD_GET_SETTINGS
ETHTOOL_SUFO n/a
-ETHTOOL_GGSO n/a
+ETHTOOL_GGSO ETHNL_CMD_GET_SETTINGS
ETHTOOL_SGSO n/a
-ETHTOOL_GFLAGS n/a
+ETHTOOL_GFLAGS ETHNL_CMD_GET_SETTINGS
ETHTOOL_SFLAGS n/a
ETHTOOL_GPFLAGS n/a
ETHTOOL_SPFLAGS n/a
ETHTOOL_GRXFH n/a
ETHTOOL_SRXFH n/a
-ETHTOOL_GGRO n/a
+ETHTOOL_GGRO ETHNL_CMD_GET_SETTINGS
ETHTOOL_SGRO n/a
ETHTOOL_GRXRINGS n/a
ETHTOOL_GRXCLSRLCNT n/a
@@ -316,7 +331,7 @@ ETHTOOL_GRXNTUPLE n/a
ETHTOOL_GSSET_INFO ETHNL_CMD_GET_STRSET
ETHTOOL_GRXFHINDIR n/a
ETHTOOL_SRXFHINDIR n/a
-ETHTOOL_GFEATURES n/a
+ETHTOOL_GFEATURES ETHNL_CMD_GET_SETTINGS
ETHTOOL_SFEATURES n/a
ETHTOOL_GCHANNELS n/a
ETHTOOL_SCHANNELS n/a
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index 66df44aa7226..06c78b281275 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -170,6 +170,7 @@ enum {
ETHA_SETTINGS_LINK_MODES, /* bitset */
ETHA_SETTINGS_PEER_MODES, /* bitset */
ETHA_SETTINGS_LINK, /* u32 */
+ ETHA_SETTINGS_FEATURES, /* nest - ETHA_FEATURES_* */
__ETHA_SETTINGS_MAX,
ETHA_SETTINGS_MAX = (__ETHA_SETTINGS_MAX - 1)
@@ -180,8 +181,20 @@ enum {
#define ETH_SETTINGS_IM_MSGLEVEL 0x04
#define ETH_SETTINGS_IM_WOLINFO 0x08
#define ETH_SETTINGS_IM_LINK 0x10
+#define ETH_SETTINGS_IM_FEATURES 0x20
-#define ETH_SETTINGS_IM_DEFAULT 0x1f
+#define ETH_SETTINGS_IM_DEFAULT 0x3f
+
+enum {
+ ETHA_FEATURES_UNSPEC,
+ ETHA_FEATURES_HW, /* bitset */
+ ETHA_FEATURES_WANTED, /* bitset */
+ ETHA_FEATURES_ACTIVE, /* bitset */
+ ETHA_FEATURES_NOCHANGE, /* bitset */
+
+ __ETHA_FEATURES_MAX,
+ ETHA_FEATURES_MAX = (__ETHA_FEATURES_MAX - 1)
+};
/* generic netlink info */
#define ETHTOOL_GENL_NAME "ethtool"
diff --git a/net/ethtool/common.h b/net/ethtool/common.h
index ec90d4ccddf7..3b00ed6bfe10 100644
--- a/net/ethtool/common.h
+++ b/net/ethtool/common.h
@@ -6,6 +6,8 @@
#include <linux/netdevice.h>
#include <linux/ethtool.h>
+#define ETHTOOL_DEV_FEATURE_WORDS ((NETDEV_FEATURE_COUNT + 31) / 32)
+
extern const char netdev_features_strings[NETDEV_FEATURE_COUNT][ETH_GSTRING_LEN];
extern const char rss_hash_func_strings[ETH_RSS_HASH_FUNCS_COUNT][ETH_GSTRING_LEN];
extern const char tunable_strings[__ETHTOOL_TUNABLE_COUNT][ETH_GSTRING_LEN];
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index 8613434b6fc0..39e9aea60516 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -54,8 +54,6 @@ EXPORT_SYMBOL(ethtool_op_get_ts_info);
/* Handlers for each ethtool command */
-#define ETHTOOL_DEV_FEATURE_WORDS ((NETDEV_FEATURE_COUNT + 31) / 32)
-
static int ethtool_get_features(struct net_device *dev, void __user *useraddr)
{
struct ethtool_gfeatures cmd = {
diff --git a/net/ethtool/settings.c b/net/ethtool/settings.c
index dd76599f311f..fdca418a6d8e 100644
--- a/net/ethtool/settings.c
+++ b/net/ethtool/settings.c
@@ -4,6 +4,66 @@
#include "common.h"
#include <linux/rtnetlink.h>
+#define FEATURE_NAME_PTR(id) [id] = netdev_features_strings[id]
+static const char *feature_names[] = {
+ FEATURE_NAME_PTR(NETIF_F_SG_BIT),
+ FEATURE_NAME_PTR(NETIF_F_IP_CSUM_BIT),
+ FEATURE_NAME_PTR(NETIF_F_HW_CSUM_BIT),
+ FEATURE_NAME_PTR(NETIF_F_IPV6_CSUM_BIT),
+ FEATURE_NAME_PTR(NETIF_F_HIGHDMA_BIT),
+ FEATURE_NAME_PTR(NETIF_F_FRAGLIST_BIT),
+ FEATURE_NAME_PTR(NETIF_F_HW_VLAN_CTAG_TX_BIT),
+
+ FEATURE_NAME_PTR(NETIF_F_HW_VLAN_CTAG_RX_BIT),
+ FEATURE_NAME_PTR(NETIF_F_HW_VLAN_CTAG_FILTER_BIT),
+ FEATURE_NAME_PTR(NETIF_F_HW_VLAN_STAG_TX_BIT),
+ FEATURE_NAME_PTR(NETIF_F_HW_VLAN_STAG_RX_BIT),
+ FEATURE_NAME_PTR(NETIF_F_HW_VLAN_STAG_FILTER_BIT),
+ FEATURE_NAME_PTR(NETIF_F_VLAN_CHALLENGED_BIT),
+ FEATURE_NAME_PTR(NETIF_F_GSO_BIT),
+ FEATURE_NAME_PTR(NETIF_F_LLTX_BIT),
+ FEATURE_NAME_PTR(NETIF_F_NETNS_LOCAL_BIT),
+ FEATURE_NAME_PTR(NETIF_F_GRO_BIT),
+ FEATURE_NAME_PTR(NETIF_F_GRO_HW_BIT),
+ FEATURE_NAME_PTR(NETIF_F_LRO_BIT),
+
+ FEATURE_NAME_PTR(NETIF_F_TSO_BIT),
+ FEATURE_NAME_PTR(NETIF_F_GSO_ROBUST_BIT),
+ FEATURE_NAME_PTR(NETIF_F_TSO_ECN_BIT),
+ FEATURE_NAME_PTR(NETIF_F_TSO_MANGLEID_BIT),
+ FEATURE_NAME_PTR(NETIF_F_TSO6_BIT),
+ FEATURE_NAME_PTR(NETIF_F_FSO_BIT),
+ FEATURE_NAME_PTR(NETIF_F_GSO_GRE_BIT),
+ FEATURE_NAME_PTR(NETIF_F_GSO_GRE_CSUM_BIT),
+ FEATURE_NAME_PTR(NETIF_F_GSO_IPXIP4_BIT),
+ FEATURE_NAME_PTR(NETIF_F_GSO_IPXIP6_BIT),
+ FEATURE_NAME_PTR(NETIF_F_GSO_UDP_TUNNEL_BIT),
+ FEATURE_NAME_PTR(NETIF_F_GSO_UDP_TUNNEL_CSUM_BIT),
+ FEATURE_NAME_PTR(NETIF_F_GSO_PARTIAL_BIT),
+ FEATURE_NAME_PTR(NETIF_F_GSO_SCTP_BIT),
+ FEATURE_NAME_PTR(NETIF_F_GSO_ESP_BIT),
+ FEATURE_NAME_PTR(NETIF_F_GSO_UDP_L4_BIT),
+
+ FEATURE_NAME_PTR(NETIF_F_FCOE_CRC_BIT),
+ FEATURE_NAME_PTR(NETIF_F_SCTP_CRC_BIT),
+ FEATURE_NAME_PTR(NETIF_F_FCOE_MTU_BIT),
+ FEATURE_NAME_PTR(NETIF_F_NTUPLE_BIT),
+ FEATURE_NAME_PTR(NETIF_F_RXHASH_BIT),
+ FEATURE_NAME_PTR(NETIF_F_RXCSUM_BIT),
+ FEATURE_NAME_PTR(NETIF_F_NOCACHE_COPY_BIT),
+ FEATURE_NAME_PTR(NETIF_F_LOOPBACK_BIT),
+ FEATURE_NAME_PTR(NETIF_F_RXFCS_BIT),
+ FEATURE_NAME_PTR(NETIF_F_RXALL_BIT),
+ FEATURE_NAME_PTR(NETIF_F_HW_L2FW_DOFFLOAD_BIT),
+ FEATURE_NAME_PTR(NETIF_F_HW_TC_BIT),
+ FEATURE_NAME_PTR(NETIF_F_HW_ESP_BIT),
+ FEATURE_NAME_PTR(NETIF_F_HW_ESP_TX_CSUM_BIT),
+ FEATURE_NAME_PTR(NETIF_F_RX_UDP_TUNNEL_PORT_BIT),
+ FEATURE_NAME_PTR(NETIF_F_HW_TLS_RECORD_BIT),
+ FEATURE_NAME_PTR(NETIF_F_HW_TLS_TX_BIT),
+ FEATURE_NAME_PTR(NETIF_F_HW_TLS_RX_BIT),
+};
+
struct settings_data {
struct ethtool_link_ksettings ksettings;
struct ethtool_link_settings *lsettings;
@@ -12,6 +72,12 @@ struct settings_data {
u32 msglevel;
bool lpm_empty;
u32 req_mask;
+ struct {
+ u32 hw[ETHTOOL_DEV_FEATURE_WORDS];
+ u32 wanted[ETHTOOL_DEV_FEATURE_WORDS];
+ u32 active[ETHTOOL_DEV_FEATURE_WORDS];
+ u32 nochange[ETHTOOL_DEV_FEATURE_WORDS];
+ } features;
};
struct settings_reqinfo {
@@ -51,6 +117,7 @@ static const struct nla_policy settings_policy[ETHA_SETTINGS_MAX + 1] = {
[ETHA_SETTINGS_LINK_MODES] = { .type = NLA_NESTED },
[ETHA_SETTINGS_PEER_MODES] = { .type = NLA_NESTED },
[ETHA_SETTINGS_LINK] = { .type = NLA_FLAG },
+ [ETHA_SETTINGS_FEATURES] = { .type = NLA_NESTED },
};
/* To keep things simple, reserve space for some attributes which may not
@@ -105,6 +172,35 @@ static int settings_size(struct settings_data *data,
}
if (req_mask & ETH_SETTINGS_IM_LINK)
len += nla_total_size(sizeof(u32));
+ if (req_mask & ETH_SETTINGS_IM_FEATURES) {
+ ret = ethnl_bitset32_size(compact, NETDEV_FEATURE_COUNT,
+ data->features.hw, NULL,
+ feature_names);
+ if (ret < 0)
+ return ret;
+ len += ret;
+ ret = ethnl_bitset32_size(compact, NETDEV_FEATURE_COUNT,
+ data->features.wanted,
+ data->features.wanted,
+ feature_names);
+ if (ret < 0)
+ return ret;
+ len += ret;
+ ret = ethnl_bitset32_size(compact, NETDEV_FEATURE_COUNT,
+ data->features.active,
+ data->features.active,
+ feature_names);
+ if (ret < 0)
+ return ret;
+ len += ret;
+ ret = ethnl_bitset32_size(compact, NETDEV_FEATURE_COUNT,
+ data->features.nochange,
+ data->features.nochange,
+ feature_names);
+ if (ret < 0)
+ return ret;
+ len += ret;
+ }
return len;
}
@@ -150,6 +246,24 @@ static int ethnl_get_wol(struct genl_info *info, struct net_device *dev,
return ret;
}
+static void features_to_bitmap(u32 *dest, netdev_features_t src)
+{
+ unsigned int i;
+
+ for (i = 0; i < ETHTOOL_DEV_FEATURE_WORDS; i++)
+ dest[i] = (u32)(src >> (32 * i));
+}
+
+static int ethnl_get_features(struct net_device *dev,
+ struct settings_data *data)
+{
+ features_to_bitmap(data->features.hw, dev->hw_features);
+ features_to_bitmap(data->features.wanted, dev->wanted_features);
+ features_to_bitmap(data->features.active, dev->features);
+ features_to_bitmap(data->features.nochange, NETIF_F_NEVER_CHANGE);
+ return 0;
+}
+
static int parse_settings_req(struct settings_reqinfo *req_info,
struct genl_info *info, struct sk_buff *skb,
const struct nlmsghdr *nlhdr)
@@ -233,6 +347,8 @@ static int prepare_settings(struct settings_data *data,
}
if (req_mask & ETH_SETTINGS_IM_LINK)
data->link = __ethtool_get_link(dev);
+ if (req_mask & ETH_SETTINGS_IM_FEATURES)
+ ethnl_get_features(dev, data);
if (!req_info->have_rtnl)
rtnl_unlock();
@@ -316,6 +432,41 @@ static int fill_settings(struct sk_buff *rskb, struct settings_data *data,
if (nla_put_u32(rskb, ETHA_SETTINGS_LINK, data->link))
return ret;
}
+ if (req_mask & ETH_SETTINGS_IM_FEATURES) {
+ struct nlattr *feat_attr;
+
+ feat_attr = ethnl_nest_start(rskb, ETHA_SETTINGS_FEATURES);
+ if (!feat_attr)
+ return -ENOMEM;
+ ret = ethnl_put_bitset32(rskb, ETHA_FEATURES_HW,
+ compact, NETDEV_FEATURE_COUNT,
+ data->features.hw, NULL,
+ feature_names);
+ if (ret < 0)
+ return ret;
+ ret = ethnl_put_bitset32(rskb, ETHA_FEATURES_WANTED,
+ compact, NETDEV_FEATURE_COUNT,
+ data->features.wanted,
+ data->features.wanted,
+ feature_names);
+ if (ret < 0)
+ return ret;
+ ret = ethnl_put_bitset32(rskb, ETHA_FEATURES_ACTIVE,
+ compact, NETDEV_FEATURE_COUNT,
+ data->features.active,
+ data->features.active,
+ feature_names);
+ if (ret < 0)
+ return ret;
+ ret = ethnl_put_bitset32(rskb, ETHA_FEATURES_NOCHANGE,
+ compact, NETDEV_FEATURE_COUNT,
+ data->features.nochange,
+ data->features.nochange,
+ feature_names);
+ if (ret < 0)
+ return ret;
+ nla_nest_end(rskb, feat_attr);
+ }
return 0;
}
--
2.18.0
SET_SETTINGS notification message has the same format as response to
GET_SETTINGS request and is broadcasted on change of relevant fields. Info
mask can be used to limit the information passed to userspace.
Also trigger the notification on analogous changes performed via the legacy
ioctl interface.
Signed-off-by: Michal Kubecek <[email protected]>
---
Documentation/networking/ethtool-netlink.txt | 3 +-
net/core/dev.c | 3 ++
net/ethtool/ioctl.c | 36 +++++++++++++++--
net/ethtool/netlink.c | 2 +
net/ethtool/settings.c | 41 ++++++++++++++++++++
5 files changed, 80 insertions(+), 5 deletions(-)
diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt
index 82b839c03707..3993652f81a9 100644
--- a/Documentation/networking/ethtool-netlink.txt
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -263,7 +263,8 @@ requests.
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.
+netlink or ioctl ethtool interface; feature notifications are also sent
+whenever netdev_update_features() or netdev_change_features() is called.
Request translation
diff --git a/net/core/dev.c b/net/core/dev.c
index 8a0773a52dd7..66932a9f133e 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -93,6 +93,7 @@
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/ethtool.h>
+#include <linux/ethtool_netlink.h>
#include <linux/notifier.h>
#include <linux/skbuff.h>
#include <linux/bpf.h>
@@ -1318,6 +1319,8 @@ int dev_get_alias(const struct net_device *dev, char *name, size_t len)
void netdev_features_change(struct net_device *dev)
{
call_netdevice_notifiers(NETDEV_FEAT_CHANGE, dev);
+ netdev_ethtool_info_change(dev, NULL, ETHNL_CMD_SET_SETTINGS,
+ ETH_SETTINGS_IM_FEATURES);
}
EXPORT_SYMBOL(netdev_features_change);
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index 39e9aea60516..2754d3f6fd75 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -27,6 +27,7 @@
#include <linux/rtnetlink.h>
#include <linux/sched/signal.h>
#include <linux/net.h>
+#include <linux/ethtool_netlink.h>
#include "common.h"
/*
@@ -125,6 +126,8 @@ static int ethtool_set_features(struct net_device *dev, void __user *useraddr)
dev->wanted_features &= ~valid;
dev->wanted_features |= wanted & valid;
__netdev_update_features(dev);
+ netdev_ethtool_info_change(dev, NULL, ETHNL_CMD_SET_SETTINGS,
+ ETH_SETTINGS_IM_FEATURES);
if ((dev->wanted_features ^ dev->features) & valid)
ret |= ETHTOOL_F_WISH;
@@ -243,6 +246,8 @@ static int ethtool_set_one_feature(struct net_device *dev,
dev->wanted_features &= ~mask;
__netdev_update_features(dev);
+ netdev_ethtool_info_change(dev, NULL, ETHNL_CMD_SET_SETTINGS,
+ ETH_SETTINGS_IM_FEATURES);
return 0;
}
@@ -298,6 +303,8 @@ static int __ethtool_set_flags(struct net_device *dev, u32 data)
(dev->wanted_features & ~changed) | (features & changed);
__netdev_update_features(dev);
+ netdev_ethtool_info_change(dev, NULL, ETHNL_CMD_SET_SETTINGS,
+ ETH_SETTINGS_IM_FEATURES);
return 0;
}
@@ -638,6 +645,7 @@ static int ethtool_get_settings(struct net_device *dev, void __user *useraddr)
static int ethtool_set_settings(struct net_device *dev, void __user *useraddr)
{
struct ethtool_cmd cmd;
+ int ret;
ASSERT_RTNL();
@@ -655,8 +663,14 @@ static int ethtool_set_settings(struct net_device *dev, void __user *useraddr)
link_ksettings.base.cmd = ETHTOOL_SLINKSETTINGS;
link_ksettings.base.link_mode_masks_nwords
= __ETHTOOL_LINK_MODE_MASK_NU32;
- return dev->ethtool_ops->set_link_ksettings(dev,
- &link_ksettings);
+ ret = dev->ethtool_ops->set_link_ksettings(dev,
+ &link_ksettings);
+ if (ret >= 0)
+ netdev_ethtool_info_change(dev, NULL,
+ ETHNL_CMD_SET_SETTINGS,
+ ETH_SETTINGS_IM_LINKINFO |
+ ETH_SETTINGS_IM_LINKMODES);
+ return ret;
}
/* legacy %ethtool_cmd API */
@@ -668,7 +682,12 @@ static int ethtool_set_settings(struct net_device *dev, void __user *useraddr)
if (!dev->ethtool_ops->set_settings)
return -EOPNOTSUPP;
- return dev->ethtool_ops->set_settings(dev, &cmd);
+ ret = dev->ethtool_ops->set_settings(dev, &cmd);
+ if (ret >= 0)
+ netdev_ethtool_info_change(dev, NULL, ETHNL_CMD_SET_SETTINGS,
+ ETH_SETTINGS_IM_LINKINFO |
+ ETH_SETTINGS_IM_LINKMODES);
+ return ret;
}
static noinline_for_stack int ethtool_get_drvinfo(struct net_device *dev,
@@ -1279,6 +1298,7 @@ static int ethtool_get_wol(struct net_device *dev, char __user *useraddr)
static int ethtool_set_wol(struct net_device *dev, char __user *useraddr)
{
struct ethtool_wolinfo wol;
+ int ret;
if (!dev->ethtool_ops->set_wol)
return -EOPNOTSUPP;
@@ -1286,7 +1306,11 @@ static int ethtool_set_wol(struct net_device *dev, char __user *useraddr)
if (copy_from_user(&wol, useraddr, sizeof(wol)))
return -EFAULT;
- return dev->ethtool_ops->set_wol(dev, &wol);
+ ret = dev->ethtool_ops->set_wol(dev, &wol);
+ if (ret >= 0)
+ netdev_ethtool_info_change(dev, NULL, ETHNL_CMD_SET_SETTINGS,
+ ETH_SETTINGS_IM_WOLINFO);
+ return ret;
}
static int ethtool_get_eee(struct net_device *dev, char __user *useraddr)
@@ -2478,6 +2502,10 @@ int dev_ethtool(struct net *net, struct ifreq *ifr)
case ETHTOOL_SMSGLVL:
rc = ethtool_set_value_void(dev, useraddr,
dev->ethtool_ops->set_msglevel);
+ if (rc >= 0)
+ netdev_ethtool_info_change(dev, NULL,
+ ETHNL_CMD_SET_SETTINGS,
+ ETH_SETTINGS_IM_MSGLEVEL);
break;
case ETHTOOL_GEEE:
rc = ethtool_get_eee(dev, useraddr);
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 6c14185a6466..0e2158092a44 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -629,7 +629,9 @@ int ethnl_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
typedef void (*ethnl_notify_handler_t)(struct netdev_notifier_ethtool_info *);
+void ethnl_settings_notify(struct netdev_notifier_ethtool_info *);
ethnl_notify_handler_t ethnl_notify_handlers[] = {
+ [ETHNL_CMD_SET_SETTINGS] = ethnl_settings_notify,
};
static void __ethnl_notify(struct netdev_notifier_ethtool_info *info)
diff --git a/net/ethtool/settings.c b/net/ethtool/settings.c
index fdca418a6d8e..739bd006c924 100644
--- a/net/ethtool/settings.c
+++ b/net/ethtool/settings.c
@@ -565,3 +565,44 @@ int ethnl_settings_done(struct netlink_callback *cb)
return 0;
}
+
+void ethnl_settings_notify(struct netdev_notifier_ethtool_info *info)
+{
+ struct settings_reqinfo req_info = {
+ .dev = info->info.dev,
+ .req_mask = info->ethtool_info.req_mask,
+ .compact = true,
+ .is_privileged = false,
+ .have_rtnl = true,
+ };
+ struct settings_data data;
+ struct sk_buff *skb;
+ int reply_len;
+ void *ehdr;
+ int ret;
+
+ ret = prepare_settings(&data, &req_info, NULL, req_info.dev);
+ if (ret < 0)
+ return;
+ reply_len = settings_size(&data, &req_info);
+ if (ret < 0)
+ return;
+ skb = genlmsg_new(reply_len, GFP_KERNEL);
+ if (!skb)
+ return;
+ ehdr = genlmsg_put(skb, 0, ++ethnl_bcast_seq, ðtool_genl_family, 0,
+ ETHNL_CMD_SET_SETTINGS);
+ ret = ethnl_fill_dev(skb, req_info.dev, ETHA_SETTINGS_DEV);
+ if (ret < 0)
+ goto err_skb;
+ ret = fill_settings(skb, &data, &req_info);
+ if (ret < 0)
+ goto err_skb;
+ genlmsg_end(skb, ehdr);
+
+ genlmsg_multicast(ðtool_genl_family, skb, 0, ETHNL_MCGRP_MONITOR,
+ GFP_KERNEL);
+ return;
+err_skb:
+ nlmsg_free(skb);
+}
--
2.18.0
SET_PARAMS notification message has the same format as response to
GET_PARAMS request and is broadcasted on change of relevant fields. Info
mask can be used to limit the information passed to userspace.
Also trigger the notification on analogous changes performed via the legacy
ioctl interface.
Signed-off-by: Michal Kubecek <[email protected]>
---
net/ethtool/ioctl.c | 42 ++++++++++++++++++++++++++++++++++++------
net/ethtool/netlink.c | 3 +++
net/ethtool/params.c | 40 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 79 insertions(+), 6 deletions(-)
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index 2754d3f6fd75..1dc1459dff6e 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -1337,6 +1337,7 @@ static int ethtool_get_eee(struct net_device *dev, char __user *useraddr)
static int ethtool_set_eee(struct net_device *dev, char __user *useraddr)
{
struct ethtool_eee edata;
+ int ret;
if (!dev->ethtool_ops->set_eee)
return -EOPNOTSUPP;
@@ -1344,7 +1345,11 @@ static int ethtool_set_eee(struct net_device *dev, char __user *useraddr)
if (copy_from_user(&edata, useraddr, sizeof(edata)))
return -EFAULT;
- return dev->ethtool_ops->set_eee(dev, &edata);
+ ret = dev->ethtool_ops->set_eee(dev, &edata);
+ if (ret >= 0)
+ netdev_ethtool_info_change(dev, NULL, ETHNL_CMD_SET_PARAMS,
+ ETH_PARAMS_IM_EEE);
+ return ret;
}
static int ethtool_nway_reset(struct net_device *dev)
@@ -1499,6 +1504,7 @@ static noinline_for_stack int ethtool_set_coalesce(struct net_device *dev,
void __user *useraddr)
{
struct ethtool_coalesce coalesce;
+ int ret;
if (!dev->ethtool_ops->set_coalesce)
return -EOPNOTSUPP;
@@ -1506,7 +1512,11 @@ static noinline_for_stack int ethtool_set_coalesce(struct net_device *dev,
if (copy_from_user(&coalesce, useraddr, sizeof(coalesce)))
return -EFAULT;
- return dev->ethtool_ops->set_coalesce(dev, &coalesce);
+ ret = dev->ethtool_ops->set_coalesce(dev, &coalesce);
+ if (ret >= 0)
+ netdev_ethtool_info_change(dev, NULL, ETHNL_CMD_SET_PARAMS,
+ ETH_PARAMS_IM_COALESCE);
+ return ret;
}
static int ethtool_get_ringparam(struct net_device *dev, void __user *useraddr)
@@ -1526,6 +1536,7 @@ static int ethtool_get_ringparam(struct net_device *dev, void __user *useraddr)
static int ethtool_set_ringparam(struct net_device *dev, void __user *useraddr)
{
struct ethtool_ringparam ringparam, max = { .cmd = ETHTOOL_GRINGPARAM };
+ int ret;
if (!dev->ethtool_ops->set_ringparam || !dev->ethtool_ops->get_ringparam)
return -EOPNOTSUPP;
@@ -1542,7 +1553,11 @@ static int ethtool_set_ringparam(struct net_device *dev, void __user *useraddr)
ringparam.tx_pending > max.tx_max_pending)
return -EINVAL;
- return dev->ethtool_ops->set_ringparam(dev, &ringparam);
+ ret = dev->ethtool_ops->set_ringparam(dev, &ringparam);
+ if (ret >= 0)
+ netdev_ethtool_info_change(dev, NULL, ETHNL_CMD_SET_PARAMS,
+ ETH_PARAMS_IM_RING);
+ return ret;
}
static noinline_for_stack int ethtool_get_channels(struct net_device *dev,
@@ -1565,6 +1580,7 @@ static noinline_for_stack int ethtool_set_channels(struct net_device *dev,
{
struct ethtool_channels channels, max = { .cmd = ETHTOOL_GCHANNELS };
u32 max_rx_in_use = 0;
+ int ret;
if (!dev->ethtool_ops->set_channels || !dev->ethtool_ops->get_channels)
return -EOPNOTSUPP;
@@ -1588,7 +1604,11 @@ static noinline_for_stack int ethtool_set_channels(struct net_device *dev,
(channels.combined_count + channels.rx_count) <= max_rx_in_use)
return -EINVAL;
- return dev->ethtool_ops->set_channels(dev, &channels);
+ ret = dev->ethtool_ops->set_channels(dev, &channels);
+ if (ret >= 0)
+ netdev_ethtool_info_change(dev, NULL, ETHNL_CMD_SET_PARAMS,
+ ETH_PARAMS_IM_CHANNELS);
+ return ret;
}
static int ethtool_get_pauseparam(struct net_device *dev, void __user *useraddr)
@@ -1608,6 +1628,7 @@ static int ethtool_get_pauseparam(struct net_device *dev, void __user *useraddr)
static int ethtool_set_pauseparam(struct net_device *dev, void __user *useraddr)
{
struct ethtool_pauseparam pauseparam;
+ int ret;
if (!dev->ethtool_ops->set_pauseparam)
return -EOPNOTSUPP;
@@ -1615,7 +1636,11 @@ static int ethtool_set_pauseparam(struct net_device *dev, void __user *useraddr)
if (copy_from_user(&pauseparam, useraddr, sizeof(pauseparam)))
return -EFAULT;
- return dev->ethtool_ops->set_pauseparam(dev, &pauseparam);
+ ret = dev->ethtool_ops->set_pauseparam(dev, &pauseparam);
+ if (ret >= 0)
+ netdev_ethtool_info_change(dev, NULL, ETHNL_CMD_SET_PARAMS,
+ ETH_PARAMS_IM_PAUSE);
+ return ret;
}
static int ethtool_self_test(struct net_device *dev, char __user *useraddr)
@@ -2394,6 +2419,7 @@ static int ethtool_get_fecparam(struct net_device *dev, void __user *useraddr)
static int ethtool_set_fecparam(struct net_device *dev, void __user *useraddr)
{
struct ethtool_fecparam fecparam;
+ int ret;
if (!dev->ethtool_ops->set_fecparam)
return -EOPNOTSUPP;
@@ -2401,7 +2427,11 @@ static int ethtool_set_fecparam(struct net_device *dev, void __user *useraddr)
if (copy_from_user(&fecparam, useraddr, sizeof(fecparam)))
return -EFAULT;
- return dev->ethtool_ops->set_fecparam(dev, &fecparam);
+ ret = dev->ethtool_ops->set_fecparam(dev, &fecparam);
+ if (ret >= 0)
+ netdev_ethtool_info_change(dev, NULL, ETHNL_CMD_SET_PARAMS,
+ ETH_PARAMS_IM_FEC);
+ return ret;
}
/* The main entry point in this file. Called from net/core/dev_ioctl.c */
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 721101ed2ab6..b24a0e045ec0 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -630,8 +630,11 @@ int ethnl_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
typedef void (*ethnl_notify_handler_t)(struct netdev_notifier_ethtool_info *);
void ethnl_settings_notify(struct netdev_notifier_ethtool_info *);
+void ethnl_params_notify(struct netdev_notifier_ethtool_info *);
+
ethnl_notify_handler_t ethnl_notify_handlers[] = {
[ETHNL_CMD_SET_SETTINGS] = ethnl_settings_notify,
+ [ETHNL_CMD_SET_PARAMS] = ethnl_params_notify,
};
static void __ethnl_notify(struct netdev_notifier_ethtool_info *info)
diff --git a/net/ethtool/params.c b/net/ethtool/params.c
index 07d4c527abf2..773e13fc1ae6 100644
--- a/net/ethtool/params.c
+++ b/net/ethtool/params.c
@@ -537,3 +537,43 @@ int ethnl_params_done(struct netlink_callback *cb)
return 0;
}
+
+void ethnl_params_notify(struct netdev_notifier_ethtool_info *info)
+{
+ struct params_reqinfo req_info = {
+ .dev = info->info.dev,
+ .req_mask = info->ethtool_info.req_mask,
+ .compact = true,
+ .have_rtnl = true,
+ };
+ struct params_data data;
+ struct sk_buff *skb;
+ int reply_len;
+ void *ehdr;
+ int ret;
+
+ ret = prepare_params(&data, &req_info, NULL, req_info.dev);
+ if (ret < 0)
+ return;
+ reply_len = params_size(&data, &req_info);
+ if (ret < 0)
+ return;
+ skb = genlmsg_new(reply_len, GFP_KERNEL);
+ if (!skb)
+ return;
+ ehdr = genlmsg_put(skb, 0, ++ethnl_bcast_seq, ðtool_genl_family, 0,
+ ETHNL_CMD_SET_PARAMS);
+ ret = ethnl_fill_dev(skb, req_info.dev, ETHA_PARAMS_DEV);
+ if (ret < 0)
+ goto err_skb;
+ ret = fill_params(skb, &data, &req_info);
+ if (ret < 0)
+ goto err_skb;
+ genlmsg_end(skb, ehdr);
+
+ genlmsg_multicast(ðtool_genl_family, skb, 0, ETHNL_MCGRP_MONITOR,
+ GFP_KERNEL);
+ return;
+err_skb:
+ nlmsg_free(skb);
+}
--
2.18.0
Using ETHNL_SETTINGS_FEATURES attribute, userspace can modify device
features. Actual change is subject to netdev_change_features() sanity
checks so that it can differ from what was requested. Unlike with most
other requests, kernel can reply (if ETHA_FEATURES_WANT_DIFF flag is used)
with a message in the same format but with different semantics: information
about difference between user request and actual result and difference
between old and new state of dev->features.
Signed-off-by: Michal Kubecek <[email protected]>
---
Documentation/networking/ethtool-netlink.txt | 30 +++--
include/uapi/linux/ethtool_netlink.h | 1 +
net/ethtool/settings.c | 127 +++++++++++++++++++
3 files changed, 150 insertions(+), 8 deletions(-)
diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt
index c7fe4f518972..307d8c6c6c85 100644
--- a/Documentation/networking/ethtool-netlink.txt
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -285,6 +285,9 @@ to be passed with SET_SETTINGS request:
ETHA_SETTINGS_SOPASS (binary) SecureOn(tm) password
ETHA_SETTINGS_MSGLVL (bitfield32) debug level
ETHA_SETTINGS_LINK_MODES (bitset) device link modes
+ ETHA_SETTINGS_FEATURES (nested) device features
+ ETHA_FEATURES_WANTED (bitset) wanted features
+ ETHA_FEATURES_WANT_DIFF (flag) actual diff
For both bitfield32 types, value and selector work the usual way, i.e. bits
set in selector are set to corresponding bits from value and the rest is
@@ -299,6 +302,17 @@ autoselection is done on ethtool side with ioctl interface, netlink interface
is supposed to allow requesting changes without knowing what exactly kernel
supports.
+When changing device features, only ETHA_FEATURES_WANTED is passed. As usual,
+mask defines which bits are to be set and value their values. If the request
+has ETHA_FEATURES_WANT_DIFF flag set, reply will contain a message in the same
+format as response to GET request, except only two bitsets are provided.
+ETHA_FEATURES_WANTED shows difference between requested features and actual
+result (dev->features after the operation); mask shows bits which differ and
+value their values from the original request (new values are negated). Value
+shows changes between old dev->features (before the operation) and new (after
+the operation); mask shows bits which have been changed and value their new
+values.
+
Request translation
-------------------
@@ -328,30 +342,30 @@ ETHTOOL_SRINGPARAM n/a
ETHTOOL_GPAUSEPARAM n/a
ETHTOOL_SPAUSEPARAM n/a
ETHTOOL_GRXCSUM ETHNL_CMD_GET_SETTINGS
-ETHTOOL_SRXCSUM n/a
+ETHTOOL_SRXCSUM ETHNL_CMD_SET_SETTINGS
ETHTOOL_GTXCSUM ETHNL_CMD_GET_SETTINGS
-ETHTOOL_STXCSUM n/a
+ETHTOOL_STXCSUM ETHNL_CMD_SET_SETTINGS
ETHTOOL_GSG ETHNL_CMD_GET_SETTINGS
-ETHTOOL_SSG n/a
+ETHTOOL_SSG ETHNL_CMD_SET_SETTINGS
ETHTOOL_TEST n/a
ETHTOOL_GSTRINGS ETHNL_CMD_GET_STRSET
ETHTOOL_PHYS_ID n/a
ETHTOOL_GSTATS n/a
ETHTOOL_GTSO ETHNL_CMD_GET_SETTINGS
-ETHTOOL_STSO n/a
+ETHTOOL_STSO ETHNL_CMD_SET_SETTINGS
ETHTOOL_GPERMADDR n/a
ETHTOOL_GUFO ETHNL_CMD_GET_SETTINGS
-ETHTOOL_SUFO n/a
+ETHTOOL_SUFO ETHNL_CMD_SET_SETTINGS
ETHTOOL_GGSO ETHNL_CMD_GET_SETTINGS
-ETHTOOL_SGSO n/a
+ETHTOOL_SGSO ETHNL_CMD_SET_SETTINGS
ETHTOOL_GFLAGS ETHNL_CMD_GET_SETTINGS
-ETHTOOL_SFLAGS n/a
+ETHTOOL_SFLAGS ETHNL_CMD_SET_SETTINGS
ETHTOOL_GPFLAGS n/a
ETHTOOL_SPFLAGS n/a
ETHTOOL_GRXFH n/a
ETHTOOL_SRXFH n/a
ETHTOOL_GGRO ETHNL_CMD_GET_SETTINGS
-ETHTOOL_SGRO n/a
+ETHTOOL_SGRO ETHNL_CMD_SET_SETTINGS
ETHTOOL_GRXRINGS n/a
ETHTOOL_GRXCLSRLCNT n/a
ETHTOOL_GRXCLSRULE n/a
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index 06c78b281275..8dfcb9ef4009 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -191,6 +191,7 @@ enum {
ETHA_FEATURES_WANTED, /* bitset */
ETHA_FEATURES_ACTIVE, /* bitset */
ETHA_FEATURES_NOCHANGE, /* bitset */
+ ETHA_FEATURES_WANT_DIFF, /* flag */
__ETHA_FEATURES_MAX,
ETHA_FEATURES_MAX = (__ETHA_FEATURES_MAX - 1)
diff --git a/net/ethtool/settings.c b/net/ethtool/settings.c
index 678199e621a2..475582b7950c 100644
--- a/net/ethtool/settings.c
+++ b/net/ethtool/settings.c
@@ -1016,6 +1016,122 @@ static int ethnl_update_lsettings(struct genl_info *info, struct nlattr **tb,
return 0;
}
+static const struct nla_policy features_policy[ETHA_FEATURES_MAX + 1] = {
+ [ETHA_FEATURES_UNSPEC] = { .type = NLA_UNSPEC },
+ [ETHA_FEATURES_HW] = { .type = NLA_NESTED },
+ [ETHA_FEATURES_WANTED] = { .type = NLA_NESTED },
+ [ETHA_FEATURES_ACTIVE] = { .type = NLA_NESTED },
+ [ETHA_FEATURES_NOCHANGE] = { .type = NLA_NESTED },
+ [ETHA_FEATURES_WANT_DIFF] = { .type = NLA_FLAG },
+};
+
+static void bitmap_from_features(unsigned long *bitmap, netdev_features_t val)
+{
+ const unsigned int words = BITS_TO_LONGS(NETDEV_FEATURE_COUNT);
+ unsigned int i;
+
+ bitmap_zero(bitmap, NETDEV_FEATURE_COUNT);
+ for (i = 0; i < words; i++)
+ bitmap[i] = (unsigned long)(val >> (i * BITS_PER_LONG));
+}
+
+static netdev_features_t features_from_bitmap(unsigned long *bitmap)
+{
+ const unsigned int words = BITS_TO_LONGS(NETDEV_FEATURE_COUNT);
+ netdev_features_t ret = 0;
+ unsigned int i;
+
+ for (i = 0; i < words; i++)
+ ret |= (netdev_features_t)(bitmap[i]) << (i * BITS_PER_LONG);
+ return ret;
+}
+
+int update_features(struct genl_info *info, struct net_device *dev,
+ const struct nlattr *nest, bool compact)
+{
+ struct nlattr *tb[ETHA_FEATURES_MAX + 1];
+ DECLARE_BITMAP(old_active, NETDEV_FEATURE_COUNT);
+ DECLARE_BITMAP(req_wanted, NETDEV_FEATURE_COUNT);
+ DECLARE_BITMAP(req_mask, NETDEV_FEATURE_COUNT);
+ DECLARE_BITMAP(new_active, NETDEV_FEATURE_COUNT);
+ DECLARE_BITMAP(wanted_diff_mask, NETDEV_FEATURE_COUNT);
+ DECLARE_BITMAP(active_diff_mask, NETDEV_FEATURE_COUNT);
+ struct nlattr *feat_attr;
+ unsigned int reply_len;
+ struct sk_buff *rskb;
+ bool mod = false;
+ void *ehdr;
+ int ret;
+
+ ret = nla_parse_nested(tb, ETHA_FEATURES_MAX, nest, features_policy,
+ info->extack);
+ if (ret < 0)
+ return ret;
+ if (tb[ETHA_FEATURES_HW] || !tb[ETHA_FEATURES_WANTED] ||
+ tb[ETHA_FEATURES_ACTIVE] || tb[ETHA_FEATURES_NOCHANGE])
+ return -EINVAL;
+
+ bitmap_from_features(old_active, dev->features);
+ bitmap_copy(req_wanted, old_active, NETDEV_FEATURE_COUNT);
+ bitmap_zero(req_mask, NETDEV_FEATURE_COUNT);
+ mod = ethnl_update_bitset(req_wanted, req_mask, NETDEV_FEATURE_COUNT,
+ tb[ETHA_FEATURES_WANTED], &ret, feature_names,
+ info);
+ if (ret < 0 || !mod)
+ return ret;
+
+ dev->wanted_features = features_from_bitmap(req_wanted);
+ netdev_update_features(dev);
+ if (!tb[ETHA_FEATURES_WANT_DIFF])
+ return 0;
+ bitmap_from_features(new_active, dev->features);
+ bitmap_xor(wanted_diff_mask, req_wanted, new_active,
+ NETDEV_FEATURE_COUNT);
+ bitmap_xor(active_diff_mask, old_active, new_active,
+ NETDEV_FEATURE_COUNT);
+ bitmap_and(wanted_diff_mask, wanted_diff_mask, req_mask,
+ NETDEV_FEATURE_COUNT);
+ bitmap_and(req_wanted, req_wanted, wanted_diff_mask,
+ NETDEV_FEATURE_COUNT);
+ bitmap_and(new_active, new_active, active_diff_mask,
+ NETDEV_FEATURE_COUNT);
+
+ reply_len = ethnl_bitset_size(compact, NETDEV_FEATURE_COUNT, req_wanted,
+ wanted_diff_mask, feature_names) +
+ ethnl_bitset_size(compact, NETDEV_FEATURE_COUNT, new_active,
+ active_diff_mask, feature_names);
+ reply_len = nla_total_size(reply_len);
+ rskb = ethnl_reply_init(reply_len, dev, ETHNL_CMD_SET_SETTINGS,
+ ETHA_SETTINGS_DEV, info, &ehdr);
+ if (!rskb)
+ goto err;
+ ret = -EMSGSIZE;
+
+ feat_attr = ethnl_nest_start(rskb, ETHA_SETTINGS_FEATURES);
+ if (!feat_attr)
+ goto err;
+ ret = ethnl_put_bitset(rskb, ETHA_FEATURES_WANTED, compact,
+ NETDEV_FEATURE_COUNT, req_wanted,
+ wanted_diff_mask, feature_names);
+ if (ret < 0)
+ goto err;
+ ret = ethnl_put_bitset(rskb, ETHA_FEATURES_ACTIVE, compact,
+ NETDEV_FEATURE_COUNT, new_active,
+ active_diff_mask, feature_names);
+ if (ret < 0)
+ goto err;
+ nla_nest_end(rskb, feat_attr);
+
+ genlmsg_end(rskb, ehdr);
+ return genlmsg_reply(rskb, info);
+err:
+ WARN_ONCE(ret == -EMSGSIZE,
+ "calculated message payload length (%d) not sufficient\n",
+ reply_len);
+ nlmsg_free(rskb);
+ return ret;
+}
+
int ethnl_set_settings(struct sk_buff *skb, struct genl_info *info)
{
struct nlattr *tb[ETHA_SETTINGS_MAX + 1];
@@ -1092,6 +1208,17 @@ int ethnl_set_settings(struct sk_buff *skb, struct genl_info *info)
req_mask |= ETH_SETTINGS_IM_MSGLEVEL;
}
}
+ if (tb[ETHA_SETTINGS_FEATURES]) {
+ bool compact = tb[ETHA_SETTINGS_COMPACT];
+
+ ret = update_features(info, dev, tb[ETHA_SETTINGS_FEATURES],
+ compact);
+ if (ret > 0) {
+ req_mask |= ETH_SETTINGS_IM_FEATURES;
+ ret = 0;
+ }
+
+ }
ret = 0;
out_unlock:
--
2.18.0
Requests the same information as ETHTOOL_GDRVINFO command in ioct
interface. This is read-only so that corresponding SET_DRVINFO exists but
is only used in kernel replies.
Signed-off-by: Michal Kubecek <[email protected]>
---
Documentation/networking/ethtool-netlink.txt | 38 +++++-
include/uapi/linux/ethtool_netlink.h | 22 ++++
net/ethtool/Makefile | 4 +-
net/ethtool/common.c | 43 ++++++
net/ethtool/common.h | 3 +
net/ethtool/drvinfo.c | 131 +++++++++++++++++++
net/ethtool/ioctl.c | 42 +-----
net/ethtool/netlink.c | 8 ++
8 files changed, 252 insertions(+), 39 deletions(-)
create mode 100644 net/ethtool/drvinfo.c
diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt
index 8b43f41a8140..1e3d5ffc97ab 100644
--- a/Documentation/networking/ethtool-netlink.txt
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -121,6 +121,8 @@ List of message types
ETHNL_CMD_EVENT notification only
ETHNL_CMD_GET_STRSET
ETHNL_CMD_SET_STRSET response only
+ ETHNL_CMD_GET_DRVINFO
+ ETHNL_CMD_SET_DRVINFO response only
All constants use ETHNL_CMD_ prefix, usually followed by "GET", "SET" or "ACT"
to indicate the type.
@@ -156,6 +158,40 @@ and also multiple events of the same type (e.g. two or more newly registered
devices).
+GET_DRVINFO
+-----------
+
+GET_DRVINFO request corresponds to ETHTOOL_GDRVINFO ioctl command and provides
+basic driver information.
+
+Request contents:
+
+ ETHA_DRVINFO_DEV (nested) device identification
+
+Kernel response contents:
+
+ ETHA_DRVINFO_DEV (nested) device identification
+ 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
+(ETHNL_CMD_SET_DRVINFO messages are sent only by kernel in response to
+GET_DRVINFO requests).
+
+GET_DRVINFO requests allow dumps.
+
+
Request translation
-------------------
@@ -167,7 +203,7 @@ ioctl command netlink command
---------------------------------------------------------------------
ETHTOOL_GSET n/a
ETHTOOL_SSET n/a
-ETHTOOL_GDRVINFO n/a
+ETHTOOL_GDRVINFO ETHNL_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 5177c1940c2b..df4de61fac48 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_DRVINFO,
+ ETHNL_CMD_SET_DRVINFO, /* only for reply */
__ETHNL_CMD_MAX,
ETHNL_CMD_MAX = (__ETHNL_CMD_MAX - 1)
@@ -124,6 +126,26 @@ enum {
ETHA_STRSET_MAX = (__ETHA_STRSET_MAX - 1)
};
+/* GET_DRVINFO / SET_DRVINFO */
+
+enum {
+ ETHA_DRVINFO_UNSPEC,
+ ETHA_DRVINFO_DEV, /* nest - ETHA_DEV_* */
+ 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/ethtool/Makefile b/net/ethtool/Makefile
index ba260d5b53b2..2e840ae0ba1e 100644
--- a/net/ethtool/Makefile
+++ b/net/ethtool/Makefile
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
-obj-y += ioctl.o
+obj-y += ioctl.o common.o
obj-$(CONFIG_ETHTOOL_NETLINK) += ethtool_nl.o
-ethtool_nl-y := netlink.o strset.o
+ethtool_nl-y := netlink.o strset.o drvinfo.o
diff --git a/net/ethtool/common.c b/net/ethtool/common.c
index 208259c51b73..1dc4a6515996 100644
--- a/net/ethtool/common.c
+++ b/net/ethtool/common.c
@@ -1,5 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#include <linux/rtnetlink.h>
#include "common.h"
const char netdev_features_strings[NETDEV_FEATURE_COUNT][ETH_GSTRING_LEN] = {
@@ -85,3 +86,45 @@ phy_tunable_strings[__ETHTOOL_PHY_TUNABLE_COUNT][ETH_GSTRING_LEN] = {
[ETHTOOL_PHY_DOWNSHIFT] = "phy-downshift",
};
EXPORT_SYMBOL(phy_tunable_strings);
+
+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/ethtool/common.h b/net/ethtool/common.h
index 45c6492e4aee..0f768c1be527 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 netdev_features_strings[NETDEV_FEATURE_COUNT][ETH_GSTRING_LEN];
@@ -10,4 +11,6 @@ extern const char rss_hash_func_strings[ETH_RSS_HASH_FUNCS_COUNT][ETH_GSTRING_LE
extern const char 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/drvinfo.c b/net/ethtool/drvinfo.c
new file mode 100644
index 000000000000..2bdaf6d7f28c
--- /dev/null
+++ b/net/ethtool/drvinfo.c
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#include "netlink.h"
+#include "common.h"
+
+static const struct nla_policy get_drvinfo_policy[ETHA_DRVINFO_MAX + 1] = {
+ [ETHA_DRVINFO_DEV] = { .type = NLA_NESTED },
+};
+
+static int prepare_drvinfo(struct ethtool_drvinfo *data, struct genl_info *info,
+ struct net_device *dev)
+{
+ int ret;
+
+ memset(data, '\0', sizeof(*data));
+ rtnl_lock();
+ ret = __ethtool_get_drvinfo(dev, data);
+ rtnl_unlock();
+ if (ret < 0) {
+ ETHNL_SET_ERRMSG(info, "failed to retrieve driver info");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int 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 fill_drvinfo(struct sk_buff *skb, struct net_device *dev,
+ struct ethtool_drvinfo *drvinfo)
+{
+ int ret;
+
+ ret = -EMSGSIZE;
+ if (ethnl_put_str_ifne(skb, ETHA_DRVINFO_DRIVER, drvinfo->driver) ||
+ ethnl_put_str_ifne(skb, ETHA_DRVINFO_VERSION, drvinfo->version) ||
+ 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) ||
+ nla_put_u32(skb, ETHA_DRVINFO_N_PRIV_FLAGS,
+ drvinfo->n_priv_flags) ||
+ nla_put_u32(skb, ETHA_DRVINFO_N_STATS, drvinfo->n_stats) ||
+ nla_put_u32(skb, ETHA_DRVINFO_TESTINFO_LEN,
+ drvinfo->testinfo_len) ||
+ nla_put_u32(skb, ETHA_DRVINFO_EEDUMP_LEN, drvinfo->eedump_len) ||
+ nla_put_u32(skb, ETHA_DRVINFO_REGDUMP_LEN, drvinfo->regdump_len))
+ return ret;
+
+ return 0;
+}
+
+int ethnl_get_drvinfo(struct sk_buff *skb, struct genl_info *info)
+{
+ struct nlattr *tb[ETHA_DRVINFO_MAX + 1];
+ struct ethtool_drvinfo drvinfo;
+ struct net_device *dev;
+ struct sk_buff *rskb;
+ int reply_len;
+ void *ehdr;
+ int ret;
+
+ ret = genlmsg_parse(info->nlhdr, ðtool_genl_family, tb,
+ ETHA_DRVINFO_MAX, get_drvinfo_policy, info->extack);
+ if (ret < 0)
+ return ret;
+ dev = ethnl_dev_get(info, tb[ETHA_DRVINFO_DEV]);
+ if (IS_ERR(dev))
+ return PTR_ERR(dev);
+
+ ret = prepare_drvinfo(&drvinfo, info, dev);
+ if (ret < 0)
+ goto err_dev;
+ reply_len = drvinfo_size(&drvinfo);
+ rskb = ethnl_reply_init(reply_len, dev, ETHNL_CMD_SET_DRVINFO,
+ ETHA_DRVINFO_DEV, info, &ehdr);
+ if (!rskb)
+ return -ENOMEM;
+ ret = fill_drvinfo(rskb, dev, &drvinfo);
+ if (ret < 0)
+ goto err;
+
+ genlmsg_end(rskb, ehdr);
+ dev_put(dev);
+ return genlmsg_reply(rskb, info);
+
+err:
+ WARN_ONCE(ret == -EMSGSIZE,
+ "calculated message payload length (%d) not sufficient\n",
+ reply_len);
+ nlmsg_free(rskb);
+err_dev:
+ dev_put(dev);
+ return ret;
+}
+
+static int drvinfo_dump(struct sk_buff *skb, struct netlink_callback *cb,
+ struct net_device *dev)
+{
+ struct ethtool_drvinfo drvinfo;
+ int ret;
+
+ ret = prepare_drvinfo(&drvinfo, NULL, dev);
+ if (ret < 0)
+ return ret;
+ ret = ethnl_fill_dev(skb, dev, ETHA_DRVINFO_DEV);
+ ret = fill_drvinfo(skb, dev, &drvinfo);
+ return ret;
+}
+
+int ethnl_drvinfo_start(struct netlink_callback *cb)
+{
+ cb->args[0] = (long)drvinfo_dump;
+ cb->args[1] = ETHNL_CMD_SET_DRVINFO;
+
+ return 0;
+}
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index a91b597073f8..7b5831d35bca 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -27,6 +27,7 @@
#include <linux/rtnetlink.h>
#include <linux/sched/signal.h>
#include <linux/net.h>
+#include "common.h"
/*
* Some useful ethtool_ops methods that're device independent.
@@ -768,45 +769,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/ethtool/netlink.c b/net/ethtool/netlink.c
index 237a2cb40be4..305baa02ff70 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -670,8 +670,10 @@ static struct notifier_block ethnl_netdev_notifier = {
/* genetlink setup */
int ethnl_get_strset(struct sk_buff *skb, struct genl_info *info);
+int ethnl_get_drvinfo(struct sk_buff *skb, struct genl_info *info);
int ethnl_strset_start(struct netlink_callback *cb);
+int ethnl_drvinfo_start(struct netlink_callback *cb);
int ethnl_strset_done(struct netlink_callback *cb);
@@ -683,6 +685,12 @@ static const struct genl_ops ethtool_genl_ops[] = {
.dumpit = ethnl_dumpit,
.done = ethnl_strset_done,
},
+ {
+ .cmd = ETHNL_CMD_GET_DRVINFO,
+ .doit = ethnl_get_drvinfo,
+ .start = ethnl_drvinfo_start,
+ .dumpit = ethnl_dumpit,
+ },
};
static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
--
2.18.0
Add infrastructure for ethtool netlink notifications. There is only one
multicast group, "monitor" which userspace can use to get notifications.
Notifications are supposed to be broadcasted on every configuration change,
whether it is done using the netlink interface or legacy ioctl one.
To trigger a notification, netlink code calls ethtool_notify(), external
code (ioctl interface) uses NETDEV_ETHTOOL event, preferrably by the means
of netdev_ethtool_info_change() helper. For both, the caller must hold
RTNL (and, obviously, allow sleeping).
Signed-off-by: Michal Kubecek <[email protected]>
---
include/linux/ethtool_netlink.h | 5 ++
include/linux/netdevice.h | 23 ++++++++
include/uapi/linux/ethtool_netlink.h | 2 +
net/core/dev.c | 24 +++++++-
net/ethtool/netlink.c | 82 +++++++++++++++++++++++++++-
net/ethtool/netlink.h | 5 ++
6 files changed, 139 insertions(+), 2 deletions(-)
diff --git a/include/linux/ethtool_netlink.h b/include/linux/ethtool_netlink.h
index 0412adb4f42f..2a15e64a16f3 100644
--- a/include/linux/ethtool_netlink.h
+++ b/include/linux/ethtool_netlink.h
@@ -5,5 +5,10 @@
#include <uapi/linux/ethtool_netlink.h>
#include <linux/ethtool.h>
+#include <linux/netdevice.h>
+
+enum ethtool_multicast_groups {
+ ETHNL_MCGRP_MONITOR,
+};
#endif /* _LINUX_ETHTOOL_NETLINK_H_ */
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index c1295c7a452e..c4b0c575d57e 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -2444,6 +2444,7 @@ enum netdev_cmd {
NETDEV_CVLAN_FILTER_DROP_INFO,
NETDEV_SVLAN_FILTER_PUSH_INFO,
NETDEV_SVLAN_FILTER_DROP_INFO,
+ NETDEV_ETHTOOL,
};
const char *netdev_cmd_to_name(enum netdev_cmd cmd);
@@ -4221,6 +4222,28 @@ struct netdev_notifier_bonding_info {
void netdev_bonding_info_change(struct net_device *dev,
struct netdev_bonding_info *bonding_info);
+struct netdev_ethtool_info {
+ unsigned int cmd;
+ u32 req_mask;
+};
+
+struct netdev_notifier_ethtool_info {
+ struct netdev_notifier_info info; /* must be first */
+ struct netdev_ethtool_info ethtool_info;
+};
+
+#if IS_ENABLED(CONFIG_ETHTOOL_NETLINK)
+int netdev_ethtool_info_change(struct net_device *dev,
+ struct netlink_ext_ack *extack,
+ unsigned int cmd, u32 req_mask);
+#else
+static inline void netdev_ethtool_info_change(struct net_device *dev,
+ struct netlink_ext_ack *extack,
+ unsigned int cmd, u32 req_mask)
+{
+}
+#endif
+
static inline
struct sk_buff *skb_gso_segment(struct sk_buff *skb, netdev_features_t features)
{
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index 98d6fae315f3..444a668e4a08 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -58,4 +58,6 @@ enum {
#define ETHTOOL_GENL_NAME "ethtool"
#define ETHTOOL_GENL_VERSION 1
+#define ETHTOOL_MCGRP_MONITOR_NAME "monitor"
+
#endif /* _UAPI_LINUX_ETHTOOL_NETLINK_H_ */
diff --git a/net/core/dev.c b/net/core/dev.c
index 87c42c8249ae..8a0773a52dd7 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -1585,7 +1585,7 @@ const char *netdev_cmd_to_name(enum netdev_cmd cmd)
N(PRECHANGEUPPER) N(CHANGELOWERSTATE) N(UDP_TUNNEL_PUSH_INFO)
N(UDP_TUNNEL_DROP_INFO) N(CHANGE_TX_QUEUE_LEN)
N(CVLAN_FILTER_PUSH_INFO) N(CVLAN_FILTER_DROP_INFO)
- N(SVLAN_FILTER_PUSH_INFO) N(SVLAN_FILTER_DROP_INFO)
+ N(SVLAN_FILTER_PUSH_INFO) N(SVLAN_FILTER_DROP_INFO) N(ETHTOOL)
}
#undef N
return "UNKNOWN_NETDEV_EVENT";
@@ -7058,6 +7058,28 @@ void netdev_bonding_info_change(struct net_device *dev,
}
EXPORT_SYMBOL(netdev_bonding_info_change);
+#if IS_ENABLED(CONFIG_ETHTOOL_NETLINK)
+int netdev_ethtool_info_change(struct net_device *dev,
+ struct netlink_ext_ack *extack,
+ unsigned int cmd, u32 req_mask)
+{
+ struct netdev_notifier_ethtool_info ethtool_info = {
+ .info = {
+ .dev = dev,
+ .extack = extack,
+ },
+ .ethtool_info = {
+ .cmd = cmd,
+ .req_mask = req_mask,
+ },
+ };
+
+ return call_netdevice_notifiers_info(NETDEV_ETHTOOL,
+ ðtool_info.info);
+}
+EXPORT_SYMBOL(netdev_ethtool_info_change);
+#endif
+
static void netdev_adjacent_add_links(struct net_device *dev)
{
struct netdev_adjacent *iter;
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index df065fd3dc80..e4a20bb6c1d4 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -6,6 +6,8 @@
#include <linux/ethtool_netlink.h>
#include "netlink.h"
+u32 ethnl_bcast_seq;
+
static const struct nla_policy dev_policy[ETHA_DEV_MAX + 1] = {
[ETHA_DEV_UNSPEC] = { .type = NLA_UNSPEC },
[ETHA_DEV_INDEX] = { .type = NLA_U32 },
@@ -19,6 +21,11 @@ struct net_device *ethnl_dev_get(struct genl_info *info, struct nlattr *nest)
struct net_device *dev;
int ret;
+ if (!nest) {
+ ETHNL_SET_ERRMSG(info,
+ "mandatory device identification missing");
+ return ERR_PTR(-EINVAL);
+ }
ret = nla_parse_nested(tb, ETHA_DEV_MAX, nest, dev_policy,
info->extack);
if (ret < 0)
@@ -562,11 +569,69 @@ int ethnl_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
return ret;
}
+/* notifications */
+
+typedef void (*ethnl_notify_handler_t)(struct netdev_notifier_ethtool_info *);
+
+ethnl_notify_handler_t ethnl_notify_handlers[] = {
+};
+
+static void __ethnl_notify(struct netdev_notifier_ethtool_info *info)
+{
+ unsigned int cmd = info->ethtool_info.cmd;
+
+ ASSERT_RTNL();
+ if (likely(cmd < ARRAY_SIZE(ethnl_notify_handlers) &&
+ ethnl_notify_handlers[cmd]))
+ ethnl_notify_handlers[cmd](info);
+ else
+ WARN_ONCE(1, "notification %u not implemented (dev=%s, req_mask=0x%x\n",
+ cmd, netdev_name(info->info.dev),
+ info->ethtool_info.req_mask);
+}
+
+void ethnl_notify(struct net_device *dev, struct netlink_ext_ack *extack,
+ unsigned int cmd, u32 req_mask)
+{
+ struct netdev_notifier_ethtool_info ethtool_info = {
+ .info = {
+ .dev = dev,
+ .extack = extack,
+ },
+ .ethtool_info = {
+ .cmd = cmd,
+ .req_mask = req_mask,
+ },
+ };
+
+ return __ethnl_notify(ðtool_info);
+}
+
+static int ethnl_netdev_event(struct notifier_block *this, unsigned long event,
+ void *ptr)
+{
+ switch(event) {
+ case NETDEV_ETHTOOL:
+ __ethnl_notify(ptr);
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block ethnl_netdev_notifier = {
+ .notifier_call = ethnl_netdev_event,
+};
+
/* genetlink setup */
static const struct genl_ops ethtool_genl_ops[] = {
};
+static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
+ [ETHNL_MCGRP_MONITOR] = { .name = ETHTOOL_MCGRP_MONITOR_NAME },
+};
+
struct genl_family ethtool_genl_family = {
.hdrsize = 0,
.name = ETHTOOL_GENL_NAME,
@@ -575,17 +640,32 @@ struct genl_family ethtool_genl_family = {
.parallel_ops = true,
.ops = ethtool_genl_ops,
.n_ops = ARRAY_SIZE(ethtool_genl_ops),
+ .mcgrps = ethtool_nl_mcgrps,
+ .n_mcgrps = ARRAY_SIZE(ethtool_nl_mcgrps),
};
/* module setup */
static int __init ethtool_nl_init(void)
{
- return genl_register_family(ðtool_genl_family);
+ int ret;
+
+ ret = genl_register_family(ðtool_genl_family);
+ if (ret < 0)
+ return ret;
+ ret = register_netdevice_notifier(ðnl_netdev_notifier);
+ if (ret < 0)
+ goto err_unregister;
+ return 0;
+
+err_unregister:
+ genl_unregister_family(ðtool_genl_family);
+ return ret;
}
static void __exit ethtool_nl_exit(void)
{
+ unregister_netdevice_notifier(ðnl_netdev_notifier);
genl_unregister_family(ðtool_genl_family);
}
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 6e9e854eec5d..94c14ec2c3fc 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -11,6 +11,8 @@
#define ETHNL_SET_ERRMSG(info, msg) \
do { if (info) GENL_SET_ERR_MSG(info, msg); } while (0)
+extern u32 ethnl_bcast_seq;
+
extern struct genl_family ethtool_genl_family;
struct net_device *ethnl_dev_get(struct genl_info *info, struct nlattr *nest);
@@ -159,4 +161,7 @@ static inline bool ethnl_is_privileged(struct sk_buff *skb)
return netlink_ns_capable(skb, net->user_ns, CAP_NET_ADMIN);
}
+void ethnl_notify(struct net_device *dev, struct netlink_ext_ack *extack,
+ unsigned int cmd, u32 req_mask);
+
#endif /* _NET_ETHTOOL_NETLINK_H */
--
2.18.0
Mon, Jul 30, 2018 at 02:52:42PM CEST, [email protected] wrote:
>A netlink based interface for ethtool is a recurring discussion theme;
>such discussion happens from time to time but never seems to reach any
>clear conclusion except that netlink interface is desperately needed.
>I'm sending this hoping that having a proposal (even if partial) on the
>table could help move the discussion further.
>
>The interface used for communication between ethtool and kernel is based on
>ioctl() and suffers from many problems. The most pressing seems the be the
>lack of extensibility. While some of the newer commands use structures
>designed to allow future extensions (e.g. GFEATURES or TEST), most either
>allow no extension at all (GPAUSEPARAM, GCOALESCE) or only limited set of
>reserved fields (GDRVINFO, GEEE). Even most of those which support future
>extensions limit the data types that can be used.
>
>This series aims to provide an alternative interface based on netlink which
>is what other network configuration utilities use. In particular, it uses
>generic netlink (family "ethtool"). The goal is to provide an interface
>which would be extensible, flexible and practical both for ethtool and for
>other network configuration tools (e.g. wicked, systemd-networkd or
>NetworkManager).
>
>The interface is documented in Documentation/networking/ethtool-netlink.txt
>
>A series for ethtool utility will follow shortly.
>
>Basic concepts:
>
>- the interface is based on generic netlink (family name "ethtool")
>
>- the goal is to provide all features of ioctl interface but allow
> easier future extensions
>
>- inextensibility of ioctl interface resulted in way too many commands,
> many of them obsoleted by newer ones; reduce the number by ignoring the
> obsolete commands and grouping some together
>
>- for "set" type commands, netlink allows providing only the attributes to
> be changed; therefore we don't need a get-modify-set cycle (which is
> inherently racy), userspace can simply say what it wants to change
>
>- provide notifications to multicast group "monitor" like rtnetlink
> does, i.e. in the form of messages close to replies to "get" requests
>
>- allow dump requests to get some information about all network defices
> providing it
>
>- be less dependent on ethtool and kernel being in sync; allow e.g. saying
> "ethtool -s eth0 advertise foo off" without ethtool knowing what "foo"
> means; it's kernel's job to know what mode "xyz" is and if it exists
> and is supported
>
>Main changes again RFC v1:
>
>- support dumps for all "get" requests
>- provide notifications for changes related to supported request types
>- support getting string sets (both global and per device)
>- support getting/setting device features
>- get rid of family specific header, everything passed as attributes
>- split netlink code into multiple files in net/ethtool/ directory
>
>ToDo / open questions:
>
>- as some comments in discussion on v1 pointed out, some features of
> ethtool would rather belong to devlink; phy_tunables and phy_stats
> seem to be candidates, maybe part of drvinfo; are there more?
>
>- another question is where to do the split; should ethtool use devlink
> API for these or can we provide them in ethtool API as well but with
> devlink backend (for communication with NIC)
My idea was to provide a compat layer for it. But I also wanted to
change the implementation of driver callbacks and provide compat layer
for it too. Current ethtool callbacks are very tightly wrapped around
the uapi structs.
Please see the last slide:
http://vger.kernel.org/netconf2018_files/JiriPirko_netconf2018.pdf
I started to work on POC implementation but got distracted.
Perhaps we can sync over a beer :)
>
>- currently, all communication with drivers via ethtool_ops is done
> under RTNL as this is what ioctl interface does and I suspect many
> ethtool_ops rely on that; can we do without RTNL?
>
>- notifications are sent whenever a change is done via netlink API or
> ioctl API and for netdev features also whenever they are updated using
> netdev_change_features(); it would be desirable to notify also about
> link state and negotiation result (speed/duplex and partner link
> modes) but it would be more tricky
>
>- find reasonable format for data transfers (e.g. eeprom dump or flash);
> I don't have clear idea how big these can get and if 64 KB limit on
> attribute size (including nested ones) is a problem; if so, dumps can
> be an answer for dumps, some kind of multi-message requests would be
> needed for flashes
This is another thing that should go into devlink as it is not
netdev-handled. We have now a "region" facility there which can be
re-used.
>
>- while the netlink interface allows easy future extensions, ethtool_ops
> interface does not; some settings could be implemented using tunables and
> accessed via relevant netlink messages (as well as tunables) from
> userspace but in the long term, something better will be needed
>
>- it would be nice if driver could provide useful error/warning messages to
> be passed to userspace via extended ACK; example: while testing, I found
> a driver which only allows values 0, 1, 3 and 10000 for certain parameter
> but the only way poor user can find out is either by trying all values or
> by checking driver source
>
>- some of the functions for GET_SETTINGS and GET_PARAMS are quite
> similar (e.g. ethtool_get_*); it might be beneficial to introduce some
> "ops", leave only "parse", "prepare", "size" and "fill" handlers and
> make the rest generic (like ethnl_dumpit()).
>
>- the counts and sizes in GET_DRVINFO reply seem to be a relic of the
> past and if userspace needs them, there are (or will be) other ways to
> get them; they should most likely go
>
>
>Michal Kubecek (17):
> netlink: introduce nla_put_bitfield32()
> ethtool: move to its own directory
> ethtool: introduce ethtool netlink interface
> ethtool: helper functions for netlink interface
> ethtool: netlink bitset handling
> ethtool: support for netlink notifications
> ethtool: implement EVENT notifications
> ethtool: implement GET_STRSET message
> ethtool: implement GET_DRVINFO message
> ethtool: implement GET_SETTINGS message
> ethtool: implement GET_SETTINGS request for features
> ethtool: implement SET_SETTINGS notification
> ethtool: implement SET_SETTINGS message
> ethtool: implement SET_SETTINGS request for features
> ethtool: implement GET_PARAMS message
> ethtool: implement SET_PARAMS notification
> ethtool: implement SET_PARAMS message
>
> Documentation/networking/ethtool-netlink.txt | 558 ++++++++
> include/linux/ethtool_netlink.h | 17 +
> include/linux/netdevice.h | 25 +
> include/net/netlink.h | 15 +
> include/uapi/linux/ethtool.h | 7 +
> include/uapi/linux/ethtool_netlink.h | 325 +++++
> net/Kconfig | 7 +
> net/Makefile | 2 +-
> net/core/Makefile | 2 +-
> net/core/dev.c | 27 +-
> net/ethtool/Makefile | 7 +
> net/ethtool/common.c | 242 ++++
> net/ethtool/common.h | 26 +
> net/ethtool/drvinfo.c | 131 ++
> net/{core/ethtool.c => ethtool/ioctl.c} | 310 ++---
> net/ethtool/netlink.c | 840 ++++++++++++
> net/ethtool/netlink.h | 169 +++
> net/ethtool/params.c | 1008 ++++++++++++++
> net/ethtool/settings.c | 1230 ++++++++++++++++++
> net/ethtool/strset.c | 552 ++++++++
> 20 files changed, 5269 insertions(+), 231 deletions(-)
> create mode 100644 Documentation/networking/ethtool-netlink.txt
> create mode 100644 include/linux/ethtool_netlink.h
> create mode 100644 include/uapi/linux/ethtool_netlink.h
> create mode 100644 net/ethtool/Makefile
> create mode 100644 net/ethtool/common.c
> create mode 100644 net/ethtool/common.h
> create mode 100644 net/ethtool/drvinfo.c
> rename net/{core/ethtool.c => ethtool/ioctl.c} (88%)
> create mode 100644 net/ethtool/netlink.c
> create mode 100644 net/ethtool/netlink.h
> create mode 100644 net/ethtool/params.c
> create mode 100644 net/ethtool/settings.c
> create mode 100644 net/ethtool/strset.c
>
>--
>2.18.0
>
Mon, Jul 30, 2018 at 02:53:12PM CEST, [email protected] wrote:
[...]
>diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
>index c1295c7a452e..c4b0c575d57e 100644
>--- a/include/linux/netdevice.h
>+++ b/include/linux/netdevice.h
>@@ -2444,6 +2444,7 @@ enum netdev_cmd {
> NETDEV_CVLAN_FILTER_DROP_INFO,
> NETDEV_SVLAN_FILTER_PUSH_INFO,
> NETDEV_SVLAN_FILTER_DROP_INFO,
>+ NETDEV_ETHTOOL,
I don't understand why this goes through netdev notifier. What's the
reason?
Mon, Jul 30, 2018 at 02:53:27PM CEST, [email protected] wrote:
[...]
>+/* GET_DRVINFO / SET_DRVINFO */
>+
>+enum {
>+ ETHA_DRVINFO_UNSPEC,
>+ ETHA_DRVINFO_DEV, /* nest - ETHA_DEV_* */
>+ 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 */
This is a nice example of why 1:1 ioctl->netlink conversion would be
a big mistake.
I understand that for ioclt, getting lengths of various things is
important. Userspace can prepare buffer for next ioctl which would
actually do dump transfer. However in netlink, this is totally pointless
as the dump goes into userspace in multiple netlink messages.
We need to figure out the netlink uapi from scratch.
[...]
On Mon, Jul 30, 2018 at 02:53:27PM +0200, Michal Kubecek wrote:
> Requests the same information as ETHTOOL_GDRVINFO command in ioct
> interface. This is read-only so that corresponding SET_DRVINFO exists but
> is only used in kernel replies.
>
> Signed-off-by: Michal Kubecek <[email protected]>
> ---
> Documentation/networking/ethtool-netlink.txt | 38 +++++-
> include/uapi/linux/ethtool_netlink.h | 22 ++++
> net/ethtool/Makefile | 4 +-
> net/ethtool/common.c | 43 ++++++
> net/ethtool/common.h | 3 +
> net/ethtool/drvinfo.c | 131 +++++++++++++++++++
> net/ethtool/ioctl.c | 42 +-----
> net/ethtool/netlink.c | 8 ++
> 8 files changed, 252 insertions(+), 39 deletions(-)
> create mode 100644 net/ethtool/drvinfo.c
>
> diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt
> index 8b43f41a8140..1e3d5ffc97ab 100644
> --- a/Documentation/networking/ethtool-netlink.txt
> +++ b/Documentation/networking/ethtool-netlink.txt
> @@ -121,6 +121,8 @@ List of message types
> ETHNL_CMD_EVENT notification only
> ETHNL_CMD_GET_STRSET
> ETHNL_CMD_SET_STRSET response only
> + ETHNL_CMD_GET_DRVINFO
> + ETHNL_CMD_SET_DRVINFO response only
>
> All constants use ETHNL_CMD_ prefix, usually followed by "GET", "SET" or "ACT"
> to indicate the type.
> @@ -156,6 +158,40 @@ and also multiple events of the same type (e.g. two or more newly registered
> devices).
>
>
> +GET_DRVINFO
> +-----------
> +
> +GET_DRVINFO request corresponds to ETHTOOL_GDRVINFO ioctl command and provides
> +basic driver information.
> +
> +Request contents:
> +
> + ETHA_DRVINFO_DEV (nested) device identification
> +
> +Kernel response contents:
> +
> + ETHA_DRVINFO_DEV (nested) device identification
> + ETHA_DRVINFO_DRIVER (string) driver name
> + ETHA_DRVINFO_VERSION (string) driver version
Driver version is generally useless. Is version X.Y.Z of a driver the
same when backported to some ancient enterprise kernel with 1000s of
patches? Now seems like a good opportunity to drop it.
> + 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
I know there is at least one driver that has somewhat dynamic number
of statistics. It would be better to have the strings and the values
in the same message, so there is no need to first get the number of
strings, allocate the memory, get the strings, then get the values.
> + ETHA_DRVINFO_TESTINFO_LEN (u32) number of test results
In theory, this also does not need to be fixed.
> + ETHA_DRVINFO_EEDUMP_LEN (u32) EEPROM dump size
> + ETHA_DRVINFO_REGDUMP_LEN (u32) register dump size
I would suggest removing all these _LEN properties, and let netlink
return what it needs to return.
Andrew
On Mon, Jul 30, 2018 at 03:21:07PM +0200, Jiri Pirko wrote:
> Mon, Jul 30, 2018 at 02:53:27PM CEST, [email protected] wrote:
>
> [...]
>
> >+/* GET_DRVINFO / SET_DRVINFO */
> >+
> >+enum {
> >+ ETHA_DRVINFO_UNSPEC,
> >+ ETHA_DRVINFO_DEV, /* nest - ETHA_DEV_* */
> >+ 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 */
>
> This is a nice example of why 1:1 ioctl->netlink conversion would be
> a big mistake.
>
> I understand that for ioclt, getting lengths of various things is
> important. Userspace can prepare buffer for next ioctl which would
> actually do dump transfer. However in netlink, this is totally pointless
> as the dump goes into userspace in multiple netlink messages.
Right, I already mentioned this in the ToDo part of cover letter. It
makes indeed little sense to put this information here - and even less
to put only dome of the counts and lengths.
Michal Kubecek
On Mon, Jul 30, 2018 at 04:28:25PM +0200, Andrew Lunn wrote:
> On Mon, Jul 30, 2018 at 02:53:27PM +0200, Michal Kubecek wrote:
>
> > + 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
>
> I know there is at least one driver that has somewhat dynamic number
> of statistics. It would be better to have the strings and the values
> in the same message, so there is no need to first get the number of
> strings, allocate the memory, get the strings, then get the values.
>
> > + ETHA_DRVINFO_TESTINFO_LEN (u32) number of test results
>
> In theory, this also does not need to be fixed.
This is interesting. It would mean current (ioctl) ethtool approach with
string set may not work correctly either. On the other hand, this should
not be a problem for netlink interface. Statistics are unlikely to
appear in notifications and daemons collecting them periodically will
have to learn with it. Adding test name to notification "test started"
or "test finished" seems quite natural.
> > + ETHA_DRVINFO_EEDUMP_LEN (u32) EEPROM dump size
> > + ETHA_DRVINFO_REGDUMP_LEN (u32) register dump size
>
> I would suggest removing all these _LEN properties, and let netlink
> return what it needs to return.
Agreed.
Michal Kubecek
> This is interesting. It would mean current (ioctl) ethtool approach with
> string set may not work correctly either.
Hi Michal
For the statistics, it is a bit of a corner case. One of the Ethernet
switches in DSA can have two different PHYs linked to one MAC. One PHY
is built in, the second is connected via a SERDES interface. Which
every gets link first is used. However, the SERDES interface has
additional statistics counters. So if the SERDES is in use, we return
more statistics. If somebody was to plug in the cable at just the
wrong/right time, the count of statistics could be different to the
number of statistics.
Another corner case i can think of. Some drivers return statistics per
queue. And there is an ioctl to change the number of queues....
I could also imaging tests being similar. There are more loopback
tests you can do with a SERDES which you cannot do with a built in
PHY. But so far, i've not seen anything like that.
Andrew
On Mon, Jul 30, 2018 at 05:48:03PM +0200, Andrew Lunn wrote:
>
> For the statistics, it is a bit of a corner case. One of the Ethernet
> switches in DSA can have two different PHYs linked to one MAC. One PHY
> is built in, the second is connected via a SERDES interface. Which
> every gets link first is used. However, the SERDES interface has
> additional statistics counters. So if the SERDES is in use, we return
> more statistics. If somebody was to plug in the cable at just the
> wrong/right time, the count of statistics could be different to the
> number of statistics.
>
> Another corner case i can think of. Some drivers return statistics per
> queue. And there is an ioctl to change the number of queues....
>
> I could also imaging tests being similar. There are more loopback
> tests you can do with a SERDES which you cannot do with a built in
> PHY. But so far, i've not seen anything like that.
Thank you for the explanation. What I have in mind is that there are two
different types of userspace applications: one shot and running long
term. It can be seen in my series in the way e.g. link modes are
handled. There are two formats in which a bitset can be passed: verbose
(list of bits with names) or compact (just bitmaps). For one shot
queries (e.g. "ethtool eth0") it's easier to use verbose format so that
you get names with bit values in one package. But for long running
applications processing many messages ("ethtool --monitor" or config
management daemons), it's more practical to load the names once and pass
only data with each notification or response.
I suppose in the scenarios you mentioned the driver would be able to
trigger a notification whenever the list changes so that userspace
application could reload the string set.
Michal Kubecek
On Mon, Jul 30, 2018 at 03:16:55PM +0200, Jiri Pirko wrote:
> Mon, Jul 30, 2018 at 02:53:12PM CEST, [email protected] wrote:
>
> [...]
>
> >diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
> >index c1295c7a452e..c4b0c575d57e 100644
> >--- a/include/linux/netdevice.h
> >+++ b/include/linux/netdevice.h
> >@@ -2444,6 +2444,7 @@ enum netdev_cmd {
> > NETDEV_CVLAN_FILTER_DROP_INFO,
> > NETDEV_SVLAN_FILTER_PUSH_INFO,
> > NETDEV_SVLAN_FILTER_DROP_INFO,
> >+ NETDEV_ETHTOOL,
>
> I don't understand why this goes through netdev notifier. What's the
> reason?
To allow triggering a notification from other code (ethtool ioctl or
e.g. netdev_features_change()) when netlink interface is built as a
module. If it's a (performance?) problem, an alternative could be having
a global pointer which would be either null or point to ethtool_notify()
depending on whether the module is loaded (and ready).
(Which made me realize I forgot to handle a race between module
unloading and processing a notification.)
Another question is if we really need the option to build the netlink
interface as a module. I must admit my main motivation to have it as
a module is that it makes testing and debugging easier.
Michal
> +/* 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.
~/linux/drivers$ grep -r [.]get_settings *
net/ethernet/8390/etherh.c: .get_settings = etherh_get_settings,
I don't think it is worth adding support for .get_settings for just
one driver. It is better to just convert that driver to the new API.
Andrew
> + ETHA_SETTINGS_MDIO_SUPPORT (bitfield32) MDIO support flags
Now might be a good time to drop this. The mdio drivers don't have
this information. There is no API between the MAC driver and the MDIO
driver to ask for it. Only a couple of drivers which ignore the kernel
MDIO code set this.
Andrew
> +Response contents:
> +
> + ETHA_SETTINGS_LINK (u32) link state
> + [ETHA_SETTINGS_LINK] = { .type = NLA_FLAG },
Is this correct?
The link is either up or down. So a u32 seems a bit big.
Andrew
On Mon, Jul 30, 2018 at 09:09:41PM +0200, Andrew Lunn wrote:
> > +Response contents:
> > +
> > + ETHA_SETTINGS_LINK (u32) link state
>
> > + [ETHA_SETTINGS_LINK] = { .type = NLA_FLAG },
>
> Is this correct?
NLA_FLAG is wrong, we need three states: on/off/unknown for "get"
replies and on/off/keep for "set" requests.
> The link is either up or down. So a u32 seems a bit big.
I tend to use u32 everywhere with some obvious exceptions. The reason is
that netlink attributes are padded to 32 bits so that no matter if you
use u8, u16 or u32, the attribute still ends up taking 8 bytes. But yes,
this looks like an obvious exception where u8 wouldn't mean any risk of
running out of values one day.
Michal Kubecek
On Mon, 30 Jul 2018 15:07:47 +0200, Jiri Pirko wrote:
> >- find reasonable format for data transfers (e.g. eeprom dump or flash);
> > I don't have clear idea how big these can get and if 64 KB limit on
> > attribute size (including nested ones) is a problem; if so, dumps can
> > be an answer for dumps, some kind of multi-message requests would be
> > needed for flashes
>
> This is another thing that should go into devlink as it is not
> netdev-handled. We have now a "region" facility there which can be
> re-used.
+1 regions should cover the current use cases and are a better API IMHO.
On Mon, 30 Jul 2018 14:53:27 +0200 (CEST), Michal Kubecek wrote:
> +GET_DRVINFO
> +-----------
> +
> +GET_DRVINFO request corresponds to ETHTOOL_GDRVINFO ioctl command and provides
> +basic driver information.
> +
> +Request contents:
> +
> + ETHA_DRVINFO_DEV (nested) device identification
> +
> +Kernel response contents:
> +
> + ETHA_DRVINFO_DEV (nested) device identification
> + ETHA_DRVINFO_DRIVER (string) driver name
> + ETHA_DRVINFO_VERSION (string) driver version
> + ETHA_DRVINFO_FWVERSION (string) firmware version
FWIW I think fwinfo belongs to devlink, and should be split. Most
modern drivers provide versions of multiple FW components smooshed into
a single string. Perhaps it's time to allow this facility to carry
multiple key: value entries?
> + ETHA_DRVINFO_BUSINFO (string) device bus address
I wonder if some of this information is also not duplicated with
sysfs. There should be a link from the netdev to a sysfs device.
Same for driver name.
I'm probably missing some uses (in embedded world?) and will be
corrected.. :)
> + 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
Mon, Jul 30, 2018 at 07:01:21PM CEST, [email protected] wrote:
>On Mon, Jul 30, 2018 at 03:16:55PM +0200, Jiri Pirko wrote:
>> Mon, Jul 30, 2018 at 02:53:12PM CEST, [email protected] wrote:
>>
>> [...]
>>
>> >diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
>> >index c1295c7a452e..c4b0c575d57e 100644
>> >--- a/include/linux/netdevice.h
>> >+++ b/include/linux/netdevice.h
>> >@@ -2444,6 +2444,7 @@ enum netdev_cmd {
>> > NETDEV_CVLAN_FILTER_DROP_INFO,
>> > NETDEV_SVLAN_FILTER_PUSH_INFO,
>> > NETDEV_SVLAN_FILTER_DROP_INFO,
>> >+ NETDEV_ETHTOOL,
>>
>> I don't understand why this goes through netdev notifier. What's the
>> reason?
>
>To allow triggering a notification from other code (ethtool ioctl or
>e.g. netdev_features_change()) when netlink interface is built as a
>module. If it's a (performance?) problem, an alternative could be having
>a global pointer which would be either null or point to ethtool_notify()
>depending on whether the module is loaded (and ready).
>
>(Which made me realize I forgot to handle a race between module
>unloading and processing a notification.)
>
>Another question is if we really need the option to build the netlink
>interface as a module. I must admit my main motivation to have it as
>a module is that it makes testing and debugging easier.
Yeah. It is very core thing. I think it does not have to be a module.
On Mon, Jul 30, 2018 at 08:54:55PM +0200, Andrew Lunn wrote:
> > +/* 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.
>
> ~/linux/drivers$ grep -r [.]get_settings *
> net/ethernet/8390/etherh.c: .get_settings = etherh_get_settings,
>
> I don't think it is worth adding support for .get_settings for just
> one driver. It is better to just convert that driver to the new API.
I have prepared a patch converting 8390/etherh driver to use
{g,s}et_link_ksettings and I'm going to submit it when net-next opens.
Do you think we can then drop {g,s}et_settings callbacks completely
(i.e. also from ioctl() code and ethtool_ops)? Do we care about
unconverted out of tree drivers?
Michal Kubecek
On Tue, Aug 21, 2018 at 11:32:46AM +0200, Michal Kubecek wrote:
> On Mon, Jul 30, 2018 at 08:54:55PM +0200, Andrew Lunn wrote:
> > > +/* 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.
> >
> > ~/linux/drivers$ grep -r [.]get_settings *
> > net/ethernet/8390/etherh.c: .get_settings = etherh_get_settings,
> >
> > I don't think it is worth adding support for .get_settings for just
> > one driver. It is better to just convert that driver to the new API.
>
> I have prepared a patch converting 8390/etherh driver to use
> {g,s}et_link_ksettings and I'm going to submit it when net-next opens.
> Do you think we can then drop {g,s}et_settings callbacks completely
> (i.e. also from ioctl() code and ethtool_ops)? Do we care about
> unconverted out of tree drivers?
Hi Michal
We cannot break ethtool, the ABI it uses. But there is already code to
use get_link_ksettings() and only fall back to get_settings if it does
not exist. So we can clean up all the fallback code, remove the
ethtool_ops, etc.
I personally don't care about out of tree drivers. They have had over
2 years to change to the new API.
Andrew
On Tue, Aug 21, 2018 at 04:10:22PM +0200, Andrew Lunn wrote:
> On Tue, Aug 21, 2018 at 11:32:46AM +0200, Michal Kubecek wrote:
> > I have prepared a patch converting 8390/etherh driver to use
> > {g,s}et_link_ksettings and I'm going to submit it when net-next opens.
> > Do you think we can then drop {g,s}et_settings callbacks completely
> > (i.e. also from ioctl() code and ethtool_ops)? Do we care about
> > unconverted out of tree drivers?
>
> We cannot break ethtool, the ABI it uses. But there is already code to
> use get_link_ksettings() and only fall back to get_settings if it does
> not exist. So we can clean up all the fallback code, remove the
> ethtool_ops, etc.
Yes, that's what I have in mind: keep the ethtool interface as is (both
ETHTOOL_{G,S}SET and ETHTOOL_{G,S}LINKSETTINGS) but drop get_settings
and set_settings from ethtool_ops and the code that falls back to them.
This way both old and new versions of ethtool would still work and the
only problem would be with out of tree drivers not converted to
{s,g}et_link_ksettings (which won't build so that it won't be silent
breakage).
Michal Kubecek