linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [RFC PATCH ethtool v2 00/23] ethtool netlink interface (userspace side) (WiP)
@ 2018-07-30 12:55 Michal Kubecek
  2018-07-30 12:55 ` [RFC PATCH ethtool v2 01/23] move UAPI header copies to a separate directory Michal Kubecek
                   ` (22 more replies)
  0 siblings, 23 replies; 24+ messages in thread
From: Michal Kubecek @ 2018-07-30 12:55 UTC (permalink / raw)
  To: netdev; +Cc: linux-kernel, John W. Linville

(The series is based on v4.17)

This patch series is and ethtool counterpart to kernel series "ethtool
netlink interface (WiP)"; it's marked as "v2" to match kernel series
version even if I never really sent "v1" to the list. It's only an RFC
as the netlink API it implements is still only a proposal.

The series modifies ethtool to use the netlink based kernel API when
available. If not, it falls back to the ioctl API. The aim is to
preserve command line syntax (with few extensions) and preserve the
output as much as possible.

This brings a natural question: why? It would be certainly tempting to
start with a new tool without the limitations of the old command line
syntax and without having to immitatte strange artefacts of the past.
It may still come to that but experience with iproute2 shows even if you
provide a new tool with cleaner syntax, more features and better
organized output, 19 years later you may still meet (a lot of) people
still using the old tool and claiming they are not going to learn some
new tools with "strange syntax and incomprehensible output" (the sadest
part being that many of them haven't learn using ifconfig before it had
been already obsolete for ten years). That's why I tried to give this
approach at least a try.

As both parser and dump functions in current ethtool code are closely
tied to data structures used by ioctl API, only very little of existing
code could be reused. The parts used also by netlink code were moved to
common.h and common.c. New netlink related code resides in netlink/
subdirectory. Copies of kernel uapi headers were moved to uapi/
directory to make their status more apparent. Netlink code uses libmnl
for message composition and parsing.

Currently reimplemented functions are

  -i / --driver
  (no option)
  -s
  -k / --show-features / --show-offload
  -K / --features / --offload
  -c / --show-coalesce
  -C / --coalesce
  -g / --show-ring
  -G / --set-ring
  -a / --show-pause
  -A / --pause
  -l / --show-channels
  -L / --set-channels
  --show-eee
  --set-eee
  --show-fec
  --set-fec

For all "get" type options, "*" can be used as device name to request
information about all interfaces supporting that request type.

The most interesting new feature is notification monitoring. When
started as

  ethtool --monitor --all

ethtool will listen to netlink notifications and display information
about changes. Replacing "--all" with one of the options above will show
only notifications about changes related to that option. A device name
can be also used to display only notifications related to that device.

ToDo / open questions:

- the most important question still is if we want to reimplement ethtool
  or come with a completely new utility instead

- if we go on with teaching ethtool netlink, another interesting
  question is how closely we need to stick to current output and its
  formatting; even if people are told not to, there are certainly tons
  of scripts parsing ethtool output which may break on any change

- an example of this are legacy "flags" for netdev features; we will
  have to make sure "ethtool -K dev tso off" will do what users expect
  it to but can we get rid of them at least in "ethtool -k" output?

- having to say "--monitor -all" for monitoring all events while just
  "--monitor" only watches for sset ("-s") type changes is probably
  counterintuitive; would it be better to make "--all" default and leave
  only "--monitor -s" for watching only "sset" events

Michal Kubecek (23):
  move UAPI header copies to a separate directory
  update UAPI header copies
  netlink: add netlink interface
  netlink: add support for string sets
  netlink: add notification monitor
  netlink: add netlink handler for gdrv (-i)
  netlink: add netlink handler for gset (no option)
  netlink: add helpers for command line parsing
  netlink: add netlink handler for sset (-s)
  netlink: add netlink handler for gfeatures (-k)
  netlink: add netlink handler for sfeatures (-K)
  netlink: add netlink handler for gcoalesce (-c)
  netlink: add netlink handler for gring (-g)
  netlink: add netlink handler for gpause (-a)
  netlink: add netlink handler for gchannels (-l)
  netlink: add netlink handler for geee (--show-eee)
  netlink: add netlink handler for gfec (--show-fec)
  netlink: add netlink handler for scoalesce (-C)
  netlink: add netlink handler for sring (-G)
  netlink: add netlink handler for spause (-A)
  netlink: add netlink handler for schannels (-L)
  netlink: add netlink handler for seee (--set-eee)
  netlink: add netlink handler for sfec (--set-fec)

 Makefile.am                                  |   20 +-
 common.c                                     |  325 ++++++
 common.h                                     |   83 ++
 configure.ac                                 |   14 +-
 ethtool.c                                    |  437 +++-----
 internal.h                                   |   12 +-
 netlink/drvinfo.c                            |   43 +
 netlink/extapi.h                             |   37 +
 netlink/monitor.c                            |  283 +++++
 netlink/netlink.c                            |  681 ++++++++++++
 netlink/netlink.h                            |  189 ++++
 netlink/params.c                             |  659 +++++++++++
 netlink/parser.c                             |  559 ++++++++++
 netlink/parser.h                             |   49 +
 netlink/settings.c                           | 1035 ++++++++++++++++++
 netlink/strset.c                             |  265 +++++
 netlink/strset.h                             |   16 +
 ethtool-copy.h => uapi/linux/ethtool.h       |   13 +-
 uapi/linux/ethtool_netlink.h                 |  325 ++++++
 uapi/linux/genetlink.h                       |   89 ++
 net_tstamp-copy.h => uapi/linux/net_tstamp.h |   19 +
 uapi/linux/netlink.h                         |  247 +++++
 22 files changed, 5134 insertions(+), 266 deletions(-)
 create mode 100644 common.c
 create mode 100644 common.h
 create mode 100644 netlink/drvinfo.c
 create mode 100644 netlink/extapi.h
 create mode 100644 netlink/monitor.c
 create mode 100644 netlink/netlink.c
 create mode 100644 netlink/netlink.h
 create mode 100644 netlink/params.c
 create mode 100644 netlink/parser.c
 create mode 100644 netlink/parser.h
 create mode 100644 netlink/settings.c
 create mode 100644 netlink/strset.c
 create mode 100644 netlink/strset.h
 rename ethtool-copy.h => uapi/linux/ethtool.h (99%)
 create mode 100644 uapi/linux/ethtool_netlink.h
 create mode 100644 uapi/linux/genetlink.h
 rename net_tstamp-copy.h => uapi/linux/net_tstamp.h (89%)
 create mode 100644 uapi/linux/netlink.h

-- 
2.18.0


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [RFC PATCH ethtool v2 01/23] move UAPI header copies to a separate directory
  2018-07-30 12:55 [RFC PATCH ethtool v2 00/23] ethtool netlink interface (userspace side) (WiP) Michal Kubecek
@ 2018-07-30 12:55 ` Michal Kubecek
  2018-07-30 12:55 ` [RFC PATCH ethtool v2 02/23] update UAPI header copies Michal Kubecek
                   ` (21 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Michal Kubecek @ 2018-07-30 12:55 UTC (permalink / raw)
  To: netdev; +Cc: linux-kernel, John W. Linville

The upcoming netlink series is going to add more local copies of kernel
UAPI header files and some of them are going to include others. Keeping
them in the main directory under modified name would require modifying
those includes as well which would be impractical.

Create a subdirectory uapi and move the UAPI headers there to allow
including them in the usual way.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Makefile.am                                  | 6 +++---
 internal.h                                   | 4 ++--
 ethtool-copy.h => uapi/linux/ethtool.h       | 0
 net_tstamp-copy.h => uapi/linux/net_tstamp.h | 0
 4 files changed, 5 insertions(+), 5 deletions(-)
 rename ethtool-copy.h => uapi/linux/ethtool.h (100%)
 rename net_tstamp-copy.h => uapi/linux/net_tstamp.h (100%)

diff --git a/Makefile.am b/Makefile.am
index 14f79b68a9ee..e7da37a85499 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,12 +1,12 @@
-AM_CFLAGS = -Wall
+AM_CFLAGS = -I./uapi -Wall
 LDADD = -lm
 
 man_MANS = ethtool.8
 EXTRA_DIST = LICENSE ethtool.8 ethtool.spec.in aclocal.m4 ChangeLog autogen.sh
 
 sbin_PROGRAMS = ethtool
-ethtool_SOURCES = ethtool.c ethtool-copy.h internal.h net_tstamp-copy.h \
-		  rxclass.c
+ethtool_SOURCES = ethtool.c uapi/linux/ethtool.h internal.h \
+		  uapi/linux/net_tstamp.h rxclass.c
 if ETHTOOL_ENABLE_PRETTY_DUMP
 ethtool_SOURCES += \
 		  amd8111e.c de2104x.c e100.c e1000.c et131x.c igb.c	\
diff --git a/internal.h b/internal.h
index b239dc7acbd8..0b04a9794b6b 100644
--- a/internal.h
+++ b/internal.h
@@ -42,8 +42,8 @@ typedef int32_t s32;
 #define __KERNEL_DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d))
 #endif
 
-#include "ethtool-copy.h"
-#include "net_tstamp-copy.h"
+#include <linux/ethtool.h>
+#include <linux/net_tstamp.h>
 
 #if __BYTE_ORDER == __BIG_ENDIAN
 static inline u16 cpu_to_be16(u16 value)
diff --git a/ethtool-copy.h b/uapi/linux/ethtool.h
similarity index 100%
rename from ethtool-copy.h
rename to uapi/linux/ethtool.h
diff --git a/net_tstamp-copy.h b/uapi/linux/net_tstamp.h
similarity index 100%
rename from net_tstamp-copy.h
rename to uapi/linux/net_tstamp.h
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [RFC PATCH ethtool v2 02/23] update UAPI header copies
  2018-07-30 12:55 [RFC PATCH ethtool v2 00/23] ethtool netlink interface (userspace side) (WiP) Michal Kubecek
  2018-07-30 12:55 ` [RFC PATCH ethtool v2 01/23] move UAPI header copies to a separate directory Michal Kubecek
@ 2018-07-30 12:55 ` Michal Kubecek
  2018-07-30 12:55 ` [RFC PATCH ethtool v2 03/23] netlink: add netlink interface Michal Kubecek
                   ` (20 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Michal Kubecek @ 2018-07-30 12:55 UTC (permalink / raw)
  To: netdev; +Cc: linux-kernel, John W. Linville

Update to current net-next (commit 3bc950c34b28).

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 uapi/linux/ethtool.h    |  6 +++++-
 uapi/linux/net_tstamp.h | 19 +++++++++++++++++++
 2 files changed, 24 insertions(+), 1 deletion(-)

diff --git a/uapi/linux/ethtool.h b/uapi/linux/ethtool.h
index 8cc61e9ab40b..9dd347caf4ed 100644
--- a/uapi/linux/ethtool.h
+++ b/uapi/linux/ethtool.h
@@ -215,12 +215,16 @@ struct ethtool_value {
 	__u32	data;
 };
 
+#define PFC_STORM_PREVENTION_AUTO	0xffff
+#define PFC_STORM_PREVENTION_DISABLE	0
+
 enum tunable_id {
 	ETHTOOL_ID_UNSPEC,
 	ETHTOOL_RX_COPYBREAK,
 	ETHTOOL_TX_COPYBREAK,
+	ETHTOOL_PFC_PREVENTION_TOUT, /* timeout in msecs */
 	/*
-	 * Add your fresh new tubale attribute above and remember to update
+	 * Add your fresh new tunable attribute above and remember to update
 	 * tunable_strings[] in net/core/ethtool.c
 	 */
 	__ETHTOOL_TUNABLE_COUNT,
diff --git a/uapi/linux/net_tstamp.h b/uapi/linux/net_tstamp.h
index 3d421d912193..97ff3c17ec4d 100644
--- a/uapi/linux/net_tstamp.h
+++ b/uapi/linux/net_tstamp.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
 /*
  * Userspace API for hardware time stamping of network packets
  *
@@ -140,4 +141,22 @@ struct scm_ts_pktinfo {
 	__u32 reserved[2];
 };
 
+/*
+ * SO_TXTIME gets a struct sock_txtime with flags being an integer bit
+ * field comprised of these values.
+ */
+enum txtime_flags {
+	SOF_TXTIME_DEADLINE_MODE = (1 << 0),
+	SOF_TXTIME_REPORT_ERRORS = (1 << 1),
+
+	SOF_TXTIME_FLAGS_LAST = SOF_TXTIME_REPORT_ERRORS,
+	SOF_TXTIME_FLAGS_MASK = (SOF_TXTIME_FLAGS_LAST - 1) |
+				 SOF_TXTIME_FLAGS_LAST
+};
+
+struct sock_txtime {
+	clockid_t	clockid;	/* reference clockid */
+	__u32		flags;		/* as defined by enum txtime_flags */
+};
+
 #endif /* _NET_TIMESTAMPING_H */
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [RFC PATCH ethtool v2 03/23] netlink: add netlink interface
  2018-07-30 12:55 [RFC PATCH ethtool v2 00/23] ethtool netlink interface (userspace side) (WiP) Michal Kubecek
  2018-07-30 12:55 ` [RFC PATCH ethtool v2 01/23] move UAPI header copies to a separate directory Michal Kubecek
  2018-07-30 12:55 ` [RFC PATCH ethtool v2 02/23] update UAPI header copies Michal Kubecek
@ 2018-07-30 12:55 ` Michal Kubecek
  2018-07-30 12:56 ` [RFC PATCH ethtool v2 04/23] netlink: add support for string sets Michal Kubecek
                   ` (19 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Michal Kubecek @ 2018-07-30 12:55 UTC (permalink / raw)
  To: netdev; +Cc: linux-kernel, John W. Linville

Add basic netlink interface based on genetlink and libmnl. This commit only
adds the generic infrastructure but does not override any ethtool command
(so that there is no actual behaviour change).

Netlink handlers for ethtool subcommands are added as nl_func members to
args array in ethtool.c. Netlink handler is used if it is available (i.e.
nl_func is not null) and ethtool succeeds to open a netlink socket and get
id of genetlink family "ethtool". At the moment, all nl_func are null so
that ioctl() is always used.

Running configure with --disable-netlink disables netlink interface
completely.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Makefile.am                  |  11 +
 configure.ac                 |  14 +-
 ethtool.c                    | 178 ++++++++----
 internal.h                   |   8 +
 netlink/extapi.h             |  16 ++
 netlink/netlink.c            | 522 +++++++++++++++++++++++++++++++++++
 netlink/netlink.h            | 154 +++++++++++
 uapi/linux/ethtool.h         |   7 +
 uapi/linux/ethtool_netlink.h | 325 ++++++++++++++++++++++
 uapi/linux/genetlink.h       |  89 ++++++
 uapi/linux/netlink.h         | 247 +++++++++++++++++
 11 files changed, 1514 insertions(+), 57 deletions(-)
 create mode 100644 netlink/extapi.h
 create mode 100644 netlink/netlink.c
 create mode 100644 netlink/netlink.h
 create mode 100644 uapi/linux/ethtool_netlink.h
 create mode 100644 uapi/linux/genetlink.h
 create mode 100644 uapi/linux/netlink.h

diff --git a/Makefile.am b/Makefile.am
index e7da37a85499..bdb4f4df2b0f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -7,6 +7,8 @@ EXTRA_DIST = LICENSE ethtool.8 ethtool.spec.in aclocal.m4 ChangeLog autogen.sh
 sbin_PROGRAMS = ethtool
 ethtool_SOURCES = ethtool.c uapi/linux/ethtool.h internal.h \
 		  uapi/linux/net_tstamp.h rxclass.c
+ethtool_CFLAGS = -I./uapi -Wall
+ethtool_LDADD =  -lm
 if ETHTOOL_ENABLE_PRETTY_DUMP
 ethtool_SOURCES += \
 		  amd8111e.c de2104x.c e100.c e1000.c et131x.c igb.c	\
@@ -17,6 +19,15 @@ ethtool_SOURCES += \
 		  ixgbevf.c tse.c vmxnet3.c qsfp.c qsfp.h fjes.c lan78xx.c
 endif
 
+if ETHTOOL_ENABLE_NETLINK
+ethtool_SOURCES += \
+		  netlink/netlink.c netlink/netlink.h netlink/extapi.h \
+		  uapi/linux/ethtool_netlink.h \
+		  uapi/linux/netlink.h uapi/linux/genetlink.h
+ethtool_CFLAGS += @MNL_CFLAGS@
+ethtool_LDADD += @MNL_LIBS@
+endif
+
 TESTS = test-cmdline test-features
 check_PROGRAMS = test-cmdline test-features
 test_cmdline_SOURCES = test-cmdline.c test-common.c $(ethtool_SOURCES) 
diff --git a/configure.ac b/configure.ac
index e891d917cf11..138039d49a2c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@ dnl Process this file with autoconf to produce a configure script.
 AC_INIT(ethtool, 4.17, netdev@vger.kernel.org)
 AC_PREREQ(2.52)
 AC_CONFIG_SRCDIR([ethtool.c])
-AM_INIT_AUTOMAKE([gnu])
+AM_INIT_AUTOMAKE([gnu subdir-objects])
 AC_CONFIG_HEADERS([ethtool-config.h])
 
 AM_MAINTAINER_MODE
@@ -40,5 +40,17 @@ if test x$enable_pretty_dump = xyes; then
 fi
 AM_CONDITIONAL([ETHTOOL_ENABLE_PRETTY_DUMP], [test x$enable_pretty_dump = xyes])
 
+AC_ARG_ENABLE(netlink,
+	      [  --enable-netlink	  enable netlink interface (enabled by default)],
+	      ,
+	      enable_netlink=yes)
+if test x$enable_netlink = xyes; then
+    PKG_PROG_PKG_CONFIG
+    PKG_CHECK_MODULES([MNL], [libmnl])
+    AC_DEFINE(ETHTOOL_ENABLE_NETLINK, 1,
+	      Define this to support netlink interface to talk to kernel.)
+fi
+AM_CONDITIONAL([ETHTOOL_ENABLE_NETLINK], [test x$enable_netlink = xyes])
+
 AC_CONFIG_FILES([Makefile ethtool.spec ethtool.8])
 AC_OUTPUT
diff --git a/ethtool.c b/ethtool.c
index fb93ae898312..e8044a6af76c 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -48,6 +48,10 @@
 #include <linux/sockios.h>
 #include <linux/netlink.h>
 
+#ifdef ETHTOOL_ENABLE_NETLINK
+#include "netlink/extapi.h"
+#endif
+
 #ifndef MAX_ADDR_LEN
 #define MAX_ADDR_LEN	32
 #endif
@@ -5048,14 +5052,22 @@ int send_ioctl(struct cmd_context *ctx, void *cmd)
 
 static int show_usage(struct cmd_context *ctx);
 
+#ifndef ETHTOOL_ENABLE_NETLINK
+/* Just define all netlink handlers as null when building without netlink
+ * support so that we do not get unresolved symbols in args array below
+ */
+#endif
+
 static const struct option {
 	const char *opts;
 	int want_device;
 	int (*func)(struct cmd_context *);
+	int (*nl_func)(struct cmd_context *);
 	char *help;
 	char *opthelp;
 } args[] = {
-	{ "-s|--change", 1, do_sset, "Change generic options",
+	{ "-s|--change", 1, do_sset, NULL,
+	  "Change generic options",
 	  "		[ speed %d ]\n"
 	  "		[ duplex half|full ]\n"
 	  "		[ port tp|aui|bnc|mii|fibre ]\n"
@@ -5067,13 +5079,17 @@ static const struct option {
 	  "		[ wol p|u|m|b|a|g|s|d... ]\n"
 	  "		[ sopass %x:%x:%x:%x:%x:%x ]\n"
 	  "		[ msglvl %d | msglvl type on|off ... ]\n" },
-	{ "-a|--show-pause", 1, do_gpause, "Show pause options" },
-	{ "-A|--pause", 1, do_spause, "Set pause options",
+	{ "-a|--show-pause", 1, do_gpause, NULL,
+	  "Show pause options" },
+	{ "-A|--pause", 1, do_spause, NULL,
+	  "Set pause options",
 	  "		[ autoneg on|off ]\n"
 	  "		[ rx on|off ]\n"
 	  "		[ tx on|off ]\n" },
-	{ "-c|--show-coalesce", 1, do_gcoalesce, "Show coalesce options" },
-	{ "-C|--coalesce", 1, do_scoalesce, "Set coalesce options",
+	{ "-c|--show-coalesce", 1, do_gcoalesce, NULL,
+	  "Show coalesce options" },
+	{ "-C|--coalesce", 1, do_scoalesce, NULL,
+	  "Set coalesce options",
 	  "		[adaptive-rx on|off]\n"
 	  "		[adaptive-tx on|off]\n"
 	  "		[rx-usecs N]\n"
@@ -5096,46 +5112,54 @@ static const struct option {
 	  "		[tx-usecs-high N]\n"
 	  "		[tx-frames-high N]\n"
 	  "		[sample-interval N]\n" },
-	{ "-g|--show-ring", 1, do_gring, "Query RX/TX ring parameters" },
-	{ "-G|--set-ring", 1, do_sring, "Set RX/TX ring parameters",
+	{ "-g|--show-ring", 1, do_gring, NULL,
+	  "Query RX/TX ring parameters" },
+	{ "-G|--set-ring", 1, do_sring, NULL,
+	  "Set RX/TX ring parameters",
 	  "		[ rx N ]\n"
 	  "		[ rx-mini N ]\n"
 	  "		[ rx-jumbo N ]\n"
 	  "		[ tx N ]\n" },
-	{ "-k|--show-features|--show-offload", 1, do_gfeatures,
+	{ "-k|--show-features|--show-offload", 1, do_gfeatures, NULL,
 	  "Get state of protocol offload and other features" },
-	{ "-K|--features|--offload", 1, do_sfeatures,
+	{ "-K|--features|--offload", 1, do_sfeatures, NULL,
 	  "Set protocol offload and other features",
 	  "		FEATURE on|off ...\n" },
-	{ "-i|--driver", 1, do_gdrv, "Show driver information" },
-	{ "-d|--register-dump", 1, do_gregs, "Do a register dump",
+	{ "-i|--driver", 1, do_gdrv, NULL,
+	  "Show driver information" },
+	{ "-d|--register-dump", 1, do_gregs, NULL,
+	  "Do a register dump",
 	  "		[ raw on|off ]\n"
 	  "		[ file FILENAME ]\n" },
-	{ "-e|--eeprom-dump", 1, do_geeprom, "Do a EEPROM dump",
+	{ "-e|--eeprom-dump", 1, do_geeprom, NULL,
+	  "Do a EEPROM dump",
 	  "		[ raw on|off ]\n"
 	  "		[ offset N ]\n"
 	  "		[ length N ]\n" },
-	{ "-E|--change-eeprom", 1, do_seeprom,
+	{ "-E|--change-eeprom", 1, do_seeprom, NULL,
 	  "Change bytes in device EEPROM",
 	  "		[ magic N ]\n"
 	  "		[ offset N ]\n"
 	  "		[ length N ]\n"
 	  "		[ value N ]\n" },
-	{ "-r|--negotiate", 1, do_nway_rst, "Restart N-WAY negotiation" },
-	{ "-p|--identify", 1, do_phys_id,
+	{ "-r|--negotiate", 1, do_nway_rst, NULL,
+	  "Restart N-WAY negotiation" },
+	{ "-p|--identify", 1, do_phys_id, NULL,
 	  "Show visible port identification (e.g. blinking)",
 	  "               [ TIME-IN-SECONDS ]\n" },
-	{ "-t|--test", 1, do_test, "Execute adapter self test",
+	{ "-t|--test", 1, do_test, NULL,
+	  "Execute adapter self test",
 	  "               [ online | offline | external_lb ]\n" },
-	{ "-S|--statistics", 1, do_gnicstats, "Show adapter statistics" },
-	{ "--phy-statistics", 1, do_gphystats,
+	{ "-S|--statistics", 1, do_gnicstats, NULL,
+	  "Show adapter statistics" },
+	{ "--phy-statistics", 1, do_gphystats, NULL,
 	  "Show phy statistics" },
-	{ "-n|-u|--show-nfc|--show-ntuple", 1, do_grxclass,
+	{ "-n|-u|--show-nfc|--show-ntuple", 1, do_grxclass, NULL,
 	  "Show Rx network flow classification options or rules",
 	  "		[ rx-flow-hash tcp4|udp4|ah4|esp4|sctp4|"
 	  "tcp6|udp6|ah6|esp6|sctp6 [context %d] |\n"
 	  "		  rule %d ]\n" },
-	{ "-N|-U|--config-nfc|--config-ntuple", 1, do_srxclass,
+	{ "-N|-U|--config-nfc|--config-ntuple", 1, do_srxclass, NULL,
 	  "Configure Rx network flow classification options or rules",
 	  "		rx-flow-hash tcp4|udp4|ah4|esp4|sctp4|"
 	  "tcp6|udp6|ah6|esp6|sctp6 m|v|t|s|d|f|n|r... [context %d] |\n"
@@ -5160,55 +5184,64 @@ static const struct option {
 	  "			[ context %d ]\n"
 	  "			[ loc %d]] |\n"
 	  "		delete %d\n" },
-	{ "-T|--show-time-stamping", 1, do_tsinfo,
+	{ "-T|--show-time-stamping", 1, do_tsinfo, NULL,
 	  "Show time stamping capabilities" },
-	{ "-x|--show-rxfh-indir|--show-rxfh", 1, do_grxfh,
+	{ "-x|--show-rxfh-indir|--show-rxfh", 1, do_grxfh, NULL,
 	  "Show Rx flow hash indirection table and/or RSS hash key",
 	  "		[ context %d ]\n" },
-	{ "-X|--set-rxfh-indir|--rxfh", 1, do_srxfh,
+	{ "-X|--set-rxfh-indir|--rxfh", 1, do_srxfh, NULL,
 	  "Set Rx flow hash indirection table and/or RSS hash key",
 	  "		[ context %d|new ]\n"
 	  "		[ equal N | weight W0 W1 ... | default ]\n"
 	  "		[ hkey %x:%x:%x:%x:%x:.... ]\n"
 	  "		[ hfunc FUNC ]\n"
 	  "		[ delete ]\n" },
-	{ "-f|--flash", 1, do_flash,
+	{ "-f|--flash", 1, do_flash, NULL,
 	  "Flash firmware image from the specified file to a region on the device",
 	  "               FILENAME [ REGION-NUMBER-TO-FLASH ]\n" },
-	{ "-P|--show-permaddr", 1, do_permaddr,
+	{ "-P|--show-permaddr", 1, do_permaddr, NULL,
 	  "Show permanent hardware address" },
-	{ "-w|--get-dump", 1, do_getfwdump,
+	{ "-w|--get-dump", 1, do_getfwdump, NULL,
 	  "Get dump flag, data",
 	  "		[ data FILENAME ]\n" },
-	{ "-W|--set-dump", 1, do_setfwdump,
+	{ "-W|--set-dump", 1, do_setfwdump, NULL,
 	  "Set dump flag of the device",
 	  "		N\n"},
-	{ "-l|--show-channels", 1, do_gchannels, "Query Channels" },
-	{ "-L|--set-channels", 1, do_schannels, "Set Channels",
+	{ "-l|--show-channels", 1, do_gchannels, NULL,
+	  "Query Channels" },
+	{ "-L|--set-channels", 1, do_schannels, NULL,
+	  "Set Channels",
 	  "               [ rx N ]\n"
 	  "               [ tx N ]\n"
 	  "               [ other N ]\n"
 	  "               [ combined N ]\n" },
-	{ "--show-priv-flags", 1, do_gprivflags, "Query private flags" },
-	{ "--set-priv-flags", 1, do_sprivflags, "Set private flags",
+	{ "--show-priv-flags", 1, do_gprivflags, NULL,
+	  "Query private flags" },
+	{ "--set-priv-flags", 1, do_sprivflags, NULL,
+	  "Set private flags",
 	  "		FLAG on|off ...\n" },
-	{ "-m|--dump-module-eeprom|--module-info", 1, do_getmodule,
+	{ "-m|--dump-module-eeprom|--module-info", 1, do_getmodule, NULL,
 	  "Query/Decode Module EEPROM information and optical diagnostics if available",
 	  "		[ raw on|off ]\n"
 	  "		[ hex on|off ]\n"
 	  "		[ offset N ]\n"
 	  "		[ length N ]\n" },
-	{ "--show-eee", 1, do_geee, "Show EEE settings"},
-	{ "--set-eee", 1, do_seee, "Set EEE settings",
+	{ "--show-eee", 1, do_geee, NULL,
+	  "Show EEE settings"},
+	{ "--set-eee", 1, do_seee, NULL,
+	  "Set EEE settings",
 	  "		[ eee on|off ]\n"
 	  "		[ advertise %x ]\n"
 	  "		[ tx-lpi on|off ]\n"
 	  "		[ tx-timer %d ]\n"},
-	{ "--set-phy-tunable", 1, do_set_phy_tunable, "Set PHY tunable",
+	{ "--set-phy-tunable", 1, do_set_phy_tunable, NULL,
+	  "Set PHY tunable",
 	  "		[ downshift on|off [count N] ]\n"},
-	{ "--get-phy-tunable", 1, do_get_phy_tunable, "Get PHY tunable",
+	{ "--get-phy-tunable", 1, do_get_phy_tunable, NULL,
+	  "Get PHY tunable",
 	  "		[ downshift ]\n"},
-	{ "--reset", 1, do_reset, "Reset components",
+	{ "--reset", 1, do_reset, NULL,
+	  "Reset components",
 	  "		[ flags %x ]\n"
 	  "		[ mgmt ]\n"
 	  "		[ mgmt-shared ]\n"
@@ -5230,11 +5263,15 @@ static const struct option {
 	  "		[ ap-shared ]\n"
 	  "		[ dedicated ]\n"
 	  "		[ all ]\n"},
-	{ "--show-fec", 1, do_gfec, "Show FEC settings"},
-	{ "--set-fec", 1, do_sfec, "Set FEC settings",
+	{ "--show-fec", 1, do_gfec, NULL,
+	  "Show FEC settings"},
+	{ "--set-fec", 1, do_sfec, NULL,
+	  "Set FEC settings",
 	  "		[ encoding auto|off|rs|baser ]\n"},
-	{ "-h|--help", 0, show_usage, "Show this help" },
-	{ "--version", 0, do_version, "Show version number" },
+	{ "-h|--help", 0, show_usage, NULL,
+	 "Show this help" },
+	{ "--version", 0, do_version, NULL,
+	  "Show version number" },
 	{}
 };
 
@@ -5261,11 +5298,35 @@ static int show_usage(struct cmd_context *ctx)
 	return 0;
 }
 
+static int ioctl_init(struct cmd_context *ctx, int want_device)
+{
+	if (want_device) {
+		/* Setup our control structures. */
+		memset(&ctx->ifr, 0, sizeof(ctx->ifr));
+		strcpy(ctx->ifr.ifr_name, ctx->devname);
+
+		/* Open control socket. */
+		ctx->fd = socket(AF_INET, SOCK_DGRAM, 0);
+		if (ctx->fd < 0)
+			ctx->fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
+		if (ctx->fd < 0) {
+			perror("Cannot get control socket");
+			return 70;
+		}
+	} else {
+		ctx->fd = -1;
+	}
+
+	return 0;
+}
+
 int main(int argc, char **argp)
 {
 	int (*func)(struct cmd_context *);
+	int (*nl_func)(struct cmd_context *) = NULL;
 	int want_device;
 	struct cmd_context ctx;
+	int ret;
 	int k;
 
 	init_global_link_mode_masks();
@@ -5291,6 +5352,7 @@ int main(int argc, char **argp)
 				argp++;
 				argc--;
 				func = args[k].func;
+				nl_func = args[k].nl_func;
 				want_device = args[k].want_device;
 				goto opt_found;
 			}
@@ -5305,6 +5367,15 @@ int main(int argc, char **argp)
 	want_device = 1;
 
 opt_found:
+#ifdef ETHTOOL_ENABLE_NETLINK
+	if (nl_func) {
+		if (netlink_init(&ctx))
+			nl_func = NULL;		/* fallback to ioctl() */
+	}
+#else
+	nl_func = NULL;
+#endif
+
 	if (want_device) {
 		ctx.devname = *argp++;
 		argc--;
@@ -5313,25 +5384,20 @@ opt_found:
 			exit_bad_args();
 		if (strlen(ctx.devname) >= IFNAMSIZ)
 			exit_bad_args();
-
-		/* Setup our control structures. */
-		memset(&ctx.ifr, 0, sizeof(ctx.ifr));
-		strcpy(ctx.ifr.ifr_name, ctx.devname);
-
-		/* Open control socket. */
-		ctx.fd = socket(AF_INET, SOCK_DGRAM, 0);
-		if (ctx.fd < 0)
-			ctx.fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
-		if (ctx.fd < 0) {
-			perror("Cannot get control socket");
-			return 70;
-		}
-	} else {
-		ctx.fd = -1;
 	}
 
 	ctx.argc = argc;
 	ctx.argp = argp;
 
+	if (nl_func) {
+		ret = nl_func(&ctx);
+		if ((ret != -EOPNOTSUPP) || !func)
+			return (ret >= 0) ? ret : 1;
+	}
+
+	ret = ioctl_init(&ctx, want_device);
+	if (ret)
+		return ret;
+
 	return func(&ctx);
 }
diff --git a/internal.h b/internal.h
index 0b04a9794b6b..f7feade9f57e 100644
--- a/internal.h
+++ b/internal.h
@@ -23,6 +23,11 @@
 #include <sys/ioctl.h>
 #include <net/if.h>
 
+/* internal for netlink interface */
+#ifdef ETHTOOL_ENABLE_NETLINK
+struct nl_context;
+#endif
+
 /* ethtool.h expects these to be defined by <linux/types.h> */
 #ifndef HAVE_BE_TYPES
 typedef uint16_t __be16;
@@ -195,6 +200,9 @@ struct cmd_context {
 	struct ifreq ifr;	/* ifreq suitable for ethtool ioctl */
 	int argc;		/* number of arguments to the sub-command */
 	char **argp;		/* arguments to the sub-command */
+#ifdef ETHTOOL_ENABLE_NETLINK
+	struct nl_context *nlctx;	/* netlink context (opaque) */
+#endif
 };
 
 #ifdef TEST_ETHTOOL
diff --git a/netlink/extapi.h b/netlink/extapi.h
new file mode 100644
index 000000000000..05ab083d9b9c
--- /dev/null
+++ b/netlink/extapi.h
@@ -0,0 +1,16 @@
+/*
+ * extapi.h - external netlink interface
+ *
+ * interface for general non-netlink code
+ */
+
+#ifndef ETHTOOL_EXTAPI_H__
+#define ETHTOOL_EXTAPI_H__
+
+struct cmd_context;
+struct nl_context;
+
+int netlink_init(struct cmd_context *ctx);
+int netlink_done(struct cmd_context *ctx);
+
+#endif /* ETHTOOL_EXTAPI_H__ */
diff --git a/netlink/netlink.c b/netlink/netlink.c
new file mode 100644
index 000000000000..0671c4589c72
--- /dev/null
+++ b/netlink/netlink.c
@@ -0,0 +1,522 @@
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+
+#include "../internal.h"
+#include "netlink.h"
+#include "extapi.h"
+
+/* misc helpers */
+
+unsigned int nl_copy_payload(void *buff, unsigned int maxlen,
+			     const struct nlattr *attr)
+{
+	unsigned int len = mnl_attr_get_payload_len(attr);
+
+	if (len > maxlen)
+		len = maxlen;
+	memcpy(buff, mnl_attr_get_payload(attr), len);
+
+	return len;
+}
+
+/* standard attribute parser callback
+ * While we trust kernel not to send us malformed messages, we must expect
+ * to run on top of newer kernel which may send attributes that we do not
+ * know (yet). Rather than treating them as an error, just ignore them.
+ */
+int attr_cb(const struct nlattr *attr, void *data)
+{
+	const struct attr_tb_info *tb_info = data;
+	int type = mnl_attr_get_type(attr);
+
+	if (type >= 0 && type <= tb_info->max_type)
+		tb_info->tb[type] = attr;
+
+	return MNL_CB_OK;
+}
+
+uint32_t bitset_get_count(const struct nlattr *bitset, int *retptr)
+{
+	const struct nlattr *attr;
+
+	mnl_attr_for_each_nested(attr, bitset) {
+		if (mnl_attr_get_type(attr) != ETHA_BITSET_SIZE)
+			continue;
+		*retptr = 0;
+		return mnl_attr_get_u32(attr);
+	}
+
+	*retptr = -EFAULT;
+	return 0;
+}
+
+bool bitset_get_bit(const struct nlattr *bitset, bool mask, unsigned int idx,
+		    int *retptr)
+{
+	const struct nlattr *bitset_tb[ETHA_BITSET_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(bitset_tb);
+	const struct nlattr *bits;
+	const struct nlattr *bit;
+	int ret;
+
+	*retptr = 0;
+	ret = mnl_attr_parse_nested(bitset, attr_cb, &bitset_tb_info);
+	if (ret < 0)
+		goto err;
+
+	bits = mask ? bitset_tb[ETHA_BITSET_MASK] :
+		      bitset_tb[ETHA_BITSET_VALUE];
+	if (bits) {
+		const uint32_t *bitmap =
+			(const uint32_t *)mnl_attr_get_payload(bits);
+
+		if (idx >= 8 * mnl_attr_get_payload_len(bits))
+			return false;
+		return bitmap[idx / 32] & (1U << (idx % 32));
+	}
+
+	bits = bitset_tb[ETHA_BITSET_BITS];
+	if (!bits)
+		goto err;
+	mnl_attr_for_each_nested(bit, bits) {
+		const struct nlattr *tb[ETHA_BIT_MAX + 1] = {};
+		DECLARE_ATTR_TB_INFO(tb);
+		unsigned int my_idx;
+
+		if (mnl_attr_get_type(bit) != ETHA_BITS_BIT)
+			continue;
+		ret = mnl_attr_parse_nested(bit, attr_cb, &tb_info);
+		if (ret < 0)
+			goto err;
+		ret = -EFAULT;
+		if (!tb[ETHA_BIT_INDEX])
+			goto err;
+
+		my_idx = mnl_attr_get_u32(tb[ETHA_BIT_INDEX]);
+		if (my_idx == idx)
+			return mask || tb[ETHA_BIT_VALUE];
+	}
+
+	return false;
+err:
+	fprintf(stderr, "malformed netlink message (bitset)\n");
+	*retptr = ret;
+	return false;
+}
+
+bool bitset_is_empty(const struct nlattr *bitset, bool mask, int *retptr)
+{
+	const struct nlattr *bitset_tb[ETHA_BITSET_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(bitset_tb);
+	const struct nlattr *bits;
+	const struct nlattr *bit;
+	int ret;
+
+	*retptr = 0;
+	ret = mnl_attr_parse_nested(bitset, attr_cb, &bitset_tb_info);
+	if (ret < 0)
+		goto err;
+
+	bits = mask ? bitset_tb[ETHA_BITSET_MASK] :
+		      bitset_tb[ETHA_BITSET_VALUE];
+	if (bits) {
+		const uint32_t *bitmap =
+			(const uint32_t *)mnl_attr_get_payload(bits);
+		unsigned int n = mnl_attr_get_payload_len(bits);
+		unsigned int i;
+
+		ret = -EFAULT;
+		if (n % 4)
+			goto err;
+		for (i = 0; i < n / 4; i++)
+			if (bitmap[i])
+				return false;
+		return true;
+	}
+
+	bits = bitset_tb[ETHA_BITSET_BITS];
+	if (!bits)
+		goto err;
+	mnl_attr_for_each_nested(bit, bits) {
+		const struct nlattr *tb[ETHA_BIT_MAX + 1] = {};
+		DECLARE_ATTR_TB_INFO(tb);
+
+		if (mnl_attr_get_type(bit) != ETHA_BITS_BIT)
+			continue;
+		if (mask)
+			return false;
+
+		ret = mnl_attr_parse_nested(bit, attr_cb, &tb_info);
+		if (ret < 0)
+			goto err;
+		if (tb[ETHA_BIT_VALUE])
+			return false;
+	}
+
+	return true;
+err:
+	fprintf(stderr, "malformed netlink message (bitset)\n");
+	*retptr = ret;
+	return true;
+}
+
+static int __msg_init(struct nl_context *nlctx, int family, int cmd,
+		      unsigned int flags, int version)
+{
+	struct nlmsghdr *nlhdr;
+	struct genlmsghdr *gnlhdr;
+
+	nlctx->buff = malloc(MNL_SOCKET_BUFFER_SIZE);
+	if (!nlctx->buff)
+		return -ENOMEM;
+	nlctx->buffsize = MNL_SOCKET_BUFFER_SIZE;
+	nlctx->seq++;
+	memset(nlctx->buff, '\0', NLMSG_HDRLEN + GENL_HDRLEN);
+
+	nlhdr = mnl_nlmsg_put_header(nlctx->buff);
+	nlhdr->nlmsg_type = family;
+	nlhdr->nlmsg_flags = flags;
+	nlhdr->nlmsg_seq = nlctx->seq;
+	nlctx->nlhdr = nlhdr;
+
+	gnlhdr = mnl_nlmsg_put_extra_header(nlhdr, sizeof(*gnlhdr));
+	gnlhdr->cmd = cmd;
+	gnlhdr->version = version;
+	nlctx->gnlhdr = gnlhdr;
+
+	return 0;
+}
+
+static int msg_realloc(struct nl_context *nlctx, unsigned int new_size)
+{
+	unsigned int nlhdr_offset = (char *)nlctx->nlhdr - nlctx->buff;
+	unsigned int gnlhdr_offset = (char *)nlctx->gnlhdr - nlctx->buff;
+	unsigned int msg_offset = (char *)nlctx->msg - nlctx->buff;
+	unsigned int old_size = nlctx->buffsize;
+	char *new_buff;
+
+	if (!new_size)
+		new_size = old_size + MNL_SOCKET_BUFFER_SIZE;
+	if (new_size > MAX_MSG_SIZE)
+		return -EMSGSIZE;
+	new_buff = realloc(nlctx->buff, new_size);
+	if (!new_buff)
+		return -ENOMEM;
+	if (new_buff != nlctx->buff) {
+		memset(new_buff + old_size, '\0', new_size - old_size);
+		nlctx->nlhdr = (struct nlmsghdr *)(new_buff + nlhdr_offset);
+		nlctx->gnlhdr = (struct genlmsghdr *)(new_buff + gnlhdr_offset);
+		nlctx->msg = new_buff + msg_offset;
+		nlctx->buff = new_buff;
+	}
+	nlctx->buffsize = new_size;
+
+	return 0;
+}
+
+int msg_init(struct nl_context *nlctx, int cmd, unsigned int flags)
+{
+	int ret;
+
+	ret = __msg_init(nlctx, nlctx->ethnl_fam, cmd, flags,
+			 ETHTOOL_GENL_VERSION);
+	if (ret < 0)
+		return ret;
+	nlctx->msg = mnl_nlmsg_get_payload_offset(nlctx->nlhdr, GENL_HDRLEN);
+
+	return 0;
+}
+
+static int ethnl_process_ack(struct nl_context *nlctx, ssize_t len)
+{
+	struct nlmsghdr *nlhdr = (struct nlmsghdr *)nlctx->buff;
+	struct nlmsgerr *nlerr = mnl_nlmsg_get_payload(nlhdr);
+	const struct nlattr *tb[NLMSGERR_ATTR_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	unsigned int tlv_offset = sizeof(*nlerr);
+
+	if (len < NLMSG_HDRLEN + sizeof(*nlerr))
+		return -EFAULT;
+	if (!(nlhdr->nlmsg_flags & NLM_F_ACK_TLVS))
+		goto out;
+	if (!(nlhdr->nlmsg_flags & NLM_F_CAPPED))
+		tlv_offset += MNL_ALIGN(mnl_nlmsg_get_payload_len(&nlerr->msg));
+
+	if (mnl_attr_parse(nlhdr, tlv_offset, attr_cb, &tb_info) < 0)
+		goto out;
+	if (tb[NLMSGERR_ATTR_MSG]) {
+		const char *msg = mnl_attr_get_str(tb[NLMSGERR_ATTR_MSG]);
+
+		fprintf(stderr, "netlink %s: %s\n",
+			nlerr->error ? "error" : "warning", msg);
+	}
+
+out:
+	if (nlerr->error) {
+		errno = -nlerr->error;
+		perror("netlink error");
+	}
+	return nlerr->error;
+}
+
+int ethnl_process_reply(struct nl_context *nlctx, mnl_cb_t reply_cb)
+{
+	struct nlmsghdr *nlhdr;
+	ssize_t len;
+	int ret;
+
+	do {
+		msg_realloc(nlctx, 65536);
+		len = mnl_socket_recvfrom(nlctx->sk, nlctx->buff,
+					  nlctx->buffsize);
+		if (len <= 0)
+			return (len ? -EFAULT : 0);
+		if (len < NLMSG_HDRLEN)
+			return -EFAULT;
+
+		nlhdr = (struct nlmsghdr *)nlctx->buff;
+		if (nlhdr->nlmsg_type == NLMSG_ERROR)
+			return ethnl_process_ack(nlctx, len);
+
+		nlctx->nlhdr = nlhdr;
+		nlctx->gnlhdr = mnl_nlmsg_get_payload(nlhdr);
+		nlctx->msg = mnl_nlmsg_get_payload_offset(nlhdr, GENL_HDRLEN);
+		ret = mnl_cb_run(nlctx->buff, len, nlctx->seq, nlctx->port,
+				 reply_cb, nlctx);
+	} while (ret > 0);
+
+	return ret;
+}
+
+/* safe message composition */
+
+bool ethnla_put(struct nl_context *nlctx, uint16_t type, size_t len,
+		const void *data)
+{
+	struct nlmsghdr *nlhdr = nlctx->nlhdr;
+
+	while (!mnl_attr_put_check(nlhdr, nlctx->buffsize, type, len, data)) {
+		int ret = msg_realloc(nlctx, 0);
+
+		if (ret < 0)
+			return true;
+	}
+
+	return false;
+}
+
+struct nlattr *ethnla_nest_start(struct nl_context *nlctx, uint16_t type)
+{
+	struct nlmsghdr *nlhdr = nlctx->nlhdr;
+	struct nlattr *attr;
+
+	do {
+		attr = mnl_attr_nest_start_check(nlhdr, nlctx->buffsize, type);
+		if (attr)
+			return attr;
+	} while (msg_realloc(nlctx, 0) == 0);
+
+	return NULL;
+}
+
+bool ethnla_put_dev(struct nl_context *nlctx, uint16_t type,
+		    const char *devname)
+{
+	struct nlattr *nest = ethnla_nest_start(nlctx, type);
+	struct nlmsghdr *nlhdr = nlctx->nlhdr;
+
+	if (!nest)
+		return true;
+	if (ethnla_put_strz(nlctx, ETHA_DEV_NAME, devname)) {
+		mnl_attr_nest_cancel(nlhdr, nest);
+		return true;
+	}
+	mnl_attr_nest_end(nlhdr, nest);
+
+	return false;
+}
+
+const char *get_dev_name(const struct nlattr *nest)
+{
+	const struct nlattr *dev_tb[ETHA_DEV_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(dev_tb);
+	int ret;
+
+	if (!nest)
+		return NULL;
+	ret = mnl_attr_parse_nested(nest, attr_cb, &dev_tb_info);
+	if (ret < 0 || !dev_tb[ETHA_DEV_NAME])
+		return "(none)";
+	return mnl_attr_get_str(dev_tb[ETHA_DEV_NAME]);
+}
+
+/* request helpers */
+
+int ethnl_prep_get_request(struct cmd_context *ctx, unsigned int nlcmd,
+			   uint16_t dev_attrtype)
+{
+	bool is_dev = ctx->devname && strcmp(ctx->devname, WILDCARD_DEVNAME);
+	struct nl_context *nlctx = ctx->nlctx;
+	int ret;
+
+	nlctx->is_dump = !is_dev;
+	ret = msg_init(nlctx, nlcmd,
+		       NLM_F_REQUEST | NLM_F_ACK | (is_dev ? 0 : NLM_F_DUMP));
+	if (ret < 0)
+		return ret;
+
+	if (is_dev) {
+		if (ethnla_put_dev(nlctx, dev_attrtype, ctx->devname))
+			return -EMSGSIZE;
+	}
+
+	return 0;
+}
+
+int ethnl_send_get_request(struct nl_context *nlctx, mnl_cb_t cb)
+{
+	int ret;
+
+	ret = ethnl_sendmsg(nlctx);
+	if (ret < 0)
+		goto err;
+	ret = ethnl_process_reply(nlctx, cb);
+	if (ret == 0)
+		return 0;
+err:
+	return nlctx->exit_code ?: 1;
+}
+
+/* get ethtool family id */
+
+static int ethnl_family_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+	struct nl_context *nlctx = data;
+	struct nlattr *attr;
+
+	nlctx->ethnl_fam = 0;
+	mnl_attr_for_each(attr, nlhdr, GENL_HDRLEN) {
+		if (mnl_attr_get_type(attr) == CTRL_ATTR_FAMILY_ID) {
+			nlctx->ethnl_fam = mnl_attr_get_u16(attr);
+			break;
+		}
+	}
+
+	return (nlctx->ethnl_fam ? MNL_CB_OK : MNL_CB_ERROR);
+}
+
+static int get_ethnl_family(struct nl_context *nlctx)
+{
+	int ret;
+
+	ret = __msg_init(nlctx, GENL_ID_CTRL, CTRL_CMD_GETFAMILY,
+			 NLM_F_REQUEST | NLM_F_ACK, 1);
+	if (ret < 0)
+		return ret;
+	mnl_attr_put_strz(nlctx->nlhdr, CTRL_ATTR_FAMILY_NAME,
+			  ETHTOOL_GENL_NAME);
+
+	ethnl_sendmsg(nlctx);
+	ethnl_process_reply(nlctx, ethnl_family_cb);
+
+	return (nlctx->ethnl_fam ? 0 : -EADDRNOTAVAIL);
+}
+
+/* initialization */
+
+static int nlctx_init(struct nl_context *nlctx)
+{
+	int ret;
+	int val;
+
+	memset(nlctx, '\0', sizeof(*nlctx));
+	nlctx->seq = (int)time(NULL);
+	nlctx->sk = mnl_socket_open(NETLINK_GENERIC);
+	if (!nlctx->sk)
+		return -ECONNREFUSED;
+	val = 1;
+	mnl_socket_setsockopt(nlctx->sk, NETLINK_EXT_ACK, &val, sizeof(val));
+	ret = mnl_socket_bind(nlctx->sk, 0, MNL_SOCKET_AUTOPID);
+	if (ret < 0)
+		return ret;
+	nlctx->port = mnl_socket_get_portid(nlctx->sk);
+
+	return 0;
+}
+
+static int nlctx_done(struct nl_context *nlctx)
+{
+	if (nlctx->sk)
+		mnl_socket_close(nlctx->sk);
+	free(nlctx->buff);
+	if (nlctx->aux_nlctx) {
+		nlctx_done(nlctx->aux_nlctx);
+		free(nlctx->aux_nlctx);
+	}
+	free(nlctx->cmd_private);
+	memset(nlctx, '\0', sizeof(*nlctx));
+
+	return 0;
+}
+
+int __init_aux_nlctx(struct nl_context *nlctx)
+{
+	struct nl_context *aux;
+	int ret;
+
+	if (nlctx->aux_nlctx)
+		return 0;
+	aux = malloc(sizeof(*aux));
+	if (!aux)
+		return -ENOMEM;
+	ret = nlctx_init(aux);
+	if (ret < 0) {
+		free(aux);
+		return ret;
+	}
+
+	aux->ethnl_fam = nlctx->ethnl_fam;
+	nlctx->aux_nlctx = aux;
+
+	return 0;
+}
+
+int netlink_init(struct cmd_context *ctx)
+{
+	struct nl_context *nlctx;
+	int ret;
+
+	nlctx = malloc(sizeof(*nlctx));
+	if (!nlctx)
+		return -ENOMEM;
+	ret = nlctx_init(nlctx);
+	if (ret < 0)
+		goto err_freenlctx;
+
+	ret = get_ethnl_family(nlctx);
+	if (ret < 0)
+		goto err_uninit;
+
+	ctx->nlctx = nlctx;
+	return 0;
+
+err_uninit:
+	nlctx_done(nlctx);
+err_freenlctx:
+	free(nlctx);
+	ctx->nlctx = NULL;
+	return ret;
+}
+
+int netlink_done(struct cmd_context *ctx)
+{
+	if (ctx->nlctx) {
+		nlctx_done(ctx->nlctx);
+		free(ctx->nlctx);
+		ctx->nlctx = NULL;
+	}
+
+	return 0;
+}
diff --git a/netlink/netlink.h b/netlink/netlink.h
new file mode 100644
index 000000000000..547c865ed535
--- /dev/null
+++ b/netlink/netlink.h
@@ -0,0 +1,154 @@
+/*
+ * netlink.h - common interface for all netlink code
+ *
+ * Declarations of data structures, global data and helpers for netlink code
+ */
+
+#ifndef ETHTOOL_NETLINK_INT_H__
+#define ETHTOOL_NETLINK_INT_H__
+
+#include <libmnl/libmnl.h>
+#include <linux/netlink.h>
+#include <linux/genetlink.h>
+#include <linux/ethtool_netlink.h>
+
+#define MAX_MSG_SIZE (4 << 20)		/* 4 MB */
+#define WILDCARD_DEVNAME "*"
+
+struct nl_context {
+	int ethnl_fam;
+	struct mnl_socket *sk;
+	struct nl_context *aux_nlctx;
+	void *cmd_private;
+	char *buff;
+	unsigned int buffsize;
+	unsigned int port;
+	unsigned int seq;
+	struct nlmsghdr *nlhdr;
+	struct genlmsghdr *gnlhdr;
+	void *msg;
+	const char *devname;
+	bool is_dump;
+	int exit_code;
+};
+
+struct attr_tb_info {
+	const struct nlattr **tb;
+	unsigned int max_type;
+};
+#define DECLARE_ATTR_TB_INFO(tbl) \
+	struct attr_tb_info tbl ## _info = { (tbl), (MNL_ARRAY_SIZE(tbl) - 1) }
+
+unsigned int nl_copy_payload(void *buff, unsigned int maxlen,
+			     const struct nlattr *attr);
+bool ethnla_put(struct nl_context *nlctx, uint16_t type, size_t len,
+		const void *data);
+struct nlattr *ethnla_nest_start(struct nl_context *nlctx, uint16_t type);
+bool ethnla_put_dev(struct nl_context *nlctx, uint16_t type,
+		    const char *devname);
+const char *get_dev_name(const struct nlattr *nest);
+
+uint32_t bitset_get_count(const struct nlattr *bitset, int *retptr);
+bool bitset_get_bit(const struct nlattr *bitset, bool mask, unsigned int idx,
+		    int *retptr);
+bool bitset_is_empty(const struct nlattr *bitset, bool mask, int *retptr);
+
+int msg_init(struct nl_context *nlctx, int cmd, unsigned int flags);
+int ethnl_process_reply(struct nl_context *nlctx, mnl_cb_t reply_cb);
+int attr_cb(const struct nlattr *attr, void *data);
+int ethnl_prep_get_request(struct cmd_context *ctx, unsigned int nlcmd,
+			   uint16_t dev_attrtype);
+int ethnl_send_get_request(struct nl_context *nlctx, mnl_cb_t cb);
+int __init_aux_nlctx(struct nl_context *nlctx);
+
+/* put data wrappers */
+
+static inline bool ethnla_put_u32(struct nl_context *nlctx, uint16_t type,
+				  u32 data)
+{
+	return ethnla_put(nlctx, type, sizeof(u32), &data);
+}
+
+static inline bool ethnla_put_u8(struct nl_context *nlctx, uint16_t type,
+				 u8 data)
+{
+	return ethnla_put(nlctx, type, sizeof(u8), &data);
+}
+
+static inline bool ethnla_put_flag(struct nl_context *nlctx, uint16_t type,
+				   bool val)
+{
+	if (val)
+		return ethnla_put(nlctx, type, 0, &val);
+	else
+		return false;
+}
+
+static inline bool ethnla_put_bitfield32(struct nl_context *nlctx,
+					 uint16_t type, u32 value, u32 selector)
+{
+	struct nla_bitfield32 val = {
+		.value		= value,
+		.selector	= selector,
+	};
+
+	return ethnla_put(nlctx, type, sizeof(val), &val);
+}
+
+static inline bool ethnla_put_strz(struct nl_context *nlctx, uint16_t type,
+				   const char *data)
+{
+	return ethnla_put(nlctx, type, strlen(data) + 1, data);
+}
+
+static inline ssize_t ethnl_sendmsg(struct nl_context *nlctx)
+{
+	struct nlmsghdr *nlhdr = nlctx->nlhdr;
+
+	return mnl_socket_sendto(nlctx->sk, nlhdr, nlhdr->nlmsg_len);
+}
+
+/* dump helpers */
+
+static inline const char *u8_to_bool(const struct nlattr *attr)
+{
+	if (attr)
+		return mnl_attr_get_u8(attr) ? "on" : "off";
+	else
+		return "n/a";
+}
+
+static inline void show_u32(const struct nlattr *attr, const char *lbl)
+{
+	if (attr)
+		printf("%s%u\n", lbl, mnl_attr_get_u32(attr));
+}
+
+static inline void show_bool(const struct nlattr *attr, const char *lbl)
+{
+	if (attr)
+		printf("%s%s\n", lbl, mnl_attr_get_u8(attr) ? "on" : "off");
+}
+
+static inline void show_string(const struct nlattr **tb, unsigned int idx,
+			       const char *label)
+{
+	printf("%s: %s\n", label, tb[idx] ? mnl_attr_get_str(tb[idx]) : "");
+}
+
+static inline void show_u32_yn(const struct nlattr **tb, unsigned int idx,
+			       const char *label)
+{
+	if (tb[idx])
+		printf("%s: %s\n", label,
+		       mnl_attr_get_u32(tb[idx]) ? "yes" : "no");
+}
+
+/* misc */
+
+static inline int init_aux_nlctx(struct nl_context *nlctx)
+{
+	return nlctx->aux_nlctx ? 0 : __init_aux_nlctx(nlctx);
+}
+
+#endif /* ETHTOOL_NETLINK_INT_H__ */
diff --git a/uapi/linux/ethtool.h b/uapi/linux/ethtool.h
index 9dd347caf4ed..c51a966bb46e 100644
--- a/uapi/linux/ethtool.h
+++ b/uapi/linux/ethtool.h
@@ -141,6 +141,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
@@ -576,6 +579,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/uapi/linux/ethtool_netlink.h b/uapi/linux/ethtool_netlink.h
new file mode 100644
index 000000000000..13882326976e
--- /dev/null
+++ b/uapi/linux/ethtool_netlink.h
@@ -0,0 +1,325 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#ifndef _LINUX_ETHTOOL_NETLINK_H_
+#define _LINUX_ETHTOOL_NETLINK_H_
+
+#include <linux/ethtool.h>
+
+enum {
+	ETHNL_CMD_NOOP,
+	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_GET_SETTINGS,
+	ETHNL_CMD_SET_SETTINGS,
+	ETHNL_CMD_GET_PARAMS,
+	ETHNL_CMD_SET_PARAMS,
+
+	__ETHNL_CMD_MAX,
+	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)
+};
+
+/* 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)
+};
+
+/* 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)
+};
+
+/* 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)
+};
+
+/* 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)
+};
+
+/* 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_FEATURES,			/* nest - ETHA_FEATURES_* */
+
+	__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_FEATURES		0x20
+
+#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_WANT_DIFF,		/* flag */
+
+	__ETHA_FEATURES_MAX,
+	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
+
+#define ETHTOOL_MCGRP_MONITOR_NAME "monitor"
+
+#endif /* _LINUX_ETHTOOL_NETLINK_H_ */
diff --git a/uapi/linux/genetlink.h b/uapi/linux/genetlink.h
new file mode 100644
index 000000000000..1317119cbff8
--- /dev/null
+++ b/uapi/linux/genetlink.h
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_GENERIC_NETLINK_H
+#define __LINUX_GENERIC_NETLINK_H
+
+#include <linux/types.h>
+#include <linux/netlink.h>
+
+#define GENL_NAMSIZ	16	/* length of family name */
+
+#define GENL_MIN_ID	NLMSG_MIN_TYPE
+#define GENL_MAX_ID	1023
+
+struct genlmsghdr {
+	__u8	cmd;
+	__u8	version;
+	__u16	reserved;
+};
+
+#define GENL_HDRLEN	NLMSG_ALIGN(sizeof(struct genlmsghdr))
+
+#define GENL_ADMIN_PERM		0x01
+#define GENL_CMD_CAP_DO		0x02
+#define GENL_CMD_CAP_DUMP	0x04
+#define GENL_CMD_CAP_HASPOL	0x08
+#define GENL_UNS_ADMIN_PERM	0x10
+
+/*
+ * List of reserved static generic netlink identifiers:
+ */
+#define GENL_ID_CTRL		NLMSG_MIN_TYPE
+#define GENL_ID_VFS_DQUOT	(NLMSG_MIN_TYPE + 1)
+#define GENL_ID_PMCRAID		(NLMSG_MIN_TYPE + 2)
+/* must be last reserved + 1 */
+#define GENL_START_ALLOC	(NLMSG_MIN_TYPE + 3)
+
+/**************************************************************************
+ * Controller
+ **************************************************************************/
+
+enum {
+	CTRL_CMD_UNSPEC,
+	CTRL_CMD_NEWFAMILY,
+	CTRL_CMD_DELFAMILY,
+	CTRL_CMD_GETFAMILY,
+	CTRL_CMD_NEWOPS,
+	CTRL_CMD_DELOPS,
+	CTRL_CMD_GETOPS,
+	CTRL_CMD_NEWMCAST_GRP,
+	CTRL_CMD_DELMCAST_GRP,
+	CTRL_CMD_GETMCAST_GRP, /* unused */
+	__CTRL_CMD_MAX,
+};
+
+#define CTRL_CMD_MAX (__CTRL_CMD_MAX - 1)
+
+enum {
+	CTRL_ATTR_UNSPEC,
+	CTRL_ATTR_FAMILY_ID,
+	CTRL_ATTR_FAMILY_NAME,
+	CTRL_ATTR_VERSION,
+	CTRL_ATTR_HDRSIZE,
+	CTRL_ATTR_MAXATTR,
+	CTRL_ATTR_OPS,
+	CTRL_ATTR_MCAST_GROUPS,
+	__CTRL_ATTR_MAX,
+};
+
+#define CTRL_ATTR_MAX (__CTRL_ATTR_MAX - 1)
+
+enum {
+	CTRL_ATTR_OP_UNSPEC,
+	CTRL_ATTR_OP_ID,
+	CTRL_ATTR_OP_FLAGS,
+	__CTRL_ATTR_OP_MAX,
+};
+
+#define CTRL_ATTR_OP_MAX (__CTRL_ATTR_OP_MAX - 1)
+
+enum {
+	CTRL_ATTR_MCAST_GRP_UNSPEC,
+	CTRL_ATTR_MCAST_GRP_NAME,
+	CTRL_ATTR_MCAST_GRP_ID,
+	__CTRL_ATTR_MCAST_GRP_MAX,
+};
+
+#define CTRL_ATTR_MCAST_GRP_MAX (__CTRL_ATTR_MCAST_GRP_MAX - 1)
+
+
+#endif /* __LINUX_GENERIC_NETLINK_H */
diff --git a/uapi/linux/netlink.h b/uapi/linux/netlink.h
new file mode 100644
index 000000000000..0b2c29bd081f
--- /dev/null
+++ b/uapi/linux/netlink.h
@@ -0,0 +1,247 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_NETLINK_H
+#define __LINUX_NETLINK_H
+
+#include <linux/kernel.h>
+#include <linux/socket.h> /* for __kernel_sa_family_t */
+#include <linux/types.h>
+
+#define NETLINK_ROUTE		0	/* Routing/device hook				*/
+#define NETLINK_UNUSED		1	/* Unused number				*/
+#define NETLINK_USERSOCK	2	/* Reserved for user mode socket protocols 	*/
+#define NETLINK_FIREWALL	3	/* Unused number, formerly ip_queue		*/
+#define NETLINK_SOCK_DIAG	4	/* socket monitoring				*/
+#define NETLINK_NFLOG		5	/* netfilter/iptables ULOG */
+#define NETLINK_XFRM		6	/* ipsec */
+#define NETLINK_SELINUX		7	/* SELinux event notifications */
+#define NETLINK_ISCSI		8	/* Open-iSCSI */
+#define NETLINK_AUDIT		9	/* auditing */
+#define NETLINK_FIB_LOOKUP	10	
+#define NETLINK_CONNECTOR	11
+#define NETLINK_NETFILTER	12	/* netfilter subsystem */
+#define NETLINK_IP6_FW		13
+#define NETLINK_DNRTMSG		14	/* DECnet routing messages */
+#define NETLINK_KOBJECT_UEVENT	15	/* Kernel messages to userspace */
+#define NETLINK_GENERIC		16
+/* leave room for NETLINK_DM (DM Events) */
+#define NETLINK_SCSITRANSPORT	18	/* SCSI Transports */
+#define NETLINK_ECRYPTFS	19
+#define NETLINK_RDMA		20
+#define NETLINK_CRYPTO		21	/* Crypto layer */
+#define NETLINK_SMC		22	/* SMC monitoring */
+
+#define NETLINK_INET_DIAG	NETLINK_SOCK_DIAG
+
+#define MAX_LINKS 32		
+
+struct sockaddr_nl {
+	__kernel_sa_family_t	nl_family;	/* AF_NETLINK	*/
+	unsigned short	nl_pad;		/* zero		*/
+	__u32		nl_pid;		/* port ID	*/
+       	__u32		nl_groups;	/* multicast groups mask */
+};
+
+struct nlmsghdr {
+	__u32		nlmsg_len;	/* Length of message including header */
+	__u16		nlmsg_type;	/* Message content */
+	__u16		nlmsg_flags;	/* Additional flags */
+	__u32		nlmsg_seq;	/* Sequence number */
+	__u32		nlmsg_pid;	/* Sending process port ID */
+};
+
+/* Flags values */
+
+#define NLM_F_REQUEST		0x01	/* It is request message. 	*/
+#define NLM_F_MULTI		0x02	/* Multipart message, terminated by NLMSG_DONE */
+#define NLM_F_ACK		0x04	/* Reply with ack, with zero or error code */
+#define NLM_F_ECHO		0x08	/* Echo this request 		*/
+#define NLM_F_DUMP_INTR		0x10	/* Dump was inconsistent due to sequence change */
+#define NLM_F_DUMP_FILTERED	0x20	/* Dump was filtered as requested */
+
+/* Modifiers to GET request */
+#define NLM_F_ROOT	0x100	/* specify tree	root	*/
+#define NLM_F_MATCH	0x200	/* return all matching	*/
+#define NLM_F_ATOMIC	0x400	/* atomic GET		*/
+#define NLM_F_DUMP	(NLM_F_ROOT|NLM_F_MATCH)
+
+/* Modifiers to NEW request */
+#define NLM_F_REPLACE	0x100	/* Override existing		*/
+#define NLM_F_EXCL	0x200	/* Do not touch, if it exists	*/
+#define NLM_F_CREATE	0x400	/* Create, if it does not exist	*/
+#define NLM_F_APPEND	0x800	/* Add to end of list		*/
+
+/* Modifiers to DELETE request */
+#define NLM_F_NONREC	0x100	/* Do not delete recursively	*/
+
+/* Flags for ACK message */
+#define NLM_F_CAPPED	0x100	/* request was capped */
+#define NLM_F_ACK_TLVS	0x200	/* extended ACK TVLs were included */
+
+/*
+   4.4BSD ADD		NLM_F_CREATE|NLM_F_EXCL
+   4.4BSD CHANGE	NLM_F_REPLACE
+
+   True CHANGE		NLM_F_CREATE|NLM_F_REPLACE
+   Append		NLM_F_CREATE
+   Check		NLM_F_EXCL
+ */
+
+#define NLMSG_ALIGNTO	4U
+#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
+#define NLMSG_HDRLEN	 ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
+#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)
+#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
+#define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
+#define NLMSG_NEXT(nlh,len)	 ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
+				  (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))
+#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \
+			   (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \
+			   (nlh)->nlmsg_len <= (len))
+#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))
+
+#define NLMSG_NOOP		0x1	/* Nothing.		*/
+#define NLMSG_ERROR		0x2	/* Error		*/
+#define NLMSG_DONE		0x3	/* End of a dump	*/
+#define NLMSG_OVERRUN		0x4	/* Data lost		*/
+
+#define NLMSG_MIN_TYPE		0x10	/* < 0x10: reserved control messages */
+
+struct nlmsgerr {
+	int		error;
+	struct nlmsghdr msg;
+	/*
+	 * followed by the message contents unless NETLINK_CAP_ACK was set
+	 * or the ACK indicates success (error == 0)
+	 * message length is aligned with NLMSG_ALIGN()
+	 */
+	/*
+	 * followed by TLVs defined in enum nlmsgerr_attrs
+	 * if NETLINK_EXT_ACK was set
+	 */
+};
+
+/**
+ * enum nlmsgerr_attrs - nlmsgerr attributes
+ * @NLMSGERR_ATTR_UNUSED: unused
+ * @NLMSGERR_ATTR_MSG: error message string (string)
+ * @NLMSGERR_ATTR_OFFS: offset of the invalid attribute in the original
+ *	 message, counting from the beginning of the header (u32)
+ * @NLMSGERR_ATTR_COOKIE: arbitrary subsystem specific cookie to
+ *	be used - in the success case - to identify a created
+ *	object or operation or similar (binary)
+ * @__NLMSGERR_ATTR_MAX: number of attributes
+ * @NLMSGERR_ATTR_MAX: highest attribute number
+ */
+enum nlmsgerr_attrs {
+	NLMSGERR_ATTR_UNUSED,
+	NLMSGERR_ATTR_MSG,
+	NLMSGERR_ATTR_OFFS,
+	NLMSGERR_ATTR_COOKIE,
+
+	__NLMSGERR_ATTR_MAX,
+	NLMSGERR_ATTR_MAX = __NLMSGERR_ATTR_MAX - 1
+};
+
+#define NETLINK_ADD_MEMBERSHIP		1
+#define NETLINK_DROP_MEMBERSHIP		2
+#define NETLINK_PKTINFO			3
+#define NETLINK_BROADCAST_ERROR		4
+#define NETLINK_NO_ENOBUFS		5
+#define NETLINK_RX_RING			6
+#define NETLINK_TX_RING			7
+#define NETLINK_LISTEN_ALL_NSID		8
+#define NETLINK_LIST_MEMBERSHIPS	9
+#define NETLINK_CAP_ACK			10
+#define NETLINK_EXT_ACK			11
+
+struct nl_pktinfo {
+	__u32	group;
+};
+
+struct nl_mmap_req {
+	unsigned int	nm_block_size;
+	unsigned int	nm_block_nr;
+	unsigned int	nm_frame_size;
+	unsigned int	nm_frame_nr;
+};
+
+struct nl_mmap_hdr {
+	unsigned int	nm_status;
+	unsigned int	nm_len;
+	__u32		nm_group;
+	/* credentials */
+	__u32		nm_pid;
+	__u32		nm_uid;
+	__u32		nm_gid;
+};
+
+enum nl_mmap_status {
+	NL_MMAP_STATUS_UNUSED,
+	NL_MMAP_STATUS_RESERVED,
+	NL_MMAP_STATUS_VALID,
+	NL_MMAP_STATUS_COPY,
+	NL_MMAP_STATUS_SKIP,
+};
+
+#define NL_MMAP_MSG_ALIGNMENT		NLMSG_ALIGNTO
+#define NL_MMAP_MSG_ALIGN(sz)		__ALIGN_KERNEL(sz, NL_MMAP_MSG_ALIGNMENT)
+#define NL_MMAP_HDRLEN			NL_MMAP_MSG_ALIGN(sizeof(struct nl_mmap_hdr))
+
+#define NET_MAJOR 36		/* Major 36 is reserved for networking 						*/
+
+enum {
+	NETLINK_UNCONNECTED = 0,
+	NETLINK_CONNECTED,
+};
+
+/*
+ *  <------- NLA_HDRLEN ------> <-- NLA_ALIGN(payload)-->
+ * +---------------------+- - -+- - - - - - - - - -+- - -+
+ * |        Header       | Pad |     Payload       | Pad |
+ * |   (struct nlattr)   | ing |                   | ing |
+ * +---------------------+- - -+- - - - - - - - - -+- - -+
+ *  <-------------- nlattr->nla_len -------------->
+ */
+
+struct nlattr {
+	__u16           nla_len;
+	__u16           nla_type;
+};
+
+/*
+ * nla_type (16 bits)
+ * +---+---+-------------------------------+
+ * | N | O | Attribute Type                |
+ * +---+---+-------------------------------+
+ * N := Carries nested attributes
+ * O := Payload stored in network byte order
+ *
+ * Note: The N and O flag are mutually exclusive.
+ */
+#define NLA_F_NESTED		(1 << 15)
+#define NLA_F_NET_BYTEORDER	(1 << 14)
+#define NLA_TYPE_MASK		~(NLA_F_NESTED | NLA_F_NET_BYTEORDER)
+
+#define NLA_ALIGNTO		4
+#define NLA_ALIGN(len)		(((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1))
+#define NLA_HDRLEN		((int) NLA_ALIGN(sizeof(struct nlattr)))
+
+/* Generic 32 bitflags attribute content sent to the kernel.
+ *
+ * The value is a bitmap that defines the values being set
+ * The selector is a bitmask that defines which value is legit
+ *
+ * Examples:
+ *  value = 0x0, and selector = 0x1
+ *  implies we are selecting bit 1 and we want to set its value to 0.
+ *
+ *  value = 0x2, and selector = 0x2
+ *  implies we are selecting bit 2 and we want to set its value to 1.
+ *
+ */
+struct nla_bitfield32 {
+	__u32 value;
+	__u32 selector;
+};
+
+#endif /* __LINUX_NETLINK_H */
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [RFC PATCH ethtool v2 04/23] netlink: add support for string sets
  2018-07-30 12:55 [RFC PATCH ethtool v2 00/23] ethtool netlink interface (userspace side) (WiP) Michal Kubecek
                   ` (2 preceding siblings ...)
  2018-07-30 12:55 ` [RFC PATCH ethtool v2 03/23] netlink: add netlink interface Michal Kubecek
@ 2018-07-30 12:56 ` Michal Kubecek
  2018-07-30 12:56 ` [RFC PATCH ethtool v2 05/23] netlink: add notification monitor Michal Kubecek
                   ` (18 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Michal Kubecek @ 2018-07-30 12:56 UTC (permalink / raw)
  To: netdev; +Cc: linux-kernel, John W. Linville

Add infrastructure for querying kernel string sets (analog to ioct commands
ETHTOOL_GSSET_INFO and ETHTOOL_GSTRINGS).

Let notification monitor request string sets on start so that it can
resolve bit indices to names when processing bitsets in compact format.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Makefile.am      |   1 +
 netlink/strset.c | 265 +++++++++++++++++++++++++++++++++++++++++++++++
 netlink/strset.h |  16 +++
 3 files changed, 282 insertions(+)
 create mode 100644 netlink/strset.c
 create mode 100644 netlink/strset.h

diff --git a/Makefile.am b/Makefile.am
index bdb4f4df2b0f..6a4d7083919e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -22,6 +22,7 @@ endif
 if ETHTOOL_ENABLE_NETLINK
 ethtool_SOURCES += \
 		  netlink/netlink.c netlink/netlink.h netlink/extapi.h \
+		  netlink/strset.c netlink/strset.h \
 		  uapi/linux/ethtool_netlink.h \
 		  uapi/linux/netlink.h uapi/linux/genetlink.h
 ethtool_CFLAGS += @MNL_CFLAGS@
diff --git a/netlink/strset.c b/netlink/strset.c
new file mode 100644
index 000000000000..9ea456748531
--- /dev/null
+++ b/netlink/strset.c
@@ -0,0 +1,265 @@
+#include <errno.h>
+#include <string.h>
+
+#include "../internal.h"
+#include "netlink.h"
+
+struct stringset {
+	const char		**strings;
+	void			*raw_data;
+	unsigned int		count;
+};
+
+struct perdev_strings {
+	char			devname[IFNAMSIZ];
+	struct stringset	strings[ETH_SS_MAX + 1];
+	struct perdev_strings	*next;
+};
+
+/* universal string sets */
+static struct stringset global_strings[ETH_SS_MAX + 1];
+/* linked list of string sets related to network devices */
+static struct perdev_strings *device_strings;
+
+static void drop_stringset(struct stringset *set)
+{
+	free(set->strings);
+	free(set->raw_data);
+	set->count = 0;
+}
+
+static int import_stringset(struct stringset *dest, const struct nlattr *nest)
+{
+	const struct nlattr *tb_stringset[ETHA_STRINGSET_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb_stringset);
+	const struct nlattr *string;
+	unsigned int size;
+	unsigned int count;
+	unsigned int idx;
+	int ret;
+
+	ret = mnl_attr_parse_nested(nest, attr_cb, &tb_stringset_info);
+	if (ret < 0)
+		return ret;
+	if (!tb_stringset[ETHA_STRINGSET_ID] ||
+	    !tb_stringset[ETHA_STRINGSET_COUNT] ||
+	    !tb_stringset[ETHA_STRINGSET_STRINGS])
+		return -EFAULT;
+	idx = mnl_attr_get_u32(tb_stringset[ETHA_STRINGSET_ID]);
+	if (idx > ETH_SS_MAX)
+		return 0;
+	count = mnl_attr_get_u32(tb_stringset[ETHA_STRINGSET_COUNT]);
+	if (count == 0)
+		return 0;
+
+	size = mnl_attr_get_len(tb_stringset[ETHA_STRINGSET_STRINGS]);
+	ret = -ENOMEM;
+	dest[idx].raw_data = malloc(size);
+	if (!dest[idx].raw_data)
+		goto err;
+	memcpy(dest[idx].raw_data, tb_stringset[ETHA_STRINGSET_STRINGS], size);
+	dest[idx].strings = malloc(count * sizeof(dest[idx].strings[0]));
+	if (!dest[idx].strings)
+		goto err;
+	dest[idx].count = count;
+
+	nest = dest[idx].raw_data;
+	mnl_attr_for_each_nested(string, nest) {
+		const struct nlattr *tb[ETHA_STRING_MAX + 1] = {};
+		DECLARE_ATTR_TB_INFO(tb);
+		unsigned int i;
+
+		if (mnl_attr_get_type(string) != ETHA_STRINGS_STRING)
+			continue;
+		ret = mnl_attr_parse_nested(string, attr_cb, &tb_info);
+		if (ret < 0)
+			goto err;
+		ret = -EFAULT;
+		if (!tb[ETHA_STRING_INDEX] || !tb[ETHA_STRING_VALUE])
+			goto err;
+
+		i = mnl_attr_get_u32(tb[ETHA_STRING_INDEX]);
+		if (i >= count)
+			goto err;
+		dest[idx].strings[i] =
+			mnl_attr_get_payload(tb[ETHA_STRING_VALUE]);
+	}
+
+	return 0;
+err:
+	drop_stringset(&dest[idx]);
+	return ret;
+}
+
+static const char *stringset_names[] = {
+	[ETH_SS_TEST] = "test",
+	[ETH_SS_STATS] = "stats",
+	[ETH_SS_PRIV_FLAGS] = "priv-flags",
+	[ETH_SS_NTUPLE_FILTERS] = "ntuple-filters",
+	[ETH_SS_FEATURES] = "features",
+	[ETH_SS_RSS_HASH_FUNCS] = "rss-hash-funcs",
+	[ETH_SS_TUNABLES] = "tunables",
+	[ETH_SS_PHY_STATS] = "phy-stats",
+	[ETH_SS_PHY_TUNABLES] = "phy-tunables",
+	[ETH_SS_LINK_MODES] = "link-modes",
+};
+
+void debug_stringsets(const struct stringset *sets)
+{
+	unsigned int i;
+
+	for (i = 0; i <= ETH_SS_MAX; i++) {
+		if (sets[i].count > 0) {
+			printf("    set %s, count %u\n", stringset_names[i],
+			       sets[i].count);
+		}
+	}
+}
+
+void debug_strings()
+{
+	struct perdev_strings *pd;
+
+	fputs("global strings:\n", stdout);
+	debug_stringsets(global_strings);
+
+	for (pd = device_strings; pd; pd = pd->next) {
+		printf("strings for %s:\n", pd->devname);
+		debug_stringsets(pd->strings);
+	}
+}
+
+static int strset_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+	const struct nlattr *tb[ETHA_STRSET_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	struct nl_context *nlctx = data;
+	struct stringset *dest;
+	struct nlattr *attr;
+	unsigned int i;
+	int ret;
+
+	ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+	if (ret < 0)
+		return ret;
+	nlctx->devname = get_dev_name(tb[ETHA_STRSET_DEV]);
+	if (!dev_ok(nlctx))
+		return MNL_CB_OK;
+
+	if (nlctx->devname) {
+		struct perdev_strings *perdev = device_strings;
+
+		while (perdev && strcmp(perdev->devname, nlctx->devname))
+			perdev = perdev->next;
+		if (perdev) {
+			for (i = 0; i <= ETH_SS_MAX; i++)
+				drop_stringset(&perdev->strings[i]);
+		} else {
+			perdev = calloc(sizeof(*perdev), 1);
+			if (!perdev)
+				return -ENOMEM;
+			strncpy(perdev->devname, nlctx->devname, IFNAMSIZ);
+			perdev->devname[IFNAMSIZ - 1] = '\0';
+			perdev->next = device_strings;
+			device_strings = perdev;
+		}
+		dest = perdev->strings;
+	} else {
+		for (i = 0; i <= ETH_SS_MAX; i++)
+			drop_stringset(&global_strings[i]);
+		dest = global_strings;
+	}
+
+	mnl_attr_for_each(attr, nlhdr, GENL_HDRLEN) {
+		if (mnl_attr_get_type(attr) == ETHA_STRSET_STRINGSET)
+			import_stringset(dest, attr);
+	}
+
+	return MNL_CB_OK;
+}
+
+/* interface */
+
+const struct stringset *global_stringset(unsigned int type)
+{
+	if (type > ETH_SS_MAX)
+		return NULL;
+	return &global_strings[type];
+}
+
+const struct stringset *perdev_stringset(const char *dev, unsigned int type)
+{
+	const struct perdev_strings *p;
+
+	if (type > ETH_SS_MAX)
+		return NULL;
+	for (p = device_strings; p; p = p->next)
+		if (!strcmp(p->devname, dev))
+			return &p->strings[type];
+
+	return NULL;
+}
+
+unsigned int get_count(const struct stringset *set)
+{
+	return set->count;
+}
+
+const char *get_string(const struct stringset *set, unsigned int idx)
+{
+	if (idx > set->count)
+		return NULL;
+	return set->strings[idx];
+}
+
+int load_global_strings(struct nl_context *nlctx)
+{
+	int ret;
+
+	ret = msg_init(nlctx, ETHNL_CMD_GET_STRSET, NLM_F_REQUEST | NLM_F_ACK);
+	if (ret < 0)
+		return ret;
+	ret = ethnl_send_get_request(nlctx, strset_reply_cb);
+	return ret;
+}
+
+int load_perdev_strings(struct nl_context *nlctx, const char *dev)
+{
+	int ret;
+
+	ret = msg_init(nlctx, ETHNL_CMD_GET_STRSET,
+		       NLM_F_REQUEST | NLM_F_ACK | (dev ? 0 : NLM_F_DUMP));
+	if (ret < 0)
+		return ret;
+	if (dev) {
+		if (ethnla_put_dev(nlctx, ETHA_STRSET_DEV, dev))
+			return -EMSGSIZE;
+	}
+	ret = ethnl_send_get_request(nlctx, strset_reply_cb);
+	return ret;
+}
+
+void free_perdev_strings(const char *devname)
+{
+	struct perdev_strings **p = &device_strings;
+	unsigned int i;
+
+	p = &device_strings;
+	while (*p) {
+		struct perdev_strings *perdev = *p;
+
+		if (devname && strcmp(perdev->devname, devname)) {
+			p = &((*p)->next);
+			continue;
+		}
+		*p = perdev->next;
+		for (i = 0; i <= ETH_SS_MAX; i++)
+			drop_stringset(&perdev->strings[i]);
+		free(perdev);
+	}
+
+	if (!devname) {
+		for (i = 0; i <= ETH_SS_MAX; i++)
+			drop_stringset(&global_strings[i]);
+	}
+}
diff --git a/netlink/strset.h b/netlink/strset.h
new file mode 100644
index 000000000000..81ddf33a5d15
--- /dev/null
+++ b/netlink/strset.h
@@ -0,0 +1,16 @@
+#ifndef ETHTOOL_NETLINK_STRSET_H__
+#define ETHTOOL_NETLINK_STRSET_H__
+
+struct stringset;
+
+const struct stringset *global_stringset(unsigned int type);
+const struct stringset *perdev_stringset(const char *dev, unsigned int type);
+
+unsigned int get_count(const struct stringset *set);
+const char *get_string(const struct stringset *set, unsigned int idx);
+
+int load_global_strings(struct nl_context *nlctx);
+int load_perdev_strings(struct nl_context *nlctx, const char *dev);
+void free_perdev_strings(const char *devname);
+
+#endif /* ETHTOOL_NETLINK_STRSET_H__ */
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [RFC PATCH ethtool v2 05/23] netlink: add notification monitor
  2018-07-30 12:55 [RFC PATCH ethtool v2 00/23] ethtool netlink interface (userspace side) (WiP) Michal Kubecek
                   ` (3 preceding siblings ...)
  2018-07-30 12:56 ` [RFC PATCH ethtool v2 04/23] netlink: add support for string sets Michal Kubecek
@ 2018-07-30 12:56 ` Michal Kubecek
  2018-07-30 12:56 ` [RFC PATCH ethtool v2 06/23] netlink: add netlink handler for gdrv (-i) Michal Kubecek
                   ` (17 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Michal Kubecek @ 2018-07-30 12:56 UTC (permalink / raw)
  To: netdev; +Cc: linux-kernel, John W. Linville

With 'ethtool --monitor [ --all | opt ] [dev]', let ethtool listen to
netlink notification and display them in a format similar to output of
related "get" commands. With --all, show all types of notifications. If
device name is specified, show only notifications for that device, if no
device name or "*" is passed, show notifications for all devices.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Makefile.am       |   2 +-
 ethtool.c         |  17 ++++
 netlink/extapi.h  |   4 +
 netlink/monitor.c | 218 ++++++++++++++++++++++++++++++++++++++++++++++
 netlink/netlink.c |  32 ++++++-
 netlink/netlink.h |  26 ++++++
 6 files changed, 297 insertions(+), 2 deletions(-)
 create mode 100644 netlink/monitor.c

diff --git a/Makefile.am b/Makefile.am
index 6a4d7083919e..a412734fffd1 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -22,7 +22,7 @@ endif
 if ETHTOOL_ENABLE_NETLINK
 ethtool_SOURCES += \
 		  netlink/netlink.c netlink/netlink.h netlink/extapi.h \
-		  netlink/strset.c netlink/strset.h \
+		  netlink/strset.c netlink/strset.h netlink/monitor.c \
 		  uapi/linux/ethtool_netlink.h \
 		  uapi/linux/netlink.h uapi/linux/genetlink.h
 ethtool_CFLAGS += @MNL_CFLAGS@
diff --git a/ethtool.c b/ethtool.c
index e8044a6af76c..b7f40d2f3826 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -5294,6 +5294,9 @@ static int show_usage(struct cmd_context *ctx)
 		if (args[i].opthelp)
 			fputs(args[i].opthelp, stdout);
 	}
+#ifdef ETHTOOL_ENABLE_NETLINK
+	monitor_usage();
+#endif
 
 	return 0;
 }
@@ -5335,6 +5338,20 @@ int main(int argc, char **argp)
 	argp++;
 	argc--;
 
+#ifdef ETHTOOL_ENABLE_NETLINK
+	if (*argp && !strcmp(*argp, "--monitor")) {
+		if (netlink_init(&ctx)) {
+			fprintf(stderr,
+				"Option --monitor is only available with netlink.\n");
+			return 1;
+		} else {
+			ctx.argp = ++argp;
+			ctx.argc = --argc;
+			return nl_monitor(&ctx);
+		}
+	}
+#endif
+
 	/* First argument must be either a valid option or a device
 	 * name to get settings for (which we don't expect to begin
 	 * with '-').
diff --git a/netlink/extapi.h b/netlink/extapi.h
index 05ab083d9b9c..827e3732888a 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -13,4 +13,8 @@ struct nl_context;
 int netlink_init(struct cmd_context *ctx);
 int netlink_done(struct cmd_context *ctx);
 
+int nl_monitor(struct cmd_context *ctx);
+
+void monitor_usage();
+
 #endif /* ETHTOOL_EXTAPI_H__ */
diff --git a/netlink/monitor.c b/netlink/monitor.c
new file mode 100644
index 000000000000..d7ed562356f1
--- /dev/null
+++ b/netlink/monitor.c
@@ -0,0 +1,218 @@
+#include <errno.h>
+
+#include "../internal.h"
+#include "netlink.h"
+#include "strset.h"
+
+static void monitor_newdev(struct nl_context *nlctx, struct nlattr *evattr)
+{
+	const struct nlattr *tb[ETHA_NEWDEV_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	const char *devname;
+	int ret;
+
+	ret = mnl_attr_parse_nested(evattr, attr_cb, &tb_info);
+	if (ret < 0)
+		return;
+	if (!tb[ETHA_NEWDEV_DEV])
+		return;
+	devname = get_dev_name(tb[ETHA_NEWDEV_DEV]);
+	if (!devname)
+		return;
+	printf("New device %s registered.\n", devname);
+
+	ret = init_aux_nlctx(nlctx);
+	if (ret < 0)
+		return;
+	load_perdev_strings(nlctx->aux_nlctx, devname);
+}
+
+static void monitor_deldev(struct nl_context *nlctx, struct nlattr *evattr)
+{
+	const struct nlattr *tb[ETHA_DELDEV_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	const char *devname;
+	int ret;
+
+	ret = mnl_attr_parse_nested(evattr, attr_cb, &tb_info);
+	if (ret < 0)
+		return;
+	if (!tb[ETHA_DELDEV_DEV])
+		return;
+	devname = get_dev_name(tb[ETHA_DELDEV_DEV]);
+	if (!devname)
+		return;
+	printf("Device %s unregistered.\n", devname);
+
+	free_perdev_strings(devname);
+}
+
+static int monitor_event_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+	struct nl_context *nlctx = data;
+	struct nlattr *evattr;
+
+	mnl_attr_for_each(evattr, nlhdr, GENL_HDRLEN) {
+		switch(mnl_attr_get_type(evattr)) {
+		case ETHA_EVENT_NEWDEV:
+			monitor_newdev(nlctx, evattr);
+			break;
+		case ETHA_EVENT_DELDEV:
+			monitor_deldev(nlctx, evattr);
+			break;
+		}
+	}
+
+	return MNL_CB_OK;
+}
+
+static struct {
+	uint8_t		cmd;
+	mnl_cb_t	cb;
+} monitor_callbacks[] = {
+	{
+		.cmd	= ETHNL_CMD_EVENT,
+		.cb	= monitor_event_cb,
+	},
+};
+
+static int monitor_any_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+	const struct genlmsghdr *ghdr = (const struct genlmsghdr *)(nlhdr + 1);
+	struct nl_context *nlctx = data;
+	unsigned i;
+
+	if (nlctx->filter_cmd && ghdr->cmd != nlctx->filter_cmd)
+		return MNL_CB_OK;
+
+	for (i = 0; i < MNL_ARRAY_SIZE(monitor_callbacks); i++)
+		if (monitor_callbacks[i].cmd == ghdr->cmd)
+			return monitor_callbacks[i].cb(nlhdr, data);
+
+	return MNL_CB_OK;
+}
+
+struct monitor_option {
+	const char	*pattern;
+	uint8_t		cmd;
+	uint32_t	info_mask;
+};
+
+static struct monitor_option monitor_opts[] = {
+	{
+		.pattern	= "--all",
+		.cmd		= 0,
+	},
+};
+
+static bool pattern_match(const char *s, const char *pattern)
+{
+	const char *opt = pattern;
+	const char *next;
+	int slen = strlen(s);
+	int optlen;
+
+	do {
+		next = opt;
+		while (*next && *next != '|')
+			next++;
+		optlen = next - opt;
+		if (slen == optlen && !strncmp(s, opt, optlen))
+			return true;
+
+		opt = next;
+		if (*opt == '|')
+			opt++;
+	} while (*opt);
+
+	return false;
+}
+
+static int parse_monitor(struct cmd_context *ctx)
+{
+	struct nl_context *nlctx = ctx->nlctx;
+	char **argp = ctx->argp;
+	int argc = ctx->argc;
+	const char *opt = "";
+	unsigned int i;
+
+	if (*argp && argp[0][0] == '-') {
+		opt = *argp;
+		argp++;
+		argc--;
+	}
+	for (i = 0; i < MNL_ARRAY_SIZE(monitor_opts); i++) {
+		if (pattern_match(opt, monitor_opts[i].pattern)) {
+			nlctx->filter_cmd = monitor_opts[i].cmd;
+			nlctx->filter_mask = monitor_opts[i].info_mask;
+			goto opt_found;
+		}
+	}
+	fprintf(stderr, "monitoring for option '%s' not supported\n", *argp);
+	return -1;
+
+opt_found:
+	if (*argp && strcmp(*argp, WILDCARD_DEVNAME))
+		ctx->devname = *argp;
+	return 0;
+}
+
+int nl_monitor(struct cmd_context *ctx)
+{
+	bool is_dev;
+	struct nl_context *nlctx = ctx->nlctx;
+	uint32_t grpid = nlctx->mon_mcgrp_id;
+	int ret;
+
+	if (!grpid) {
+		fprintf(stderr, "multicast group 'monitor' not found\n");
+		return -EOPNOTSUPP;
+	}
+	if (parse_monitor(ctx) < 0)
+		return 1;
+	is_dev = ctx->devname && strcmp(ctx->devname, WILDCARD_DEVNAME);
+
+	ret = load_global_strings(nlctx);
+	if (ret < 0)
+		return ret;
+	ret = mnl_socket_setsockopt(nlctx->sk, NETLINK_ADD_MEMBERSHIP,
+				    &grpid, sizeof(grpid));
+	if (ret < 0)
+		return ret;
+	ret = load_perdev_strings(nlctx, is_dev ? ctx->devname : NULL);
+	if (ret < 0)
+		return ret;
+
+	nlctx->filter_devname = ctx->devname;
+	nlctx->is_monitor = true;
+	nlctx->port = 0;
+	nlctx->seq = 0;
+
+	fputs("listening...\n", stdout);
+	fflush(stdout);
+	ret = ethnl_process_reply(nlctx, monitor_any_cb);
+	free_perdev_strings(NULL);
+	return ret;
+}
+
+void monitor_usage()
+{
+	const char *p;
+	unsigned i;
+
+	fputs("        ethtool --monitor               Show kernel notifications\n",
+	      stdout);
+	for (i = 0; i < MNL_ARRAY_SIZE(monitor_opts); i++) {
+		if (i > 0)
+			fputs("\n                  | ", stdout);
+		else
+			fputs("                ( ", stdout);
+		for (p = monitor_opts[i].pattern; *p; p++)
+			if (*p == '|')
+				fputs(" | ", stdout);
+			else
+				fputc(*p, stdout);
+	}
+	fputs(" )\n", stdout);
+	fputs("                [ DEVNAME | * ]\n", stdout);
+}
diff --git a/netlink/netlink.c b/netlink/netlink.c
index 0671c4589c72..69f692d2cf04 100644
--- a/netlink/netlink.c
+++ b/netlink/netlink.c
@@ -391,6 +391,31 @@ err:
 
 /* get ethtool family id */
 
+static void ethnl_find_monitor_group(struct nl_context *nlctx,
+				     struct nlattr *nest)
+{
+	const struct nlattr *grp_tb[CTRL_ATTR_MCAST_GRP_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(grp_tb);
+	struct nlattr *grp_attr;
+	int ret;
+
+	nlctx->mon_mcgrp_id = 0;
+	mnl_attr_for_each_nested(grp_attr, nest) {
+		ret = mnl_attr_parse_nested(grp_attr, attr_cb, &grp_tb_info);
+		if (ret < 0)
+			return;
+		if (!grp_tb[CTRL_ATTR_MCAST_GRP_NAME] ||
+		    !grp_tb[CTRL_ATTR_MCAST_GRP_ID])
+			continue;
+		if (strcmp(mnl_attr_get_str(grp_tb[CTRL_ATTR_MCAST_GRP_NAME]),
+			   ETHTOOL_MCGRP_MONITOR_NAME))
+			continue;
+		nlctx->mon_mcgrp_id =
+			mnl_attr_get_u32(grp_tb[CTRL_ATTR_MCAST_GRP_ID]);
+		return;
+	}
+}
+
 static int ethnl_family_cb(const struct nlmsghdr *nlhdr, void *data)
 {
 	struct nl_context *nlctx = data;
@@ -398,9 +423,13 @@ static int ethnl_family_cb(const struct nlmsghdr *nlhdr, void *data)
 
 	nlctx->ethnl_fam = 0;
 	mnl_attr_for_each(attr, nlhdr, GENL_HDRLEN) {
-		if (mnl_attr_get_type(attr) == CTRL_ATTR_FAMILY_ID) {
+		switch(mnl_attr_get_type(attr)) {
+		case CTRL_ATTR_FAMILY_ID:
 			nlctx->ethnl_fam = mnl_attr_get_u16(attr);
 			break;
+		case CTRL_ATTR_MCAST_GROUPS:
+			ethnl_find_monitor_group(nlctx, attr);
+			break;
 		}
 	}
 
@@ -478,6 +507,7 @@ int __init_aux_nlctx(struct nl_context *nlctx)
 	}
 
 	aux->ethnl_fam = nlctx->ethnl_fam;
+	aux->mon_mcgrp_id = nlctx->mon_mcgrp_id;
 	nlctx->aux_nlctx = aux;
 
 	return 0;
diff --git a/netlink/netlink.h b/netlink/netlink.h
index 547c865ed535..15bf9f3873b0 100644
--- a/netlink/netlink.h
+++ b/netlink/netlink.h
@@ -17,6 +17,7 @@
 
 struct nl_context {
 	int ethnl_fam;
+	uint32_t mon_mcgrp_id;
 	struct mnl_socket *sk;
 	struct nl_context *aux_nlctx;
 	void *cmd_private;
@@ -30,6 +31,10 @@ struct nl_context {
 	const char *devname;
 	bool is_dump;
 	int exit_code;
+	bool is_monitor;
+	uint8_t filter_cmd;
+	uint32_t filter_mask;
+	const char *filter_devname;
 };
 
 struct attr_tb_info {
@@ -144,6 +149,27 @@ static inline void show_u32_yn(const struct nlattr **tb, unsigned int idx,
 		       mnl_attr_get_u32(tb[idx]) ? "yes" : "no");
 }
 
+/* reply content filtering */
+
+static inline bool mask_ok(const struct nl_context *nlctx, uint32_t bits)
+{
+	return !nlctx->filter_mask || (nlctx->filter_mask & bits);
+}
+
+static inline bool dev_ok(const struct nl_context *nlctx)
+{
+	return !nlctx->filter_devname ||
+	       (nlctx->devname &&
+		!strcmp(nlctx->devname, nlctx->filter_devname));
+}
+
+static inline bool show_only(const struct nl_context *nlctx, uint32_t bits)
+{
+	if (nlctx->is_monitor || !nlctx->filter_mask)
+		return false;
+	return nlctx->filter_mask & ~bits;
+}
+
 /* misc */
 
 static inline int init_aux_nlctx(struct nl_context *nlctx)
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [RFC PATCH ethtool v2 06/23] netlink: add netlink handler for gdrv (-i)
  2018-07-30 12:55 [RFC PATCH ethtool v2 00/23] ethtool netlink interface (userspace side) (WiP) Michal Kubecek
                   ` (4 preceding siblings ...)
  2018-07-30 12:56 ` [RFC PATCH ethtool v2 05/23] netlink: add notification monitor Michal Kubecek
@ 2018-07-30 12:56 ` Michal Kubecek
  2018-07-30 12:56 ` [RFC PATCH ethtool v2 07/23] netlink: add netlink handler for gset (no option) Michal Kubecek
                   ` (16 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Michal Kubecek @ 2018-07-30 12:56 UTC (permalink / raw)
  To: netdev; +Cc: linux-kernel, John W. Linville

Implement "-i" subcommand using netlink interface command
ETHNL_CMD_GET_DRVINFO.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Makefile.am       |  1 +
 ethtool.c         |  3 ++-
 netlink/drvinfo.c | 43 +++++++++++++++++++++++++++++++++++++++++++
 netlink/extapi.h  |  1 +
 netlink/monitor.c | 10 ++++++++++
 5 files changed, 57 insertions(+), 1 deletion(-)
 create mode 100644 netlink/drvinfo.c

diff --git a/Makefile.am b/Makefile.am
index a412734fffd1..7e24fe8c50f4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -23,6 +23,7 @@ if ETHTOOL_ENABLE_NETLINK
 ethtool_SOURCES += \
 		  netlink/netlink.c netlink/netlink.h netlink/extapi.h \
 		  netlink/strset.c netlink/strset.h netlink/monitor.c \
+		  netlink/drvinfo.c \
 		  uapi/linux/ethtool_netlink.h \
 		  uapi/linux/netlink.h uapi/linux/genetlink.h
 ethtool_CFLAGS += @MNL_CFLAGS@
diff --git a/ethtool.c b/ethtool.c
index b7f40d2f3826..0edb80b19b9d 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -5056,6 +5056,7 @@ static int show_usage(struct cmd_context *ctx);
 /* Just define all netlink handlers as null when building without netlink
  * support so that we do not get unresolved symbols in args array below
  */
+#define nl_gdrv		NULL
 #endif
 
 static const struct option {
@@ -5125,7 +5126,7 @@ static const struct option {
 	{ "-K|--features|--offload", 1, do_sfeatures, NULL,
 	  "Set protocol offload and other features",
 	  "		FEATURE on|off ...\n" },
-	{ "-i|--driver", 1, do_gdrv, NULL,
+	{ "-i|--driver", 1, do_gdrv, nl_gdrv,
 	  "Show driver information" },
 	{ "-d|--register-dump", 1, do_gregs, NULL,
 	  "Do a register dump",
diff --git a/netlink/drvinfo.c b/netlink/drvinfo.c
new file mode 100644
index 000000000000..78b4394788a5
--- /dev/null
+++ b/netlink/drvinfo.c
@@ -0,0 +1,43 @@
+#include "../internal.h"
+#include "netlink.h"
+
+int drvinfo_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+	const struct nlattr *tb[ETHA_DRVINFO_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	struct nl_context *nlctx = data;
+	int ret;
+
+	ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+	if (ret < 0)
+		return ret;
+	nlctx->devname = get_dev_name(tb[ETHA_DRVINFO_DEV]);
+	if (!dev_ok(nlctx))
+		return MNL_CB_OK;
+	if (nlctx->is_dump || nlctx->is_monitor)
+		fprintf(stdout, "\nDriver info for %s:\n", nlctx->devname);
+
+	show_string(tb, ETHA_DRVINFO_DRIVER, "driver");
+	show_string(tb, ETHA_DRVINFO_VERSION, "version");
+	show_string(tb, ETHA_DRVINFO_FWVERSION, "firmware-version");
+	show_string(tb, ETHA_DRVINFO_EROM_VER, "expansion-rom-version");
+	show_string(tb, ETHA_DRVINFO_BUSINFO, "bus-info");
+	show_u32_yn(tb, ETHA_DRVINFO_N_STATS, "supports-statistics");
+	show_u32_yn(tb, ETHA_DRVINFO_TESTINFO_LEN, "supports-test");
+	show_u32_yn(tb, ETHA_DRVINFO_EEDUMP_LEN, "supports-eeprom-access");
+	show_u32_yn(tb, ETHA_DRVINFO_REGDUMP_LEN, "supports-register-dump");
+	show_u32_yn(tb, ETHA_DRVINFO_N_PRIV_FLAGS, "supports-priv-flags");
+
+	return MNL_CB_OK;
+}
+
+int nl_gdrv(struct cmd_context *ctx)
+{
+	int ret;
+
+	ret = ethnl_prep_get_request(ctx, ETHNL_CMD_GET_DRVINFO,
+				     ETHA_DRVINFO_DEV);
+	if (ret < 0)
+		return ret;
+	return ethnl_send_get_request(ctx->nlctx, drvinfo_reply_cb);
+}
diff --git a/netlink/extapi.h b/netlink/extapi.h
index 827e3732888a..546090a02a0d 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -13,6 +13,7 @@ struct nl_context;
 int netlink_init(struct cmd_context *ctx);
 int netlink_done(struct cmd_context *ctx);
 
+int nl_gdrv(struct cmd_context *ctx);
 int nl_monitor(struct cmd_context *ctx);
 
 void monitor_usage();
diff --git a/netlink/monitor.c b/netlink/monitor.c
index d7ed562356f1..ae7d6080e03d 100644
--- a/netlink/monitor.c
+++ b/netlink/monitor.c
@@ -66,6 +66,8 @@ static int monitor_event_cb(const struct nlmsghdr *nlhdr, void *data)
 	return MNL_CB_OK;
 }
 
+int drvinfo_reply_cb(const struct nlmsghdr *nlhdr, void *data);
+
 static struct {
 	uint8_t		cmd;
 	mnl_cb_t	cb;
@@ -74,6 +76,10 @@ static struct {
 		.cmd	= ETHNL_CMD_EVENT,
 		.cb	= monitor_event_cb,
 	},
+	{
+		.cmd	= ETHNL_CMD_SET_DRVINFO,
+		.cb	= drvinfo_reply_cb,
+	},
 };
 
 static int monitor_any_cb(const struct nlmsghdr *nlhdr, void *data)
@@ -103,6 +109,10 @@ static struct monitor_option monitor_opts[] = {
 		.pattern	= "--all",
 		.cmd		= 0,
 	},
+	{
+		.pattern	= "-i|--driver",
+		.cmd		= ETHNL_CMD_SET_DRVINFO,
+	},
 };
 
 static bool pattern_match(const char *s, const char *pattern)
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [RFC PATCH ethtool v2 07/23] netlink: add netlink handler for gset (no option)
  2018-07-30 12:55 [RFC PATCH ethtool v2 00/23] ethtool netlink interface (userspace side) (WiP) Michal Kubecek
                   ` (5 preceding siblings ...)
  2018-07-30 12:56 ` [RFC PATCH ethtool v2 06/23] netlink: add netlink handler for gdrv (-i) Michal Kubecek
@ 2018-07-30 12:56 ` Michal Kubecek
  2018-07-30 12:56 ` [RFC PATCH ethtool v2 08/23] netlink: add helpers for command line parsing Michal Kubecek
                   ` (15 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Michal Kubecek @ 2018-07-30 12:56 UTC (permalink / raw)
  To: netdev; +Cc: linux-kernel, John W. Linville

Implement "ethtool <dev>" subcommand using netlink interface command
ETHNL_CMD_GET_SETTINGS.

Move some output helpers used by both ioctl() and netlink from ethtool.c
into new file common.c.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Makefile.am        |   4 +-
 common.c           | 285 ++++++++++++++++++++++++++++++++++++++++++
 common.h           |  60 +++++++++
 ethtool.c          | 151 ++---------------------
 netlink/extapi.h   |   1 +
 netlink/monitor.c  |  14 +++
 netlink/netlink.c  | 120 ++++++++++++++++++
 netlink/netlink.h  |   4 +
 netlink/settings.c | 301 +++++++++++++++++++++++++++++++++++++++++++++
 9 files changed, 796 insertions(+), 144 deletions(-)
 create mode 100644 common.c
 create mode 100644 common.h
 create mode 100644 netlink/settings.c

diff --git a/Makefile.am b/Makefile.am
index 7e24fe8c50f4..629f04e1202b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -6,7 +6,7 @@ EXTRA_DIST = LICENSE ethtool.8 ethtool.spec.in aclocal.m4 ChangeLog autogen.sh
 
 sbin_PROGRAMS = ethtool
 ethtool_SOURCES = ethtool.c uapi/linux/ethtool.h internal.h \
-		  uapi/linux/net_tstamp.h rxclass.c
+		  uapi/linux/net_tstamp.h rxclass.c common.c common.h
 ethtool_CFLAGS = -I./uapi -Wall
 ethtool_LDADD =  -lm
 if ETHTOOL_ENABLE_PRETTY_DUMP
@@ -23,7 +23,7 @@ if ETHTOOL_ENABLE_NETLINK
 ethtool_SOURCES += \
 		  netlink/netlink.c netlink/netlink.h netlink/extapi.h \
 		  netlink/strset.c netlink/strset.h netlink/monitor.c \
-		  netlink/drvinfo.c \
+		  netlink/drvinfo.c netlink/settings.c \
 		  uapi/linux/ethtool_netlink.h \
 		  uapi/linux/netlink.h uapi/linux/genetlink.h
 ethtool_CFLAGS += @MNL_CFLAGS@
diff --git a/common.c b/common.c
new file mode 100644
index 000000000000..59897acd2512
--- /dev/null
+++ b/common.c
@@ -0,0 +1,285 @@
+#include "internal.h"
+#include "common.h"
+
+#ifndef HAVE_NETIF_MSG
+enum {
+	NETIF_MSG_DRV		= 0x0001,
+	NETIF_MSG_PROBE		= 0x0002,
+	NETIF_MSG_LINK		= 0x0004,
+	NETIF_MSG_TIMER		= 0x0008,
+	NETIF_MSG_IFDOWN	= 0x0010,
+	NETIF_MSG_IFUP		= 0x0020,
+	NETIF_MSG_RX_ERR	= 0x0040,
+	NETIF_MSG_TX_ERR	= 0x0080,
+	NETIF_MSG_TX_QUEUED	= 0x0100,
+	NETIF_MSG_INTR		= 0x0200,
+	NETIF_MSG_TX_DONE	= 0x0400,
+	NETIF_MSG_RX_STATUS	= 0x0800,
+	NETIF_MSG_PKTDATA	= 0x1000,
+	NETIF_MSG_HW		= 0x2000,
+	NETIF_MSG_WOL		= 0x4000,
+};
+#endif
+
+const struct flag_info flags_msglvl[] = {
+	{ "drv",	NETIF_MSG_DRV },
+	{ "probe",	NETIF_MSG_PROBE },
+	{ "link",	NETIF_MSG_LINK },
+	{ "timer",	NETIF_MSG_TIMER },
+	{ "ifdown",	NETIF_MSG_IFDOWN },
+	{ "ifup",	NETIF_MSG_IFUP },
+	{ "rx_err",	NETIF_MSG_RX_ERR },
+	{ "tx_err",	NETIF_MSG_TX_ERR },
+	{ "tx_queued",	NETIF_MSG_TX_QUEUED },
+	{ "intr",	NETIF_MSG_INTR },
+	{ "tx_done",	NETIF_MSG_TX_DONE },
+	{ "rx_status",	NETIF_MSG_RX_STATUS },
+	{ "pktdata",	NETIF_MSG_PKTDATA },
+	{ "hw",		NETIF_MSG_HW },
+	{ "wol",	NETIF_MSG_WOL },
+	{}
+};
+const unsigned int n_flags_msglvl = ARRAY_SIZE(flags_msglvl) - 1;
+
+const char *names_duplex[] = {
+	[DUPLEX_HALF]		= "Half",
+	[DUPLEX_FULL]		= "Full",
+};
+DEFINE_ENUM_COUNT(duplex);
+
+const char *names_port[] = {
+	[PORT_TP]		= "Twisted Pair",
+	[PORT_AUI]		= "AUI",
+	[PORT_BNC]		= "BNC",
+	[PORT_MII]		= "MII",
+	[PORT_FIBRE]		= "FIBRE",
+	[PORT_DA]		= "Direct Attach Copper",
+	[PORT_NONE]		= "None",
+	[PORT_OTHER]		= "Other",
+};
+DEFINE_ENUM_COUNT(port);
+
+const char *names_transceiver[] = {
+	[XCVR_INTERNAL]		= "internal",
+	[XCVR_EXTERNAL]		= "external",
+};
+DEFINE_ENUM_COUNT(transceiver);
+
+/* the practice of putting completely unrelated flags into link mode bitmaps
+ * is rather unfortunate but as even ethtool_link_ksettings preserved that,
+ * there is little chance of getting them separated any time soon so let's
+ * sort them out ourselves
+ */
+const struct link_mode_info link_modes[] = {
+        [ETHTOOL_LINK_MODE_10baseT_Half_BIT] =
+		{ LM_CLASS_REAL,	10,	DUPLEX_HALF },
+        [ETHTOOL_LINK_MODE_10baseT_Full_BIT] =
+		{ LM_CLASS_REAL,	10,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_100baseT_Half_BIT] =
+		{ LM_CLASS_REAL,	100,	DUPLEX_HALF },
+        [ETHTOOL_LINK_MODE_100baseT_Full_BIT] =
+		{ LM_CLASS_REAL,	100,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_1000baseT_Half_BIT] =
+		{ LM_CLASS_REAL,	1000,	DUPLEX_HALF },
+        [ETHTOOL_LINK_MODE_1000baseT_Full_BIT] =
+		{ LM_CLASS_REAL,	1000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_Autoneg_BIT] =
+		{ LM_CLASS_AUTONEG },
+        [ETHTOOL_LINK_MODE_TP_BIT] =
+		{ LM_CLASS_PORT },
+        [ETHTOOL_LINK_MODE_AUI_BIT] =
+		{ LM_CLASS_PORT },
+        [ETHTOOL_LINK_MODE_MII_BIT] =
+		{ LM_CLASS_PORT },
+        [ETHTOOL_LINK_MODE_FIBRE_BIT] =
+		{ LM_CLASS_PORT },
+        [ETHTOOL_LINK_MODE_BNC_BIT] =
+		{ LM_CLASS_PORT },
+        [ETHTOOL_LINK_MODE_10000baseT_Full_BIT] =
+		{ LM_CLASS_REAL,	10000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_Pause_BIT] =
+		{ LM_CLASS_PAUSE },
+        [ETHTOOL_LINK_MODE_Asym_Pause_BIT] =
+		{ LM_CLASS_PAUSE },
+        [ETHTOOL_LINK_MODE_2500baseX_Full_BIT] =
+		{ LM_CLASS_REAL,	2500,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_Backplane_BIT] =
+		{ LM_CLASS_PORT },
+        [ETHTOOL_LINK_MODE_1000baseKX_Full_BIT] =
+		{ LM_CLASS_REAL,	1000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT] =
+		{ LM_CLASS_REAL,	10000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_10000baseKR_Full_BIT] =
+		{ LM_CLASS_REAL,	10000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_10000baseR_FEC_BIT] =
+		{ LM_CLASS_REAL,	10000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_20000baseMLD2_Full_BIT] =
+		{ LM_CLASS_REAL,	20000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_20000baseKR2_Full_BIT] =
+		{ LM_CLASS_REAL,	20000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT] =
+		{ LM_CLASS_REAL,	40000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT] =
+		{ LM_CLASS_REAL,	40000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT] =
+		{ LM_CLASS_REAL,	40000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT] =
+		{ LM_CLASS_REAL,	40000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_56000baseKR4_Full_BIT] =
+		{ LM_CLASS_REAL,	56000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_56000baseCR4_Full_BIT] =
+		{ LM_CLASS_REAL,	56000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_56000baseSR4_Full_BIT] =
+		{ LM_CLASS_REAL,	56000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_56000baseLR4_Full_BIT] =
+		{ LM_CLASS_REAL,	56000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_25000baseCR_Full_BIT] =
+		{ LM_CLASS_REAL,	25000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_25000baseKR_Full_BIT] =
+		{ LM_CLASS_REAL,	25000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_25000baseSR_Full_BIT] =
+		{ LM_CLASS_REAL,	25000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT] =
+		{ LM_CLASS_REAL,	50000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT] =
+		{ LM_CLASS_REAL,	50000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT] =
+		{ LM_CLASS_REAL,	100000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT] =
+		{ LM_CLASS_REAL,	100000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT] =
+		{ LM_CLASS_REAL,	100000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT] =
+		{ LM_CLASS_REAL,	100000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT] =
+		{ LM_CLASS_REAL,	50000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_1000baseX_Full_BIT] =
+		{ LM_CLASS_REAL,	1000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_10000baseCR_Full_BIT] =
+		{ LM_CLASS_REAL,	10000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_10000baseSR_Full_BIT] =
+		{ LM_CLASS_REAL,	10000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_10000baseLR_Full_BIT] =
+		{ LM_CLASS_REAL,	10000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT] =
+		{ LM_CLASS_REAL,	10000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_10000baseER_Full_BIT] =
+		{ LM_CLASS_REAL,	10000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_2500baseT_Full_BIT] =
+		{ LM_CLASS_REAL,	2500,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_5000baseT_Full_BIT] =
+		{ LM_CLASS_REAL,	5000,	DUPLEX_FULL },
+        [ETHTOOL_LINK_MODE_FEC_NONE_BIT] =
+		{ LM_CLASS_FEC },
+        [ETHTOOL_LINK_MODE_FEC_RS_BIT] =
+		{ LM_CLASS_FEC },
+        [ETHTOOL_LINK_MODE_FEC_BASER_BIT] =
+		{ LM_CLASS_FEC },
+};
+const unsigned int link_modes_count = ARRAY_SIZE(link_modes);
+
+void print_flags(const struct flag_info *info, unsigned int n_info, u32 value)
+{
+	const char *sep = "";
+
+	while (n_info) {
+		if (value & info->value) {
+			printf("%s%s", sep, info->name);
+			sep = " ";
+			value &= ~info->value;
+		}
+		++info;
+		--n_info;
+	}
+
+	/* Print any unrecognised flags in hex */
+	if (value)
+		printf("%s%#x", sep, value);
+}
+
+void __print_enum(const char *const *info, unsigned int n_info, unsigned int val,
+		const char *label, const char *unknown)
+{
+	if (val >= n_info || !info[val]) {
+		printf("%s", label);
+		printf(unknown, val);
+		fputc('\n', stdout);
+	} else {
+		printf("%s%s\n", label, info[val]);
+	}
+}
+
+static char *unparse_wolopts(int wolopts)
+{
+	static char buf[16];
+	char *p = buf;
+
+	memset(buf, 0, sizeof(buf));
+
+	if (wolopts) {
+		if (wolopts & WAKE_PHY)
+			*p++ = 'p';
+		if (wolopts & WAKE_UCAST)
+			*p++ = 'u';
+		if (wolopts & WAKE_MCAST)
+			*p++ = 'm';
+		if (wolopts & WAKE_BCAST)
+			*p++ = 'b';
+		if (wolopts & WAKE_ARP)
+			*p++ = 'a';
+		if (wolopts & WAKE_MAGIC)
+			*p++ = 'g';
+		if (wolopts & WAKE_MAGICSECURE)
+			*p++ = 's';
+	} else {
+		*p = 'd';
+	}
+
+	return buf;
+}
+
+int dump_wol(struct ethtool_wolinfo *wol)
+{
+	fprintf(stdout, "	Supports Wake-on: %s\n",
+		unparse_wolopts(wol->supported));
+	fprintf(stdout, "	Wake-on: %s\n",
+		unparse_wolopts(wol->wolopts));
+	if (wol->supported & WAKE_MAGICSECURE) {
+		int i;
+		int delim = 0;
+
+		fprintf(stdout, "        SecureOn password: ");
+		for (i = 0; i < SOPASS_MAX; i++) {
+			fprintf(stdout, "%s%02x", delim?":":"", wol->sopass[i]);
+			delim = 1;
+		}
+		fprintf(stdout, "\n");
+	}
+
+	return 0;
+}
+
+void dump_mdix(u8 mdix, u8 mdix_ctrl)
+{
+	fprintf(stdout, "	MDI-X: ");
+	if (mdix_ctrl == ETH_TP_MDI) {
+		fprintf(stdout, "off (forced)\n");
+	} else if (mdix_ctrl == ETH_TP_MDI_X) {
+		fprintf(stdout, "on (forced)\n");
+	} else {
+		switch (mdix) {
+		case ETH_TP_MDI:
+			fprintf(stdout, "off");
+			break;
+		case ETH_TP_MDI_X:
+			fprintf(stdout, "on");
+			break;
+		default:
+			fprintf(stdout, "Unknown");
+			break;
+		}
+		if (mdix_ctrl == ETH_TP_MDI_AUTO)
+			fprintf(stdout, " (auto)");
+		fprintf(stdout, "\n");
+	}
+}
diff --git a/common.h b/common.h
new file mode 100644
index 000000000000..a980b844a0ed
--- /dev/null
+++ b/common.h
@@ -0,0 +1,60 @@
+#ifndef ETHTOOL_COMMON_H__
+#define ETHTOOL_COMMON_H__
+
+struct flag_info {
+	const char *name;
+	u32 value;
+};
+
+extern const struct flag_info flags_msglvl[];
+extern const unsigned int n_flags_msglvl;
+
+
+enum link_mode_class {
+	LM_CLASS_UNKNOWN,
+	LM_CLASS_REAL,
+	LM_CLASS_AUTONEG,
+	LM_CLASS_PORT,
+	LM_CLASS_PAUSE,
+	LM_CLASS_FEC,
+};
+
+struct link_mode_info {
+	enum link_mode_class	class;
+	u32			speed;
+	u8			duplex;
+};
+
+extern const struct link_mode_info link_modes[];
+extern const unsigned int link_modes_count;
+
+static inline bool lm_class_match(unsigned int mode, enum link_mode_class class)
+{
+	unsigned int mode_class = (mode < link_modes_count) ?
+				   link_modes[mode].class : LM_CLASS_UNKNOWN;
+
+	if (mode_class == class)
+		return true;
+	return (mode_class == class) ||
+	       ((class == LM_CLASS_REAL) && (mode_class = LM_CLASS_UNKNOWN));
+}
+
+#define DECLARE_ENUM_NAMES(obj) \
+	extern const char *names_ ## obj[]; \
+	extern const unsigned int names_ ## obj ## _count
+#define DEFINE_ENUM_COUNT(obj) \
+	const unsigned int names_ ## obj ## _count = ARRAY_SIZE(names_ ## obj)
+#define print_enum(array, val, label, unknown) \
+	__print_enum(array, array ## _count, val, label, unknown)
+
+DECLARE_ENUM_NAMES(duplex);
+DECLARE_ENUM_NAMES(port);
+DECLARE_ENUM_NAMES(transceiver);
+
+void print_flags(const struct flag_info *info, unsigned int n_info, u32 value);
+void __print_enum(const char *const *info, unsigned int n_info,
+		  unsigned int val, const char *label, const char *unknown);
+int dump_wol(struct ethtool_wolinfo *wol);
+void dump_mdix(u8 mdix, u8 mdix_ctrl);
+
+#endif /* ETHTOOL_COMMON_H__ */
diff --git a/ethtool.c b/ethtool.c
index 0edb80b19b9d..bc7969f829f6 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -30,6 +30,7 @@
  */
 
 #include "internal.h"
+#include "common.h"
 #include <string.h>
 #include <stdlib.h>
 #include <sys/stat.h>
@@ -56,26 +57,6 @@
 #define MAX_ADDR_LEN	32
 #endif
 
-#ifndef HAVE_NETIF_MSG
-enum {
-	NETIF_MSG_DRV		= 0x0001,
-	NETIF_MSG_PROBE		= 0x0002,
-	NETIF_MSG_LINK		= 0x0004,
-	NETIF_MSG_TIMER		= 0x0008,
-	NETIF_MSG_IFDOWN	= 0x0010,
-	NETIF_MSG_IFUP		= 0x0020,
-	NETIF_MSG_RX_ERR	= 0x0040,
-	NETIF_MSG_TX_ERR	= 0x0080,
-	NETIF_MSG_TX_QUEUED	= 0x0100,
-	NETIF_MSG_INTR		= 0x0200,
-	NETIF_MSG_TX_DONE	= 0x0400,
-	NETIF_MSG_RX_STATUS	= 0x0800,
-	NETIF_MSG_PKTDATA	= 0x1000,
-	NETIF_MSG_HW		= 0x2000,
-	NETIF_MSG_WOL		= 0x4000,
-};
-#endif
-
 #ifndef NETLINK_GENERIC
 #define NETLINK_GENERIC	16
 #endif
@@ -123,29 +104,6 @@ struct cmdline_info {
 	void *seen_val;
 };
 
-struct flag_info {
-	const char *name;
-	u32 value;
-};
-
-static const struct flag_info flags_msglvl[] = {
-	{ "drv",	NETIF_MSG_DRV },
-	{ "probe",	NETIF_MSG_PROBE },
-	{ "link",	NETIF_MSG_LINK },
-	{ "timer",	NETIF_MSG_TIMER },
-	{ "ifdown",	NETIF_MSG_IFDOWN },
-	{ "ifup",	NETIF_MSG_IFUP },
-	{ "rx_err",	NETIF_MSG_RX_ERR },
-	{ "tx_err",	NETIF_MSG_TX_ERR },
-	{ "tx_queued",	NETIF_MSG_TX_QUEUED },
-	{ "intr",	NETIF_MSG_INTR },
-	{ "tx_done",	NETIF_MSG_TX_DONE },
-	{ "rx_status",	NETIF_MSG_RX_STATUS },
-	{ "pktdata",	NETIF_MSG_PKTDATA },
-	{ "hw",		NETIF_MSG_HW },
-	{ "wol",	NETIF_MSG_WOL },
-};
-
 struct off_flag_def {
 	const char *short_name;
 	const char *long_name;
@@ -428,26 +386,6 @@ static void flag_to_cmdline_info(const char *name, u32 value,
 	cli->seen_val = mask;
 }
 
-static void
-print_flags(const struct flag_info *info, unsigned int n_info, u32 value)
-{
-	const char *sep = "";
-
-	while (n_info) {
-		if (value & info->value) {
-			printf("%s%s", sep, info->name);
-			sep = " ";
-			value &= ~info->value;
-		}
-		++info;
-		--n_info;
-	}
-
-	/* Print any unrecognised flags in hex */
-	if (value)
-		printf("%s%#x", sep, value);
-}
-
 static int rxflow_str_to_type(const char *str)
 {
 	int flow_type = 0;
@@ -853,31 +791,9 @@ dump_link_usettings(const struct ethtool_link_usettings *link_usettings)
 		(link_usettings->base.autoneg == AUTONEG_DISABLE) ?
 		"off" : "on");
 
-	if (link_usettings->base.port == PORT_TP) {
-		fprintf(stdout, "	MDI-X: ");
-		if (link_usettings->base.eth_tp_mdix_ctrl == ETH_TP_MDI) {
-			fprintf(stdout, "off (forced)\n");
-		} else if (link_usettings->base.eth_tp_mdix_ctrl
-			   == ETH_TP_MDI_X) {
-			fprintf(stdout, "on (forced)\n");
-		} else {
-			switch (link_usettings->base.eth_tp_mdix) {
-			case ETH_TP_MDI:
-				fprintf(stdout, "off");
-				break;
-			case ETH_TP_MDI_X:
-				fprintf(stdout, "on");
-				break;
-			default:
-				fprintf(stdout, "Unknown");
-				break;
-			}
-			if (link_usettings->base.eth_tp_mdix_ctrl
-			    == ETH_TP_MDI_AUTO)
-				fprintf(stdout, " (auto)");
-			fprintf(stdout, "\n");
-		}
-	}
+	if (link_usettings->base.port == PORT_TP)
+		dump_mdix(link_usettings->base.eth_tp_mdix,
+			  link_usettings->base.eth_tp_mdix_ctrl);
 
 	return 0;
 }
@@ -946,56 +862,6 @@ static int parse_wolopts(char *optstr, u32 *data)
 	return 0;
 }
 
-static char *unparse_wolopts(int wolopts)
-{
-	static char buf[16];
-	char *p = buf;
-
-	memset(buf, 0, sizeof(buf));
-
-	if (wolopts) {
-		if (wolopts & WAKE_PHY)
-			*p++ = 'p';
-		if (wolopts & WAKE_UCAST)
-			*p++ = 'u';
-		if (wolopts & WAKE_MCAST)
-			*p++ = 'm';
-		if (wolopts & WAKE_BCAST)
-			*p++ = 'b';
-		if (wolopts & WAKE_ARP)
-			*p++ = 'a';
-		if (wolopts & WAKE_MAGIC)
-			*p++ = 'g';
-		if (wolopts & WAKE_MAGICSECURE)
-			*p++ = 's';
-	} else {
-		*p = 'd';
-	}
-
-	return buf;
-}
-
-static int dump_wol(struct ethtool_wolinfo *wol)
-{
-	fprintf(stdout, "	Supports Wake-on: %s\n",
-		unparse_wolopts(wol->supported));
-	fprintf(stdout, "	Wake-on: %s\n",
-		unparse_wolopts(wol->wolopts));
-	if (wol->supported & WAKE_MAGICSECURE) {
-		int i;
-		int delim = 0;
-
-		fprintf(stdout, "        SecureOn password: ");
-		for (i = 0; i < SOPASS_MAX; i++) {
-			fprintf(stdout, "%s%02x", delim?":":"", wol->sopass[i]);
-			delim = 1;
-		}
-		fprintf(stdout, "\n");
-	}
-
-	return 0;
-}
-
 static int parse_rxfhashopts(char *optstr, u32 *data)
 {
 	*data = 0;
@@ -2738,8 +2604,7 @@ static int do_gset(struct cmd_context *ctx)
 		fprintf(stdout, "	Current message level: 0x%08x (%d)\n"
 			"			       ",
 			edata.data, edata.data);
-		print_flags(flags_msglvl, ARRAY_SIZE(flags_msglvl),
-			    edata.data);
+		print_flags(flags_msglvl, n_flags_msglvl, edata.data);
 		fprintf(stdout, "\n");
 		allfail = 0;
 	} else if (errno != EOPNOTSUPP) {
@@ -2785,13 +2650,13 @@ static int do_sset(struct cmd_context *ctx)
 	int msglvl_changed = 0;
 	u32 msglvl_wanted = 0;
 	u32 msglvl_mask = 0;
-	struct cmdline_info cmdline_msglvl[ARRAY_SIZE(flags_msglvl)];
+	struct cmdline_info cmdline_msglvl[n_flags_msglvl];
 	int argc = ctx->argc;
 	char **argp = ctx->argp;
 	int i;
 	int err = 0;
 
-	for (i = 0; i < ARRAY_SIZE(flags_msglvl); i++)
+	for (i = 0; i < n_flags_msglvl; i++)
 		flag_to_cmdline_info(flags_msglvl[i].name,
 				     flags_msglvl[i].value,
 				     &msglvl_wanted, &msglvl_mask,
@@ -5057,6 +4922,7 @@ static int show_usage(struct cmd_context *ctx);
  * support so that we do not get unresolved symbols in args array below
  */
 #define nl_gdrv		NULL
+#define nl_gset		NULL
 #endif
 
 static const struct option {
@@ -5381,6 +5247,7 @@ int main(int argc, char **argp)
 	}
 	if ((*argp)[0] == '-')
 		exit_bad_args();
+	nl_func = nl_gset;
 	func = do_gset;
 	want_device = 1;
 
diff --git a/netlink/extapi.h b/netlink/extapi.h
index 546090a02a0d..20c9b03b2d3c 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -14,6 +14,7 @@ int netlink_init(struct cmd_context *ctx);
 int netlink_done(struct cmd_context *ctx);
 
 int nl_gdrv(struct cmd_context *ctx);
+int nl_gset(struct cmd_context *ctx);
 int nl_monitor(struct cmd_context *ctx);
 
 void monitor_usage();
diff --git a/netlink/monitor.c b/netlink/monitor.c
index ae7d6080e03d..32d842611011 100644
--- a/netlink/monitor.c
+++ b/netlink/monitor.c
@@ -67,6 +67,7 @@ static int monitor_event_cb(const struct nlmsghdr *nlhdr, void *data)
 }
 
 int drvinfo_reply_cb(const struct nlmsghdr *nlhdr, void *data);
+int settings_reply_cb(const struct nlmsghdr *nlhdr, void *data);
 
 static struct {
 	uint8_t		cmd;
@@ -80,6 +81,10 @@ static struct {
 		.cmd	= ETHNL_CMD_SET_DRVINFO,
 		.cb	= drvinfo_reply_cb,
 	},
+	{
+		.cmd	= ETHNL_CMD_SET_SETTINGS,
+		.cb	= settings_reply_cb,
+	},
 };
 
 static int monitor_any_cb(const struct nlmsghdr *nlhdr, void *data)
@@ -113,6 +118,15 @@ static struct monitor_option monitor_opts[] = {
 		.pattern	= "-i|--driver",
 		.cmd		= ETHNL_CMD_SET_DRVINFO,
 	},
+	{
+		.pattern	= "|-s|--change",
+		.cmd		= ETHNL_CMD_SET_SETTINGS,
+		.info_mask	= ETH_SETTINGS_IM_LINKINFO |
+				  ETH_SETTINGS_IM_LINKMODES |
+				  ETH_SETTINGS_IM_MSGLEVEL |
+				  ETH_SETTINGS_IM_WOLINFO |
+				  ETH_SETTINGS_IM_LINK,
+	},
 };
 
 static bool pattern_match(const char *s, const char *pattern)
diff --git a/netlink/netlink.c b/netlink/netlink.c
index 69f692d2cf04..fc87577191f0 100644
--- a/netlink/netlink.c
+++ b/netlink/netlink.c
@@ -4,8 +4,10 @@
 #include <errno.h>
 
 #include "../internal.h"
+#include "../common.h"
 #include "netlink.h"
 #include "extapi.h"
+#include "strset.h"
 
 /* misc helpers */
 
@@ -352,6 +354,124 @@ const char *get_dev_name(const struct nlattr *nest)
 	return mnl_attr_get_str(dev_tb[ETHA_DEV_NAME]);
 }
 
+int dump_link_modes(const struct nlattr *bitset, bool mask, unsigned class,
+		    const char *before, const char *between, const char *after,
+		    const char *if_none)
+{
+	const struct nlattr *bitset_tb[ETHA_BITSET_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(bitset_tb);
+	const unsigned int before_len = strlen(before);
+	const struct nlattr *bits;
+	const struct nlattr *bit;
+	bool first = true;
+	int prev = -2;
+	int ret;
+
+	ret = mnl_attr_parse_nested(bitset, attr_cb, &bitset_tb_info);
+	bits = bitset_tb[ETHA_BITSET_BITS];
+	if (ret < 0)
+		goto err_nonl;
+	if (!bits) {
+		const struct stringset *lm_strings =
+			global_stringset(ETH_SS_LINK_MODES);
+		unsigned int count;
+		unsigned int idx;
+		const char *name;
+
+		bits = mask ? bitset_tb[ETHA_BITSET_MASK] :
+			      bitset_tb[ETHA_BITSET_VALUE];
+		ret = -EFAULT;
+		if (!bits || !bitset_tb[ETHA_BITSET_SIZE])
+			goto err_nonl;
+		count = mnl_attr_get_u32(bitset_tb[ETHA_BITSET_SIZE]);
+		if (mnl_attr_get_payload_len(bits) / 4 < (count + 31) / 32)
+			goto err_nonl;
+
+		printf("\t%s", before);
+		for (idx = 0; idx < count; idx++) {
+			const uint32_t *raw_data = mnl_attr_get_payload(bits);
+			char buff[10];
+
+			if (!(raw_data[idx / 32] & (1U << (idx % 32))))
+				continue;
+			if (!lm_class_match(idx, class))
+				continue;
+			name = get_string(lm_strings, idx);
+			if (!name) {
+				snprintf(buff, sizeof(buff), "b%d", idx);
+				name = buff;
+			}
+			if (first)
+				first = false;
+			/* ugly hack to preserve old output format */
+			if ((class == LM_CLASS_REAL) && (prev == idx - 1) &&
+			    (prev < link_modes_count) &&
+			    (link_modes[prev].class == LM_CLASS_REAL) &&
+			    (link_modes[prev].duplex == DUPLEX_HALF))
+				putchar(' ');
+			else if (between)
+				printf("\t%s", between);
+			else
+				printf("\n\t%*s", before_len, "");
+			printf("%s", name);
+			prev = idx;
+		}
+		goto after;
+	}
+
+	printf("\t%s", before);
+	mnl_attr_for_each_nested(bit, bits) {
+		const struct nlattr *tb[ETHA_BIT_MAX + 1] = {};
+		DECLARE_ATTR_TB_INFO(tb);
+		unsigned int idx;
+		const char *name;
+
+		if (mnl_attr_get_type(bit) != ETHA_BITS_BIT)
+			continue;
+		ret = mnl_attr_parse_nested(bit, attr_cb, &tb_info);
+		if (ret < 0)
+			goto err;
+		ret = -EFAULT;
+		if (!tb[ETHA_BIT_INDEX] || !tb[ETHA_BIT_NAME])
+			goto err;
+		if (!mask && !tb[ETHA_BIT_VALUE])
+			continue;
+
+		idx = mnl_attr_get_u32(tb[ETHA_BIT_INDEX]);
+		name = mnl_attr_get_str(tb[ETHA_BIT_NAME]);
+		if (!lm_class_match(idx, class))
+			continue;
+		if (first) {
+			first = false;
+		} else {
+			/* ugly hack to preserve old output format */
+			if ((class == LM_CLASS_REAL) && (prev == idx - 1) &&
+			    (prev < link_modes_count) &&
+			    (link_modes[prev].class == LM_CLASS_REAL) &&
+			    (link_modes[prev].duplex == DUPLEX_HALF))
+				putchar(' ');
+			else if (between)
+				printf("\t%s", between);
+			else
+				printf("\n\t%*s", before_len, "");
+		}
+		printf("%s", name);
+		prev = idx;
+	}
+after:
+	if (first && if_none)
+		printf("%s", if_none);
+	printf(after);
+
+	return 0;
+err:
+	putchar('\n');
+err_nonl:
+	fflush(stdout);
+	fprintf(stderr, "malformed netlink message (link_modes)\n");
+	return ret;
+}
+
 /* request helpers */
 
 int ethnl_prep_get_request(struct cmd_context *ctx, unsigned int nlcmd,
diff --git a/netlink/netlink.h b/netlink/netlink.h
index 15bf9f3873b0..a572a165718e 100644
--- a/netlink/netlink.h
+++ b/netlink/netlink.h
@@ -58,6 +58,10 @@ bool bitset_get_bit(const struct nlattr *bitset, bool mask, unsigned int idx,
 		    int *retptr);
 bool bitset_is_empty(const struct nlattr *bitset, bool mask, int *retptr);
 
+int dump_link_modes(const struct nlattr *bitset, bool mask, unsigned class,
+		    const char *before, const char *between, const char *after,
+		    const char *if_none);
+
 int msg_init(struct nl_context *nlctx, int cmd, unsigned int flags);
 int ethnl_process_reply(struct nl_context *nlctx, mnl_cb_t reply_cb);
 int attr_cb(const struct nlattr *attr, void *data);
diff --git a/netlink/settings.c b/netlink/settings.c
new file mode 100644
index 000000000000..120fc95aad19
--- /dev/null
+++ b/netlink/settings.c
@@ -0,0 +1,301 @@
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+
+/* GET_SETTINGS */
+
+static int dump_pause(const struct nlattr *attr, bool mask, const char *label)
+{
+	bool pause, asym;
+	int ret = 0;
+
+	pause = bitset_get_bit(attr, mask, ETHTOOL_LINK_MODE_Pause_BIT, &ret);
+	if (ret < 0)
+		goto err;
+	asym = bitset_get_bit(attr, mask, ETHTOOL_LINK_MODE_Asym_Pause_BIT,
+			      &ret);
+	if (ret < 0)
+		goto err;
+
+	printf("\t%s", label);
+	if (pause)
+		printf("%s\n", asym ?  "Symmetric Receive-only" : "Symmetric");
+	else
+		printf("%s\n", asym ? "Transmit-only" : "No");
+
+	return 0;
+err:
+	fprintf(stderr, "malformed netlink message (pause modes)\n");
+	return ret;
+}
+
+int settings_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+	const struct nlattr *tb[ETHA_SETTINGS_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	struct nl_context *nlctx = data;
+	bool allfail = true;
+	bool first = true;
+	int port = -1;
+	int ret;
+
+	ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+	if (ret < 0)
+		return ret;
+	nlctx->devname = get_dev_name(tb[ETHA_SETTINGS_DEV]);
+	if (!dev_ok(nlctx))
+		return MNL_CB_OK;
+
+	if (tb[ETHA_SETTINGS_LINK_MODES] &&
+	    mask_ok(nlctx, ETH_SETTINGS_IM_LINKMODES)) {
+		const struct nlattr *attr = tb[ETHA_SETTINGS_LINK_MODES];
+		bool autoneg;
+
+		if (first) {
+			printf("Settings for %s:\n", nlctx->devname);
+			first = false;
+		}
+		ret = dump_link_modes(attr, true, LM_CLASS_PORT,
+				      "Supported ports: [ ", " ", " ]\n", NULL);
+		if (ret < 0)
+			return ret;
+		ret = dump_link_modes(attr, true, LM_CLASS_REAL,
+				      "Supported link modes:   ", NULL, "\n",
+				      "Not reported");
+		if (ret < 0)
+			return ret;
+		ret = dump_pause(attr, true, "Supported pause frame use: ");
+		if (ret < 0)
+			return ret;
+		autoneg = bitset_get_bit(attr, true,
+					 ETHTOOL_LINK_MODE_Autoneg_BIT, &ret);
+		if (ret < 0)
+			return ret;
+		printf("\tSupports auto-negotiation: %s\n",
+		       autoneg ? "Yes" : "No");
+		ret = dump_link_modes(attr, true, LM_CLASS_FEC,
+				      "Supported FEC modes: ", " ", "\n",
+				      "No");
+		if (ret < 0)
+			return ret;
+
+		ret = dump_link_modes(attr, false, LM_CLASS_REAL,
+				      "Advertised link modes:  ", NULL, "\n",
+				      "Not reported");
+		if (ret < 0)
+			return ret;
+		ret = dump_pause(attr, false, "Advertised pause frame use: ");
+		if (ret < 0)
+			return ret;
+		autoneg = bitset_get_bit(attr, false,
+					 ETHTOOL_LINK_MODE_Autoneg_BIT, &ret);
+		if (ret < 0)
+			return ret;
+		printf("\tAdvertised auto-negotiation: %s\n",
+		       autoneg ? "Yes" : "No");
+		if (first) {
+			printf("Settings for %s:\n", nlctx->devname);
+			first = false;
+		}
+		ret = dump_link_modes(attr, true, LM_CLASS_FEC,
+				      "Advertised FEC modes: ", " ", "\n",
+				      "No");
+		if (ret < 0)
+			return ret;
+	}
+	if (tb[ETHA_SETTINGS_PEER_MODES] &&
+	    mask_ok(nlctx, ETH_SETTINGS_IM_LINKMODES)) {
+		const struct nlattr *attr = tb[ETHA_SETTINGS_PEER_MODES];
+		bool autoneg;
+
+		if (first) {
+			printf("Settings for %s:\n", nlctx->devname);
+			first = false;
+		}
+		ret = dump_link_modes(attr, false, LM_CLASS_REAL,
+				      "Link partner advertised link modes:  ",
+				      NULL, "\n", "Not reported");
+		if (ret < 0)
+			return ret;
+		ret = dump_pause(attr, false,
+				 "Link partner advertised pause frame use: ");
+		if (ret < 0)
+			return ret;
+		autoneg = bitset_get_bit(attr, false,
+					 ETHTOOL_LINK_MODE_Autoneg_BIT, &ret);
+		if (ret < 0)
+			return ret;
+		printf("\tLink partner advertised auto-negotiation: %s\n",
+		       autoneg ? "Yes" : "No");
+		ret = dump_link_modes(attr, true, LM_CLASS_FEC,
+				      "Link partner advertised FEC modes: ",
+				      " ", "\n", "No");
+		if (ret < 0)
+			return ret;
+	}
+	if (tb[ETHA_SETTINGS_SPEED] &&
+	    mask_ok(nlctx, ETH_SETTINGS_IM_LINKINFO)) {
+		u32 val = mnl_attr_get_u32(tb[ETHA_SETTINGS_SPEED]);
+
+		if (first) {
+			printf("Settings for %s:\n", nlctx->devname);
+			first = false;
+		}
+		if (val == 0 || val == (u16)(-1) || val == (u32)(-1))
+			printf("\tSpeed: Unknown!\n");
+		else
+			printf("\tSpeed: %uMb/s\n", val);
+	}
+	if (tb[ETHA_SETTINGS_DUPLEX] &&
+	    mask_ok(nlctx, ETH_SETTINGS_IM_LINKINFO)) {
+		u8 val = mnl_attr_get_u8(tb[ETHA_SETTINGS_DUPLEX]);
+
+		if (first) {
+			printf("Settings for %s:\n", nlctx->devname);
+			first = false;
+		}
+		print_enum(names_duplex, val, "\tDuplex: ", "Unknown! (%i)");
+	}
+	if (tb[ETHA_SETTINGS_PORT] &&
+	    mask_ok(nlctx, ETH_SETTINGS_IM_LINKINFO)) {
+		u8 val = mnl_attr_get_u8(tb[ETHA_SETTINGS_PORT]);
+
+		if (first) {
+			printf("Settings for %s:\n", nlctx->devname);
+			first = false;
+		}
+		print_enum(names_port, val, "\tPort: ", "Unknown! (%i)\n");
+		port = val;
+	}
+	if (tb[ETHA_SETTINGS_PHYADDR] &&
+	    mask_ok(nlctx, ETH_SETTINGS_IM_LINKINFO)) {
+		u8 val = mnl_attr_get_u8(tb[ETHA_SETTINGS_PHYADDR]);
+
+		if (first) {
+			printf("Settings for %s:\n", nlctx->devname);
+			first = false;
+		}
+		printf("\tPHYAD: %u\n", val);
+	}
+	if (tb[ETHA_SETTINGS_TRANSCEIVER] &&
+	    mask_ok(nlctx, ETH_SETTINGS_IM_LINKINFO)) {
+		u8 val = mnl_attr_get_u8(tb[ETHA_SETTINGS_TRANSCEIVER]);
+
+		if (first) {
+			printf("Settings for %s:\n", nlctx->devname);
+			first = false;
+		}
+		print_enum(names_transceiver, val, "\tTransceiver: ",
+			   "Unknown!");
+	}
+	if (tb[ETHA_SETTINGS_AUTONEG] &&
+	    mask_ok(nlctx, ETH_SETTINGS_IM_LINKINFO)) {
+		u8 val = mnl_attr_get_u8(tb[ETHA_SETTINGS_AUTONEG]);
+
+		if (first) {
+			printf("Settings for %s:\n", nlctx->devname);
+			first = false;
+		}
+		printf("\tAuto-negotiation: %s\n",
+		       (val == AUTONEG_DISABLE) ? "off" : "on");
+	}
+	if (tb[ETHA_SETTINGS_TP_MDIX] && tb[ETHA_SETTINGS_TP_MDIX] &&
+	    port == PORT_TP && mask_ok(nlctx, ETH_SETTINGS_IM_LINKINFO)) {
+		u8 mdix = mnl_attr_get_u8(tb[ETHA_SETTINGS_TP_MDIX]);
+		u8 mdix_ctrl = mnl_attr_get_u8(tb[ETHA_SETTINGS_TP_MDIX_CTRL]);
+
+		if (first) {
+			printf("Settings for %s:\n", nlctx->devname);
+			first = false;
+		}
+		dump_mdix(mdix, mdix_ctrl);
+	}
+	if (tb[ETHA_SETTINGS_WOL_MODES] &&
+	    mask_ok(nlctx, ETH_SETTINGS_IM_WOLINFO)) {
+		struct ethtool_wolinfo wolinfo = {};
+		const struct nla_bitfield32 *wol_bf =
+			mnl_attr_get_payload(tb[ETHA_SETTINGS_WOL_MODES]);
+
+		if (first) {
+			printf("Settings for %s:\n", nlctx->devname);
+			first = false;
+		}
+		wolinfo.wolopts = wol_bf->value;
+		wolinfo.supported = wol_bf->selector;
+		if (tb[ETHA_SETTINGS_SOPASS])
+			nl_copy_payload(wolinfo.sopass, SOPASS_MAX,
+					tb[ETHA_SETTINGS_SOPASS]);
+		ret = dump_wol(&wolinfo);
+		if (ret)
+			return ret;
+		allfail = false;
+	}
+	if (tb[ETHA_SETTINGS_MSGLVL] &&
+	    mask_ok(nlctx, ETH_SETTINGS_IM_MSGLEVEL)) {
+		struct nla_bitfield32 *val =
+			mnl_attr_get_payload(tb[ETHA_SETTINGS_MSGLVL]);
+		u32 msglvl = val->value;
+
+		if (first) {
+			printf("Settings for %s:\n", nlctx->devname);
+			first = false;
+		}
+		printf("	Current message level: 0x%08x (%d)\n"
+			"			       ",
+				msglvl, msglvl);
+		print_flags(flags_msglvl, n_flags_msglvl, msglvl);
+		fputc('\n', stdout);
+		allfail = false;
+	}
+	if (tb[ETHA_SETTINGS_LINK] && mask_ok(nlctx, ETH_SETTINGS_IM_LINK)) {
+		u32 link = mnl_attr_get_u32(tb[ETHA_SETTINGS_LINK]);
+
+		if (first) {
+			printf("Settings for %s:\n", nlctx->devname);
+			first = false;
+		}
+		printf("\tLink detected: %s\n", link ? "yes" : "no");
+		allfail = false;
+	};
+
+	if (allfail && !nlctx->is_monitor) {
+		fputs("No data available\n", stdout);
+		nlctx->exit_code = 75;
+		return MNL_CB_ERROR;
+	}
+	return MNL_CB_OK;
+}
+
+int settings_request(struct cmd_context *ctx, uint32_t info_mask)
+{
+	bool compact = info_mask & ETH_SETTINGS_IM_FEATURES;
+	struct nl_context *nlctx = ctx->nlctx;
+	int ret;
+
+	if (compact)
+		load_global_strings(nlctx);
+
+	ret = ethnl_prep_get_request(ctx, ETHNL_CMD_GET_SETTINGS,
+				     ETHA_SETTINGS_DEV);
+	if (ret < 0)
+		return ret;
+	if (ethnla_put_u32(nlctx, ETHA_SETTINGS_INFOMASK, info_mask) ||
+	    ethnla_put_flag(nlctx, ETHA_SETTINGS_COMPACT, compact))
+		return -EMSGSIZE;
+	return ethnl_send_get_request(nlctx, settings_reply_cb);
+}
+
+int nl_gset(struct cmd_context *ctx)
+{
+	return settings_request(ctx, ETH_SETTINGS_IM_LINKINFO |
+				     ETH_SETTINGS_IM_LINKMODES |
+				     ETH_SETTINGS_IM_MSGLEVEL |
+				     ETH_SETTINGS_IM_WOLINFO |
+				     ETH_SETTINGS_IM_LINK);
+}
+
+}
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [RFC PATCH ethtool v2 08/23] netlink: add helpers for command line parsing
  2018-07-30 12:55 [RFC PATCH ethtool v2 00/23] ethtool netlink interface (userspace side) (WiP) Michal Kubecek
                   ` (6 preceding siblings ...)
  2018-07-30 12:56 ` [RFC PATCH ethtool v2 07/23] netlink: add netlink handler for gset (no option) Michal Kubecek
@ 2018-07-30 12:56 ` Michal Kubecek
  2018-07-30 12:56 ` [RFC PATCH ethtool v2 09/23] netlink: add netlink handler for sset (-s) Michal Kubecek
                   ` (14 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Michal Kubecek @ 2018-07-30 12:56 UTC (permalink / raw)
  To: netdev; +Cc: linux-kernel, John W. Linville

Support for easier parsing of subcommand parameters and their values from
command line. Existing parser helpers in ethtool.c are closely tied to
ioctl() interface and using them would be often inconvenient.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Makefile.am       |   1 +
 netlink/netlink.h |   4 +
 netlink/parser.c  | 527 ++++++++++++++++++++++++++++++++++++++++++++++
 netlink/parser.h  |  45 ++++
 4 files changed, 577 insertions(+)
 create mode 100644 netlink/parser.c
 create mode 100644 netlink/parser.h

diff --git a/Makefile.am b/Makefile.am
index 629f04e1202b..7cc4adc53c59 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -23,6 +23,7 @@ if ETHTOOL_ENABLE_NETLINK
 ethtool_SOURCES += \
 		  netlink/netlink.c netlink/netlink.h netlink/extapi.h \
 		  netlink/strset.c netlink/strset.h netlink/monitor.c \
+		  netlink/parser.c netlink/parser.h \
 		  netlink/drvinfo.c netlink/settings.c \
 		  uapi/linux/ethtool_netlink.h \
 		  uapi/linux/netlink.h uapi/linux/genetlink.h
diff --git a/netlink/netlink.h b/netlink/netlink.h
index a572a165718e..32c2f9f33ba1 100644
--- a/netlink/netlink.h
+++ b/netlink/netlink.h
@@ -30,6 +30,10 @@ struct nl_context {
 	void *msg;
 	const char *devname;
 	bool is_dump;
+	const char *cmd;
+	const char *param;
+	char **argp;
+	int argc;
 	int exit_code;
 	bool is_monitor;
 	uint8_t filter_cmd;
diff --git a/netlink/parser.c b/netlink/parser.c
new file mode 100644
index 000000000000..589004d1f9f9
--- /dev/null
+++ b/netlink/parser.c
@@ -0,0 +1,527 @@
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "parser.h"
+
+static void parser_err_unknown_param(struct nl_context *nlctx)
+{
+	fprintf(stderr, "ethtool (%s): unknown parameter '%s'\n", nlctx->cmd,
+		nlctx->param);
+}
+
+static void parser_err_dup_param(struct nl_context *nlctx)
+{
+	fprintf(stderr, "ethtool (%s): duplicate parameter '%s'\n", nlctx->cmd,
+		nlctx->param);
+}
+
+static void parser_err_min_argc(struct nl_context *nlctx, unsigned int min_argc)
+{
+	if (min_argc == 1)
+		fprintf(stderr, "ethtool (%s): no value for parameter '%s'\n",
+			nlctx->cmd, nlctx->param);
+	else
+		fprintf(stderr,
+			"ethtool (%s): parameter '%s' requires %u words\n",
+			nlctx->cmd, nlctx->param, min_argc);
+}
+
+static void parser_err_invalid_value(struct nl_context *nlctx, const char *val)
+{
+	fprintf(stderr, "ethtool (%s): invalid value '%s' for parameter '%s'\n",
+		nlctx->cmd, val, nlctx->param);
+}
+
+static void parser_err_invalid_flag(struct nl_context *nlctx, const char *flag)
+{
+	fprintf(stderr, "ethtool (%s): flag '%s' for parameter '%s' is not "
+			"followed by 'on' or 'off'\n",
+		nlctx->cmd, flag, nlctx->param);
+}
+
+static int __parse_u32(const char *arg, u32 *result, u32 min, u32 max, int base)
+{
+	unsigned long long val;
+	char *endptr;
+
+	if (!arg || !arg[0])
+		return -EINVAL;
+	val = strtoul(arg, &endptr, base);
+	if (*endptr || val < min || val > max)
+		return -EINVAL;
+
+	*result = (u32)val;
+	return 0;
+}
+
+static int parse_u32d(const char *arg, u32 *result)
+{
+	return __parse_u32(arg, result, 0, 0xffffffff, 10);
+}
+
+static int parse_x32(const char *arg, u32 *result)
+{
+	return __parse_u32(arg, result, 0, 0xffffffff, 16);
+}
+
+static int parse_u32(const char *arg, u32 *result)
+{
+	if (!arg)
+		return -EINVAL;
+	if ((arg[0] == '0') && (arg[1] == 'x' || arg[1] == 'X'))
+		return parse_x32(arg + 2, result);
+	else
+		return parse_u32d(arg, result);
+}
+
+static int parse_u8(const char *arg, u8 *result)
+{
+	u32 val;
+	int ret = parse_u32(arg, &val);
+
+	if (ret < 0)
+		return ret;
+	if (val > UINT8_MAX)
+		return -EINVAL;
+
+	*result = (u8)val;
+	return 0;
+}
+
+static int lookup_u8(const char *arg, u8 *result,
+		     const struct lookup_entry_u8 *tbl)
+{
+	if (!arg)
+		return -EINVAL;
+	while (tbl->arg) {
+		if (!strcmp(tbl->arg, arg)) {
+			*result = tbl->val;
+			return 0;
+		}
+		tbl++;
+	}
+
+	return -EINVAL;
+}
+
+int nl_parse_direct_u32(struct nl_context *nlctx, uint16_t type,
+			const void *data)
+{
+	const char *arg = *nlctx->argp;
+	u32 val;
+	int ret;
+
+	nlctx->argp++;
+	nlctx->argc--;
+	ret = parse_u32(arg, &val);
+	if (ret < 0) {
+		parser_err_invalid_value(nlctx, arg);
+		return ret;
+	}
+
+	return ethnla_put_u32(nlctx, type, val) ? -EMSGSIZE : 0;
+}
+
+int nl_parse_direct_u8(struct nl_context *nlctx, uint16_t type,
+		       const void *data)
+{
+	const char *arg = *nlctx->argp;
+	u8 val;
+	int ret;
+
+	nlctx->argp++;
+	nlctx->argc--;
+	ret = parse_u8(arg, &val);
+	if (ret < 0) {
+		parser_err_invalid_value(nlctx, arg);
+		return ret;
+	}
+
+	return ethnla_put_u8(nlctx, type, val) ? -EMSGSIZE : 0;
+}
+
+int nl_parse_bool(struct nl_context *nlctx, uint16_t type, const void *data)
+{
+	const char *arg = *nlctx->argp;
+	int ret;
+
+	nlctx->argp++;
+	nlctx->argc--;
+	if (!strcmp(arg, "on"))
+		ret = ethnla_put_u8(nlctx, type, 1);
+	else if (!strcmp(arg, "off"))
+		ret = ethnla_put_u8(nlctx, type, 0);
+	else {
+		parser_err_invalid_value(nlctx, arg);
+		return -EINVAL;
+	}
+
+	return ret ? -EMSGSIZE : 0;
+}
+
+int nl_parse_lookup_u8(struct nl_context *nlctx, uint16_t type,
+		       const void *data)
+{
+	const char *arg = *nlctx->argp;
+	u8 val;
+	int ret;
+
+	nlctx->argp++;
+	nlctx->argc--;
+	ret = lookup_u8(arg, &val, data);
+	if (ret < 0) {
+		parser_err_invalid_value(nlctx, arg);
+		return ret;
+	}
+
+	return ethnla_put_u8(nlctx, type, val) ? -EMSGSIZE : 0;
+}
+
+int nl_parse_bitfield32(struct nl_context *nlctx, uint16_t type,
+			const void *data)
+{
+	const char *arg = *nlctx->argp;
+	const struct flag_info *flags = data;
+	u32 value, selector;
+	int ret;
+
+	ret = parse_u32(arg, &value);
+	if (isdigit(arg[0])) {
+		char *mask = strchr(arg, '/');
+
+		if (mask) {
+			/* numeric value / mask */
+			*mask = '\0';
+			mask++;
+			ret = parse_u32(mask, &selector);
+			if (ret < 0) {
+				parser_err_invalid_value(nlctx, mask);
+				return ret;
+			}
+		} else {
+			/* numeric value */
+			selector = ~(u32)0;
+		}
+		ret = parse_u32(arg, &value);
+		if (ret < 0) {
+			parser_err_invalid_value(nlctx, arg);
+			return ret;
+		}
+		nlctx->argp++;
+		nlctx->argc--;
+	} else {
+		/* flag on/off... [--] */
+		value = 0;
+		selector = 0;
+		while (nlctx->argc > 0) {
+			const struct flag_info *flag = flags;
+
+			if (!strcmp(*nlctx->argp, "--")) {
+				nlctx->argp++;
+				nlctx->argc--;
+				break;
+			}
+			if (nlctx->argc < 2 ||
+			    (strcmp(nlctx->argp[1], "on") &&
+			     strcmp(nlctx->argp[1], "off"))) {
+				parser_err_invalid_flag(nlctx, *nlctx->argp);
+				return -EINVAL;
+			}
+			while (flag->name && strcmp(*nlctx->argp, flag->name))
+				flag++;
+			if (!flag->name) {
+				parser_err_invalid_value(nlctx, *nlctx->argp);
+				return -EINVAL;
+			}
+			selector |= flag->value;
+			if (!strcmp(nlctx->argp[1], "on"))
+				value |= flag->value;
+
+			nlctx->argp += 2;
+			nlctx->argc -= 2;
+		}
+	}
+
+	return ethnla_put_bitfield32(nlctx, type, value, selector);
+}
+
+static bool is_hex(char c)
+{
+	if (isdigit(c))
+		return true;
+	else
+		return (c >= 'a' && c <= 'f');
+}
+
+/* Return true if a bitset argument should be parsed as numeric, i.e.
+ * (a) it starts with '0x'
+ * (b) it consists only of hex digits and at most one slash which can be
+ *     optionally followed by "0x"
+ */
+static bool is_numeric_bitset(const char *arg)
+{
+	const char *p = arg;
+	bool has_slash = false;
+
+	if (arg[0] == '0' && arg[1] == 'x')
+		return true;
+	while (*p) {
+		if (*p == '/') {
+			if (has_slash)
+				return false;
+			has_slash = true;
+			p++;
+			if (p[0] == '0' && p[1] == 'x')
+				p += 2;
+			continue;
+		}
+		if (!is_hex(*p))
+			return false;
+		p++;
+	}
+	return true;
+}
+
+/* number of significant bits */
+static unsigned int nsb(u32 x)
+{
+	unsigned int ret = 0;
+
+	if (!(x & 0xffff0000U)) {
+		x >>= 16;
+		ret += 16;
+	}
+	if (!(x & 0xff00U)) {
+		x >>= 8;
+		ret += 8;
+	}
+	if (!(x & 0xf0U)) {
+		x >>= 4;
+		ret += 4;
+	}
+	if (!(x & 0xcU)) {
+		x >>= 2;
+		ret += 2;
+	}
+	if (!(x & 0x2U)) {
+		x >>= 1;
+		ret += 1;
+	}
+
+	return ret + x;
+}
+
+/* Parse hex string (without leading "0x") into a bitmap consisting of 32-bit
+ * words. Caller must make sure arg is at least len characters long and dst has
+ * place for at least (len + 7) / 8 32-bit words.
+ * Returns number of significant bits in the bitmap on success and negative
+ * value on error.
+ */
+static int parse_hex_string(const char *arg, unsigned int len, u32 *dst)
+{
+	const char *p = arg;
+	unsigned int nwords = (len + 7) / 8;
+	unsigned int nbits = 0;
+	char buff[9];
+
+	memset(dst, '\0', nwords * sizeof(u32));
+	while (len > 0) {
+		unsigned int chunk = (len % 8) ?: 8;
+		unsigned long val;
+		char *endp;
+
+		memcpy(buff, p, chunk);
+		buff[chunk] = '\0';
+		val = strtoul(buff, &endp, 16);
+		if (*endp)
+			return -EINVAL;
+		*dst++ = (u32)val;
+		if (nbits)
+			nbits += 8 * chunk;
+		else
+			nbits = nsb(val);
+
+		p += chunk;
+		len -= chunk;
+	}
+	return nbits;
+}
+
+static int nl_parse_bitset_compact(struct nl_context *nlctx, uint16_t type)
+{
+	const char *arg = *nlctx->argp;
+	unsigned int nwords, len1, len2;
+	bool has_mask = false;
+	struct nlattr *nest;
+	const char *maskptr;
+	u32 *value = NULL;
+	u32 *mask = NULL;
+	int nbits;
+	int ret;
+
+	if (arg[0] == '0' && arg[1] == 'x')
+		arg += 2;
+
+	maskptr = strchr(arg, '/');
+	len1 = maskptr ? ( maskptr - arg) : strlen(arg);
+	nwords = (len1 + 7) / 8;
+	nbits = 0;
+
+	value = malloc(nwords * sizeof(u32));
+	if (!value)
+		return -ENOMEM;
+	ret = -ENOMEM;
+	mask = malloc(nwords * sizeof(u32));
+	if (!mask)
+		goto free_value;
+
+	if (maskptr) {
+		has_mask = true;
+		maskptr++;
+		if (maskptr[0] == '0' && maskptr[1] == 'x')
+			maskptr += 2;
+		len2 = strlen(maskptr);
+		if (len2 > len1)
+			nwords = (len2 + 7) / 8;
+		mask = malloc(nwords * sizeof(u32));
+		if (!mask)
+			return -ENOMEM;
+		ret = parse_hex_string(maskptr, strlen(maskptr),
+					  mask);
+		if (ret < 0)
+			goto free_mask;
+		nbits = ret;
+	}
+
+	ret = parse_hex_string(arg, len1, value);
+	if (ret < 0)
+		goto free_value;
+	nbits = nbits ?: ret;
+	nwords = (nbits + 31) / 32;
+
+	ret = -EMSGSIZE;
+	if (!(nest = ethnla_nest_start(nlctx, type)) ||
+	    ethnla_put_u32(nlctx, ETHA_BITSET_SIZE, nbits) ||
+	    ethnla_put(nlctx, ETHA_BITSET_VALUE, nwords * sizeof(u32), value) ||
+	    (has_mask &&
+	     ethnla_put(nlctx, ETHA_BITSET_MASK, nwords * sizeof(u32), mask)))
+		goto free_mask;
+	mnl_attr_nest_end(nlctx->nlhdr, nest);
+	ret = 0;
+
+free_mask:
+	free(mask);
+free_value:
+	free(value);
+	nlctx->argp++;
+	nlctx->argc--;
+	return ret;
+}
+
+static int nl_parse_bitset_list(struct nl_context *nlctx, uint16_t type,
+				const void *data)
+{
+	struct nlmsghdr *nlhdr = nlctx->nlhdr;
+	struct nlattr *bitset_attr;
+	struct nlattr *bits_attr;
+	struct nlattr *bit_attr;
+	int ret;
+
+	ret = -EMSGSIZE;
+	bitset_attr = ethnla_nest_start(nlctx, type);
+	if (!bitset_attr)
+		return ret;
+	bits_attr = ethnla_nest_start(nlctx, ETHA_BITSET_BITS);
+	if (!bits_attr)
+		goto err;
+
+	while (nlctx->argc > 0) {
+		if (!strcmp(*nlctx->argp, "--")) {
+			nlctx->argp++;
+			nlctx->argc--;
+			break;
+		}
+		ret = -EINVAL;
+		if (nlctx->argc < 2 ||
+		    (strcmp(nlctx->argp[1], "on") &&
+		     strcmp(nlctx->argp[1], "off"))) {
+			parser_err_invalid_flag(nlctx, *nlctx->argp);
+			goto err;
+		}
+
+		ret = -EMSGSIZE;
+		bit_attr = ethnla_nest_start(nlctx, ETHA_BITS_BIT);
+		if (!bit_attr)
+			goto err;
+		if (ethnla_put_strz(nlctx, ETHA_BIT_NAME, nlctx->argp[0]))
+			goto err;
+		if (ethnla_put_flag(nlctx, ETHA_BIT_VALUE,
+				    !strcmp(nlctx->argp[1], "on")))
+			goto err;
+		mnl_attr_nest_end(nlhdr, bit_attr);
+
+		nlctx->argp += 2;
+		nlctx->argc -= 2;
+	}
+
+	mnl_attr_nest_end(nlhdr, bits_attr);
+	mnl_attr_nest_end(nlhdr, bitset_attr);
+	return 0;
+err:
+	mnl_attr_nest_cancel(nlhdr, bitset_attr);
+	return ret;
+}
+
+int nl_parse_bitset(struct nl_context *nlctx, uint16_t type, const void *data)
+{
+	if (is_numeric_bitset(*nlctx->argp))
+		return nl_parse_bitset_compact(nlctx, type);
+	else
+		return nl_parse_bitset_list(nlctx, type, data);
+}
+
+int nl_parser(struct cmd_context *ctx, const struct param_parser *params,
+	      unsigned int max_type)
+{
+	struct nl_context *nlctx = ctx->nlctx;
+	bool attr_set[max_type];
+	int ret;
+
+	memset(attr_set, '\0', max_type * sizeof(attr_set[0]));
+
+	while (nlctx->argc > 0) {
+		const struct param_parser *parser = params;
+
+		nlctx->param = *nlctx->argp;
+		while (parser->arg) {
+			if (!strcmp(nlctx->param, parser->arg))
+				break;
+			parser++;
+		}
+		if (!parser->arg) {
+			parser_err_unknown_param(nlctx);
+			return -EINVAL;
+		}
+		nlctx->argp++;
+		nlctx->argc--;
+		if (attr_set[parser - params]) {
+			parser_err_dup_param(nlctx);
+			return -EINVAL;
+		}
+		if (nlctx->argc < parser->min_argc) {
+			parser_err_min_argc(nlctx, parser->min_argc);
+			return -EINVAL;
+		}
+
+		ret = parser->handler(nlctx, parser->type, parser->handler_data);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
diff --git a/netlink/parser.h b/netlink/parser.h
new file mode 100644
index 000000000000..7308da25f093
--- /dev/null
+++ b/netlink/parser.h
@@ -0,0 +1,45 @@
+#ifndef ETHTOOL_NETLINK_PARSER_H__
+#define ETHTOOL_NETLINK_PARSER_H__
+
+#include "netlink.h"
+
+struct lookup_entry_u32 {
+	const char	*arg;
+	u32		val;
+};
+
+struct lookup_entry_u16 {
+	const char	*arg;
+	u16		val;
+};
+
+struct lookup_entry_u8{
+	const char	*arg;
+	u8		val;
+};
+
+typedef int (*param_parser_cb_t)(struct nl_context *, uint16_t, const void *);
+
+struct param_parser {
+	const char		*arg;
+	uint16_t		type;
+	param_parser_cb_t	handler;
+	const void		*handler_data;
+	unsigned int		min_argc;
+};
+
+int nl_parse_direct_u32(struct nl_context *nlctx, uint16_t type,
+			const void *data);
+int nl_parse_direct_u8(struct nl_context *nlctx, uint16_t type,
+		       const void *data);
+int nl_parse_bool(struct nl_context *nlctx, uint16_t type, const void *data);
+int nl_parse_lookup_u8(struct nl_context *nlctx, uint16_t type,
+		       const void *data);
+int nl_parse_bitfield32(struct nl_context *nlctx, uint16_t type,
+			const void *data);
+int nl_parse_bitset(struct nl_context *nlctx, uint16_t type, const void *data);
+
+int nl_parser(struct cmd_context *ctx, const struct param_parser *params,
+	      unsigned int max_type);
+
+#endif /* ETHTOOL_NETLINK_PARSER_H__ */
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [RFC PATCH ethtool v2 09/23] netlink: add netlink handler for sset (-s)
  2018-07-30 12:55 [RFC PATCH ethtool v2 00/23] ethtool netlink interface (userspace side) (WiP) Michal Kubecek
                   ` (7 preceding siblings ...)
  2018-07-30 12:56 ` [RFC PATCH ethtool v2 08/23] netlink: add helpers for command line parsing Michal Kubecek
@ 2018-07-30 12:56 ` Michal Kubecek
  2018-07-30 12:56 ` [RFC PATCH ethtool v2 10/23] netlink: add netlink handler for gfeatures (-k) Michal Kubecek
                   ` (13 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Michal Kubecek @ 2018-07-30 12:56 UTC (permalink / raw)
  To: netdev; +Cc: linux-kernel, John W. Linville

Implement "ethtool -s <dev>" subcommand using netlink interface command
ETHNL_CMD_SET_SETTINGS.

Add specific parsers for wol modes and MAC address (used for wake-on-lan
password here).

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 ethtool.c          |   9 +-
 netlink/extapi.h   |   1 +
 netlink/netlink.c  |   9 ++
 netlink/netlink.h  |   1 +
 netlink/parser.c   |  34 ++++++-
 netlink/parser.h   |   4 +
 netlink/settings.c | 216 +++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 269 insertions(+), 5 deletions(-)

diff --git a/ethtool.c b/ethtool.c
index bc7969f829f6..0b40b96cf9bf 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -4923,6 +4923,7 @@ static int show_usage(struct cmd_context *ctx);
  */
 #define nl_gdrv		NULL
 #define nl_gset		NULL
+#define nl_sset		NULL
 #endif
 
 static const struct option {
@@ -4933,19 +4934,19 @@ static const struct option {
 	char *help;
 	char *opthelp;
 } args[] = {
-	{ "-s|--change", 1, do_sset, NULL,
+	{ "-s|--change", 1, do_sset, nl_sset,
 	  "Change generic options",
 	  "		[ speed %d ]\n"
 	  "		[ duplex half|full ]\n"
 	  "		[ port tp|aui|bnc|mii|fibre ]\n"
 	  "		[ mdix auto|on|off ]\n"
 	  "		[ autoneg on|off ]\n"
-	  "		[ advertise %x ]\n"
+	  "		[ advertise %x[/%x] | mode on|off ... [--] ]\n"
 	  "		[ phyad %d ]\n"
 	  "		[ xcvr internal|external ]\n"
-	  "		[ wol p|u|m|b|a|g|s|d... ]\n"
+	  "		[ wol %d[/%d] |  p|u|m|b|a|g|s|d...[/p|u|m|b|a|g|s|d...] ]\n"
 	  "		[ sopass %x:%x:%x:%x:%x:%x ]\n"
-	  "		[ msglvl %d | msglvl type on|off ... ]\n" },
+	  "		[ msglvl %d[/%d] | type on|off ... [--] ]\n" },
 	{ "-a|--show-pause", 1, do_gpause, NULL,
 	  "Show pause options" },
 	{ "-A|--pause", 1, do_spause, NULL,
diff --git a/netlink/extapi.h b/netlink/extapi.h
index 20c9b03b2d3c..66573f0b4304 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -15,6 +15,7 @@ int netlink_done(struct cmd_context *ctx);
 
 int nl_gdrv(struct cmd_context *ctx);
 int nl_gset(struct cmd_context *ctx);
+int nl_sset(struct cmd_context *ctx);
 int nl_monitor(struct cmd_context *ctx);
 
 void monitor_usage();
diff --git a/netlink/netlink.c b/netlink/netlink.c
index fc87577191f0..8b3dce970fd1 100644
--- a/netlink/netlink.c
+++ b/netlink/netlink.c
@@ -573,6 +573,15 @@ static int get_ethnl_family(struct nl_context *nlctx)
 	return (nlctx->ethnl_fam ? 0 : -EADDRNOTAVAIL);
 }
 
+int nomsg_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+	const struct genlmsghdr *ghdr = (const struct genlmsghdr *)(nlhdr + 1);
+
+	fprintf(stderr, "received unexpected message: len=%u type=%u cmd=%u\n",
+	       nlhdr->nlmsg_len, nlhdr->nlmsg_type, ghdr->cmd);
+	return MNL_CB_OK;
+}
+
 /* initialization */
 
 static int nlctx_init(struct nl_context *nlctx)
diff --git a/netlink/netlink.h b/netlink/netlink.h
index 32c2f9f33ba1..9c23526da627 100644
--- a/netlink/netlink.h
+++ b/netlink/netlink.h
@@ -72,6 +72,7 @@ int attr_cb(const struct nlattr *attr, void *data);
 int ethnl_prep_get_request(struct cmd_context *ctx, unsigned int nlcmd,
 			   uint16_t dev_attrtype);
 int ethnl_send_get_request(struct nl_context *nlctx, mnl_cb_t cb);
+int nomsg_reply_cb(const struct nlmsghdr *nlhdr, void *data);
 int __init_aux_nlctx(struct nl_context *nlctx);
 
 /* put data wrappers */
diff --git a/netlink/parser.c b/netlink/parser.c
index 589004d1f9f9..d1536ecb1d52 100644
--- a/netlink/parser.c
+++ b/netlink/parser.c
@@ -69,7 +69,7 @@ static int parse_x32(const char *arg, u32 *result)
 	return __parse_u32(arg, result, 0, 0xffffffff, 16);
 }
 
-static int parse_u32(const char *arg, u32 *result)
+int parse_u32(const char *arg, u32 *result)
 {
 	if (!arg)
 		return -EINVAL;
@@ -485,6 +485,38 @@ int nl_parse_bitset(struct nl_context *nlctx, uint16_t type, const void *data)
 		return nl_parse_bitset_list(nlctx, type, data);
 }
 
+int nl_parse_mac_addr(struct nl_context *nlctx, uint16_t type, const void *data)
+{
+	const char *arg = *nlctx->argp;
+	u8 val[ETH_ALEN];
+	unsigned int i;
+	const char *p;
+
+	nlctx->argp++;
+	nlctx->argc--;
+
+	p = arg;
+	i = 0;
+	while (i < ETH_ALEN && *p) {
+		char *endp;
+		unsigned long byte = strtoul(p, &endp, 16);
+
+		if ((endp - p > 2) || (*endp && *endp != ':'))
+			goto err;
+		val[i++] = (u8) byte;
+		p = endp + (*endp ? 1 : 0);
+	}
+	if (i < ETH_ALEN)
+		goto err;
+
+	return ethnla_put(nlctx, type, ETH_ALEN, val);
+
+err:
+	fprintf(stderr, "ethtool (%s): invalid value '%s' of parameter '%s'\n",
+		nlctx->cmd, arg, nlctx->param);
+	return -EINVAL;
+}
+
 int nl_parser(struct cmd_context *ctx, const struct param_parser *params,
 	      unsigned int max_type)
 {
diff --git a/netlink/parser.h b/netlink/parser.h
index 7308da25f093..2fd9a8bb23d9 100644
--- a/netlink/parser.h
+++ b/netlink/parser.h
@@ -28,6 +28,8 @@ struct param_parser {
 	unsigned int		min_argc;
 };
 
+int parse_u32(const char *arg, u32 *result);
+
 int nl_parse_direct_u32(struct nl_context *nlctx, uint16_t type,
 			const void *data);
 int nl_parse_direct_u8(struct nl_context *nlctx, uint16_t type,
@@ -38,6 +40,8 @@ int nl_parse_lookup_u8(struct nl_context *nlctx, uint16_t type,
 int nl_parse_bitfield32(struct nl_context *nlctx, uint16_t type,
 			const void *data);
 int nl_parse_bitset(struct nl_context *nlctx, uint16_t type, const void *data);
+int nl_parse_mac_addr(struct nl_context *nlctx, uint16_t type,
+		      const void *data);
 
 int nl_parser(struct cmd_context *ctx, const struct param_parser *params,
 	      unsigned int max_type);
diff --git a/netlink/settings.c b/netlink/settings.c
index 120fc95aad19..b077a96325e5 100644
--- a/netlink/settings.c
+++ b/netlink/settings.c
@@ -5,6 +5,7 @@
 #include "../internal.h"
 #include "../common.h"
 #include "netlink.h"
+#include "parser.h"
 
 /* GET_SETTINGS */
 
@@ -299,3 +300,218 @@ int nl_gset(struct cmd_context *ctx)
 }
 
 }
+
+/* SET_SETTINGS */
+
+enum {
+	WAKE_PHY_BIT		= 0,
+	WAKE_UCAST_BIT		= 1,
+	WAKE_MCAST_BIT		= 2,
+	WAKE_BCAST_BIT		= 3,
+	WAKE_ARP_BIT		= 4,
+	WAKE_MAGIC_BIT		= 5,
+	WAKE_MAGICSECURE_BIT	= 6,
+};
+
+#define WAKE_ALL (WAKE_PHY | WAKE_UCAST | WAKE_MCAST | WAKE_BCAST | WAKE_ARP | \
+		  WAKE_MAGIC | WAKE_MAGICSECURE)
+
+char wol_modes[32] = {
+	[WAKE_PHY_BIT]		= 'p',
+	[WAKE_UCAST_BIT]	= 'u',
+	[WAKE_MCAST_BIT]	= 'm',
+	[WAKE_BCAST_BIT]	= 'b',
+	[WAKE_ARP_BIT]		= 'a',
+	[WAKE_MAGIC_BIT]	= 'g',
+	[WAKE_MAGICSECURE_BIT]	= 's',
+};
+
+static int __nl_parse_wol_modes(struct nl_context *nlctx, const char *str,
+				u32 *result)
+{
+	unsigned int i;
+	const char *p;
+	int ret;
+
+	ret = parse_u32(str, result);
+	if (ret == 0)
+		return 0;
+
+	*result = 0;
+	if (str[0] == 'd' && !str[1])
+		return 0;
+
+	p = str;
+	while (*p) {
+		for (i = 0; i < 32; i++) {
+			if (wol_modes[i] == *p) {
+				*result |= (1U << i);
+				break;
+			}
+		}
+		if (i == 32) {
+			fprintf(stderr,
+				"ethtool (%s): invalid wol modes '%s'\n",
+				nlctx->cmd, str);
+			return -EINVAL;
+		}
+		p++;
+	}
+
+	return 0;
+}
+
+static int nl_parse_wol_modes(struct nl_context *nlctx, uint16_t type,
+			      const void *data)
+{
+	const char *arg = *nlctx->argp;
+	char *mask = strchr(arg, '/');
+	u32 value, selector;
+	int ret;
+
+	nlctx->argp++;
+	nlctx->argc--;
+	if (mask) {
+		*mask = '\0';
+		mask++;
+	}
+
+	ret = __nl_parse_wol_modes(nlctx, arg, &value);
+	if (ret < 0)
+		return ret;
+
+	if (!mask) {
+		selector = ~(u32)0;
+	} else {
+		ret = __nl_parse_wol_modes(nlctx, mask, &selector);
+		if (ret < 0)
+			return ret;
+	}
+
+	return ethnla_put_bitfield32(nlctx, type, value, selector);
+}
+
+static const struct lookup_entry_u32 duplex_values[] = {
+	{ .arg = "half",	.val = DUPLEX_HALF },
+	{ .arg = "full",	.val = DUPLEX_FULL },
+	{}
+};
+
+static const struct lookup_entry_u8 port_values[] = {
+	{ .arg = "tp",		.val = PORT_TP },
+	{ .arg = "aui",		.val = PORT_AUI },
+	{ .arg = "bnc",		.val = PORT_BNC },
+	{ .arg = "mii",		.val = PORT_MII },
+	{ .arg = "fibre",	.val = PORT_FIBRE },
+	{}
+};
+
+static const struct lookup_entry_u8 mdix_values[] = {
+	{ .arg = "auto",	.val = ETH_TP_MDI_AUTO },
+	{ .arg = "on",		.val = ETH_TP_MDI_X },
+	{ .arg = "off",		.val = ETH_TP_MDI },
+	{}
+};
+
+static const struct lookup_entry_u8 autoneg_values[] = {
+	{ .arg = "off",		.val = AUTONEG_DISABLE },
+	{ .arg = "on",		.val = AUTONEG_ENABLE },
+	{}
+};
+
+static const struct param_parser sset_params[] = {
+	{
+		.arg		= "speed",
+		.type		= ETHA_SETTINGS_SPEED,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "duplex",
+		.type		= ETHA_SETTINGS_DUPLEX,
+		.handler	= nl_parse_lookup_u8,
+		.handler_data	= duplex_values,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "port",
+		.type		= ETHA_SETTINGS_PORT,
+		.handler	= nl_parse_lookup_u8,
+		.handler_data	= port_values,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "mdix",
+		.type		= ETHA_SETTINGS_TP_MDIX_CTRL,
+		.handler	= nl_parse_lookup_u8,
+		.handler_data	= mdix_values,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "autoneg",
+		.type		= ETHA_SETTINGS_AUTONEG,
+		.handler	= nl_parse_lookup_u8,
+		.handler_data	= autoneg_values,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "advertise",
+		.type		= ETHA_SETTINGS_LINK_MODES,
+		.handler	= nl_parse_bitset,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "phyad",
+		.type		= ETHA_SETTINGS_PHYADDR,
+		.handler	= nl_parse_direct_u8,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "wol",
+		.type		= ETHA_SETTINGS_WOL_MODES,
+		.handler	= nl_parse_wol_modes,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "sopass",
+		.type		= ETHA_SETTINGS_SOPASS,
+		.handler	= nl_parse_mac_addr,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "msglvl",
+		.type		= ETHA_SETTINGS_MSGLVL,
+		.handler	= nl_parse_bitfield32,
+		.handler_data	= flags_msglvl,
+		.min_argc	= 1,
+	},
+	{}
+};
+
+int nl_sset(struct cmd_context *ctx)
+{
+	struct nl_context *nlctx = ctx->nlctx;
+	int ret;
+
+	nlctx->cmd = "-s";
+	nlctx->argp = ctx->argp;
+	nlctx->argc = ctx->argc;
+	ret = msg_init(nlctx, ETHNL_CMD_SET_SETTINGS,
+		       NLM_F_REQUEST | NLM_F_ACK);
+	if (ret < 0)
+		return 2;
+	if (ethnla_put_dev(nlctx, ETHA_SETTINGS_DEV, ctx->devname))
+		return -EMSGSIZE;
+
+	ret = nl_parser(ctx, sset_params, ETHA_SETTINGS_MAX);
+	if (ret < 0)
+		return 2;
+
+	ret = ethnl_sendmsg(nlctx);
+	if (ret < 0)
+		return 75;
+	ret = ethnl_process_reply(nlctx, nomsg_reply_cb);
+	if (ret == 0)
+		return 0;
+	return nlctx->exit_code ?: 75;
+}
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [RFC PATCH ethtool v2 10/23] netlink: add netlink handler for gfeatures (-k)
  2018-07-30 12:55 [RFC PATCH ethtool v2 00/23] ethtool netlink interface (userspace side) (WiP) Michal Kubecek
                   ` (8 preceding siblings ...)
  2018-07-30 12:56 ` [RFC PATCH ethtool v2 09/23] netlink: add netlink handler for sset (-s) Michal Kubecek
@ 2018-07-30 12:56 ` Michal Kubecek
  2018-07-30 12:56 ` [RFC PATCH ethtool v2 11/23] netlink: add netlink handler for sfeatures (-K) Michal Kubecek
                   ` (12 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Michal Kubecek @ 2018-07-30 12:56 UTC (permalink / raw)
  To: netdev; +Cc: linux-kernel, John W. Linville

Implement "ethtool -k <dev>" subcommand using netlink interface command
ETHNL_CMD_GET_SETTINGS with ETH_SETTINGS_IM_FEATURES mask.

The implementation tries to emulate legacy flags but the result is not
exactly equal to the ioctl code. In particular, we map netdev features to
legacy flags based on name patterns in off_flag_def[] but this mapping is
slightly different from mapping used by kernel ioctl code.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 common.c           |  30 ++++++
 common.h           |  21 +++++
 ethtool.c          |  70 +++-----------
 netlink/extapi.h   |   1 +
 netlink/monitor.c  |   6 ++
 netlink/settings.c | 226 +++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 296 insertions(+), 58 deletions(-)

diff --git a/common.c b/common.c
index 59897acd2512..db9a360ffae6 100644
--- a/common.c
+++ b/common.c
@@ -283,3 +283,33 @@ void dump_mdix(u8 mdix, u8 mdix_ctrl)
 		fprintf(stdout, "\n");
 	}
 }
+
+const struct off_flag_def off_flag_def[OFF_FLAG_DEF_SIZE] = {
+	{ "rx",     "rx-checksumming",		    "rx-checksum",
+	  ETHTOOL_GRXCSUM, ETHTOOL_SRXCSUM, ETH_FLAG_RXCSUM,	0 },
+	{ "tx",     "tx-checksumming",		    "tx-checksum-*",
+	  ETHTOOL_GTXCSUM, ETHTOOL_STXCSUM, ETH_FLAG_TXCSUM,	0 },
+	{ "sg",     "scatter-gather",		    "tx-scatter-gather*",
+	  ETHTOOL_GSG,	   ETHTOOL_SSG,     ETH_FLAG_SG,	0 },
+	{ "tso",    "tcp-segmentation-offload",	    "tx-tcp*-segmentation",
+	  ETHTOOL_GTSO,	   ETHTOOL_STSO,    ETH_FLAG_TSO,	0 },
+	{ "ufo",    "udp-fragmentation-offload",    "tx-udp-fragmentation",
+	  ETHTOOL_GUFO,	   ETHTOOL_SUFO,    ETH_FLAG_UFO,	0 },
+	{ "gso",    "generic-segmentation-offload", "tx-generic-segmentation",
+	  ETHTOOL_GGSO,	   ETHTOOL_SGSO,    ETH_FLAG_GSO,	0 },
+	{ "gro",    "generic-receive-offload",	    "rx-gro",
+	  ETHTOOL_GGRO,	   ETHTOOL_SGRO,    ETH_FLAG_GRO,	0 },
+	{ "lro",    "large-receive-offload",	    "rx-lro",
+	  0,		   0,		    ETH_FLAG_LRO,
+	  KERNEL_VERSION(2,6,24) },
+	{ "rxvlan", "rx-vlan-offload",		    "rx-vlan-hw-parse",
+	  0,		   0,		    ETH_FLAG_RXVLAN,
+	  KERNEL_VERSION(2,6,37) },
+	{ "txvlan", "tx-vlan-offload",		    "tx-vlan-hw-insert",
+	  0,		   0,		    ETH_FLAG_TXVLAN,
+	  KERNEL_VERSION(2,6,37) },
+	{ "ntuple", "ntuple-filters",		    "rx-ntuple-filter",
+	  0,		   0,		    ETH_FLAG_NTUPLE,	0 },
+	{ "rxhash", "receive-hashing",		    "rx-hashing",
+	  0,		   0,		    ETH_FLAG_RXHASH,	0 },
+};
diff --git a/common.h b/common.h
index a980b844a0ed..36f96ddaa3bd 100644
--- a/common.h
+++ b/common.h
@@ -1,6 +1,8 @@
 #ifndef ETHTOOL_COMMON_H__
 #define ETHTOOL_COMMON_H__
 
+#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
+
 struct flag_info {
 	const char *name;
 	u32 value;
@@ -25,6 +27,25 @@ struct link_mode_info {
 	u8			duplex;
 };
 
+struct off_flag_def {
+	const char *short_name;
+	const char *long_name;
+	const char *kernel_name;
+	u32 get_cmd, set_cmd;
+	u32 value;
+	/* For features exposed through ETHTOOL_GFLAGS, the oldest
+	 * kernel version for which we can trust the result.  Where
+	 * the flag was added at the same time the kernel started
+	 * supporting the feature, this is 0 (to allow for backports).
+	 * Where the feature was supported before the flag was added,
+	 * it is the version that introduced the flag.
+	 */
+	u32 min_kernel_ver;
+};
+
+#define OFF_FLAG_DEF_SIZE 12
+extern const struct off_flag_def off_flag_def[OFF_FLAG_DEF_SIZE];
+
 extern const struct link_mode_info link_modes[];
 extern const unsigned int link_modes_count;
 
diff --git a/ethtool.c b/ethtool.c
index 0b40b96cf9bf..b29086bbabde 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -61,8 +61,6 @@
 #define NETLINK_GENERIC	16
 #endif
 
-#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
-
 static void exit_bad_args(void) __attribute__((noreturn));
 
 static void exit_bad_args(void)
@@ -104,51 +102,6 @@ struct cmdline_info {
 	void *seen_val;
 };
 
-struct off_flag_def {
-	const char *short_name;
-	const char *long_name;
-	const char *kernel_name;
-	u32 get_cmd, set_cmd;
-	u32 value;
-	/* For features exposed through ETHTOOL_GFLAGS, the oldest
-	 * kernel version for which we can trust the result.  Where
-	 * the flag was added at the same time the kernel started
-	 * supporting the feature, this is 0 (to allow for backports).
-	 * Where the feature was supported before the flag was added,
-	 * it is the version that introduced the flag.
-	 */
-	u32 min_kernel_ver;
-};
-static const struct off_flag_def off_flag_def[] = {
-	{ "rx",     "rx-checksumming",		    "rx-checksum",
-	  ETHTOOL_GRXCSUM, ETHTOOL_SRXCSUM, ETH_FLAG_RXCSUM,	0 },
-	{ "tx",     "tx-checksumming",		    "tx-checksum-*",
-	  ETHTOOL_GTXCSUM, ETHTOOL_STXCSUM, ETH_FLAG_TXCSUM,	0 },
-	{ "sg",     "scatter-gather",		    "tx-scatter-gather*",
-	  ETHTOOL_GSG,	   ETHTOOL_SSG,     ETH_FLAG_SG,	0 },
-	{ "tso",    "tcp-segmentation-offload",	    "tx-tcp*-segmentation",
-	  ETHTOOL_GTSO,	   ETHTOOL_STSO,    ETH_FLAG_TSO,	0 },
-	{ "ufo",    "udp-fragmentation-offload",    "tx-udp-fragmentation",
-	  ETHTOOL_GUFO,	   ETHTOOL_SUFO,    ETH_FLAG_UFO,	0 },
-	{ "gso",    "generic-segmentation-offload", "tx-generic-segmentation",
-	  ETHTOOL_GGSO,	   ETHTOOL_SGSO,    ETH_FLAG_GSO,	0 },
-	{ "gro",    "generic-receive-offload",	    "rx-gro",
-	  ETHTOOL_GGRO,	   ETHTOOL_SGRO,    ETH_FLAG_GRO,	0 },
-	{ "lro",    "large-receive-offload",	    "rx-lro",
-	  0,		   0,		    ETH_FLAG_LRO,
-	  KERNEL_VERSION(2,6,24) },
-	{ "rxvlan", "rx-vlan-offload",		    "rx-vlan-hw-parse",
-	  0,		   0,		    ETH_FLAG_RXVLAN,
-	  KERNEL_VERSION(2,6,37) },
-	{ "txvlan", "tx-vlan-offload",		    "tx-vlan-hw-insert",
-	  0,		   0,		    ETH_FLAG_TXVLAN,
-	  KERNEL_VERSION(2,6,37) },
-	{ "ntuple", "ntuple-filters",		    "rx-ntuple-filter",
-	  0,		   0,		    ETH_FLAG_NTUPLE,	0 },
-	{ "rxhash", "receive-hashing",		    "rx-hashing",
-	  0,		   0,		    ETH_FLAG_RXHASH,	0 },
-};
-
 struct feature_def {
 	char name[ETH_GSTRING_LEN];
 	int off_flag_index; /* index in off_flag_def; negative if none match */
@@ -157,7 +110,7 @@ struct feature_def {
 struct feature_defs {
 	size_t n_features;
 	/* Number of features each offload flag is associated with */
-	unsigned int off_flag_matched[ARRAY_SIZE(off_flag_def)];
+	unsigned int off_flag_matched[OFF_FLAG_DEF_SIZE];
 	/* Name and offload flag index for each feature */
 	struct feature_def def[0];
 };
@@ -1325,7 +1278,7 @@ static void dump_features(const struct feature_defs *defs,
 	int indent;
 	int i, j;
 
-	for (i = 0; i < ARRAY_SIZE(off_flag_def); i++) {
+	for (i = 0; i < OFF_FLAG_DEF_SIZE; i++) {
 		/* Don't show features whose state is unknown on this
 		 * kernel version
 		 */
@@ -1640,7 +1593,7 @@ static struct feature_defs *get_feature_defs(struct cmd_context *ctx)
 		defs->def[i].off_flag_index = -1;
 
 		for (j = 0;
-		     j < ARRAY_SIZE(off_flag_def) &&
+		     j < OFF_FLAG_DEF_SIZE &&
 			     defs->def[i].off_flag_index < 0;
 		     j++) {
 			const char *pattern =
@@ -2082,7 +2035,7 @@ get_features(struct cmd_context *ctx, const struct feature_defs *defs)
 
 	state->off_flags = 0;
 
-	for (i = 0; i < ARRAY_SIZE(off_flag_def); i++) {
+	for (i = 0; i < OFF_FLAG_DEF_SIZE; i++) {
 		value = off_flag_def[i].value;
 		if (!off_flag_def[i].get_cmd)
 			continue;
@@ -2198,14 +2151,14 @@ static int do_sfeatures(struct cmd_context *ctx)
 	/* Generate cmdline_info for legacy flags and kernel-named
 	 * features, and parse our arguments.
 	 */
-	cmdline_features = calloc(ARRAY_SIZE(off_flag_def) + defs->n_features,
+	cmdline_features = calloc(OFF_FLAG_DEF_SIZE + defs->n_features,
 				  sizeof(cmdline_features[0]));
 	if (!cmdline_features) {
 		perror("Cannot parse arguments");
 		rc = 1;
 		goto err;
 	}
-	for (i = 0; i < ARRAY_SIZE(off_flag_def); i++)
+	for (i = 0; i < OFF_FLAG_DEF_SIZE; i++)
 		flag_to_cmdline_info(off_flag_def[i].short_name,
 				     off_flag_def[i].value,
 				     &off_flags_wanted, &off_flags_mask,
@@ -2215,9 +2168,9 @@ static int do_sfeatures(struct cmd_context *ctx)
 			defs->def[i].name, FEATURE_FIELD_FLAG(i),
 			&FEATURE_WORD(efeatures->features, i, requested),
 			&FEATURE_WORD(efeatures->features, i, valid),
-			&cmdline_features[ARRAY_SIZE(off_flag_def) + i]);
+			&cmdline_features[OFF_FLAG_DEF_SIZE + i]);
 	parse_generic_cmdline(ctx, &any_changed, cmdline_features,
-			      ARRAY_SIZE(off_flag_def) + defs->n_features);
+			      OFF_FLAG_DEF_SIZE + defs->n_features);
 	free(cmdline_features);
 
 	if (!any_changed) {
@@ -2237,7 +2190,7 @@ static int do_sfeatures(struct cmd_context *ctx)
 		 * related features that the user did not specify and that
 		 * are not fixed.  Warn if all related features are fixed.
 		 */
-		for (i = 0; i < ARRAY_SIZE(off_flag_def); i++) {
+		for (i = 0; i < OFF_FLAG_DEF_SIZE; i++) {
 			int fixed = 1;
 
 			if (!(off_flags_mask & off_flag_def[i].value))
@@ -2278,7 +2231,7 @@ static int do_sfeatures(struct cmd_context *ctx)
 			goto err;
 		}
 	} else {
-		for (i = 0; i < ARRAY_SIZE(off_flag_def); i++) {
+		for (i = 0; i < OFF_FLAG_DEF_SIZE; i++) {
 			if (!off_flag_def[i].set_cmd)
 				continue;
 			if (off_flags_mask & off_flag_def[i].value) {
@@ -4924,6 +4877,7 @@ static int show_usage(struct cmd_context *ctx);
 #define nl_gdrv		NULL
 #define nl_gset		NULL
 #define nl_sset		NULL
+#define nl_gfeatures	NULL
 #endif
 
 static const struct option {
@@ -4988,7 +4942,7 @@ static const struct option {
 	  "		[ rx-mini N ]\n"
 	  "		[ rx-jumbo N ]\n"
 	  "		[ tx N ]\n" },
-	{ "-k|--show-features|--show-offload", 1, do_gfeatures, NULL,
+	{ "-k|--show-features|--show-offload", 1, do_gfeatures, nl_gfeatures,
 	  "Get state of protocol offload and other features" },
 	{ "-K|--features|--offload", 1, do_sfeatures, NULL,
 	  "Set protocol offload and other features",
diff --git a/netlink/extapi.h b/netlink/extapi.h
index 66573f0b4304..c656ff862687 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -16,6 +16,7 @@ int netlink_done(struct cmd_context *ctx);
 int nl_gdrv(struct cmd_context *ctx);
 int nl_gset(struct cmd_context *ctx);
 int nl_sset(struct cmd_context *ctx);
+int nl_gfeatures(struct cmd_context *ctx);
 int nl_monitor(struct cmd_context *ctx);
 
 void monitor_usage();
diff --git a/netlink/monitor.c b/netlink/monitor.c
index 32d842611011..e3b8bb46f561 100644
--- a/netlink/monitor.c
+++ b/netlink/monitor.c
@@ -127,6 +127,12 @@ static struct monitor_option monitor_opts[] = {
 				  ETH_SETTINGS_IM_WOLINFO |
 				  ETH_SETTINGS_IM_LINK,
 	},
+	{
+		.pattern	= "-k|--show-features|--show-offload"
+				  "|-K|--features|--offload",
+		.cmd		= ETHNL_CMD_SET_SETTINGS,
+		.info_mask	= ETH_SETTINGS_IM_FEATURES,
+	},
 };
 
 static bool pattern_match(const char *s, const char *pattern)
diff --git a/netlink/settings.c b/netlink/settings.c
index b077a96325e5..51a01b400224 100644
--- a/netlink/settings.c
+++ b/netlink/settings.c
@@ -5,6 +5,7 @@
 #include "../internal.h"
 #include "../common.h"
 #include "netlink.h"
+#include "strset.h"
 #include "parser.h"
 
 /* GET_SETTINGS */
@@ -34,6 +35,219 @@ err:
 	return ret;
 }
 
+uint32_t *get_compact_bitset_value(const struct nlattr *bitset)
+{
+        const struct nlattr *tb[ETHA_BITSET_MAX + 1] = {};
+        DECLARE_ATTR_TB_INFO(tb);
+	unsigned int count;
+        int ret;
+
+        ret = mnl_attr_parse_nested(bitset, attr_cb, &tb_info);
+        if (ret < 0 || !tb[ETHA_BITSET_SIZE] || !tb[ETHA_BITSET_VALUE])
+		return NULL;
+	count = mnl_attr_get_u32(tb[ETHA_BITSET_SIZE]);
+	if (32 * mnl_attr_get_payload_len(tb[ETHA_BITSET_VALUE]) < count)
+		return NULL;
+
+	return mnl_attr_get_payload(tb[ETHA_BITSET_VALUE]);
+}
+
+uint32_t *get_compact_bitset_mask(const struct nlattr *bitset)
+{
+        const struct nlattr *tb[ETHA_BITSET_MAX + 1] = {};
+        DECLARE_ATTR_TB_INFO(tb);
+	unsigned int count;
+        int ret;
+
+        ret = mnl_attr_parse_nested(bitset, attr_cb, &tb_info);
+        if (ret < 0 || !tb[ETHA_BITSET_SIZE] || !tb[ETHA_BITSET_MASK])
+		return NULL;
+	count = mnl_attr_get_u32(tb[ETHA_BITSET_SIZE]);
+	if (32 * mnl_attr_get_payload_len(tb[ETHA_BITSET_MASK]) < count)
+		return NULL;
+
+	return mnl_attr_get_payload(tb[ETHA_BITSET_MASK]);
+}
+
+struct feature_results {
+	uint32_t	*hw;
+	uint32_t	*wanted;
+	uint32_t	*active;
+	uint32_t	*nochange;
+	unsigned int	count;
+	unsigned int	words;
+};
+
+static int prepare_feature_results(const struct nlattr *bitset,
+				   struct feature_results *dest)
+{
+	const struct nlattr *tb[ETHA_FEATURES_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	unsigned int count;
+	int ret;
+
+	memset(dest, '\0', sizeof(*dest));
+	ret = mnl_attr_parse_nested(bitset, attr_cb, &tb_info);
+	if (ret < 0)
+		return ret;
+	if (!tb[ETHA_FEATURES_HW] || !tb[ETHA_FEATURES_WANTED] ||
+	    !tb[ETHA_FEATURES_ACTIVE] || !tb[ETHA_FEATURES_NOCHANGE])
+		return -EFAULT;
+	count = bitset_get_count(tb[ETHA_FEATURES_HW], &ret);
+	if (ret < 0)
+		return -EFAULT;
+	if ((bitset_get_count(tb[ETHA_FEATURES_WANTED], &ret) != count) ||
+	    (bitset_get_count(tb[ETHA_FEATURES_ACTIVE], &ret) != count) ||
+	    (bitset_get_count(tb[ETHA_FEATURES_NOCHANGE], &ret) != count))
+		return -EFAULT;
+	dest->hw = get_compact_bitset_value(tb[ETHA_FEATURES_HW]);
+	dest->wanted = get_compact_bitset_value(tb[ETHA_FEATURES_WANTED]);
+	dest->active = get_compact_bitset_value(tb[ETHA_FEATURES_ACTIVE]);
+	dest->nochange = get_compact_bitset_value(tb[ETHA_FEATURES_NOCHANGE]);
+	if (!dest->hw || !dest->wanted || !dest->active || !dest->nochange)
+		return -EFAULT;
+	dest->count = count;
+	dest->words = (count + 31) / 32;
+
+	return 0;
+}
+
+static bool feature_on(const uint32_t *bitmap, unsigned int idx)
+{
+	return bitmap[idx / 32] & (1 << (idx % 32));
+}
+
+static void dump_feature(const struct feature_results *results,
+			 const uint32_t *ref, const uint32_t *ref_mask,
+			 unsigned int idx, const char *name, const char *prefix)
+{
+	const char *suffix = "";
+
+	if (!name || !*name)
+		return;
+	if (ref) {
+		if (ref_mask && !feature_on(ref_mask, idx))
+			return;
+		if ((!ref_mask || feature_on(ref_mask, idx)) &&
+		    (feature_on(results->active, idx) == feature_on(ref, idx)))
+			return;
+	}
+
+	if (!feature_on(results->hw, idx) || feature_on(results->nochange, idx))
+		suffix = " [fixed]";
+	else if (feature_on(results->active, idx) !=
+		 feature_on(results->wanted, idx))
+		suffix = feature_on(results->wanted, idx) ?
+			 " [requested on]" : " [requested off]";
+	printf("%s%s: %s%s\n", prefix, name,
+	       feature_on(results->active, idx) ? "on" : "off", suffix);
+}
+
+/* this assumes pattern contains no more than one asterisk */
+static bool _flag_pattern_match(const char *name, const char *pattern)
+{
+	const char *p_ast = strchr(pattern, '*');
+
+	if (p_ast) {
+		size_t name_len = strlen(name);
+		size_t pattern_len = strlen(pattern);
+
+		if (name_len + 1 < pattern_len)
+			return false;
+		if (strncmp(name, pattern, p_ast - pattern))
+			return false;
+		pattern_len -= (p_ast - pattern) + 1;
+		name += name_len  - pattern_len;
+		pattern = p_ast + 1;
+	}
+	return !strcmp(name, pattern);
+}
+
+static bool flag_pattern_match(const char *name, const char *pattern)
+{
+	bool ret = _flag_pattern_match(name, pattern);
+
+	return ret;
+}
+
+int dump_features(const struct nlattr *bitset)
+{
+	const struct stringset *feature_names;
+	struct feature_results results;
+	unsigned int i, j;
+	int *feature_flags = NULL;
+	int ret;
+
+	ret = prepare_feature_results(bitset, &results);
+	if (ret < 0)
+		return -EFAULT;
+
+	ret = -ENOMEM;
+	feature_flags = calloc(results.count, sizeof(feature_flags[0]));
+	if (!feature_flags)
+	       goto out_free;
+	feature_names = global_stringset(ETH_SS_FEATURES);
+
+	/* map netdev features to legacy flags */
+	for (i = 0; i < results.count; i++) {
+		const char *name = get_string(feature_names, i);
+		feature_flags[i] = -1;
+
+		if (!name || !*name)
+			continue;
+		for (j = 0; j < OFF_FLAG_DEF_SIZE; j++) {
+			const char *flag_name = off_flag_def[j].kernel_name;
+
+			if (flag_pattern_match(name, flag_name)) {
+				feature_flags[i] = j;
+				break;
+			}
+		}
+	}
+	/* show legacy flags and their matching features first */
+	for (i = 0; i < OFF_FLAG_DEF_SIZE; i++) {
+		unsigned int n_match = 0;
+		bool flag_value = false;
+
+		for (j = 0; j < results.count; j++) {
+			if (feature_flags[j] == i) {
+				n_match++;
+				flag_value = flag_value ||
+					     feature_on(results.active, j);
+			}
+		}
+		if (n_match != 1)
+			printf("%s: %s\n", off_flag_def[i].long_name,
+			       flag_value ? "on" : "off");
+		if (n_match == 0)
+			continue;
+		for (j = 0; j < results.count; j++) {
+			const char *name = get_string(feature_names, j);
+
+			if (feature_flags[j] != i)
+				continue;
+			if (n_match == 1)
+				dump_feature(&results, NULL, NULL, j,
+					     off_flag_def[i].long_name, "");
+			else
+				dump_feature(&results, NULL, NULL, j, name,
+					     "\t");
+		}
+	}
+	/* and, finally, remaining netdev_features not matching legacy flags */
+	for (i = 0; i < results.count; i++) {
+		const char *name = get_string(feature_names, i);
+
+		if (!name || !*name || feature_flags[i] >= 0)
+			continue;
+		dump_feature(&results, NULL, NULL, i, name, "");
+	}
+
+out_free:
+	free(feature_flags);
+	return 0;
+}
+
 int settings_reply_cb(const struct nlmsghdr *nlhdr, void *data)
 {
 	const struct nlattr *tb[ETHA_SETTINGS_MAX + 1] = {};
@@ -262,6 +476,15 @@ int settings_reply_cb(const struct nlmsghdr *nlhdr, void *data)
 		printf("\tLink detected: %s\n", link ? "yes" : "no");
 		allfail = false;
 	};
+	if (tb[ETHA_SETTINGS_FEATURES] &&
+	    mask_ok(nlctx, ETH_SETTINGS_IM_FEATURES)) {
+		int ret;
+
+		printf("Features for %s:\n", nlctx->devname);
+		ret = dump_features(tb[ETHA_SETTINGS_FEATURES]);
+		if (ret == 0)
+			allfail = false;
+	}
 
 	if (allfail && !nlctx->is_monitor) {
 		fputs("No data available\n", stdout);
@@ -299,6 +522,9 @@ int nl_gset(struct cmd_context *ctx)
 				     ETH_SETTINGS_IM_LINK);
 }
 
+int nl_gfeatures(struct cmd_context *ctx)
+{
+	return settings_request(ctx, ETH_SETTINGS_IM_FEATURES);
 }
 
 /* SET_SETTINGS */
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [RFC PATCH ethtool v2 11/23] netlink: add netlink handler for sfeatures (-K)
  2018-07-30 12:55 [RFC PATCH ethtool v2 00/23] ethtool netlink interface (userspace side) (WiP) Michal Kubecek
                   ` (9 preceding siblings ...)
  2018-07-30 12:56 ` [RFC PATCH ethtool v2 10/23] netlink: add netlink handler for gfeatures (-k) Michal Kubecek
@ 2018-07-30 12:56 ` Michal Kubecek
  2018-07-30 12:56 ` [RFC PATCH ethtool v2 12/23] netlink: add netlink handler for gcoalesce (-c) Michal Kubecek
                   ` (11 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Michal Kubecek @ 2018-07-30 12:56 UTC (permalink / raw)
  To: netdev; +Cc: linux-kernel, John W. Linville

Implement "ethtool -K <dev>" subcommand using netlink interface command
ETHNL_CMD_SET_SETTINGS.

The implementation tries to emulate legacy flags but the result is not
exactly equal to the ioctl code. In particular, we map netdev features to
legacy flags based on name patterns in off_flag_def[] but this mapping is
slightly different from mapping used by kernel ioctl code. It also does not
try to emulate legacy flags in "actual changes" output show when actual
change of device features does not match userspace request exactly.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 ethtool.c          |   3 +-
 netlink/extapi.h   |   1 +
 netlink/settings.c | 292 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 295 insertions(+), 1 deletion(-)

diff --git a/ethtool.c b/ethtool.c
index b29086bbabde..b2f93a331098 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -4878,6 +4878,7 @@ static int show_usage(struct cmd_context *ctx);
 #define nl_gset		NULL
 #define nl_sset		NULL
 #define nl_gfeatures	NULL
+#define nl_sfeatures	NULL
 #endif
 
 static const struct option {
@@ -4944,7 +4945,7 @@ static const struct option {
 	  "		[ tx N ]\n" },
 	{ "-k|--show-features|--show-offload", 1, do_gfeatures, nl_gfeatures,
 	  "Get state of protocol offload and other features" },
-	{ "-K|--features|--offload", 1, do_sfeatures, NULL,
+	{ "-K|--features|--offload", 1, do_sfeatures, nl_sfeatures,
 	  "Set protocol offload and other features",
 	  "		FEATURE on|off ...\n" },
 	{ "-i|--driver", 1, do_gdrv, nl_gdrv,
diff --git a/netlink/extapi.h b/netlink/extapi.h
index c656ff862687..ad67e81449ba 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -17,6 +17,7 @@ int nl_gdrv(struct cmd_context *ctx);
 int nl_gset(struct cmd_context *ctx);
 int nl_sset(struct cmd_context *ctx);
 int nl_gfeatures(struct cmd_context *ctx);
+int nl_sfeatures(struct cmd_context *ctx);
 int nl_monitor(struct cmd_context *ctx);
 
 void monitor_usage();
diff --git a/netlink/settings.c b/netlink/settings.c
index 51a01b400224..b7bfe14668df 100644
--- a/netlink/settings.c
+++ b/netlink/settings.c
@@ -741,3 +741,295 @@ int nl_sset(struct cmd_context *ctx)
 		return 0;
 	return nlctx->exit_code ?: 75;
 }
+
+/* features */
+
+struct sfeatures_context {
+	uint32_t		req_mask[0];
+};
+
+static void show_feature_changes(struct nl_context *nlctx,
+				 const struct nlattr *feat_attr)
+{
+	struct sfeatures_context *sfctx = nlctx->cmd_private;
+	const struct stringset *feature_names =
+		global_stringset(ETH_SS_FEATURES);
+	const unsigned int count = get_count(feature_names);
+	const unsigned int words = (count + 31) / 32;
+	const struct nlattr *tb[ETHA_FEATURES_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	const uint32_t *wanted_val;
+	const uint32_t *wanted_mask;
+	const uint32_t *active_val;
+	const uint32_t *active_mask;
+	unsigned int i;
+	bool diff;
+	int ret;
+
+	ret = mnl_attr_parse_nested(feat_attr, attr_cb, &tb_info);
+	if (ret < 0)
+		goto err;
+	if (!tb[ETHA_FEATURES_WANTED] || !tb[ETHA_FEATURES_ACTIVE])
+		goto err;
+	if (bitset_get_count(tb[ETHA_FEATURES_WANTED], &ret) != count ||
+	    ret < 0)
+		goto err;
+	if (bitset_get_count(tb[ETHA_FEATURES_ACTIVE], &ret) != count ||
+	    ret < 0)
+		goto err;
+	wanted_val = get_compact_bitset_value(tb[ETHA_FEATURES_WANTED]);
+	wanted_mask = get_compact_bitset_mask(tb[ETHA_FEATURES_WANTED]);
+	active_val = get_compact_bitset_value(tb[ETHA_FEATURES_ACTIVE]);
+	active_mask = get_compact_bitset_mask(tb[ETHA_FEATURES_ACTIVE]);
+	if (!wanted_val || !wanted_mask || !active_val || !active_mask)
+		goto err;
+
+	diff = false;
+	for (i = 0; i < words; i++)
+		if (wanted_mask[i] || active_mask[i])
+			diff = true;
+	if (!diff)
+		return;
+
+	/* result is not exactly as requested, show differences */
+	printf("Actual changes:\n");
+	for (i = 0; i < count; i++) {
+		const char *name = get_string(feature_names, i);
+
+		if (!name)
+			continue;
+		if (!feature_on(wanted_mask, i) && !feature_on(active_mask, i))
+			continue;
+		printf("%s: ", name);
+		if (feature_on(wanted_mask, i))
+			/* we requested a value but result is different */
+			printf("%s [requested %s]",
+			       feature_on(wanted_val, i) ? "off" : "on",
+			       feature_on(wanted_val, i) ? "on" : "off");
+		else if (!feature_on(sfctx->req_mask, i))
+			/* not requested but changed anyway */
+			printf("%s [not requested]",
+			       feature_on(active_val, i) ? "on" : "off");
+		else
+			printf("%s", feature_on(active_val, i) ? "on" : "off");
+		fputc('\n', stdout);
+	}
+
+	return;
+err:
+	fprintf(stderr, "malformed diff info from kernel\n");
+}
+
+static int find_feature(const char *name)
+{
+	const struct stringset *feature_names =
+		global_stringset(ETH_SS_FEATURES);
+	const unsigned int count = get_count(feature_names);
+	unsigned int i;
+
+	for (i = 0; i < count; i++)
+		if (!strcmp(name, get_string(feature_names, i)))
+			return i;
+
+	return -1;
+}
+
+static int fill_feature(struct nl_context *nlctx, const char *name, bool val)
+{
+	struct nlattr *bit_attr;
+
+	bit_attr = ethnla_nest_start(nlctx, ETHA_BITS_BIT);
+	if (!bit_attr)
+		return -EMSGSIZE;
+	if (ethnla_put_strz(nlctx, ETHA_BIT_NAME, name))
+		return -EMSGSIZE;
+	if (ethnla_put_flag(nlctx, ETHA_BIT_VALUE, val))
+		return -EMSGSIZE;
+	mnl_attr_nest_end(nlctx->nlhdr, bit_attr);
+
+	return 0;
+}
+
+static void set_sf_req_mask(struct nl_context *nlctx, unsigned int idx)
+{
+	struct sfeatures_context *sfctx = nlctx->cmd_private;
+
+	sfctx->req_mask[idx / 32] |= (1 << (idx % 32));
+}
+
+static int fill_legacy_flag(struct nl_context *nlctx, const char *flag_name,
+			    bool val)
+{
+	const struct stringset *feature_names =
+		global_stringset(ETH_SS_FEATURES);
+	const unsigned int count = get_count(feature_names);
+	unsigned int i, j;
+	int ret;
+
+	for (i = 0; i < OFF_FLAG_DEF_SIZE; i++) {
+		const char *pattern;
+
+		if (strcmp(flag_name, off_flag_def[i].short_name) &&
+		    strcmp(flag_name, off_flag_def[i].long_name))
+			continue;
+		pattern = off_flag_def[i].kernel_name;
+
+		for (j = 0; j < count; j++) {
+			const char *name = get_string(feature_names, j);
+
+			if (flag_pattern_match(name, pattern)) {
+				ret = fill_feature(nlctx, name, val);
+				if (ret < 0)
+					return ret;
+				set_sf_req_mask(nlctx, j);
+			}
+		}
+
+		return 0;
+	}
+
+	return 1;
+}
+
+int sfeatures_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+	const struct genlmsghdr *ghdr = (const struct genlmsghdr *)(nlhdr + 1);
+	const struct nlattr *tb[ETHA_SETTINGS_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	struct nl_context *nlctx = data;
+	const char *devname;
+	int ret;
+
+	if (ghdr->cmd != ETHNL_CMD_SET_SETTINGS) {
+		fprintf(stderr, "warning: unexpected reply message type %u\n",
+			ghdr->cmd);
+		return MNL_CB_OK;
+	}
+	ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+	if (ret < 0)
+		return ret;
+	devname = get_dev_name(tb[ETHA_SETTINGS_DEV]);
+	if (strcmp(devname, nlctx->devname)) {
+		fprintf(stderr, "warning: unexpected message for device %s\n",
+			devname);
+		return MNL_CB_OK;
+	}
+	if (!tb[ETHA_SETTINGS_FEATURES])
+		return MNL_CB_OK;
+
+	show_feature_changes(nlctx, tb[ETHA_SETTINGS_FEATURES]);
+	return MNL_CB_OK;
+}
+
+int fill_sfeatures_bitmap(struct nl_context *nlctx)
+{
+	struct nlmsghdr *nlhdr = nlctx->nlhdr;
+	struct nlattr *bitset_attr;
+	struct nlattr *bits_attr;
+	int ret;
+
+	ret = -EMSGSIZE;
+	bitset_attr = ethnla_nest_start(nlctx, ETHA_FEATURES_WANTED);
+	if (!bitset_attr)
+		return ret;
+	bits_attr = ethnla_nest_start(nlctx, ETHA_BITSET_BITS);
+	if (!bits_attr)
+		goto err;
+
+	while (nlctx->argc > 0) {
+		bool val;
+
+		if (!strcmp(*nlctx->argp, "--")) {
+			nlctx->argp++;
+			nlctx->argc--;
+			break;
+		}
+		ret = -EINVAL;
+		if (nlctx->argc < 2 ||
+		    (strcmp(nlctx->argp[1], "on") &&
+		     strcmp(nlctx->argp[1], "off"))) {
+			fprintf(stderr,
+				"ethtool (%s): flag '%s' for parameter '%s' is"
+				" not followed by 'on' or 'off'\n",
+				nlctx->cmd, nlctx->argp[1], nlctx->param);
+			goto err;
+		}
+
+		val = !strcmp(nlctx->argp[1], "on");
+		ret = fill_legacy_flag(nlctx, nlctx->argp[0], val);
+		if (ret > 0) {
+			ret = fill_feature(nlctx, nlctx->argp[0], val);
+			if (ret == 0) {
+				int idx = find_feature(nlctx->argp[0]);
+
+				if (idx >= 0)
+					set_sf_req_mask(nlctx, idx);
+			}
+		}
+		if (ret < 0)
+			goto err;
+
+		nlctx->argp += 2;
+		nlctx->argc -= 2;
+	}
+
+	mnl_attr_nest_end(nlhdr, bits_attr);
+	mnl_attr_nest_end(nlhdr, bitset_attr);
+	return 0;
+err:
+	mnl_attr_nest_cancel(nlhdr, bitset_attr);
+	return ret;
+}
+
+int nl_sfeatures(struct cmd_context *ctx)
+{
+	const struct stringset *feature_names;
+	struct sfeatures_context *sfctx;
+	struct nl_context *nlctx = ctx->nlctx;
+	struct nlattr *feat_attr;
+	unsigned int words;
+	int ret;
+
+	nlctx->cmd = "-K";
+	nlctx->argp = ctx->argp;
+	nlctx->argc = ctx->argc;
+	nlctx->cmd_private = &sfctx;
+
+	load_global_strings(nlctx);
+	feature_names = global_stringset(ETH_SS_FEATURES);
+	words = (get_count(feature_names) + 31) / 32;
+	sfctx = malloc(sizeof(*sfctx) + words * sizeof(sfctx->req_mask[0]));
+	if (!sfctx)
+		return -ENOMEM;
+	memset(sfctx, '\0',
+	       sizeof(*sfctx) + words * sizeof(sfctx->req_mask[0]));
+	nlctx->cmd_private = sfctx;
+
+	nlctx->devname = ctx->devname;
+	ret = msg_init(nlctx, ETHNL_CMD_SET_SETTINGS,
+		       NLM_F_REQUEST | NLM_F_ACK);
+	if (ret < 0)
+		return 2;
+	if (ethnla_put_dev(nlctx, ETHA_SETTINGS_DEV, ctx->devname))
+		return -EMSGSIZE;
+	if (ethnla_put_flag(nlctx, ETHA_SETTINGS_COMPACT, true))
+		return -EMSGSIZE;
+
+	feat_attr = ethnla_nest_start(nlctx, ETHA_SETTINGS_FEATURES);
+	if (!feat_attr)
+		return -EMSGSIZE;
+	ret = fill_sfeatures_bitmap(nlctx);
+	if (ret < 0)
+		return ret;
+	if (ethnla_put_flag(nlctx, ETHA_FEATURES_WANT_DIFF, true))
+		return -EMSGSIZE;
+	mnl_attr_nest_end(nlctx->nlhdr, feat_attr);
+
+	ret = ethnl_sendmsg(nlctx);
+	if (ret < 0)
+		return 92;
+	ret = ethnl_process_reply(nlctx, sfeatures_reply_cb);
+	if (ret == 0)
+		return 0;
+	return nlctx->exit_code ?: 92;
+}
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [RFC PATCH ethtool v2 12/23] netlink: add netlink handler for gcoalesce (-c)
  2018-07-30 12:55 [RFC PATCH ethtool v2 00/23] ethtool netlink interface (userspace side) (WiP) Michal Kubecek
                   ` (10 preceding siblings ...)
  2018-07-30 12:56 ` [RFC PATCH ethtool v2 11/23] netlink: add netlink handler for sfeatures (-K) Michal Kubecek
@ 2018-07-30 12:56 ` Michal Kubecek
  2018-07-30 12:56 ` [RFC PATCH ethtool v2 13/23] netlink: add netlink handler for gring (-g) Michal Kubecek
                   ` (10 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Michal Kubecek @ 2018-07-30 12:56 UTC (permalink / raw)
  To: netdev; +Cc: linux-kernel, John W. Linville

Implement "ethtool -c <dev>" subcommand using netlink interface command
ETHNL_CMD_GET_PARAMS with ETH_PARAMS_IM_COALESCE mask.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Makefile.am       |   2 +-
 ethtool.c         |   3 +-
 netlink/extapi.h  |   1 +
 netlink/monitor.c |  10 +++++
 netlink/params.c  | 101 ++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 115 insertions(+), 2 deletions(-)
 create mode 100644 netlink/params.c

diff --git a/Makefile.am b/Makefile.am
index 7cc4adc53c59..bedf9edaac47 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -24,7 +24,7 @@ ethtool_SOURCES += \
 		  netlink/netlink.c netlink/netlink.h netlink/extapi.h \
 		  netlink/strset.c netlink/strset.h netlink/monitor.c \
 		  netlink/parser.c netlink/parser.h \
-		  netlink/drvinfo.c netlink/settings.c \
+		  netlink/drvinfo.c netlink/settings.c netlink/params.c \
 		  uapi/linux/ethtool_netlink.h \
 		  uapi/linux/netlink.h uapi/linux/genetlink.h
 ethtool_CFLAGS += @MNL_CFLAGS@
diff --git a/ethtool.c b/ethtool.c
index b2f93a331098..24c70c372914 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -4879,6 +4879,7 @@ static int show_usage(struct cmd_context *ctx);
 #define nl_sset		NULL
 #define nl_gfeatures	NULL
 #define nl_sfeatures	NULL
+#define nl_gcoalesce	NULL
 #endif
 
 static const struct option {
@@ -4909,7 +4910,7 @@ static const struct option {
 	  "		[ autoneg on|off ]\n"
 	  "		[ rx on|off ]\n"
 	  "		[ tx on|off ]\n" },
-	{ "-c|--show-coalesce", 1, do_gcoalesce, NULL,
+	{ "-c|--show-coalesce", 1, do_gcoalesce, nl_gcoalesce,
 	  "Show coalesce options" },
 	{ "-C|--coalesce", 1, do_scoalesce, NULL,
 	  "Set coalesce options",
diff --git a/netlink/extapi.h b/netlink/extapi.h
index ad67e81449ba..8030bda4cbc9 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -18,6 +18,7 @@ int nl_gset(struct cmd_context *ctx);
 int nl_sset(struct cmd_context *ctx);
 int nl_gfeatures(struct cmd_context *ctx);
 int nl_sfeatures(struct cmd_context *ctx);
+int nl_gcoalesce(struct cmd_context *ctx);
 int nl_monitor(struct cmd_context *ctx);
 
 void monitor_usage();
diff --git a/netlink/monitor.c b/netlink/monitor.c
index e3b8bb46f561..5d494ba43923 100644
--- a/netlink/monitor.c
+++ b/netlink/monitor.c
@@ -68,6 +68,7 @@ static int monitor_event_cb(const struct nlmsghdr *nlhdr, void *data)
 
 int drvinfo_reply_cb(const struct nlmsghdr *nlhdr, void *data);
 int settings_reply_cb(const struct nlmsghdr *nlhdr, void *data);
+int params_reply_cb(const struct nlmsghdr *nlhdr, void *data);
 
 static struct {
 	uint8_t		cmd;
@@ -85,6 +86,10 @@ static struct {
 		.cmd	= ETHNL_CMD_SET_SETTINGS,
 		.cb	= settings_reply_cb,
 	},
+	{
+		.cmd	= ETHNL_CMD_SET_PARAMS,
+		.cb	= params_reply_cb,
+	},
 };
 
 static int monitor_any_cb(const struct nlmsghdr *nlhdr, void *data)
@@ -133,6 +138,11 @@ static struct monitor_option monitor_opts[] = {
 		.cmd		= ETHNL_CMD_SET_SETTINGS,
 		.info_mask	= ETH_SETTINGS_IM_FEATURES,
 	},
+	{
+		.pattern	= "-c|--show-coalesce|-C|--coalesce",
+		.cmd		= ETHNL_CMD_SET_PARAMS,
+		.info_mask	= ETH_PARAMS_IM_COALESCE,
+	},
 };
 
 static bool pattern_match(const char *s, const char *pattern)
diff --git a/netlink/params.c b/netlink/params.c
new file mode 100644
index 000000000000..30aee5758c42
--- /dev/null
+++ b/netlink/params.c
@@ -0,0 +1,101 @@
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "strset.h"
+#include "parser.h"
+
+/* GET_PARAMS */
+
+static int show_coalesce(struct nl_context *nlctx, const struct nlattr *nest)
+{
+	const struct nlattr *tb[ETHA_COALESCE_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	int ret;
+
+	if (!nest)
+		return -EOPNOTSUPP;
+	ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
+	if (ret < 0)
+		return ret;
+
+	printf("Coalesce parameters for %s:\n", nlctx->devname);
+	printf("Adaptive RX: %s  TX: %s\n",
+	       u8_to_bool(tb[ETHA_COALESCE_RX_USE_ADAPTIVE]),
+	       u8_to_bool(tb[ETHA_COALESCE_TX_USE_ADAPTIVE]));
+	show_u32(tb[ETHA_COALESCE_STATS_BLOCK_USECS], "stats-block-usecs: ");
+	show_u32(tb[ETHA_COALESCE_RATE_SAMPLE_INTERVAL], "sample-interval: ");
+	show_u32(tb[ETHA_COALESCE_PKT_RATE_LOW], "pkt-rate-low: ");
+	show_u32(tb[ETHA_COALESCE_PKT_RATE_HIGH], "pkt-rate-high: ");
+	putchar('\n');
+	show_u32(tb[ETHA_COALESCE_RX_USECS], "rx-usecs: ");
+	show_u32(tb[ETHA_COALESCE_RX_MAXFRM], "rx-frames: ");
+	show_u32(tb[ETHA_COALESCE_RX_USECS_IRQ], "rx-usecs-irq: ");
+	show_u32(tb[ETHA_COALESCE_RX_MAXFRM_IRQ], "rx-frames-irq: ");
+	putchar('\n');
+	show_u32(tb[ETHA_COALESCE_TX_USECS], "tx-usecs: ");
+	show_u32(tb[ETHA_COALESCE_TX_MAXFRM], "tx-frames: ");
+	show_u32(tb[ETHA_COALESCE_TX_USECS_IRQ], "tx-usecs-irq: ");
+	show_u32(tb[ETHA_COALESCE_TX_MAXFRM_IRQ], "tx-frames-irq: ");
+	putchar('\n');
+	show_u32(tb[ETHA_COALESCE_RX_USECS_LOW], "rx-usecs-low: ");
+	show_u32(tb[ETHA_COALESCE_RX_MAXFRM_LOW], "rx-frame-low: ");
+	show_u32(tb[ETHA_COALESCE_TX_USECS_LOW], "tx-usecs-low: ");
+	show_u32(tb[ETHA_COALESCE_TX_MAXFRM_LOW], "tx-frame-low: ");
+	putchar('\n');
+	show_u32(tb[ETHA_COALESCE_RX_USECS_HIGH], "rx-usecs-high: ");
+	show_u32(tb[ETHA_COALESCE_RX_MAXFRM_HIGH], "rx-frame-high: ");
+	show_u32(tb[ETHA_COALESCE_TX_USECS_HIGH], "tx-usecs-high: ");
+	show_u32(tb[ETHA_COALESCE_TX_MAXFRM_HIGH], "tx-frame-high: ");
+	putchar('\n');
+
+	return 0;
+}
+
+int params_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+	const struct nlattr *tb[ETHA_PARAMS_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	struct nl_context *nlctx = data;
+	int ret;
+
+	ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+	if (ret < 0)
+		return ret;
+	nlctx->devname = get_dev_name(tb[ETHA_PARAMS_DEV]);
+	if (!dev_ok(nlctx))
+		return MNL_CB_OK;
+
+	if (mask_ok(nlctx, ETH_PARAMS_IM_COALESCE)) {
+		ret = show_coalesce(nlctx, tb[ETHA_PARAMS_COALESCE]);
+		if ((ret < 0) && show_only(nlctx, ETH_PARAMS_IM_COALESCE)) {
+			nlctx->exit_code = 82;
+			errno = -ret;
+			perror("Cannot get device coalesce settings");
+			return MNL_CB_ERROR;
+		}
+	}
+
+	return MNL_CB_OK;
+}
+
+static int params_request(struct cmd_context *ctx, uint32_t info_mask)
+{
+	int ret;
+
+	ret = ethnl_prep_get_request(ctx, ETHNL_CMD_GET_PARAMS,
+				     ETHA_PARAMS_DEV);
+	if (ret < 0)
+		return ret;
+	if (ethnla_put_u32(ctx->nlctx, ETHA_PARAMS_INFOMASK, info_mask))
+		return -EMSGSIZE;
+	return ethnl_send_get_request(ctx->nlctx, params_reply_cb);
+}
+
+int nl_gcoalesce(struct cmd_context *ctx)
+{
+	return params_request(ctx, ETH_PARAMS_IM_COALESCE);
+}
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [RFC PATCH ethtool v2 13/23] netlink: add netlink handler for gring (-g)
  2018-07-30 12:55 [RFC PATCH ethtool v2 00/23] ethtool netlink interface (userspace side) (WiP) Michal Kubecek
                   ` (11 preceding siblings ...)
  2018-07-30 12:56 ` [RFC PATCH ethtool v2 12/23] netlink: add netlink handler for gcoalesce (-c) Michal Kubecek
@ 2018-07-30 12:56 ` Michal Kubecek
  2018-07-30 12:56 ` [RFC PATCH ethtool v2 14/23] netlink: add netlink handler for gpause (-a) Michal Kubecek
                   ` (9 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Michal Kubecek @ 2018-07-30 12:56 UTC (permalink / raw)
  To: netdev; +Cc: linux-kernel, John W. Linville

Implement "ethtool -g <dev>" subcommand using netlink interface command
ETHNL_CMD_GET_PARAMS with ETH_PARAMS_IM_RING mask.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 ethtool.c         |  3 ++-
 netlink/extapi.h  |  1 +
 netlink/monitor.c |  5 +++++
 netlink/params.c  | 42 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 50 insertions(+), 1 deletion(-)

diff --git a/ethtool.c b/ethtool.c
index 24c70c372914..ba5c73e9abbe 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -4880,6 +4880,7 @@ static int show_usage(struct cmd_context *ctx);
 #define nl_gfeatures	NULL
 #define nl_sfeatures	NULL
 #define nl_gcoalesce	NULL
+#define nl_gring	NULL
 #endif
 
 static const struct option {
@@ -4936,7 +4937,7 @@ static const struct option {
 	  "		[tx-usecs-high N]\n"
 	  "		[tx-frames-high N]\n"
 	  "		[sample-interval N]\n" },
-	{ "-g|--show-ring", 1, do_gring, NULL,
+	{ "-g|--show-ring", 1, do_gring, nl_gring,
 	  "Query RX/TX ring parameters" },
 	{ "-G|--set-ring", 1, do_sring, NULL,
 	  "Set RX/TX ring parameters",
diff --git a/netlink/extapi.h b/netlink/extapi.h
index 8030bda4cbc9..cc13436c4517 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -19,6 +19,7 @@ int nl_sset(struct cmd_context *ctx);
 int nl_gfeatures(struct cmd_context *ctx);
 int nl_sfeatures(struct cmd_context *ctx);
 int nl_gcoalesce(struct cmd_context *ctx);
+int nl_gring(struct cmd_context *ctx);
 int nl_monitor(struct cmd_context *ctx);
 
 void monitor_usage();
diff --git a/netlink/monitor.c b/netlink/monitor.c
index 5d494ba43923..10dad082de98 100644
--- a/netlink/monitor.c
+++ b/netlink/monitor.c
@@ -143,6 +143,11 @@ static struct monitor_option monitor_opts[] = {
 		.cmd		= ETHNL_CMD_SET_PARAMS,
 		.info_mask	= ETH_PARAMS_IM_COALESCE,
 	},
+	{
+		.pattern	= "-g|--show-ring|-G|--set-ring",
+		.cmd		= ETHNL_CMD_SET_PARAMS,
+		.info_mask	= ETH_PARAMS_IM_RING,
+	},
 };
 
 static bool pattern_match(const char *s, const char *pattern)
diff --git a/netlink/params.c b/netlink/params.c
index 30aee5758c42..6782d98a6956 100644
--- a/netlink/params.c
+++ b/netlink/params.c
@@ -55,6 +55,34 @@ static int show_coalesce(struct nl_context *nlctx, const struct nlattr *nest)
 	return 0;
 }
 
+static int show_ring(struct nl_context *nlctx, const struct nlattr *nest)
+{
+	const struct nlattr *tb[ETHA_RING_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	int ret;
+
+	if (!nest)
+		return -EOPNOTSUPP;
+	ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
+	if (ret < 0)
+		return ret;
+
+	printf("Ring parameters for %s:\n", nlctx->devname);
+	printf("Pre-set maximums:\n");
+	show_u32(tb[ETHA_RING_RX_MAX_PENDING], "RX:\t\t");
+	show_u32(tb[ETHA_RING_RX_MINI_MAX_PENDING], "RX Mini:\t");
+	show_u32(tb[ETHA_RING_RX_JUMBO_MAX_PENDING], "RX Jumbo:\t");
+	show_u32(tb[ETHA_RING_TX_MAX_PENDING], "TX:\t\t");
+	printf("Current hardware settings:\n");
+	show_u32(tb[ETHA_RING_RX_PENDING], "RX:\t\t");
+	show_u32(tb[ETHA_RING_RX_MINI_PENDING], "RX Mini:\t");
+	show_u32(tb[ETHA_RING_RX_JUMBO_PENDING], "RX Jumbo:\t");
+	show_u32(tb[ETHA_RING_TX_PENDING], "TX:\t\t");
+	putchar('\n');
+
+	return 0;
+}
+
 int params_reply_cb(const struct nlmsghdr *nlhdr, void *data)
 {
 	const struct nlattr *tb[ETHA_PARAMS_MAX + 1] = {};
@@ -78,6 +106,15 @@ int params_reply_cb(const struct nlmsghdr *nlhdr, void *data)
 			return MNL_CB_ERROR;
 		}
 	}
+	if (mask_ok(nlctx, ETH_PARAMS_IM_RING)) {
+		ret = show_ring(nlctx, tb[ETHA_PARAMS_RING]);
+		if ((ret < 0) && show_only(nlctx, ETH_PARAMS_IM_RING)) {
+			nlctx->exit_code = 76;
+			errno = -ret;
+			perror("Cannot get device ring settings");
+			return MNL_CB_ERROR;
+		}
+	}
 
 	return MNL_CB_OK;
 }
@@ -99,3 +136,8 @@ int nl_gcoalesce(struct cmd_context *ctx)
 {
 	return params_request(ctx, ETH_PARAMS_IM_COALESCE);
 }
+
+int nl_gring(struct cmd_context *ctx)
+{
+	return params_request(ctx, ETH_PARAMS_IM_RING);
+}
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [RFC PATCH ethtool v2 14/23] netlink: add netlink handler for gpause (-a)
  2018-07-30 12:55 [RFC PATCH ethtool v2 00/23] ethtool netlink interface (userspace side) (WiP) Michal Kubecek
                   ` (12 preceding siblings ...)
  2018-07-30 12:56 ` [RFC PATCH ethtool v2 13/23] netlink: add netlink handler for gring (-g) Michal Kubecek
@ 2018-07-30 12:56 ` Michal Kubecek
  2018-07-30 12:56 ` [RFC PATCH ethtool v2 15/23] netlink: add netlink handler for gchannels (-l) Michal Kubecek
                   ` (8 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Michal Kubecek @ 2018-07-30 12:56 UTC (permalink / raw)
  To: netdev; +Cc: linux-kernel, John W. Linville

Implement "ethtool -a <dev>" subcommand using netlink interface command
ETHNL_CMD_GET_PARAMS with ETH_PARAMS_IM_PAUSE mask.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 ethtool.c         |  3 ++-
 netlink/extapi.h  |  1 +
 netlink/monitor.c |  5 +++++
 netlink/params.c  | 36 ++++++++++++++++++++++++++++++++++++
 4 files changed, 44 insertions(+), 1 deletion(-)

diff --git a/ethtool.c b/ethtool.c
index ba5c73e9abbe..3a12b7c0c7ce 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -4881,6 +4881,7 @@ static int show_usage(struct cmd_context *ctx);
 #define nl_sfeatures	NULL
 #define nl_gcoalesce	NULL
 #define nl_gring	NULL
+#define nl_gpause	NULL
 #endif
 
 static const struct option {
@@ -4904,7 +4905,7 @@ static const struct option {
 	  "		[ wol %d[/%d] |  p|u|m|b|a|g|s|d...[/p|u|m|b|a|g|s|d...] ]\n"
 	  "		[ sopass %x:%x:%x:%x:%x:%x ]\n"
 	  "		[ msglvl %d[/%d] | type on|off ... [--] ]\n" },
-	{ "-a|--show-pause", 1, do_gpause, NULL,
+	{ "-a|--show-pause", 1, do_gpause, nl_gpause,
 	  "Show pause options" },
 	{ "-A|--pause", 1, do_spause, NULL,
 	  "Set pause options",
diff --git a/netlink/extapi.h b/netlink/extapi.h
index cc13436c4517..3b918a443b96 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -20,6 +20,7 @@ int nl_gfeatures(struct cmd_context *ctx);
 int nl_sfeatures(struct cmd_context *ctx);
 int nl_gcoalesce(struct cmd_context *ctx);
 int nl_gring(struct cmd_context *ctx);
+int nl_gpause(struct cmd_context *ctx);
 int nl_monitor(struct cmd_context *ctx);
 
 void monitor_usage();
diff --git a/netlink/monitor.c b/netlink/monitor.c
index 10dad082de98..18e151980c3f 100644
--- a/netlink/monitor.c
+++ b/netlink/monitor.c
@@ -148,6 +148,11 @@ static struct monitor_option monitor_opts[] = {
 		.cmd		= ETHNL_CMD_SET_PARAMS,
 		.info_mask	= ETH_PARAMS_IM_RING,
 	},
+	{
+		.pattern	= "-a|--show-pause|-A|--pause",
+		.cmd		= ETHNL_CMD_SET_PARAMS,
+		.info_mask	= ETH_PARAMS_IM_PAUSE,
+	},
 };
 
 static bool pattern_match(const char *s, const char *pattern)
diff --git a/netlink/params.c b/netlink/params.c
index 6782d98a6956..b45aac166dcd 100644
--- a/netlink/params.c
+++ b/netlink/params.c
@@ -83,6 +83,28 @@ static int show_ring(struct nl_context *nlctx, const struct nlattr *nest)
 	return 0;
 }
 
+static int show_pause(struct nl_context *nlctx, const struct nlattr *nest)
+{
+	const struct nlattr *tb[ETHA_PAUSE_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	int ret;
+
+	if (!nest)
+		return -EOPNOTSUPP;
+	ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
+	if (ret < 0)
+		return ret;
+
+	printf("Pause parameters for %s:\n", nlctx->devname);
+	show_bool(tb[ETHA_PAUSE_AUTONEG], "Autonegotiate:\t");
+	show_bool(tb[ETHA_PAUSE_RX], "RX:\t\t");
+	show_bool(tb[ETHA_PAUSE_TX], "TX:\t\t");
+	/* ToDo: query negotiated pause frame usage */
+	putchar('\n');
+
+	return 0;
+}
+
 int params_reply_cb(const struct nlmsghdr *nlhdr, void *data)
 {
 	const struct nlattr *tb[ETHA_PARAMS_MAX + 1] = {};
@@ -115,6 +137,15 @@ int params_reply_cb(const struct nlmsghdr *nlhdr, void *data)
 			return MNL_CB_ERROR;
 		}
 	}
+	if (mask_ok(nlctx, ETH_PARAMS_IM_PAUSE)) {
+		ret = show_pause(nlctx, tb[ETHA_PARAMS_PAUSE]);
+		if ((ret < 0) && show_only(nlctx, ETH_PARAMS_IM_PAUSE)) {
+			nlctx->exit_code = 76;
+			errno = -ret;
+			perror("Cannot get device pause settings");
+			return MNL_CB_ERROR;
+		}
+	}
 
 	return MNL_CB_OK;
 }
@@ -141,3 +172,8 @@ int nl_gring(struct cmd_context *ctx)
 {
 	return params_request(ctx, ETH_PARAMS_IM_RING);
 }
+
+int nl_gpause(struct cmd_context *ctx)
+{
+	return params_request(ctx, ETH_PARAMS_IM_PAUSE);
+}
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [RFC PATCH ethtool v2 15/23] netlink: add netlink handler for gchannels (-l)
  2018-07-30 12:55 [RFC PATCH ethtool v2 00/23] ethtool netlink interface (userspace side) (WiP) Michal Kubecek
                   ` (13 preceding siblings ...)
  2018-07-30 12:56 ` [RFC PATCH ethtool v2 14/23] netlink: add netlink handler for gpause (-a) Michal Kubecek
@ 2018-07-30 12:56 ` Michal Kubecek
  2018-07-30 12:57 ` [RFC PATCH ethtool v2 16/23] netlink: add netlink handler for geee (--show-eee) Michal Kubecek
                   ` (7 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Michal Kubecek @ 2018-07-30 12:56 UTC (permalink / raw)
  To: netdev; +Cc: linux-kernel, John W. Linville

Implement "ethtool -l <dev>" subcommand using netlink interface command
ETHNL_CMD_GET_PARAMS with ETH_PARAMS_IM_CHANNELS mask.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 ethtool.c         |  3 ++-
 netlink/extapi.h  |  1 +
 netlink/monitor.c |  5 +++++
 netlink/params.c  | 42 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 50 insertions(+), 1 deletion(-)

diff --git a/ethtool.c b/ethtool.c
index 3a12b7c0c7ce..2bcc1944ba29 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -4882,6 +4882,7 @@ static int show_usage(struct cmd_context *ctx);
 #define nl_gcoalesce	NULL
 #define nl_gring	NULL
 #define nl_gpause	NULL
+#define nl_gchannels	NULL
 #endif
 
 static const struct option {
@@ -5033,7 +5034,7 @@ static const struct option {
 	{ "-W|--set-dump", 1, do_setfwdump, NULL,
 	  "Set dump flag of the device",
 	  "		N\n"},
-	{ "-l|--show-channels", 1, do_gchannels, NULL,
+	{ "-l|--show-channels", 1, do_gchannels, nl_gchannels,
 	  "Query Channels" },
 	{ "-L|--set-channels", 1, do_schannels, NULL,
 	  "Set Channels",
diff --git a/netlink/extapi.h b/netlink/extapi.h
index 3b918a443b96..fc027f62398c 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -21,6 +21,7 @@ int nl_sfeatures(struct cmd_context *ctx);
 int nl_gcoalesce(struct cmd_context *ctx);
 int nl_gring(struct cmd_context *ctx);
 int nl_gpause(struct cmd_context *ctx);
+int nl_gchannels(struct cmd_context *ctx);
 int nl_monitor(struct cmd_context *ctx);
 
 void monitor_usage();
diff --git a/netlink/monitor.c b/netlink/monitor.c
index 18e151980c3f..1b277606a78c 100644
--- a/netlink/monitor.c
+++ b/netlink/monitor.c
@@ -153,6 +153,11 @@ static struct monitor_option monitor_opts[] = {
 		.cmd		= ETHNL_CMD_SET_PARAMS,
 		.info_mask	= ETH_PARAMS_IM_PAUSE,
 	},
+	{
+		.pattern	= "-l|--show-channels|-L|--set-channels",
+		.cmd		= ETHNL_CMD_SET_PARAMS,
+		.info_mask	= ETH_PARAMS_IM_CHANNELS,
+	},
 };
 
 static bool pattern_match(const char *s, const char *pattern)
diff --git a/netlink/params.c b/netlink/params.c
index b45aac166dcd..81547492532f 100644
--- a/netlink/params.c
+++ b/netlink/params.c
@@ -105,6 +105,34 @@ static int show_pause(struct nl_context *nlctx, const struct nlattr *nest)
 	return 0;
 }
 
+static int show_channels(struct nl_context *nlctx, const struct nlattr *nest)
+{
+	const struct nlattr *tb[ETHA_CHANNELS_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	int ret;
+
+	if (!nest)
+		return -EOPNOTSUPP;
+	ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
+	if (ret < 0)
+		return ret;
+
+	printf("Channel parameters for %s:\n", nlctx->devname);
+	printf("Pre-set maximums:\n");
+	show_u32(tb[ETHA_CHANNELS_MAX_RX], "RX:\t\t");
+	show_u32(tb[ETHA_CHANNELS_MAX_TX], "TX:\t\t");
+	show_u32(tb[ETHA_CHANNELS_MAX_OTHER], "Other:\t\t");
+	show_u32(tb[ETHA_CHANNELS_MAX_COMBINED], "Combined:\t");
+	printf("Current hardware settings:\n");
+	show_u32(tb[ETHA_CHANNELS_RX_COUNT], "RX:\t\t");
+	show_u32(tb[ETHA_CHANNELS_TX_COUNT], "TX:\t\t");
+	show_u32(tb[ETHA_CHANNELS_OTHER_COUNT], "Other:\t\t");
+	show_u32(tb[ETHA_CHANNELS_COMBINED_COUNT], "Combined:\t");
+	putchar('\n');
+
+	return 0;
+}
+
 int params_reply_cb(const struct nlmsghdr *nlhdr, void *data)
 {
 	const struct nlattr *tb[ETHA_PARAMS_MAX + 1] = {};
@@ -146,6 +174,15 @@ int params_reply_cb(const struct nlmsghdr *nlhdr, void *data)
 			return MNL_CB_ERROR;
 		}
 	}
+	if (mask_ok(nlctx, ETH_PARAMS_IM_CHANNELS)) {
+		ret = show_channels(nlctx, tb[ETHA_PARAMS_CHANNELS]);
+		if ((ret < 0) && show_only(nlctx, ETH_PARAMS_IM_CHANNELS)) {
+			nlctx->exit_code = 1;
+			errno = -ret;
+			perror("Cannot get device channel parameters");
+			return MNL_CB_ERROR;
+		}
+	}
 
 	return MNL_CB_OK;
 }
@@ -177,3 +214,8 @@ int nl_gpause(struct cmd_context *ctx)
 {
 	return params_request(ctx, ETH_PARAMS_IM_PAUSE);
 }
+
+int nl_gchannels(struct cmd_context *ctx)
+{
+	return params_request(ctx, ETH_PARAMS_IM_CHANNELS);
+}
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [RFC PATCH ethtool v2 16/23] netlink: add netlink handler for geee (--show-eee)
  2018-07-30 12:55 [RFC PATCH ethtool v2 00/23] ethtool netlink interface (userspace side) (WiP) Michal Kubecek
                   ` (14 preceding siblings ...)
  2018-07-30 12:56 ` [RFC PATCH ethtool v2 15/23] netlink: add netlink handler for gchannels (-l) Michal Kubecek
@ 2018-07-30 12:57 ` Michal Kubecek
  2018-07-30 12:57 ` [RFC PATCH ethtool v2 17/23] netlink: add netlink handler for gfec (--show-fec) Michal Kubecek
                   ` (6 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Michal Kubecek @ 2018-07-30 12:57 UTC (permalink / raw)
  To: netdev; +Cc: linux-kernel, John W. Linville

Implement "ethtool --show-eee <dev>" subcommand using netlink interface
command ETHNL_CMD_GET_PARAMS with ETH_PARAMS_IM_EEE mask.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 ethtool.c         |  3 ++-
 netlink/extapi.h  |  1 +
 netlink/monitor.c |  5 ++++
 netlink/params.c  | 66 +++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 74 insertions(+), 1 deletion(-)

diff --git a/ethtool.c b/ethtool.c
index 2bcc1944ba29..1ad0cd10735a 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -4883,6 +4883,7 @@ static int show_usage(struct cmd_context *ctx);
 #define nl_gring	NULL
 #define nl_gpause	NULL
 #define nl_gchannels	NULL
+#define nl_geee		NULL
 #endif
 
 static const struct option {
@@ -5053,7 +5054,7 @@ static const struct option {
 	  "		[ hex on|off ]\n"
 	  "		[ offset N ]\n"
 	  "		[ length N ]\n" },
-	{ "--show-eee", 1, do_geee, NULL,
+	{ "--show-eee", 1, do_geee, nl_geee,
 	  "Show EEE settings"},
 	{ "--set-eee", 1, do_seee, NULL,
 	  "Set EEE settings",
diff --git a/netlink/extapi.h b/netlink/extapi.h
index fc027f62398c..40d708a4cb85 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -22,6 +22,7 @@ int nl_gcoalesce(struct cmd_context *ctx);
 int nl_gring(struct cmd_context *ctx);
 int nl_gpause(struct cmd_context *ctx);
 int nl_gchannels(struct cmd_context *ctx);
+int nl_geee(struct cmd_context *ctx);
 int nl_monitor(struct cmd_context *ctx);
 
 void monitor_usage();
diff --git a/netlink/monitor.c b/netlink/monitor.c
index 1b277606a78c..799371fdebc2 100644
--- a/netlink/monitor.c
+++ b/netlink/monitor.c
@@ -158,6 +158,11 @@ static struct monitor_option monitor_opts[] = {
 		.cmd		= ETHNL_CMD_SET_PARAMS,
 		.info_mask	= ETH_PARAMS_IM_CHANNELS,
 	},
+	{
+		.pattern	= "--show-eee|--set-eee",
+		.cmd		= ETHNL_CMD_SET_PARAMS,
+		.info_mask	= ETH_PARAMS_IM_EEE,
+	},
 };
 
 static bool pattern_match(const char *s, const char *pattern)
diff --git a/netlink/params.c b/netlink/params.c
index 81547492532f..55138b6fdc83 100644
--- a/netlink/params.c
+++ b/netlink/params.c
@@ -133,6 +133,58 @@ static int show_channels(struct nl_context *nlctx, const struct nlattr *nest)
 	return 0;
 }
 
+static int show_eee(struct nl_context *nlctx, const struct nlattr *nest)
+{
+	const struct nlattr *tb[ETHA_EEE_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	bool active, enabled, tx_lpi_enabled;
+	int ret;
+
+	if (!nest)
+		return -EOPNOTSUPP;
+	ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
+	if (ret < 0)
+		return ret;
+	if (!tb[ETHA_EEE_LINK_MODES] || !tb[ETHA_EEE_PEER_MODES] ||
+	    !tb[ETHA_EEE_ACTIVE] || !tb[ETHA_EEE_ENABLED] ||
+	    !tb[ETHA_EEE_TX_LPI_ENABLED] || !tb[ETHA_EEE_TX_LPI_TIMER])
+		return -EFAULT;
+
+
+	active = mnl_attr_get_u8(tb[ETHA_EEE_ACTIVE]);
+	enabled = mnl_attr_get_u8(tb[ETHA_EEE_ENABLED]);
+	tx_lpi_enabled = mnl_attr_get_u8(tb[ETHA_EEE_TX_LPI_ENABLED]);
+
+	printf("EEE Settings for %s:\n", nlctx->devname);
+	printf("\tEEE status: ");
+	if (bitset_is_empty(tb[ETHA_EEE_LINK_MODES], true, &ret)) {
+		printf("not supported\n");
+		return 0;
+	}
+	if (!enabled)
+		printf("disabled\n");
+	else
+		printf("enabled - %s\n", active ? "active" : "inactive");
+	printf("\tTx LPI: ");
+	if (tx_lpi_enabled)
+		printf("%u (us)\n",
+		       mnl_attr_get_u32(tb[ETHA_EEE_TX_LPI_TIMER]));
+	else
+		printf("disabled\n");
+
+	ret = dump_link_modes(tb[ETHA_EEE_LINK_MODES], true, LM_CLASS_REAL,
+			      "Supported EEE link modes:  ", NULL, "\n",
+			      "Not reported");
+	ret = dump_link_modes(tb[ETHA_EEE_LINK_MODES], false, LM_CLASS_REAL,
+			      "Advertised EEE link modes:  ", NULL, "\n",
+			      "Not reported");
+	ret = dump_link_modes(tb[ETHA_EEE_PEER_MODES], false, LM_CLASS_REAL,
+			      "Link partner advertised EEE link modes:  ", NULL,
+			      "\n", "Not reported");
+
+	return 0;
+}
+
 int params_reply_cb(const struct nlmsghdr *nlhdr, void *data)
 {
 	const struct nlattr *tb[ETHA_PARAMS_MAX + 1] = {};
@@ -183,6 +235,15 @@ int params_reply_cb(const struct nlmsghdr *nlhdr, void *data)
 			return MNL_CB_ERROR;
 		}
 	}
+	if (mask_ok(nlctx, ETH_PARAMS_IM_EEE)) {
+		ret = show_eee(nlctx, tb[ETHA_PARAMS_EEE]);
+		if ((ret < 0) && show_only(nlctx, ETH_PARAMS_IM_EEE)) {
+			nlctx->exit_code = 1;
+			errno = -ret;
+			perror("Cannot get device EEE settings");
+			return MNL_CB_ERROR;
+		}
+	}
 
 	return MNL_CB_OK;
 }
@@ -219,3 +280,8 @@ int nl_gchannels(struct cmd_context *ctx)
 {
 	return params_request(ctx, ETH_PARAMS_IM_CHANNELS);
 }
+
+int nl_geee(struct cmd_context *ctx)
+{
+	return params_request(ctx, ETH_PARAMS_IM_EEE);
+}
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [RFC PATCH ethtool v2 17/23] netlink: add netlink handler for gfec (--show-fec)
  2018-07-30 12:55 [RFC PATCH ethtool v2 00/23] ethtool netlink interface (userspace side) (WiP) Michal Kubecek
                   ` (15 preceding siblings ...)
  2018-07-30 12:57 ` [RFC PATCH ethtool v2 16/23] netlink: add netlink handler for geee (--show-eee) Michal Kubecek
@ 2018-07-30 12:57 ` Michal Kubecek
  2018-07-30 12:57 ` [RFC PATCH ethtool v2 18/23] netlink: add netlink handler for scoalesce (-C) Michal Kubecek
                   ` (5 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Michal Kubecek @ 2018-07-30 12:57 UTC (permalink / raw)
  To: netdev; +Cc: linux-kernel, John W. Linville

Implement "ethtool --show-fec <dev>" subcommand showing Forward Error
Correction (FEC) settings using netlink interface command
ETHNL_CMD_GET_PARAMS with ETH_PARAMS_IM_FEC mask.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 common.c          | 10 ++++++++++
 common.h          |  2 ++
 ethtool.c         |  3 ++-
 netlink/extapi.h  |  1 +
 netlink/monitor.c |  5 +++++
 netlink/params.c  | 43 +++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 63 insertions(+), 1 deletion(-)

diff --git a/common.c b/common.c
index db9a360ffae6..085014e895bf 100644
--- a/common.c
+++ b/common.c
@@ -41,6 +41,16 @@ const struct flag_info flags_msglvl[] = {
 };
 const unsigned int n_flags_msglvl = ARRAY_SIZE(flags_msglvl) - 1;
 
+const struct flag_info flags_fecenc[] = {
+	{ "none",	ETHTOOL_FEC_NONE },
+	{ "auto",	ETHTOOL_FEC_AUTO },
+	{ "off",	ETHTOOL_FEC_OFF },
+	{ "RS",		ETHTOOL_FEC_RS },
+	{ "baser",	ETHTOOL_FEC_BASER },
+	{}
+};
+const unsigned int n_flags_fecenc = ARRAY_SIZE(flags_fecenc) - 1;
+
 const char *names_duplex[] = {
 	[DUPLEX_HALF]		= "Half",
 	[DUPLEX_FULL]		= "Full",
diff --git a/common.h b/common.h
index 36f96ddaa3bd..c05e51b645f8 100644
--- a/common.h
+++ b/common.h
@@ -10,6 +10,8 @@ struct flag_info {
 
 extern const struct flag_info flags_msglvl[];
 extern const unsigned int n_flags_msglvl;
+extern const struct flag_info flags_fecenc[];
+extern const unsigned int n_flags_fecenc;
 
 
 enum link_mode_class {
diff --git a/ethtool.c b/ethtool.c
index 1ad0cd10735a..2c838b7c7f53 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -4884,6 +4884,7 @@ static int show_usage(struct cmd_context *ctx);
 #define nl_gpause	NULL
 #define nl_gchannels	NULL
 #define nl_geee		NULL
+#define nl_gfec		NULL
 #endif
 
 static const struct option {
@@ -5091,7 +5092,7 @@ static const struct option {
 	  "		[ ap-shared ]\n"
 	  "		[ dedicated ]\n"
 	  "		[ all ]\n"},
-	{ "--show-fec", 1, do_gfec, NULL,
+	{ "--show-fec", 1, do_gfec, nl_gfec,
 	  "Show FEC settings"},
 	{ "--set-fec", 1, do_sfec, NULL,
 	  "Set FEC settings",
diff --git a/netlink/extapi.h b/netlink/extapi.h
index 40d708a4cb85..b40db721a4c5 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -23,6 +23,7 @@ int nl_gring(struct cmd_context *ctx);
 int nl_gpause(struct cmd_context *ctx);
 int nl_gchannels(struct cmd_context *ctx);
 int nl_geee(struct cmd_context *ctx);
+int nl_gfec(struct cmd_context *ctx);
 int nl_monitor(struct cmd_context *ctx);
 
 void monitor_usage();
diff --git a/netlink/monitor.c b/netlink/monitor.c
index 799371fdebc2..92031052a425 100644
--- a/netlink/monitor.c
+++ b/netlink/monitor.c
@@ -163,6 +163,11 @@ static struct monitor_option monitor_opts[] = {
 		.cmd		= ETHNL_CMD_SET_PARAMS,
 		.info_mask	= ETH_PARAMS_IM_EEE,
 	},
+	{
+		.pattern	= "--show-fec|--set-fec",
+		.cmd		= ETHNL_CMD_SET_PARAMS,
+		.info_mask	= ETH_PARAMS_IM_FEC,
+	},
 };
 
 static bool pattern_match(const char *s, const char *pattern)
diff --git a/netlink/params.c b/netlink/params.c
index 55138b6fdc83..c187ca0b37c5 100644
--- a/netlink/params.c
+++ b/netlink/params.c
@@ -185,6 +185,35 @@ static int show_eee(struct nl_context *nlctx, const struct nlattr *nest)
 	return 0;
 }
 
+static int show_fec(struct nl_context *nlctx, const struct nlattr *nest)
+{
+	const struct nlattr *tb[ETHA_FEC_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	int ret;
+
+	if (!nest)
+		return -EOPNOTSUPP;
+	ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
+	if (ret < 0)
+		return ret;
+
+	printf("FEC parameters for %s:\n", nlctx->devname);
+	if (tb[ETHA_FEC_MODES]) {
+		const struct nla_bitfield32 *fec_encs =
+			mnl_attr_get_payload(tb[ETHA_FEC_MODES]);
+
+		printf("Active FEC encodings:\t");
+		print_flags(flags_fecenc, n_flags_fecenc, fec_encs->value);
+		putchar('\n');
+		printf("Configured FEC encodings:\t");
+		print_flags(flags_fecenc, n_flags_fecenc, fec_encs->selector);
+		putchar('\n');
+	}
+	putchar('\n');
+
+	return 0;
+}
+
 int params_reply_cb(const struct nlmsghdr *nlhdr, void *data)
 {
 	const struct nlattr *tb[ETHA_PARAMS_MAX + 1] = {};
@@ -244,6 +273,15 @@ int params_reply_cb(const struct nlmsghdr *nlhdr, void *data)
 			return MNL_CB_ERROR;
 		}
 	}
+	if (mask_ok(nlctx, ETH_PARAMS_IM_FEC)) {
+		ret = show_fec(nlctx, tb[ETHA_PARAMS_FEC]);
+		if ((ret < 0) && show_only(nlctx, ETH_PARAMS_IM_FEC)) {
+			nlctx->exit_code = 1;
+			errno = -ret;
+			perror("Cannot get device FEC settings");
+			return MNL_CB_ERROR;
+		}
+	}
 
 	return MNL_CB_OK;
 }
@@ -285,3 +323,8 @@ int nl_geee(struct cmd_context *ctx)
 {
 	return params_request(ctx, ETH_PARAMS_IM_EEE);
 }
+
+int nl_gfec(struct cmd_context *ctx)
+{
+	return params_request(ctx, ETH_PARAMS_IM_FEC);
+}
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [RFC PATCH ethtool v2 18/23] netlink: add netlink handler for scoalesce (-C)
  2018-07-30 12:55 [RFC PATCH ethtool v2 00/23] ethtool netlink interface (userspace side) (WiP) Michal Kubecek
                   ` (16 preceding siblings ...)
  2018-07-30 12:57 ` [RFC PATCH ethtool v2 17/23] netlink: add netlink handler for gfec (--show-fec) Michal Kubecek
@ 2018-07-30 12:57 ` Michal Kubecek
  2018-07-30 12:57 ` [RFC PATCH ethtool v2 19/23] netlink: add netlink handler for sring (-G) Michal Kubecek
                   ` (4 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Michal Kubecek @ 2018-07-30 12:57 UTC (permalink / raw)
  To: netdev; +Cc: linux-kernel, John W. Linville

Implement "ethtool -C <dev>" subcommand using netlink interface command
ETHNL_CMD_SET_PARAMS.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 ethtool.c        |   3 +-
 netlink/extapi.h |   1 +
 netlink/params.c | 182 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 185 insertions(+), 1 deletion(-)

diff --git a/ethtool.c b/ethtool.c
index 2c838b7c7f53..8d755b3933a9 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -4885,6 +4885,7 @@ static int show_usage(struct cmd_context *ctx);
 #define nl_gchannels	NULL
 #define nl_geee		NULL
 #define nl_gfec		NULL
+#define nl_scoalesce	NULL
 #endif
 
 static const struct option {
@@ -4917,7 +4918,7 @@ static const struct option {
 	  "		[ tx on|off ]\n" },
 	{ "-c|--show-coalesce", 1, do_gcoalesce, nl_gcoalesce,
 	  "Show coalesce options" },
-	{ "-C|--coalesce", 1, do_scoalesce, NULL,
+	{ "-C|--coalesce", 1, do_scoalesce, nl_scoalesce,
 	  "Set coalesce options",
 	  "		[adaptive-rx on|off]\n"
 	  "		[adaptive-tx on|off]\n"
diff --git a/netlink/extapi.h b/netlink/extapi.h
index b40db721a4c5..2379309f084e 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -24,6 +24,7 @@ int nl_gpause(struct cmd_context *ctx);
 int nl_gchannels(struct cmd_context *ctx);
 int nl_geee(struct cmd_context *ctx);
 int nl_gfec(struct cmd_context *ctx);
+int nl_scoalesce(struct cmd_context *ctx);
 int nl_monitor(struct cmd_context *ctx);
 
 void monitor_usage();
diff --git a/netlink/params.c b/netlink/params.c
index c187ca0b37c5..b86922b8d215 100644
--- a/netlink/params.c
+++ b/netlink/params.c
@@ -328,3 +328,185 @@ int nl_gfec(struct cmd_context *ctx)
 {
 	return params_request(ctx, ETH_PARAMS_IM_FEC);
 }
+
+/* SET_PARAMS */
+
+static int nl_set_param(struct cmd_context *ctx, const char *opt,
+			const struct param_parser *params, u16 nest_type,
+			u16 max_type)
+{
+	struct nl_context *nlctx = ctx->nlctx;
+	struct nlattr *nest;
+	int ret;
+
+	nlctx->cmd = opt;
+	nlctx->argp = ctx->argp;
+	nlctx->argc = ctx->argc;
+	ret = msg_init(nlctx, ETHNL_CMD_SET_PARAMS, NLM_F_REQUEST | NLM_F_ACK);
+	if (ret < 0)
+		return 2;
+	if (ethnla_put_dev(nlctx, ETHA_PARAMS_DEV, ctx->devname))
+		return -EMSGSIZE;
+
+	nest = ethnla_nest_start(nlctx, nest_type);
+	if (!nest) {
+		fprintf(stderr, "ethtool(%s): failed to allocate message\n",
+			opt);
+		return 76;
+	}
+
+	ret = nl_parser(ctx, params, max_type);
+	if (ret < 0)
+		return 2;
+	mnl_attr_nest_end(nlctx->nlhdr, nest);
+
+	ret = ethnl_sendmsg(nlctx);
+	if (ret < 0)
+		return 75;
+	ret = ethnl_process_reply(nlctx, nomsg_reply_cb);
+	if (ret == 0)
+		return 0;
+	return nlctx->exit_code ?: 81;
+}
+
+static const struct param_parser scoalesce_params[] = {
+	{
+		.arg		= "adaptive-rx",
+		.type		= ETHA_COALESCE_RX_USE_ADAPTIVE,
+		.handler	= nl_parse_bool,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "adaptive-tx",
+		.type		= ETHA_COALESCE_TX_USE_ADAPTIVE,
+		.handler	= nl_parse_bool,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "sample-interval",
+		.type		= ETHA_COALESCE_RATE_SAMPLE_INTERVAL,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "stats-block-usecs",
+		.type		= ETHA_COALESCE_STATS_BLOCK_USECS,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "pkt-rate-low",
+		.type		= ETHA_COALESCE_PKT_RATE_LOW,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "pkt-rate-high",
+		.type		= ETHA_COALESCE_PKT_RATE_HIGH,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "rx-usecs",
+		.type		= ETHA_COALESCE_RX_USECS,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "rx-frames",
+		.type		= ETHA_COALESCE_RX_MAXFRM,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "rx-usecs-irq",
+		.type		= ETHA_COALESCE_RX_USECS_IRQ,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "rx-frames-irq",
+		.type		= ETHA_COALESCE_RX_MAXFRM_IRQ,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "tx-usecs",
+		.type		= ETHA_COALESCE_TX_USECS,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "tx-frames",
+		.type		= ETHA_COALESCE_TX_MAXFRM,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "tx-usecs-irq",
+		.type		= ETHA_COALESCE_TX_USECS_IRQ,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "tx-frames-irq",
+		.type		= ETHA_COALESCE_TX_MAXFRM_IRQ,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "rx-usecs-low",
+		.type		= ETHA_COALESCE_RX_USECS_LOW,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "rx-frames-low",
+		.type		= ETHA_COALESCE_RX_MAXFRM_LOW,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "tx-usecs-low",
+		.type		= ETHA_COALESCE_TX_USECS_LOW,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "tx-frames-low",
+		.type		= ETHA_COALESCE_TX_MAXFRM_LOW,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "rx-usecs-high",
+		.type		= ETHA_COALESCE_RX_USECS_HIGH,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "rx-frames-high",
+		.type		= ETHA_COALESCE_RX_MAXFRM_HIGH,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "tx-usecs-high",
+		.type		= ETHA_COALESCE_TX_USECS_HIGH,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "tx-frames-high",
+		.type		= ETHA_COALESCE_TX_MAXFRM_HIGH,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{}
+};
+
+int nl_scoalesce(struct cmd_context *ctx)
+{
+	return nl_set_param(ctx, "-C", scoalesce_params, ETHA_PARAMS_COALESCE,
+			    ETHA_COALESCE_MAX);
+}
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [RFC PATCH ethtool v2 19/23] netlink: add netlink handler for sring (-G)
  2018-07-30 12:55 [RFC PATCH ethtool v2 00/23] ethtool netlink interface (userspace side) (WiP) Michal Kubecek
                   ` (17 preceding siblings ...)
  2018-07-30 12:57 ` [RFC PATCH ethtool v2 18/23] netlink: add netlink handler for scoalesce (-C) Michal Kubecek
@ 2018-07-30 12:57 ` Michal Kubecek
  2018-07-30 12:57 ` [RFC PATCH ethtool v2 20/23] netlink: add netlink handler for spause (-A) Michal Kubecek
                   ` (3 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Michal Kubecek @ 2018-07-30 12:57 UTC (permalink / raw)
  To: netdev; +Cc: linux-kernel, John W. Linville

Implement "ethtool -G <dev>" subcommand using netlink interface command
ETHNL_CMD_SET_PARAMS.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 ethtool.c        |  3 ++-
 netlink/extapi.h |  1 +
 netlink/params.c | 34 ++++++++++++++++++++++++++++++++++
 3 files changed, 37 insertions(+), 1 deletion(-)

diff --git a/ethtool.c b/ethtool.c
index 8d755b3933a9..792283c46d35 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -4886,6 +4886,7 @@ static int show_usage(struct cmd_context *ctx);
 #define nl_geee		NULL
 #define nl_gfec		NULL
 #define nl_scoalesce	NULL
+#define nl_sring	NULL
 #endif
 
 static const struct option {
@@ -4944,7 +4945,7 @@ static const struct option {
 	  "		[sample-interval N]\n" },
 	{ "-g|--show-ring", 1, do_gring, nl_gring,
 	  "Query RX/TX ring parameters" },
-	{ "-G|--set-ring", 1, do_sring, NULL,
+	{ "-G|--set-ring", 1, do_sring, nl_sring,
 	  "Set RX/TX ring parameters",
 	  "		[ rx N ]\n"
 	  "		[ rx-mini N ]\n"
diff --git a/netlink/extapi.h b/netlink/extapi.h
index 2379309f084e..d6d01ee9eae3 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -25,6 +25,7 @@ int nl_gchannels(struct cmd_context *ctx);
 int nl_geee(struct cmd_context *ctx);
 int nl_gfec(struct cmd_context *ctx);
 int nl_scoalesce(struct cmd_context *ctx);
+int nl_sring(struct cmd_context *ctx);
 int nl_monitor(struct cmd_context *ctx);
 
 void monitor_usage();
diff --git a/netlink/params.c b/netlink/params.c
index b86922b8d215..ee0d5e0c51c6 100644
--- a/netlink/params.c
+++ b/netlink/params.c
@@ -510,3 +510,37 @@ int nl_scoalesce(struct cmd_context *ctx)
 	return nl_set_param(ctx, "-C", scoalesce_params, ETHA_PARAMS_COALESCE,
 			    ETHA_COALESCE_MAX);
 }
+
+static const struct param_parser sring_params[] = {
+	{
+		.arg		= "rx",
+		.type		= ETHA_RING_RX_PENDING,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "rx-mini",
+		.type		= ETHA_RING_RX_MINI_PENDING,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "rx-jumbo",
+		.type		= ETHA_RING_RX_JUMBO_PENDING,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "tx",
+		.type		= ETHA_RING_TX_PENDING,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{}
+};
+
+int nl_sring(struct cmd_context *ctx)
+{
+	return nl_set_param(ctx, "-G", sring_params, ETHA_PARAMS_RING,
+			    ETHA_RING_MAX);
+}
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [RFC PATCH ethtool v2 20/23] netlink: add netlink handler for spause (-A)
  2018-07-30 12:55 [RFC PATCH ethtool v2 00/23] ethtool netlink interface (userspace side) (WiP) Michal Kubecek
                   ` (18 preceding siblings ...)
  2018-07-30 12:57 ` [RFC PATCH ethtool v2 19/23] netlink: add netlink handler for sring (-G) Michal Kubecek
@ 2018-07-30 12:57 ` Michal Kubecek
  2018-07-30 12:57 ` [RFC PATCH ethtool v2 21/23] netlink: add netlink handler for schannels (-L) Michal Kubecek
                   ` (2 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Michal Kubecek @ 2018-07-30 12:57 UTC (permalink / raw)
  To: netdev; +Cc: linux-kernel, John W. Linville

Implement "ethtool -A <dev>" subcommand using netlink interface command
ETHNL_CMD_SET_PARAMS.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 ethtool.c        |  3 ++-
 netlink/extapi.h |  1 +
 netlink/params.c | 28 ++++++++++++++++++++++++++++
 3 files changed, 31 insertions(+), 1 deletion(-)

diff --git a/ethtool.c b/ethtool.c
index 792283c46d35..b6e322af9c8a 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -4887,6 +4887,7 @@ static int show_usage(struct cmd_context *ctx);
 #define nl_gfec		NULL
 #define nl_scoalesce	NULL
 #define nl_sring	NULL
+#define nl_spause	NULL
 #endif
 
 static const struct option {
@@ -4912,7 +4913,7 @@ static const struct option {
 	  "		[ msglvl %d[/%d] | type on|off ... [--] ]\n" },
 	{ "-a|--show-pause", 1, do_gpause, nl_gpause,
 	  "Show pause options" },
-	{ "-A|--pause", 1, do_spause, NULL,
+	{ "-A|--pause", 1, do_spause, nl_spause,
 	  "Set pause options",
 	  "		[ autoneg on|off ]\n"
 	  "		[ rx on|off ]\n"
diff --git a/netlink/extapi.h b/netlink/extapi.h
index d6d01ee9eae3..8cf1d0093aaa 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -26,6 +26,7 @@ int nl_geee(struct cmd_context *ctx);
 int nl_gfec(struct cmd_context *ctx);
 int nl_scoalesce(struct cmd_context *ctx);
 int nl_sring(struct cmd_context *ctx);
+int nl_spause(struct cmd_context *ctx);
 int nl_monitor(struct cmd_context *ctx);
 
 void monitor_usage();
diff --git a/netlink/params.c b/netlink/params.c
index ee0d5e0c51c6..953678a0b227 100644
--- a/netlink/params.c
+++ b/netlink/params.c
@@ -544,3 +544,31 @@ int nl_sring(struct cmd_context *ctx)
 	return nl_set_param(ctx, "-G", sring_params, ETHA_PARAMS_RING,
 			    ETHA_RING_MAX);
 }
+
+static const struct param_parser spause_params[] = {
+	{
+		.arg		= "autoneg",
+		.type		= ETHA_PAUSE_AUTONEG,
+		.handler	= nl_parse_bool,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "rx",
+		.type		= ETHA_PAUSE_RX,
+		.handler	= nl_parse_bool,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "tx",
+		.type		= ETHA_PAUSE_TX,
+		.handler	= nl_parse_bool,
+		.min_argc	= 1,
+	},
+	{}
+};
+
+int nl_spause(struct cmd_context *ctx)
+{
+	return nl_set_param(ctx, "-A", spause_params, ETHA_PARAMS_PAUSE,
+			    ETHA_PAUSE_MAX);
+}
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [RFC PATCH ethtool v2 21/23] netlink: add netlink handler for schannels (-L)
  2018-07-30 12:55 [RFC PATCH ethtool v2 00/23] ethtool netlink interface (userspace side) (WiP) Michal Kubecek
                   ` (19 preceding siblings ...)
  2018-07-30 12:57 ` [RFC PATCH ethtool v2 20/23] netlink: add netlink handler for spause (-A) Michal Kubecek
@ 2018-07-30 12:57 ` Michal Kubecek
  2018-07-30 12:57 ` [RFC PATCH ethtool v2 22/23] netlink: add netlink handler for seee (--set-eee) Michal Kubecek
  2018-07-30 12:57 ` [RFC PATCH ethtool v2 23/23] netlink: add netlink handler for sfec (--set-fec) Michal Kubecek
  22 siblings, 0 replies; 24+ messages in thread
From: Michal Kubecek @ 2018-07-30 12:57 UTC (permalink / raw)
  To: netdev; +Cc: linux-kernel, John W. Linville

Implement "ethtool -L <dev>" subcommand using netlink interface command
ETHNL_CMD_SET_PARAMS.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 ethtool.c        |  3 ++-
 netlink/extapi.h |  1 +
 netlink/params.c | 34 ++++++++++++++++++++++++++++++++++
 3 files changed, 37 insertions(+), 1 deletion(-)

diff --git a/ethtool.c b/ethtool.c
index b6e322af9c8a..9ac7f3c57ecb 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -4888,6 +4888,7 @@ static int show_usage(struct cmd_context *ctx);
 #define nl_scoalesce	NULL
 #define nl_sring	NULL
 #define nl_spause	NULL
+#define nl_schannels	NULL
 #endif
 
 static const struct option {
@@ -5041,7 +5042,7 @@ static const struct option {
 	  "		N\n"},
 	{ "-l|--show-channels", 1, do_gchannels, nl_gchannels,
 	  "Query Channels" },
-	{ "-L|--set-channels", 1, do_schannels, NULL,
+	{ "-L|--set-channels", 1, do_schannels, nl_schannels,
 	  "Set Channels",
 	  "               [ rx N ]\n"
 	  "               [ tx N ]\n"
diff --git a/netlink/extapi.h b/netlink/extapi.h
index 8cf1d0093aaa..bc33be82bdd5 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -27,6 +27,7 @@ int nl_gfec(struct cmd_context *ctx);
 int nl_scoalesce(struct cmd_context *ctx);
 int nl_sring(struct cmd_context *ctx);
 int nl_spause(struct cmd_context *ctx);
+int nl_schannels(struct cmd_context *ctx);
 int nl_monitor(struct cmd_context *ctx);
 
 void monitor_usage();
diff --git a/netlink/params.c b/netlink/params.c
index 953678a0b227..5e0f0d264187 100644
--- a/netlink/params.c
+++ b/netlink/params.c
@@ -572,3 +572,37 @@ int nl_spause(struct cmd_context *ctx)
 	return nl_set_param(ctx, "-A", spause_params, ETHA_PARAMS_PAUSE,
 			    ETHA_PAUSE_MAX);
 }
+
+static const struct param_parser schannels_params[] = {
+	{
+		.arg		= "rx",
+		.type		= ETHA_CHANNELS_RX_COUNT,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "tx",
+		.type		= ETHA_CHANNELS_TX_COUNT,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "other",
+		.type		= ETHA_CHANNELS_OTHER_COUNT,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "combined",
+		.type		= ETHA_CHANNELS_COMBINED_COUNT,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{}
+};
+
+int nl_schannels(struct cmd_context *ctx)
+{
+	return nl_set_param(ctx, "-L", schannels_params, ETHA_PARAMS_CHANNELS,
+			    ETHA_CHANNELS_MAX);
+}
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [RFC PATCH ethtool v2 22/23] netlink: add netlink handler for seee (--set-eee)
  2018-07-30 12:55 [RFC PATCH ethtool v2 00/23] ethtool netlink interface (userspace side) (WiP) Michal Kubecek
                   ` (20 preceding siblings ...)
  2018-07-30 12:57 ` [RFC PATCH ethtool v2 21/23] netlink: add netlink handler for schannels (-L) Michal Kubecek
@ 2018-07-30 12:57 ` Michal Kubecek
  2018-07-30 12:57 ` [RFC PATCH ethtool v2 23/23] netlink: add netlink handler for sfec (--set-fec) Michal Kubecek
  22 siblings, 0 replies; 24+ messages in thread
From: Michal Kubecek @ 2018-07-30 12:57 UTC (permalink / raw)
  To: netdev; +Cc: linux-kernel, John W. Linville

Implement "ethtool --set-eee <dev>" subcommand using netlink interface
command ETHNL_CMD_SET_PARAMS.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 ethtool.c        |  5 +++--
 netlink/extapi.h |  1 +
 netlink/params.c | 34 ++++++++++++++++++++++++++++++++++
 3 files changed, 38 insertions(+), 2 deletions(-)

diff --git a/ethtool.c b/ethtool.c
index 9ac7f3c57ecb..bd85b0c50ce2 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -4889,6 +4889,7 @@ static int show_usage(struct cmd_context *ctx);
 #define nl_sring	NULL
 #define nl_spause	NULL
 #define nl_schannels	NULL
+#define nl_seee		NULL
 #endif
 
 static const struct option {
@@ -5061,10 +5062,10 @@ static const struct option {
 	  "		[ length N ]\n" },
 	{ "--show-eee", 1, do_geee, nl_geee,
 	  "Show EEE settings"},
-	{ "--set-eee", 1, do_seee, NULL,
+	{ "--set-eee", 1, do_seee, nl_seee,
 	  "Set EEE settings",
 	  "		[ eee on|off ]\n"
-	  "		[ advertise %x ]\n"
+	  "		[ advertise %x[/%x] | mode on|off ... ]\n"
 	  "		[ tx-lpi on|off ]\n"
 	  "		[ tx-timer %d ]\n"},
 	{ "--set-phy-tunable", 1, do_set_phy_tunable, NULL,
diff --git a/netlink/extapi.h b/netlink/extapi.h
index bc33be82bdd5..80ed4afb5227 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -28,6 +28,7 @@ int nl_scoalesce(struct cmd_context *ctx);
 int nl_sring(struct cmd_context *ctx);
 int nl_spause(struct cmd_context *ctx);
 int nl_schannels(struct cmd_context *ctx);
+int nl_seee(struct cmd_context *ctx);
 int nl_monitor(struct cmd_context *ctx);
 
 void monitor_usage();
diff --git a/netlink/params.c b/netlink/params.c
index 5e0f0d264187..1cc2ffbc4b27 100644
--- a/netlink/params.c
+++ b/netlink/params.c
@@ -606,3 +606,37 @@ int nl_schannels(struct cmd_context *ctx)
 	return nl_set_param(ctx, "-L", schannels_params, ETHA_PARAMS_CHANNELS,
 			    ETHA_CHANNELS_MAX);
 }
+
+static const struct param_parser seee_params[] = {
+	{
+		.arg		= "advertise",
+		.type		= ETHA_EEE_LINK_MODES,
+		.handler	= nl_parse_bitset,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "tx-lpi",
+		.type		= ETHA_EEE_TX_LPI_ENABLED,
+		.handler	= nl_parse_bool,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "tx-timer",
+		.type		= ETHA_EEE_TX_LPI_TIMER,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "eee",
+		.type		= ETHA_EEE_ENABLED,
+		.handler	= nl_parse_bool,
+		.min_argc	= 1,
+	},
+	{}
+};
+
+int nl_seee(struct cmd_context *ctx)
+{
+	return nl_set_param(ctx, "--set-eee", seee_params, ETHA_PARAMS_EEE,
+			    ETHA_EEE_MAX);
+}
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [RFC PATCH ethtool v2 23/23] netlink: add netlink handler for sfec (--set-fec)
  2018-07-30 12:55 [RFC PATCH ethtool v2 00/23] ethtool netlink interface (userspace side) (WiP) Michal Kubecek
                   ` (21 preceding siblings ...)
  2018-07-30 12:57 ` [RFC PATCH ethtool v2 22/23] netlink: add netlink handler for seee (--set-eee) Michal Kubecek
@ 2018-07-30 12:57 ` Michal Kubecek
  22 siblings, 0 replies; 24+ messages in thread
From: Michal Kubecek @ 2018-07-30 12:57 UTC (permalink / raw)
  To: netdev; +Cc: linux-kernel, John W. Linville

Implement "ethtool --set-fec <dev>" subcommand using netlink interface
command ETHNL_CMD_SET_PARAMS.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 ethtool.c        |  3 ++-
 netlink/extapi.h |  1 +
 netlink/params.c | 17 +++++++++++++++++
 3 files changed, 20 insertions(+), 1 deletion(-)

diff --git a/ethtool.c b/ethtool.c
index bd85b0c50ce2..8160ab200a16 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -4890,6 +4890,7 @@ static int show_usage(struct cmd_context *ctx);
 #define nl_spause	NULL
 #define nl_schannels	NULL
 #define nl_seee		NULL
+#define nl_sfec		NULL
 #endif
 
 static const struct option {
@@ -5099,7 +5100,7 @@ static const struct option {
 	  "		[ all ]\n"},
 	{ "--show-fec", 1, do_gfec, nl_gfec,
 	  "Show FEC settings"},
-	{ "--set-fec", 1, do_sfec, NULL,
+	{ "--set-fec", 1, do_sfec, nl_sfec,
 	  "Set FEC settings",
 	  "		[ encoding auto|off|rs|baser ]\n"},
 	{ "-h|--help", 0, show_usage, NULL,
diff --git a/netlink/extapi.h b/netlink/extapi.h
index 80ed4afb5227..1938c6075758 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -29,6 +29,7 @@ int nl_sring(struct cmd_context *ctx);
 int nl_spause(struct cmd_context *ctx);
 int nl_schannels(struct cmd_context *ctx);
 int nl_seee(struct cmd_context *ctx);
+int nl_sfec(struct cmd_context *ctx);
 int nl_monitor(struct cmd_context *ctx);
 
 void monitor_usage();
diff --git a/netlink/params.c b/netlink/params.c
index 1cc2ffbc4b27..95a54d0d2978 100644
--- a/netlink/params.c
+++ b/netlink/params.c
@@ -640,3 +640,20 @@ int nl_seee(struct cmd_context *ctx)
 	return nl_set_param(ctx, "--set-eee", seee_params, ETHA_PARAMS_EEE,
 			    ETHA_EEE_MAX);
 }
+
+static const struct param_parser sfec_params[] = {
+	{
+		.arg		= "modes",
+		.type		= ETHA_FEC_MODES,
+		.handler	= nl_parse_bitfield32,
+		.handler_data	= flags_fecenc,
+		.min_argc	= 1,
+	},
+	{}
+};
+
+int nl_sfec(struct cmd_context *ctx)
+{
+	return nl_set_param(ctx, "--set-fec", sfec_params, ETHA_PARAMS_FEC,
+			    ETHA_FEC_MAX);
+}
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

end of thread, other threads:[~2018-07-30 12:58 UTC | newest]

Thread overview: 24+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-07-30 12:55 [RFC PATCH ethtool v2 00/23] ethtool netlink interface (userspace side) (WiP) Michal Kubecek
2018-07-30 12:55 ` [RFC PATCH ethtool v2 01/23] move UAPI header copies to a separate directory Michal Kubecek
2018-07-30 12:55 ` [RFC PATCH ethtool v2 02/23] update UAPI header copies Michal Kubecek
2018-07-30 12:55 ` [RFC PATCH ethtool v2 03/23] netlink: add netlink interface Michal Kubecek
2018-07-30 12:56 ` [RFC PATCH ethtool v2 04/23] netlink: add support for string sets Michal Kubecek
2018-07-30 12:56 ` [RFC PATCH ethtool v2 05/23] netlink: add notification monitor Michal Kubecek
2018-07-30 12:56 ` [RFC PATCH ethtool v2 06/23] netlink: add netlink handler for gdrv (-i) Michal Kubecek
2018-07-30 12:56 ` [RFC PATCH ethtool v2 07/23] netlink: add netlink handler for gset (no option) Michal Kubecek
2018-07-30 12:56 ` [RFC PATCH ethtool v2 08/23] netlink: add helpers for command line parsing Michal Kubecek
2018-07-30 12:56 ` [RFC PATCH ethtool v2 09/23] netlink: add netlink handler for sset (-s) Michal Kubecek
2018-07-30 12:56 ` [RFC PATCH ethtool v2 10/23] netlink: add netlink handler for gfeatures (-k) Michal Kubecek
2018-07-30 12:56 ` [RFC PATCH ethtool v2 11/23] netlink: add netlink handler for sfeatures (-K) Michal Kubecek
2018-07-30 12:56 ` [RFC PATCH ethtool v2 12/23] netlink: add netlink handler for gcoalesce (-c) Michal Kubecek
2018-07-30 12:56 ` [RFC PATCH ethtool v2 13/23] netlink: add netlink handler for gring (-g) Michal Kubecek
2018-07-30 12:56 ` [RFC PATCH ethtool v2 14/23] netlink: add netlink handler for gpause (-a) Michal Kubecek
2018-07-30 12:56 ` [RFC PATCH ethtool v2 15/23] netlink: add netlink handler for gchannels (-l) Michal Kubecek
2018-07-30 12:57 ` [RFC PATCH ethtool v2 16/23] netlink: add netlink handler for geee (--show-eee) Michal Kubecek
2018-07-30 12:57 ` [RFC PATCH ethtool v2 17/23] netlink: add netlink handler for gfec (--show-fec) Michal Kubecek
2018-07-30 12:57 ` [RFC PATCH ethtool v2 18/23] netlink: add netlink handler for scoalesce (-C) Michal Kubecek
2018-07-30 12:57 ` [RFC PATCH ethtool v2 19/23] netlink: add netlink handler for sring (-G) Michal Kubecek
2018-07-30 12:57 ` [RFC PATCH ethtool v2 20/23] netlink: add netlink handler for spause (-A) Michal Kubecek
2018-07-30 12:57 ` [RFC PATCH ethtool v2 21/23] netlink: add netlink handler for schannels (-L) Michal Kubecek
2018-07-30 12:57 ` [RFC PATCH ethtool v2 22/23] netlink: add netlink handler for seee (--set-eee) Michal Kubecek
2018-07-30 12:57 ` [RFC PATCH ethtool v2 23/23] netlink: add netlink handler for sfec (--set-fec) Michal Kubecek

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).