Netdev Archive on lore.kernel.org
 help / color / Atom feed
* [PATCH net-next v7 00/17] ethtool netlink interface, part 1
@ 2019-10-09 20:59 Michal Kubecek
  2019-10-09 20:59 ` [PATCH net-next v7 01/17] rtnetlink: provide permanent hardware address in RTM_NEWLINK Michal Kubecek
                   ` (17 more replies)
  0 siblings, 18 replies; 46+ messages in thread
From: Michal Kubecek @ 2019-10-09 20:59 UTC (permalink / raw)
  To: David Miller, netdev
  Cc: Jakub Kicinski, Jiri Pirko, Andrew Lunn, Florian Fainelli,
	John Linville, Stephen Hemminger, Johannes Berg, linux-kernel

This is first part of netlink based alternative userspace interface for
ethtool. It aims to address some long known issues with the ioctl
interface, mainly lack of extensibility, raciness, limited error reporting
and absence of notifications. The goal is to allow userspace ethtool
utility to provide all features it currently does but without using the
ioctl interface. However, some features provided by ethtool ioctl API will
be available through other netlink interfaces (rtnetlink, devlink) if it's
more appropriate.

The interface uses generic netlink family "ethtool" and provides multicast
group "monitor" which is used for notifications. Documentation for the
interface is in Documentation/networking/ethtool-netlink.rst file. The
netlink interface is optional, it is built when CONFIG_ETHTOOL_NETLINK
(bool) option is enabled.

There are three types of request messages distinguished by suffix "_GET"
(query for information), "_SET" (modify parameters) and "_ACT" (perform an
action). Kernel reply messages have name with additional suffix "_REPLY"
(e.g. ETHTOOL_MSG_SETTINGS_GET_REPLY). Most "_SET" and "_ACT" message types
do not have matching reply type as only some of them need additional reply
data beyond numeric error code and extack. Kernel also broadcasts
notification messages ("_NTF" suffix) on changes.

Basic concepts:

- make extensions easier not only by allowing new attributes but also by
  imposing as few artificial limits as possible, e.g. by using arbitrary
  size bit sets for most bitmap attributes or by not using fixed size
  strings
- use extack for error reporting and warnings
- send netlink notifications on changes (even if they were done using the
  ioctl interface) and actions
- avoid the racy read/modify/write cycle between kernel and userspace by
  sending only attributes which userspace wants to change; there is still
  a read/modify/write cycle between generic kernel code and ethtool_ops
  handler in NIC driver but it is only in kernel and under RTNL lock
- reduce the number of name lists that need to be kept in sync between
  kernel and userspace (e.g. recognized link modes)
- where feasible, allow dump requests to query specific information for all
  network devices
- as parsing and generating netlink messages is more complicated than
  simply copying data structures between userspace API and ethtool_ops
  handlers (which most ioctl commands do), split the code into multiple
  files in net/ethtool directory; move net/core/ethtool.c also to this
  directory and rename it to ioctl.c

The full (work in progress) series, together with the (userspace) ethtool
counterpart can be found at https://github.com/mkubecek/ethnl

Main changes between v6 and v7:

- split complex messages into small single purpose ones (drop info and
  request masks and one level of nesting)
- separate request information and reply data into two structures
- refactor bitset handling (no simultaneous u32/ulong handling but avoid
  kmalloc() except for long bitmaps on 64-bit big endian architectures)
- use only fixed size strings internally (will be replaced by char *
  eventually but that will require rewriting also existing ioctl code)
- rework ethnl_update_* helpers to return error code
- rename request flag constants (to ETHTOOL_[GR]FLAG_ prefix)
- convert documentation to rst

Main changes between v5 and v6:

- use ETHTOOL_MSG_ prefix for message types
- replace ETHA_ prefix for netlink attributes by ETHTOOL_A_
- replace ETH_x_IM_y for infomask bits by ETHTOOL_IM_x_y
- split GET reply types from SET requests and notifications
- split kernel and userspace message types into different enums
- remove INFO_GET requests from submitted part
- drop EVENT notifications (use rtnetlink and on-demand string set load)
- reorganize patches to reduce the number of intermitent warnings
- unify request/reply header and its processing
- another nest around strings in a string set for consistency
- more consistent identifier naming
- coding style cleanup
- get rid of some of the helpers
- set bad attribute in extack where applicable
- various bug fixes
- improve documentation and code comments, more kerneldoc comments
- more verbose commit messages

Changes between v4 and v5:

- do not panic on failed initialization, only WARN()

Main changes between RFC v3 and v4:

- use more kerneldoc style comments
- strict attribute policy checking
- use macros for tables of link mode names and parameters
- provide permanent hardware address in rtnetlink
- coding style cleanup
- split too long patches, reorder
- wrap more ETHA_SETTINGS_* attributes in nests
- add also some SET_* implementation into submitted part

Main changes between RFC v2 and RFC v3:

- do not allow building as a module (no netdev notifiers needed)
- drop some obsolete fields
- add permanent hw address, timestamping and private flags support
- rework bitset handling to get rid of variable length arrays
- notify monitor on device renames
- restructure GET_SETTINGS/SET_SETTINGS messages
- split too long patches and submit only first part of the series

Main changes between RFC v1 and RFC v2:

- support dumps for all "get" requests
- provide notifications for changes related to supported request types
- support getting string sets (both global and per device)
- support getting/setting device features
- get rid of family specific header, everything passed as attributes
- split netlink code into multiple files in net/ethtool/ directory


Michal Kubecek (17):
  rtnetlink: provide permanent hardware address in RTM_NEWLINK
  netlink: rename nl80211_validate_nested() to nla_validate_nested()
  ethtool: move to its own directory
  ethtool: introduce ethtool netlink interface
  ethtool: helper functions for netlink interface
  ethtool: netlink bitset handling
  ethtool: support for netlink notifications
  ethtool: move string arrays into common file
  ethtool: generic handlers for GET requests
  ethtool: provide string sets with STRSET_GET request
  ethtool: provide link mode names as a string set
  ethtool: provide link settings with LINKINFO_GET request
  ethtool: add standard notification handler
  ethtool: set link settings with LINKINFO_SET request
  ethtool: provide link mode information with LINKMODES_GET request
  ethtool: set link modes related data with LINKMODES_SET request
  ethtool: provide link state with LINKSTATE_GET request

 Documentation/networking/ethtool-netlink.rst | 510 ++++++++++++
 include/linux/ethtool.h                      |   4 +
 include/linux/ethtool_netlink.h              |  17 +
 include/linux/netdevice.h                    |   9 +
 include/net/netlink.h                        |   8 +-
 include/uapi/linux/ethtool.h                 |   4 +
 include/uapi/linux/ethtool_netlink.h         | 215 +++++
 include/uapi/linux/if_link.h                 |   1 +
 net/Kconfig                                  |   8 +
 net/Makefile                                 |   2 +-
 net/core/Makefile                            |   2 +-
 net/core/rtnetlink.c                         |   5 +
 net/ethtool/Makefile                         |   8 +
 net/ethtool/bitset.c                         | 714 +++++++++++++++++
 net/ethtool/bitset.h                         |  28 +
 net/ethtool/common.c                         | 141 ++++
 net/ethtool/common.h                         |  24 +
 net/{core/ethtool.c => ethtool/ioctl.c}      | 156 +---
 net/ethtool/linkinfo.c                       | 180 +++++
 net/ethtool/linkmodes.c                      | 406 ++++++++++
 net/ethtool/linkstate.c                      |  77 ++
 net/ethtool/netlink.c                        | 789 +++++++++++++++++++
 net/ethtool/netlink.h                        | 358 +++++++++
 net/ethtool/strset.c                         | 435 ++++++++++
 net/wireless/nl80211.c                       |   3 +-
 25 files changed, 3960 insertions(+), 144 deletions(-)
 create mode 100644 Documentation/networking/ethtool-netlink.rst
 create mode 100644 include/linux/ethtool_netlink.h
 create mode 100644 include/uapi/linux/ethtool_netlink.h
 create mode 100644 net/ethtool/Makefile
 create mode 100644 net/ethtool/bitset.c
 create mode 100644 net/ethtool/bitset.h
 create mode 100644 net/ethtool/common.c
 create mode 100644 net/ethtool/common.h
 rename net/{core/ethtool.c => ethtool/ioctl.c} (93%)
 create mode 100644 net/ethtool/linkinfo.c
 create mode 100644 net/ethtool/linkmodes.c
 create mode 100644 net/ethtool/linkstate.c
 create mode 100644 net/ethtool/netlink.c
 create mode 100644 net/ethtool/netlink.h
 create mode 100644 net/ethtool/strset.c

-- 
2.23.0


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

* [PATCH net-next v7 01/17] rtnetlink: provide permanent hardware address in RTM_NEWLINK
  2019-10-09 20:59 [PATCH net-next v7 00/17] ethtool netlink interface, part 1 Michal Kubecek
@ 2019-10-09 20:59 ` Michal Kubecek
  2019-10-09 20:59 ` [PATCH net-next v7 02/17] netlink: rename nl80211_validate_nested() to nla_validate_nested() Michal Kubecek
                   ` (16 subsequent siblings)
  17 siblings, 0 replies; 46+ messages in thread
From: Michal Kubecek @ 2019-10-09 20:59 UTC (permalink / raw)
  To: David Miller, netdev
  Cc: Jakub Kicinski, Jiri Pirko, Andrew Lunn, Florian Fainelli,
	John Linville, Stephen Hemminger, Johannes Berg, linux-kernel

Permanent hardware address of a network device was traditionally provided
via ethtool ioctl interface but as Jiri Pirko pointed out in a review of
ethtool netlink interface, rtnetlink is much more suitable for it so let's
add it to the RTM_NEWLINK message.

Add IFLA_PERM_ADDRESS attribute to RTM_NEWLINK messages unless the
permanent address is all zeros (i.e. device driver did not fill it). As
permanent address is not modifiable, reject userspace requests containing
IFLA_PERM_ADDRESS attribute.

Note: we already provide permanent hardware address for bond slaves;
unfortunately we cannot drop that attribute for backward compatibility
reasons.

v5 -> v6: only add the attribute if permanent address is not zero

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
Acked-by: Jiri Pirko <jiri@mellanox.com>
Acked-by: Stephen Hemminger <stephen@networkplumber.org>
---
 include/uapi/linux/if_link.h | 1 +
 net/core/rtnetlink.c         | 5 +++++
 2 files changed, 6 insertions(+)

diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h
index 8aec8769d944..1d69f637c5d6 100644
--- a/include/uapi/linux/if_link.h
+++ b/include/uapi/linux/if_link.h
@@ -169,6 +169,7 @@ enum {
 	IFLA_MAX_MTU,
 	IFLA_PROP_LIST,
 	IFLA_ALT_IFNAME, /* Alternative ifname */
+	IFLA_PERM_ADDRESS,
 	__IFLA_MAX
 };
 
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index 49fa910b58af..a3c1228db273 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -1041,6 +1041,7 @@ static noinline size_t if_nlmsg_size(const struct net_device *dev,
 	       + nla_total_size(4)  /* IFLA_MIN_MTU */
 	       + nla_total_size(4)  /* IFLA_MAX_MTU */
 	       + rtnl_prop_list_size(dev)
+	       + nla_total_size(MAX_ADDR_LEN) /* IFLA_PERM_ADDRESS */
 	       + 0;
 }
 
@@ -1741,6 +1742,9 @@ static int rtnl_fill_ifinfo(struct sk_buff *skb,
 	    nla_put_s32(skb, IFLA_NEW_IFINDEX, new_ifindex) < 0)
 		goto nla_put_failure;
 
+	if (memchr_inv(dev->perm_addr, '\0', dev->addr_len) &&
+	    nla_put(skb, IFLA_PERM_ADDRESS, dev->addr_len, dev->perm_addr))
+		goto nla_put_failure;
 
 	rcu_read_lock();
 	if (rtnl_fill_link_af(skb, dev, ext_filter_mask))
@@ -1806,6 +1810,7 @@ static const struct nla_policy ifla_policy[IFLA_MAX+1] = {
 	[IFLA_PROP_LIST]	= { .type = NLA_NESTED },
 	[IFLA_ALT_IFNAME]	= { .type = NLA_STRING,
 				    .len = ALTIFNAMSIZ - 1 },
+	[IFLA_PERM_ADDRESS]	= { .type = NLA_REJECT },
 };
 
 static const struct nla_policy ifla_info_policy[IFLA_INFO_MAX+1] = {
-- 
2.23.0


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

* [PATCH net-next v7 02/17] netlink: rename nl80211_validate_nested() to nla_validate_nested()
  2019-10-09 20:59 [PATCH net-next v7 00/17] ethtool netlink interface, part 1 Michal Kubecek
  2019-10-09 20:59 ` [PATCH net-next v7 01/17] rtnetlink: provide permanent hardware address in RTM_NEWLINK Michal Kubecek
@ 2019-10-09 20:59 ` Michal Kubecek
  2019-10-09 20:59 ` [PATCH net-next v7 03/17] ethtool: move to its own directory Michal Kubecek
                   ` (15 subsequent siblings)
  17 siblings, 0 replies; 46+ messages in thread
From: Michal Kubecek @ 2019-10-09 20:59 UTC (permalink / raw)
  To: David Miller, netdev
  Cc: Jakub Kicinski, Jiri Pirko, Andrew Lunn, Florian Fainelli,
	John Linville, Stephen Hemminger, Johannes Berg, linux-kernel

Function nl80211_validate_nested() is not specific to nl80211, it's
a counterpart to nla_validate_nested_deprecated() with strict validation.
For consistency with other validation and parse functions, rename it to
nla_validate_nested().

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
Acked-by: Jiri Pirko <jiri@mellanox.com>
Reviewed-by: Johannes Berg <johannes@sipsolutions.net>
---
 include/net/netlink.h  | 8 ++++----
 net/wireless/nl80211.c | 3 +--
 2 files changed, 5 insertions(+), 6 deletions(-)

diff --git a/include/net/netlink.h b/include/net/netlink.h
index b140c8f1be22..56c365dc6dc7 100644
--- a/include/net/netlink.h
+++ b/include/net/netlink.h
@@ -1735,7 +1735,7 @@ static inline void nla_nest_cancel(struct sk_buff *skb, struct nlattr *start)
 }
 
 /**
- * nla_validate_nested - Validate a stream of nested attributes
+ * __nla_validate_nested - Validate a stream of nested attributes
  * @start: container attribute
  * @maxtype: maximum attribute type to be expected
  * @policy: validation policy
@@ -1758,9 +1758,9 @@ static inline int __nla_validate_nested(const struct nlattr *start, int maxtype,
 }
 
 static inline int
-nl80211_validate_nested(const struct nlattr *start, int maxtype,
-			const struct nla_policy *policy,
-			struct netlink_ext_ack *extack)
+nla_validate_nested(const struct nlattr *start, int maxtype,
+		    const struct nla_policy *policy,
+		    struct netlink_ext_ack *extack)
 {
 	return __nla_validate_nested(start, maxtype, policy,
 				     NL_VALIDATE_STRICT, extack);
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 141cdb171665..b551db41aa8a 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -12891,8 +12891,7 @@ static int nl80211_vendor_check_policy(const struct wiphy_vendor_command *vcmd,
 		return -EINVAL;
 	}
 
-	return nl80211_validate_nested(attr, vcmd->maxattr, vcmd->policy,
-				       extack);
+	return nla_validate_nested(attr, vcmd->maxattr, vcmd->policy, extack);
 }
 
 static int nl80211_vendor_cmd(struct sk_buff *skb, struct genl_info *info)
-- 
2.23.0


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

* [PATCH net-next v7 03/17] ethtool: move to its own directory
  2019-10-09 20:59 [PATCH net-next v7 00/17] ethtool netlink interface, part 1 Michal Kubecek
  2019-10-09 20:59 ` [PATCH net-next v7 01/17] rtnetlink: provide permanent hardware address in RTM_NEWLINK Michal Kubecek
  2019-10-09 20:59 ` [PATCH net-next v7 02/17] netlink: rename nl80211_validate_nested() to nla_validate_nested() Michal Kubecek
@ 2019-10-09 20:59 ` Michal Kubecek
  2019-10-09 20:59 ` [PATCH net-next v7 04/17] ethtool: introduce ethtool netlink interface Michal Kubecek
                   ` (14 subsequent siblings)
  17 siblings, 0 replies; 46+ messages in thread
From: Michal Kubecek @ 2019-10-09 20:59 UTC (permalink / raw)
  To: David Miller, netdev
  Cc: Jakub Kicinski, Jiri Pirko, Andrew Lunn, Florian Fainelli,
	John Linville, Stephen Hemminger, Johannes Berg, linux-kernel

The ethtool netlink interface is going to be split into multiple files so
that it will be more convenient to put all of them in a separate directory
net/ethtool. Start by moving current ethtool.c with ioctl interface into
this directory and renaming it to ioctl.c.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
Acked-by: Jiri Pirko <jiri@mellanox.com>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
---
 net/Makefile                            | 2 +-
 net/core/Makefile                       | 2 +-
 net/ethtool/Makefile                    | 3 +++
 net/{core/ethtool.c => ethtool/ioctl.c} | 0
 4 files changed, 5 insertions(+), 2 deletions(-)
 create mode 100644 net/ethtool/Makefile
 rename net/{core/ethtool.c => ethtool/ioctl.c} (100%)

diff --git a/net/Makefile b/net/Makefile
index 449fc0b221f8..848303d98d3d 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -13,7 +13,7 @@ obj-$(CONFIG_NET)		+= $(tmp-y)
 
 # LLC has to be linked before the files in net/802/
 obj-$(CONFIG_LLC)		+= llc/
-obj-$(CONFIG_NET)		+= ethernet/ 802/ sched/ netlink/ bpf/
+obj-$(CONFIG_NET)		+= ethernet/ 802/ sched/ netlink/ bpf/ ethtool/
 obj-$(CONFIG_NETFILTER)		+= netfilter/
 obj-$(CONFIG_INET)		+= ipv4/
 obj-$(CONFIG_TLS)		+= tls/
diff --git a/net/core/Makefile b/net/core/Makefile
index a104dc8faafc..3e2c378e5f31 100644
--- a/net/core/Makefile
+++ b/net/core/Makefile
@@ -8,7 +8,7 @@ obj-y := sock.o request_sock.o skbuff.o datagram.o stream.o scm.o \
 
 obj-$(CONFIG_SYSCTL) += sysctl_net_core.o
 
-obj-y		     += dev.o ethtool.o dev_addr_lists.o dst.o netevent.o \
+obj-y		     += dev.o dev_addr_lists.o dst.o netevent.o \
 			neighbour.o rtnetlink.o utils.o link_watch.o filter.o \
 			sock_diag.o dev_ioctl.o tso.o sock_reuseport.o \
 			fib_notifier.o xdp.o flow_offload.o
diff --git a/net/ethtool/Makefile b/net/ethtool/Makefile
new file mode 100644
index 000000000000..3ebfab2bca66
--- /dev/null
+++ b/net/ethtool/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-y		+= ioctl.o
diff --git a/net/core/ethtool.c b/net/ethtool/ioctl.c
similarity index 100%
rename from net/core/ethtool.c
rename to net/ethtool/ioctl.c
-- 
2.23.0


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

* [PATCH net-next v7 04/17] ethtool: introduce ethtool netlink interface
  2019-10-09 20:59 [PATCH net-next v7 00/17] ethtool netlink interface, part 1 Michal Kubecek
                   ` (2 preceding siblings ...)
  2019-10-09 20:59 ` [PATCH net-next v7 03/17] ethtool: move to its own directory Michal Kubecek
@ 2019-10-09 20:59 ` Michal Kubecek
  2019-10-09 20:59 ` [PATCH net-next v7 05/17] ethtool: helper functions for " Michal Kubecek
                   ` (13 subsequent siblings)
  17 siblings, 0 replies; 46+ messages in thread
From: Michal Kubecek @ 2019-10-09 20:59 UTC (permalink / raw)
  To: David Miller, netdev
  Cc: Jakub Kicinski, Jiri Pirko, Andrew Lunn, Florian Fainelli,
	John Linville, Stephen Hemminger, Johannes Berg, linux-kernel

Basic genetlink and init infrastructure for the netlink interface, register
genetlink family "ethtool". Add CONFIG_ETHTOOL_NETLINK Kconfig option to
make the build optional. Add initial overall interface description into
Documentation/networking/ethtool-netlink.rst, further patches will add more
detailed information.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Documentation/networking/ethtool-netlink.rst | 219 +++++++++++++++++++
 include/linux/ethtool_netlink.h              |   9 +
 include/uapi/linux/ethtool_netlink.h         |  36 +++
 net/Kconfig                                  |   8 +
 net/ethtool/Makefile                         |   6 +-
 net/ethtool/netlink.c                        |  33 +++
 net/ethtool/netlink.h                        |  10 +
 7 files changed, 320 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/networking/ethtool-netlink.rst
 create mode 100644 include/linux/ethtool_netlink.h
 create mode 100644 include/uapi/linux/ethtool_netlink.h
 create mode 100644 net/ethtool/netlink.c
 create mode 100644 net/ethtool/netlink.h

diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst
new file mode 100644
index 000000000000..3e9680b63afa
--- /dev/null
+++ b/Documentation/networking/ethtool-netlink.rst
@@ -0,0 +1,219 @@
+=============================
+Netlink interface for ethtool
+=============================
+
+
+Basic information
+=================
+
+Netlink interface for ethtool uses generic netlink family ``ethtool``
+(userspace application should use macros ``ETHTOOL_GENL_NAME`` and
+``ETHTOOL_GENL_VERSION`` defined in ``<linux/ethtool_netlink.h>`` uapi
+header). This family does not use a specific header, all information in
+requests and replies is passed using netlink attributes.
+
+The ethtool netlink interface uses extended ACK for error and warning
+reporting, userspace application developers are encouraged to make these
+messages available to user in a suitable way.
+
+Requests can be divided into three categories: "get" (retrieving information),
+"set" (setting parameters) and "action" (invoking an action).
+
+All "set" and "action" type requests require admin privileges
+(``CAP_NET_ADMIN`` in the namespace). Most "get" type requests are allowed for
+anyone but there are exceptions (where the response contains sensitive
+information). In some cases, the request as such is allowed for anyone but
+unprivileged users have attributes with sensitive information (e.g.
+wake-on-lan password) omitted.
+
+
+Conventions
+===========
+
+Attributes which represent a boolean value usually use u8 type so that we can
+distinguish three states: "on", "off" and "not present" (meaning the
+information is not available in "get" requests or value is not to be changed
+in "set" requests). For these attributes, the "true" value should be passed as
+number 1 but any non-zero value should be understood as "true" by recipient.
+
+In the message structure descriptions below, if an attribute name is suffixed
+with "+", parent nest can contain multiple attributes of the same type. This
+implements an array of entries.
+
+
+Request header
+==============
+
+Each request or reply message contains a nested attribute with common header.
+Structure of this header is
+
+  ==============================  ======  =============================
+  ``ETHTOOL_A_HEADER_DEV_INDEX``  u32     device ifindex
+  ``ETHTOOL_A_HEADER_DEV_NAME``   string  device name
+  ``ETHTOOL_A_HEADER_GFLAGS``     u32     flags common for all requests
+  ``ETHTOOL_A_HEADER_RFLAGS``     u32     request specific flags
+  ==============================  ======  =============================
+
+``ETHTOOL_A_HEADER_DEV_INDEX`` and ``ETHTOOL_A_HEADER_DEV_NAME`` identify the
+device message relates to. One of them is sufficient in requests, if both are
+used, they must identify the same device. Some requests, e.g. global string
+sets, do not require device identification. Most ``GET`` requests also allow
+dump requests without device identification to query the same information for
+all devices providing it (each device in a separate message).
+
+The two flag bitmaps are used for request options; ``ETHTOOL_A_HEADER_GFLAGS``
+holds global flags common for all request types. Recognized flags are:
+
+  =================================  ===================================
+  ``ETHTOOL_GFLAG_COMPACT_BITSETS``  use compact format bitsets in reply
+  ``ETHTOOL_GFLAG_OMIT_REPLY``       omit optional reply (_SET and _ACT)
+  =================================  ===================================
+
+Flags in ``ETHTOOL_A_HEADER_GFLAGS`` and their interpretation are specific for
+each request. They are listed and explained in further sections describing
+those request types.
+
+For both flag attributes, new flags should follow the general idea that if the
+flag is not set, the behaviour is backward compatible, i.e. requests from old
+clients not aware of the flag should be interpreted the way the client
+expects. A client must not set flags it does not understand.
+
+
+List of message types
+=====================
+
+All constants identifying message types use ``ETHTOOL_CMD_`` prefix and suffix
+according to message purpose:
+
+  ==============    ======================================
+  ``_GET``          userspace request to retrieve data
+  ``_SET``          userspace request to set data
+  ``_ACT``          userspace request to perform an action
+  ``_GET_REPLY``    kernel reply to a ``GET`` request
+  ``_SET_REPLY``    kernel reply to a ``SET`` request
+  ``_ACT_REPLY``    kernel reply to an ``ACT`` request
+  ``_NTF``          kernel notification
+  ==============    ======================================
+
+``GET`` requests are sent by userspace applications to retrieve device
+information. They usually do not contain any message specific attributes.
+Kernel replies with corresponding "GET_REPLY" message. For most types, ``GET``
+request with ``NLM_F_DUMP`` and no device identification can be used to query
+the information for all devices supporting the request.
+
+If the data can be also modified, corresponding ``SET`` message with the same
+layout as corresponding ``GET_REPLY`` is used to request changes. Only
+attributes where a change is requested are included in such request (also, not
+all attributes may be changed). Replies to most ``SET`` request consist only
+of error code and extack; if kernel provides additional data, it is sent in
+the form of corresponding ``SET_REPLY`` message which can be suppressed by
+setting ``ETHTOOL_GFLAG_OMIT_REPLY`` flag in request header.
+
+Data modification also triggers sending a ``NTF`` message with a notification.
+These usually bear only a subset of attributes which was affected by the
+change. The same notification is issued if the data is modified using other
+means (mostly ioctl ethtool interface). Unlike notifications from ethtool
+netlink code which are only sent if something actually changed, notifications
+triggered by ioctl interface may be sent even if the request did not actually
+change any data.
+
+``ACT`` messages request kernel (driver) to perform a specific action. If some
+information is reported by kernel (which can be suppressed by setting
+``ETHTOOL_GFLAG_OMIT_REPLY`` flag in request header), the reply takes form of
+an ``ACT_REPLY`` message. Performing an action also triggers a notification
+(``NTF`` message).
+
+Later sections describe the format and semantics of these messages.
+
+
+Request translation
+===================
+
+The following table maps ioctl commands to netlink commands providing their
+functionality. Entries with "n/a" in right column are commands which do not
+have their netlink replacement yet.
+
+  =================================== =====================================
+  ioctl command                       netlink command
+  =================================== =====================================
+  ``ETHTOOL_GSET``                    n/a
+  ``ETHTOOL_SSET``                    n/a
+  ``ETHTOOL_GDRVINFO``                n/a
+  ``ETHTOOL_GREGS``                   n/a
+  ``ETHTOOL_GWOL``                    n/a
+  ``ETHTOOL_SWOL``                    n/a
+  ``ETHTOOL_GMSGLVL``                 n/a
+  ``ETHTOOL_SMSGLVL``                 n/a
+  ``ETHTOOL_NWAY_RST``                n/a
+  ``ETHTOOL_GLINK``                   n/a
+  ``ETHTOOL_GEEPROM``                 n/a
+  ``ETHTOOL_SEEPROM``                 n/a
+  ``ETHTOOL_GCOALESCE``               n/a
+  ``ETHTOOL_SCOALESCE``               n/a
+  ``ETHTOOL_GRINGPARAM``              n/a
+  ``ETHTOOL_SRINGPARAM``              n/a
+  ``ETHTOOL_GPAUSEPARAM``             n/a
+  ``ETHTOOL_SPAUSEPARAM``             n/a
+  ``ETHTOOL_GRXCSUM``                 n/a
+  ``ETHTOOL_SRXCSUM``                 n/a
+  ``ETHTOOL_GTXCSUM``                 n/a
+  ``ETHTOOL_STXCSUM``                 n/a
+  ``ETHTOOL_GSG``                     n/a
+  ``ETHTOOL_SSG``                     n/a
+  ``ETHTOOL_TEST``                    n/a
+  ``ETHTOOL_GSTRINGS``                n/a
+  ``ETHTOOL_PHYS_ID``                 n/a
+  ``ETHTOOL_GSTATS``                  n/a
+  ``ETHTOOL_GTSO``                    n/a
+  ``ETHTOOL_STSO``                    n/a
+  ``ETHTOOL_GPERMADDR``               rtnetlink ``RTM_GETLINK``
+  ``ETHTOOL_GUFO``                    n/a
+  ``ETHTOOL_SUFO``                    n/a
+  ``ETHTOOL_GGSO``                    n/a
+  ``ETHTOOL_SGSO``                    n/a
+  ``ETHTOOL_GFLAGS``                  n/a
+  ``ETHTOOL_SFLAGS``                  n/a
+  ``ETHTOOL_GPFLAGS``                 n/a
+  ``ETHTOOL_SPFLAGS``                 n/a
+  ``ETHTOOL_GRXFH``                   n/a
+  ``ETHTOOL_SRXFH``                   n/a
+  ``ETHTOOL_GGRO``                    n/a
+  ``ETHTOOL_SGRO``                    n/a
+  ``ETHTOOL_GRXRINGS``                n/a
+  ``ETHTOOL_GRXCLSRLCNT``             n/a
+  ``ETHTOOL_GRXCLSRULE``              n/a
+  ``ETHTOOL_GRXCLSRLALL``             n/a
+  ``ETHTOOL_SRXCLSRLDEL``             n/a
+  ``ETHTOOL_SRXCLSRLINS``             n/a
+  ``ETHTOOL_FLASHDEV``                n/a
+  ``ETHTOOL_RESET``                   n/a
+  ``ETHTOOL_SRXNTUPLE``               n/a
+  ``ETHTOOL_GRXNTUPLE``               n/a
+  ``ETHTOOL_GSSET_INFO``              n/a
+  ``ETHTOOL_GRXFHINDIR``              n/a
+  ``ETHTOOL_SRXFHINDIR``              n/a
+  ``ETHTOOL_GFEATURES``               n/a
+  ``ETHTOOL_SFEATURES``               n/a
+  ``ETHTOOL_GCHANNELS``               n/a
+  ``ETHTOOL_SCHANNELS``               n/a
+  ``ETHTOOL_SET_DUMP``                n/a
+  ``ETHTOOL_GET_DUMP_FLAG``           n/a
+  ``ETHTOOL_GET_DUMP_DATA``           n/a
+  ``ETHTOOL_GET_TS_INFO``             n/a
+  ``ETHTOOL_GMODULEINFO``             n/a
+  ``ETHTOOL_GMODULEEEPROM``           n/a
+  ``ETHTOOL_GEEE``                    n/a
+  ``ETHTOOL_SEEE``                    n/a
+  ``ETHTOOL_GRSSH``                   n/a
+  ``ETHTOOL_SRSSH``                   n/a
+  ``ETHTOOL_GTUNABLE``                n/a
+  ``ETHTOOL_STUNABLE``                n/a
+  ``ETHTOOL_GPHYSTATS``               n/a
+  ``ETHTOOL_PERQUEUE``                n/a
+  ``ETHTOOL_GLINKSETTINGS``           n/a
+  ``ETHTOOL_SLINKSETTINGS``           n/a
+  ``ETHTOOL_PHY_GTUNABLE``            n/a
+  ``ETHTOOL_PHY_STUNABLE``            n/a
+  ``ETHTOOL_GFECPARAM``               n/a
+  ``ETHTOOL_SFECPARAM``               n/a
+  =================================== =====================================
diff --git a/include/linux/ethtool_netlink.h b/include/linux/ethtool_netlink.h
new file mode 100644
index 000000000000..0412adb4f42f
--- /dev/null
+++ b/include/linux/ethtool_netlink.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#ifndef _LINUX_ETHTOOL_NETLINK_H_
+#define _LINUX_ETHTOOL_NETLINK_H_
+
+#include <uapi/linux/ethtool_netlink.h>
+#include <linux/ethtool.h>
+
+#endif /* _LINUX_ETHTOOL_NETLINK_H_ */
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
new file mode 100644
index 000000000000..468f5b00edcc
--- /dev/null
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * include/uapi/linux/ethtool_netlink.h - netlink interface for ethtool
+ *
+ * See Documentation/networking/ethtool-netlink.txt in kernel source tree for
+ * doucumentation of the interface.
+ */
+
+#ifndef _UAPI_LINUX_ETHTOOL_NETLINK_H_
+#define _UAPI_LINUX_ETHTOOL_NETLINK_H_
+
+#include <linux/ethtool.h>
+
+/* message types - userspace to kernel */
+enum {
+	ETHTOOL_MSG_USER_NONE,
+
+	/* add new constants above here */
+	__ETHTOOL_MSG_USER_CNT,
+	ETHTOOL_MSG_USER_MAX = __ETHTOOL_MSG_USER_CNT - 1
+};
+
+/* message types - kernel to userspace */
+enum {
+	ETHTOOL_MSG_KERNEL_NONE,
+
+	/* add new constants above here */
+	__ETHTOOL_MSG_KERNEL_CNT,
+	ETHTOOL_MSG_KERNEL_MAX = __ETHTOOL_MSG_KERNEL_CNT - 1
+};
+
+/* generic netlink info */
+#define ETHTOOL_GENL_NAME "ethtool"
+#define ETHTOOL_GENL_VERSION 1
+
+#endif /* _UAPI_LINUX_ETHTOOL_NETLINK_H_ */
diff --git a/net/Kconfig b/net/Kconfig
index 3101bfcbdd7a..f6886b9d3c1c 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -448,6 +448,14 @@ config FAILOVER
 	  migration of VMs with direct attached VFs by failing over to the
 	  paravirtual datapath when the VF is unplugged.
 
+config ETHTOOL_NETLINK
+	bool "Netlink interface for ethtool"
+	default y
+	help
+	  An alternative userspace interface for ethtool based on generic
+	  netlink. It provides better extensibility and some new features,
+	  e.g. notification messages.
+
 endif   # if NET
 
 # Used by archs to tell that they support BPF JIT compiler plus which flavour.
diff --git a/net/ethtool/Makefile b/net/ethtool/Makefile
index 3ebfab2bca66..f30e0da88be5 100644
--- a/net/ethtool/Makefile
+++ b/net/ethtool/Makefile
@@ -1,3 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 
-obj-y		+= ioctl.o
+obj-y				+= ioctl.o
+
+obj-$(CONFIG_ETHTOOL_NETLINK)	+= ethtool_nl.o
+
+ethtool_nl-y	:= netlink.o
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
new file mode 100644
index 000000000000..3c98b41f04e5
--- /dev/null
+++ b/net/ethtool/netlink.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
+
+#include <linux/ethtool_netlink.h>
+#include "netlink.h"
+
+/* genetlink setup */
+
+static const struct genl_ops ethtool_genl_ops[] = {
+};
+
+static struct genl_family ethtool_genl_family = {
+	.name		= ETHTOOL_GENL_NAME,
+	.version	= ETHTOOL_GENL_VERSION,
+	.netnsok	= true,
+	.parallel_ops	= true,
+	.ops		= ethtool_genl_ops,
+	.n_ops		= ARRAY_SIZE(ethtool_genl_ops),
+};
+
+/* module setup */
+
+static int __init ethnl_init(void)
+{
+	int ret;
+
+	ret = genl_register_family(&ethtool_genl_family);
+	if (WARN(ret < 0, "ethtool: genetlink family registration failed"))
+		return ret;
+
+	return 0;
+}
+
+subsys_initcall(ethnl_init);
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
new file mode 100644
index 000000000000..257ae55ccc82
--- /dev/null
+++ b/net/ethtool/netlink.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#ifndef _NET_ETHTOOL_NETLINK_H
+#define _NET_ETHTOOL_NETLINK_H
+
+#include <linux/ethtool_netlink.h>
+#include <linux/netdevice.h>
+#include <net/genetlink.h>
+
+#endif /* _NET_ETHTOOL_NETLINK_H */
-- 
2.23.0


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

* [PATCH net-next v7 05/17] ethtool: helper functions for netlink interface
  2019-10-09 20:59 [PATCH net-next v7 00/17] ethtool netlink interface, part 1 Michal Kubecek
                   ` (3 preceding siblings ...)
  2019-10-09 20:59 ` [PATCH net-next v7 04/17] ethtool: introduce ethtool netlink interface Michal Kubecek
@ 2019-10-09 20:59 ` " Michal Kubecek
  2019-10-10 13:42   ` Jiri Pirko
  2019-10-09 20:59 ` [PATCH net-next v7 06/17] ethtool: netlink bitset handling Michal Kubecek
                   ` (12 subsequent siblings)
  17 siblings, 1 reply; 46+ messages in thread
From: Michal Kubecek @ 2019-10-09 20:59 UTC (permalink / raw)
  To: David Miller, netdev
  Cc: Jakub Kicinski, Jiri Pirko, Andrew Lunn, Florian Fainelli,
	John Linville, Stephen Hemminger, Johannes Berg, linux-kernel

Add common request/reply header definition and helpers to parse request
header and fill reply header. Provide ethnl_update_* helpers to update
structure members from request attributes (to be used for *_SET requests).

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 include/uapi/linux/ethtool_netlink.h |  22 +++
 net/ethtool/netlink.c                | 172 ++++++++++++++++++++++
 net/ethtool/netlink.h                | 208 +++++++++++++++++++++++++++
 3 files changed, 402 insertions(+)

diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index 468f5b00edcc..c58d9fd52ffc 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -29,6 +29,28 @@ enum {
 	ETHTOOL_MSG_KERNEL_MAX = __ETHTOOL_MSG_KERNEL_CNT - 1
 };
 
+/* request header */
+
+/* use compact bitsets in reply */
+#define ETHTOOL_GFLAG_COMPACT_BITSETS	(1 << 0)
+/* provide optional reply for SET or ACT requests */
+#define ETHTOOL_GFLAG_OMIT_REPLY	(1 << 1)
+
+#define ETHTOOL_GFLAG_ALL (ETHTOOL_GFLAG_COMPACT_BITSETS | \
+			   ETHTOOL_GFLAG_OMIT_REPLY)
+
+enum {
+	ETHTOOL_A_HEADER_UNSPEC,
+	ETHTOOL_A_HEADER_DEV_INDEX,		/* u32 */
+	ETHTOOL_A_HEADER_DEV_NAME,		/* string */
+	ETHTOOL_A_HEADER_GFLAGS,		/* u32 - ETHTOOL_GFLAG_* */
+	ETHTOOL_A_HEADER_RFLAGS,		/* u32 - ETHTOOL_RFLAG_*_* */
+
+	/* add new constants above here */
+	__ETHTOOL_A_HEADER_CNT,
+	ETHTOOL_A_HEADER_MAX = __ETHTOOL_A_HEADER_CNT - 1
+};
+
 /* generic netlink info */
 #define ETHTOOL_GENL_NAME "ethtool"
 #define ETHTOOL_GENL_VERSION 1
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 3c98b41f04e5..71145dace79a 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -1,8 +1,180 @@
 // SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
 
+#include <net/sock.h>
 #include <linux/ethtool_netlink.h>
 #include "netlink.h"
 
+static struct genl_family ethtool_genl_family;
+
+static const struct nla_policy dflt_header_policy[ETHTOOL_A_HEADER_MAX + 1] = {
+	[ETHTOOL_A_HEADER_UNSPEC]	= { .type = NLA_REJECT },
+	[ETHTOOL_A_HEADER_DEV_INDEX]	= { .type = NLA_U32 },
+	[ETHTOOL_A_HEADER_DEV_NAME]	= { .type = NLA_NUL_STRING,
+					    .len = IFNAMSIZ - 1 },
+	[ETHTOOL_A_HEADER_GFLAGS]	= { .type = NLA_U32 },
+	[ETHTOOL_A_HEADER_RFLAGS]	= { .type = NLA_U32 },
+};
+
+/**
+ * ethnl_parse_header() - parse request header
+ * @req_info:    structure to put results into
+ * @header:      nest attribute with request header
+ * @net:         request netns
+ * @extack:      netlink extack for error reporting
+ * @policy:      netlink attribute policy to validate header; use
+ *               @dflt_header_policy (all attributes allowed) if null
+ * @require_dev: fail if no device identiified in header
+ *
+ * Parse request header in nested attribute @nest and puts results into
+ * the structure pointed to by @req_info. Extack from @info is used for error
+ * reporting. If req_info->dev is not null on return, reference to it has
+ * been taken. If error is returned, *req_info is null initialized and no
+ * reference is held.
+ *
+ * Return: 0 on success or negative error code
+ */
+int ethnl_parse_header(struct ethnl_req_info *req_info,
+		       const struct nlattr *header, struct net *net,
+		       struct netlink_ext_ack *extack,
+		       const struct nla_policy *policy, bool require_dev)
+{
+	struct nlattr *tb[ETHTOOL_A_HEADER_MAX + 1];
+	const struct nlattr *devname_attr;
+	struct net_device *dev = NULL;
+	int ret;
+
+	if (!header) {
+		NL_SET_ERR_MSG(extack, "request header missing");
+		return -EINVAL;
+	}
+	ret = nla_parse_nested(tb, ETHTOOL_A_HEADER_MAX, header,
+			       policy ?: dflt_header_policy, extack);
+	if (ret < 0)
+		return ret;
+	devname_attr = tb[ETHTOOL_A_HEADER_DEV_NAME];
+
+	if (tb[ETHTOOL_A_HEADER_DEV_INDEX]) {
+		u32 ifindex = nla_get_u32(tb[ETHTOOL_A_HEADER_DEV_INDEX]);
+
+		dev = dev_get_by_index(net, ifindex);
+		if (!dev) {
+			NL_SET_ERR_MSG_ATTR(extack,
+					    tb[ETHTOOL_A_HEADER_DEV_INDEX],
+					    "no device matches ifindex");
+			return -ENODEV;
+		}
+		/* if both ifindex and ifname are passed, they must match */
+		if (devname_attr &&
+		    strncmp(dev->name, nla_data(devname_attr), IFNAMSIZ)) {
+			dev_put(dev);
+			NL_SET_ERR_MSG_ATTR(extack, header,
+					    "ifindex and name do not match");
+			return -ENODEV;
+		}
+	} else if (devname_attr) {
+		dev = dev_get_by_name(net, nla_data(devname_attr));
+		if (!dev) {
+			NL_SET_ERR_MSG_ATTR(extack, devname_attr,
+					    "no device matches name");
+			return -ENODEV;
+		}
+	} else if (require_dev) {
+		NL_SET_ERR_MSG_ATTR(extack, header,
+				    "neither ifindex nor name specified");
+		return -EINVAL;
+	}
+
+	if (dev && !netif_device_present(dev)) {
+		dev_put(dev);
+		NL_SET_ERR_MSG(extack, "device not present");
+		return -ENODEV;
+	}
+
+	req_info->dev = dev;
+	if (tb[ETHTOOL_A_HEADER_GFLAGS])
+		req_info->global_flags = nla_get_u32(tb[ETHTOOL_A_HEADER_GFLAGS]);
+	if (tb[ETHTOOL_A_HEADER_RFLAGS])
+		req_info->req_flags = nla_get_u32(tb[ETHTOOL_A_HEADER_RFLAGS]);
+
+	return 0;
+}
+
+/**
+ * ethnl_fill_reply_header() - Put standard header into a reply message
+ * @skb:      skb with the message
+ * @dev:      network device to describe in header
+ * @attrtype: attribute type to use for the nest
+ *
+ * Create a nested attribute with attributes describing given network device.
+ *
+ * Return: 0 on success, error value (-EMSGSIZE only) on error
+ */
+int ethnl_fill_reply_header(struct sk_buff *skb, struct net_device *dev,
+			    u16 attrtype)
+{
+	struct nlattr *nest;
+
+	if (!dev)
+		return 0;
+	nest = nla_nest_start(skb, attrtype);
+	if (!nest)
+		return -EMSGSIZE;
+
+	if (nla_put_u32(skb, ETHTOOL_A_HEADER_DEV_INDEX, (u32)dev->ifindex) ||
+	    nla_put_string(skb, ETHTOOL_A_HEADER_DEV_NAME, dev->name))
+		goto nla_put_failure;
+	/* If more attributes are put into reply header, ethnl_header_size()
+	 * must be updated to account for them.
+	 */
+
+	nla_nest_end(skb, nest);
+	return 0;
+
+nla_put_failure:
+	nla_nest_cancel(skb, nest);
+	return -EMSGSIZE;
+}
+
+/**
+ * ethnl_reply_init() - Create skb for a reply and fill device identification
+ * @payload: payload length (without netlink and genetlink header)
+ * @dev:     device the reply is about (may be null)
+ * @cmd:     ETHTOOL_MSG_* message type for reply
+ * @info:    genetlink info of the received packet we respond to
+ * @ehdrp:   place to store payload pointer returned by genlmsg_new()
+ *
+ * Return: pointer to allocated skb on success, NULL on error
+ */
+struct sk_buff *ethnl_reply_init(size_t payload, struct net_device *dev, u8 cmd,
+				 u16 hdr_attrtype, struct genl_info *info,
+				 void **ehdrp)
+{
+	struct sk_buff *skb;
+
+	skb = genlmsg_new(payload, GFP_KERNEL);
+	if (!skb)
+		goto err;
+	*ehdrp = genlmsg_put_reply(skb, info, &ethtool_genl_family, 0, cmd);
+	if (!*ehdrp)
+		goto err_free;
+
+	if (dev) {
+		int ret;
+
+		ret = ethnl_fill_reply_header(skb, dev, hdr_attrtype);
+		if (ret < 0)
+			goto err_free;
+	}
+	return skb;
+
+err_free:
+	nlmsg_free(skb);
+err:
+	if (info)
+		GENL_SET_ERR_MSG(info, "failed to setup reply message");
+	return NULL;
+}
+
 /* genetlink setup */
 
 static const struct genl_ops ethtool_genl_ops[] = {
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 257ae55ccc82..f7c0368a9fa0 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -6,5 +6,213 @@
 #include <linux/ethtool_netlink.h>
 #include <linux/netdevice.h>
 #include <net/genetlink.h>
+#include <net/sock.h>
+
+struct ethnl_req_info;
+
+int ethnl_parse_header(struct ethnl_req_info *req_info,
+		       const struct nlattr *nest, struct net *net,
+		       struct netlink_ext_ack *extack,
+		       const struct nla_policy *policy, bool require_dev);
+int ethnl_fill_reply_header(struct sk_buff *skb, struct net_device *dev,
+			    u16 attrtype);
+struct sk_buff *ethnl_reply_init(size_t payload, struct net_device *dev, u8 cmd,
+				 u16 hdr_attrtype, struct genl_info *info,
+				 void **ehdrp);
+
+/**
+ * ethnl_strz_size() - calculate attribute length for fixed size string
+ * @s: ETH_GSTRING_LEN sized string (may not be null terminated)
+ *
+ * Return: total length of an attribute with null terminated string from @s
+ */
+static inline int ethnl_strz_size(const char *s)
+{
+	return nla_total_size(strnlen(s, ETH_GSTRING_LEN) + 1);
+}
+
+/**
+ * ethnl_put_strz() - put string attribute with fixed size string
+ * @skb:     skb with the message
+ * @attrype: attribute type
+ * @s:       ETH_GSTRING_LEN sized string (may not be null terminated)
+ *
+ * Puts an attribute with null terminated string from @s into the message.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static inline int ethnl_put_strz(struct sk_buff *skb, u16 attrtype,
+				 const char *s)
+{
+	unsigned int len = strnlen(s, ETH_GSTRING_LEN);
+	struct nlattr *attr;
+
+	attr = nla_reserve(skb, attrtype, len + 1);
+	if (!attr)
+		return -EMSGSIZE;
+
+	memcpy(nla_data(attr), s, len);
+	((char *)nla_data(attr))[len] = '\0';
+	return 0;
+}
+
+/**
+ * ethnl_update_u32() - update u32 value from NLA_U32 attribute
+ * @dst:  value to update
+ * @attr: netlink attribute with new value or null
+ * @mod:  pointer to bool for modification tracking
+ *
+ * Copy the u32 value from NLA_U32 netlink attribute @attr into variable
+ * pointed to by @dst; do nothing if @attr is null. Bool pointed to by @mod
+ * is set to true if this function changed the value of *dst, otherwise it
+ * is left as is.
+ */
+static inline void ethnl_update_u32(u32 *dst, const struct nlattr *attr,
+				    bool *mod)
+{
+	u32 val;
+
+	if (!attr)
+		return;
+	val = nla_get_u32(attr);
+	if (*dst == val)
+		return;
+
+	*dst = val;
+	*mod = true;
+}
+
+/**
+ * ethnl_update_u8() - update u8 value from NLA_U8 attribute
+ * @dst:  value to update
+ * @attr: netlink attribute with new value or null
+ * @mod:  pointer to bool for modification tracking
+ *
+ * Copy the u8 value from NLA_U8 netlink attribute @attr into variable
+ * pointed to by @dst; do nothing if @attr is null. Bool pointed to by @mod
+ * is set to true if this function changed the value of *dst, otherwise it
+ * is left as is.
+ */
+static inline void ethnl_update_u8(u8 *dst, const struct nlattr *attr,
+				   bool *mod)
+{
+	u8 val;
+
+	if (!attr)
+		return;
+	val = nla_get_u32(attr);
+	if (*dst == val)
+		return;
+
+	*dst = val;
+	*mod = true;
+}
+
+/**
+ * ethnl_update_bool32() - update u32 used as bool from NLA_U8 attribute
+ * @dst:  value to update
+ * @attr: netlink attribute with new value or null
+ * @mod:  pointer to bool for modification tracking
+ *
+ * Use the u8 value from NLA_U8 netlink attribute @attr to set u32 variable
+ * pointed to by @dst to 0 (if zero) or 1 (if not); do nothing if @attr is
+ * null. Bool pointed to by @mod is set to true if this function changed the
+ * logical value of *dst, otherwise it is left as is.
+ */
+static inline void ethnl_update_bool32(u32 *dst, const struct nlattr *attr,
+				       bool *mod)
+{
+	u8 val;
+
+	if (!attr)
+		return;
+	val = !!nla_get_u8(attr);
+	if (!!*dst == val)
+		return;
+
+	*dst = val;
+	*mod = true;
+}
+
+/**
+ * ethnl_update_binary() - update binary data from NLA_BINARY atribute
+ * @dst:  value to update
+ * @len:  destination buffer length
+ * @attr: netlink attribute with new value or null
+ * @mod:  pointer to bool for modification tracking
+ *
+ * Use the u8 value from NLA_U8 netlink attribute @attr to rewrite data block
+ * of length @len at @dst by attribute payload; do nothing if @attr is null.
+ * Bool pointed to by @mod is set to true if this function changed the logical
+ * value of *dst, otherwise it is left as is.
+ */
+static inline void ethnl_update_binary(void *dst, unsigned int len,
+				       const struct nlattr *attr, bool *mod)
+{
+	if (!attr)
+		return;
+	if (nla_len(attr) < len)
+		len = nla_len(attr);
+	if (!memcmp(dst, nla_data(attr), len))
+		return;
+
+	memcpy(dst, nla_data(attr), len);
+	*mod = true;
+}
+
+/**
+ * ethnl_update_bitfield32() - update u32 value from NLA_BITFIELD32 attribute
+ * @dst:  value to update
+ * @attr: netlink attribute with new value or null
+ * @mod:  pointer to bool for modification tracking
+ *
+ * Update bits in u32 value which are set in attribute's mask to values from
+ * attribute's value. Do nothing if @attr is null or the value wouldn't change;
+ * otherwise, set bool pointed to by @mod to true.
+ */
+static inline void ethnl_update_bitfield32(u32 *dst, const struct nlattr *attr,
+					   bool *mod)
+{
+	struct nla_bitfield32 change;
+	u32 newval;
+
+	if (!attr)
+		return;
+	change = nla_get_bitfield32(attr);
+	newval = (*dst & ~change.selector) | (change.value & change.selector);
+	if (*dst == newval)
+		return;
+
+	*dst = newval;
+	*mod = true;
+}
+
+/**
+ * ethnl_reply_header_size() - total size of reply header
+ *
+ * This is an upper estimate so that we do not need to hold RTNL lock longer
+ * than necessary (to prevent rename between size estimate and composing the
+ * message). Accounts only for device ifindex and name as those are the only
+ * attributes ethnl_fill_reply_header() puts into the reply header.
+ */
+static inline unsigned int ethnl_reply_header_size(void)
+{
+	return nla_total_size(nla_total_size(sizeof(u32)) +
+			      nla_total_size(IFNAMSIZ));
+}
+
+/**
+ * struct ethnl_req_info - base type of request information for GET requests
+ * @dev:          network device the request is for (may be null)
+ * @global_flags: request flags common for all request types
+ * @req_flags:    request flags specific for each request type
+ *
+ * This is a common base, additional members may follow after this structure.
+ */
+struct ethnl_req_info {
+	struct net_device		*dev;
+	u32				global_flags;
+	u32				req_flags;
+};
 
 #endif /* _NET_ETHTOOL_NETLINK_H */
-- 
2.23.0


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

* [PATCH net-next v7 06/17] ethtool: netlink bitset handling
  2019-10-09 20:59 [PATCH net-next v7 00/17] ethtool netlink interface, part 1 Michal Kubecek
                   ` (4 preceding siblings ...)
  2019-10-09 20:59 ` [PATCH net-next v7 05/17] ethtool: helper functions for " Michal Kubecek
@ 2019-10-09 20:59 ` Michal Kubecek
  2019-10-11 13:34   ` Jiri Pirko
  2019-10-09 20:59 ` [PATCH net-next v7 07/17] ethtool: support for netlink notifications Michal Kubecek
                   ` (11 subsequent siblings)
  17 siblings, 1 reply; 46+ messages in thread
From: Michal Kubecek @ 2019-10-09 20:59 UTC (permalink / raw)
  To: David Miller, netdev
  Cc: Jakub Kicinski, Jiri Pirko, Andrew Lunn, Florian Fainelli,
	John Linville, Stephen Hemminger, Johannes Berg, linux-kernel

The ethtool netlink code uses common framework for passing arbitrary
length bit sets to allow future extensions. A bitset can be a list (only
one bitmap) or can consist of value and mask pair (used e.g. when client
want to modify only some bits). A bitset can use one of two formats:
verbose (bit by bit) or compact.

Verbose format consists of bitset size (number of bits), list flag and
an array of bit nests, telling which bits are part of the list or which
bits are in the mask and which of them are to be set. In requests, bits
can be identified by index (position) or by name. In replies, kernel
provides both index and name. Verbose format is suitable for "one shot"
applications like standard ethtool command as it avoids the need to
either keep bit names (e.g. link modes) in sync with kernel or having to
add an extra roundtrip for string set request (e.g. for private flags).

Compact format uses one (list) or two (value/mask) arrays of 32-bit
words to store the bitmap(s). It is more suitable for long running
applications (ethtool in monitor mode or network management daemons)
which can retrieve the names once and then pass only compact bitmaps to
save space.

Userspace requests can use either format; ETHTOOL_GFLAG_COMPACT_BITSETS
flag in request header tells kernel which format to use in reply.
Notifications always use compact format.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Documentation/networking/ethtool-netlink.rst |  68 ++
 include/uapi/linux/ethtool_netlink.h         |  35 +
 net/ethtool/Makefile                         |   2 +-
 net/ethtool/bitset.c                         | 714 +++++++++++++++++++
 net/ethtool/bitset.h                         |  28 +
 net/ethtool/netlink.h                        |   9 +
 6 files changed, 855 insertions(+), 1 deletion(-)
 create mode 100644 net/ethtool/bitset.c
 create mode 100644 net/ethtool/bitset.h

diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst
index 3e9680b63afa..8dda6efee060 100644
--- a/Documentation/networking/ethtool-netlink.rst
+++ b/Documentation/networking/ethtool-netlink.rst
@@ -79,6 +79,74 @@ clients not aware of the flag should be interpreted the way the client
 expects. A client must not set flags it does not understand.
 
 
+Bit sets
+========
+
+For short bitmaps of (reasonably) fixed length, standard ``NLA_BITFIELD32``
+type is used. For arbitrary length bitmaps, ethtool netlink uses a nested
+attribute with contents of one of two forms: compact (two binary bitmaps
+representing bit values and mask of affected bits) and bit-by-bit (list of
+bits identified by either index or name).
+
+Compact form: nested (bitset) atrribute contents:
+
+  ============================  ======  ============================
+  ``ETHTOOL_A_BITSET_LIST``     flag    no mask, only a list
+  ``ETHTOOL_A_BITSET_SIZE``     u32     number of significant bits
+  ``ETHTOOL_A_BITSET_VALUE``    binary  bitmap of bit values
+  ``ETHTOOL_A_BITSET_MASK``     binary  bitmap of valid bits
+  ============================  ======  ============================
+
+Value and mask must have length at least ``ETHTOOL_A_BITSET_SIZE`` bits
+rounded up to a multiple of 32 bits. They consist of 32-bit words in host byte
+order, words ordered from least significant to most significant (i.e. the same
+way as bitmaps are passed with ioctl interface).
+
+For compact form, ``ETHTOOL_A_BITSET_SIZE`` and ``ETHTOOL_A_BITSET_VALUE`` are
+mandatory.  Similar to ``NLA_BITFIELD32``, a compact form bit set requests to
+set bits in the mask to 1 (if the bit is set in value) or 0 (if not) and
+preserve the rest. If ``ETHTOOL_A_BITSET_LIST`` is present, there is no mask
+and bitset represents a simple list of bits.
+
+Kernel bit set length may differ from userspace length if older application is
+used on newer kernel or vice versa. If userspace bitmap is longer, an error is
+issued only if the request actually tries to set values of some bits not
+recognized by kernel.
+
+Bit-by-bit form: nested (bitset) attribute contents:
+
+ +---------------------------------+--------+-----------------------------+
+ | ``ETHTOOL_A_BITSET_LIST``       | flag   | no mask, only a list        |
+ +---------------------------------+--------+-----------------------------+
+ | ``ETHTOOL_A_BITSET_SIZE``       | u32    | number of significant bits  |
+ +---------------------------------+--------+-----------------------------+
+ | ``ETHTOOL_A_BITSET_BIT``        | nested | array of bits               |
+ +-+-------------------------------+--------+-----------------------------+
+ |   ``ETHTOOL_A_BITSET_BIT+``     | nested | one bit                     |
+ +-+-+-----------------------------+--------+-----------------------------+
+ | | | ``ETHTOOL_A_BIT_INDEX``     | u32    | bit index (0 for LSB)       |
+ +-+-+-----------------------------+--------+-----------------------------+
+ | | | ``ETHTOOL_A_BIT_NAME``      | string | bit name                    |
+ +-+-+-----------------------------+--------+-----------------------------+
+ | | | ``ETHTOOL_A_BIT_VALUE``     | flag   | present if bit is set       |
+ +-+-+-----------------------------+--------+-----------------------------+
+
+Bit size is optional for bit-by-bit form. ``ETHTOOL_A_BITSET_BITS`` nest can
+only contain ``ETHTOOL_A_BITS_BIT`` attributes but there can be an arbitrary
+number of them.  A bit may be identified by its index or by its name. When
+used in requests, listed bits are set to 0 or 1 according to
+``ETHTOOL_A_BIT_VALUE``, the rest is preserved. A request fails if index
+exceeds kernel bit length or if name is not recognized.
+
+When ``ETHTOOL_A_BITSET_LIST`` flag is present, bitset is interpreted as a
+simple bit list. ``ETHTOOL_A_BIT_VALUE`` attributes are not used in such case.
+Bit list represents a bitmap with listed bits set and the rest zero.
+
+In requests, application can use either form. Form used by kernel in reply is
+determined by a flag in flags field of request header. Semantics of value and
+mask depends on the attribute.
+
+
 List of message types
 =====================
 
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index c58d9fd52ffc..418f28965a04 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -51,6 +51,41 @@ enum {
 	ETHTOOL_A_HEADER_MAX = __ETHTOOL_A_HEADER_CNT - 1
 };
 
+/* bit sets */
+
+enum {
+	ETHTOOL_A_BIT_UNSPEC,
+	ETHTOOL_A_BIT_INDEX,			/* u32 */
+	ETHTOOL_A_BIT_NAME,			/* string */
+	ETHTOOL_A_BIT_VALUE,			/* flag */
+
+	/* add new constants above here */
+	__ETHTOOL_A_BIT_CNT,
+	ETHTOOL_A_BIT_MAX = __ETHTOOL_A_BIT_CNT - 1
+};
+
+enum {
+	ETHTOOL_A_BITS_UNSPEC,
+	ETHTOOL_A_BITS_BIT,
+
+	/* add new constants above here */
+	__ETHTOOL_A_BITS_CNT,
+	ETHTOOL_A_BITS_MAX = __ETHTOOL_A_BITS_CNT - 1
+};
+
+enum {
+	ETHTOOL_A_BITSET_UNSPEC,
+	ETHTOOL_A_BITSET_LIST,			/* flag */
+	ETHTOOL_A_BITSET_SIZE,			/* u32 */
+	ETHTOOL_A_BITSET_BITS,			/* nest - _A_BITS_* */
+	ETHTOOL_A_BITSET_VALUE,			/* binary */
+	ETHTOOL_A_BITSET_MASK,			/* binary */
+
+	/* add new constants above here */
+	__ETHTOOL_A_BITSET_CNT,
+	ETHTOOL_A_BITSET_MAX = __ETHTOOL_A_BITSET_CNT - 1
+};
+
 /* generic netlink info */
 #define ETHTOOL_GENL_NAME "ethtool"
 #define ETHTOOL_GENL_VERSION 1
diff --git a/net/ethtool/Makefile b/net/ethtool/Makefile
index f30e0da88be5..482fdb9380fa 100644
--- a/net/ethtool/Makefile
+++ b/net/ethtool/Makefile
@@ -4,4 +4,4 @@ obj-y				+= ioctl.o
 
 obj-$(CONFIG_ETHTOOL_NETLINK)	+= ethtool_nl.o
 
-ethtool_nl-y	:= netlink.o
+ethtool_nl-y	:= netlink.o bitset.o
diff --git a/net/ethtool/bitset.c b/net/ethtool/bitset.c
new file mode 100644
index 000000000000..aff6413d6bcc
--- /dev/null
+++ b/net/ethtool/bitset.c
@@ -0,0 +1,714 @@
+// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
+
+#include <linux/ethtool_netlink.h>
+#include <linux/bitmap.h>
+#include "netlink.h"
+#include "bitset.h"
+
+/* To reduce the number of slab allocations, the wrappers use fixed size local
+ * variables for bitmaps up to __SMALL_BITMAP_BITS bits which is the majority
+ * of bitmaps used by ethtool.
+ */
+#define __SMALL_BITMAP_BITS 128
+#define __SMALL_BITMAP_WORDS DIV_ROUND_UP(__SMALL_BITMAP_BITS, 32)
+
+static u32 __lower_bits(unsigned int n)
+{
+	return ~(u32)0 >> (32 - n % 32);
+}
+
+static u32 __upper_bits(unsigned int n)
+{
+	return ~(u32)0 << (n % 32);
+}
+
+/**
+ * __bitmap32_clear() - Clear u32 based bitmap
+ * @dst:   bitmap to clear
+ * @start: beginning of the interval
+ * @end:   end of the interval
+ * @mod:   set if bitmap was modified
+ *
+ * Clear @nbits bits of a bitmap with indices @start <= i < @end
+ */
+static void __bitmap32_clear(u32 *dst, unsigned int start, unsigned int end,
+			     bool *mod)
+{
+	unsigned int start_word = start / 32;
+	unsigned int end_word = end / 32;
+	unsigned int i;
+	u32 mask;
+
+	if (end <= start)
+		return;
+
+	if (start % 32) {
+		mask = __upper_bits(start);
+		if (end_word == start_word) {
+			mask &= __lower_bits(end);
+			if (dst[start_word] & mask) {
+				dst[start_word] &= ~mask;
+				*mod = true;
+			}
+			return;
+		}
+		if (dst[start_word] & mask) {
+			dst[start_word] &= ~mask;
+			*mod = true;
+		}
+		start_word++;
+	}
+
+	for (i = start_word; i < end_word; i++) {
+		if (dst[i]) {
+			dst[i] = 0;
+			*mod = true;
+		}
+	}
+	if (end % 32) {
+		mask = __lower_bits(end);
+		if (dst[end_word] & mask) {
+			dst[end_word] &= ~mask;
+			*mod = true;
+		}
+	}
+}
+
+/**
+ * __bitmap32_no_zero() - Check if any bit is set in an interval
+ * @map:   bitmap to test
+ * @start: beginning of the interval
+ * @end:   end of the interval
+ *
+ * Return: true if there is non-zero bit with  index @start <= i < @end,
+ *         false if the whole interval is zero
+ */
+static bool __bitmap32_not_zero(const u32 *map, unsigned int start,
+				unsigned int end)
+{
+	unsigned int start_word = start / 32;
+	unsigned int end_word = end / 32;
+	u32 mask;
+
+	if (end <= start)
+		return true;
+
+	if (start % 32) {
+		mask = __upper_bits(start);
+		if (end_word == start_word) {
+			mask &= __lower_bits(end);
+			return map[start_word] & mask;
+		}
+		if (map[start_word] & mask)
+			return true;
+		start_word++;
+	}
+
+	if (!memchr_inv(map + start_word, '\0',
+			(end_word - start_word) * sizeof(u32)))
+		return true;
+	if (end % 32 == 0)
+		return true;
+	return map[end_word] & __lower_bits(end);
+}
+
+/**
+ * __bitmap32_update() - Modify u32 based bitmap according to value/mask pair
+ * @dst:   bitmap to update
+ * @nbits: bit size of the bitmap
+ * @value: values to set
+ * @mask:  mask of bits to set
+ * @mod:   set to true if bitmap is modified, preserve if not
+ *
+ * Set bits in @dst bitmap which are set in @mask to values from @value, leave
+ * the rest untouched. If destination bitmap was modified, set @mod to true,
+ * leave as it is if not.
+ */
+static void __bitmap32_update(u32 *dst, unsigned int nbits, const u32 *value,
+			      const u32 *mask, bool *mod)
+{
+	while (nbits > 0) {
+		u32 real_mask = mask ? *mask : ~(u32)0;
+		u32 new_value;
+
+		if (nbits < 32)
+			real_mask &= __lower_bits(nbits);
+		new_value = (*dst & ~real_mask) | (*value & real_mask);
+		if (new_value != *dst) {
+			*dst = new_value;
+			*mod = true;
+		}
+
+		if (nbits <= 32)
+			break;
+		dst++;
+		nbits -= 32;
+		value++;
+		if (mask)
+			mask++;
+	}
+}
+
+static bool __bitmap32_test_bit(const u32 *map, unsigned int index)
+{
+	return map[index / 32] & (1U << (index % 32));
+}
+
+/**
+ * ethnl_bitset32_size() - Calculate size of bitset nested attribute
+ * @val:     value bitmap (u32 based)
+ * @mask:    mask bitmap (u32 based, optional)
+ * @nbits:   bit length of the bitset
+ * @names:   array of bit names (optional)
+ * @compact: assume compact format for output
+ *
+ * Estimate length of netlink attribute composed by a later call to
+ * ethnl_put_bitset32() call with the same arguments.
+ *
+ * Return: negative error code or attribute length estimate
+ */
+int ethnl_bitset32_size(const u32 *val, const u32 *mask, unsigned int nbits,
+			ethnl_string_array_t names, bool compact)
+{
+	unsigned int len = 0;
+
+	/* list flag */
+	if (!mask)
+		len += nla_total_size(sizeof(u32));
+	/* size */
+	len += nla_total_size(sizeof(u32));
+
+	if (compact) {
+		unsigned int nwords = DIV_ROUND_UP(nbits, 32);
+
+		/* value, mask */
+		len += (mask ? 2 : 1) * nla_total_size(nwords * sizeof(u32));
+	} else {
+		unsigned int bits_len = 0;
+		unsigned int bit_len, i;
+
+		for (i = 0; i < nbits; i++) {
+			const char *name = names ? names[i] : NULL;
+
+			if (!__bitmap32_test_bit(mask ?: val, i))
+				continue;
+			/* index */
+			bit_len = nla_total_size(sizeof(u32));
+			/* name */
+			if (name)
+				bit_len += ethnl_strz_size(name);
+			/* value */
+			if (mask && __bitmap32_test_bit(val, i))
+				bit_len += nla_total_size(0);
+
+			/* bit nest */
+			bits_len += nla_total_size(bit_len);
+		}
+		/* bits nest */
+		len += nla_total_size(bits_len);
+	}
+
+	/* outermost nest */
+	return nla_total_size(len);
+}
+
+/**
+ * ethnl_put_bitset32() - Put a bitset nest into a message
+ * @skb:      skb with the message
+ * @attrtype: attribute type for the bitset nest
+ * @val:      value bitmap (u32 based)
+ * @mask:     mask bitmap (u32 based, optional)
+ * @nbits:    bit length of the bitset
+ * @names:    array of bit names (optional)
+ * @compact:  use compact format for the output
+ *
+ * Compose a nested attribute representing a bitset. If @mask is null, simple
+ * bitmap (bit list) is created, if @mask is provided, represent a value/mask
+ * pair. Bit names are only used in verbose mode and when provided by calller.
+ *
+ * Return:    0 on success, negative error value on error
+ */
+int ethnl_put_bitset32(struct sk_buff *skb, int attrtype, const u32 *val,
+		       const u32 *mask, unsigned int nbits,
+		       ethnl_string_array_t names, bool compact)
+{
+	struct nlattr *nest;
+	struct nlattr *attr;
+
+	nest = nla_nest_start(skb, attrtype);
+	if (!nest)
+		return -EMSGSIZE;
+
+	if (!mask && nla_put_flag(skb, ETHTOOL_A_BITSET_LIST))
+		goto nla_put_failure;
+	if (nla_put_u32(skb, ETHTOOL_A_BITSET_SIZE, nbits))
+		goto nla_put_failure;
+	if (compact) {
+		unsigned int nwords = DIV_ROUND_UP(nbits, 32);
+		unsigned int nbytes = nwords * sizeof(u32);
+		u32 *dst;
+
+		attr = nla_reserve(skb, ETHTOOL_A_BITSET_VALUE, nbytes);
+		if (!attr)
+			goto nla_put_failure;
+		dst = nla_data(attr);
+		memcpy(dst, val, nbytes);
+		if (nbits % 32)
+			dst[nwords - 1] &= __lower_bits(nbits);
+
+		if (mask) {
+			attr = nla_reserve(skb, ETHTOOL_A_BITSET_MASK, nbytes);
+			if (!attr)
+				goto nla_put_failure;
+			dst = nla_data(attr);
+			memcpy(dst, mask, nbytes);
+			if (nbits % 32)
+				dst[nwords - 1] &= __lower_bits(nbits);
+		}
+	} else {
+		struct nlattr *bits;
+		unsigned int i;
+
+		bits = nla_nest_start(skb, ETHTOOL_A_BITSET_BITS);
+		if (!bits)
+			goto nla_put_failure;
+		for (i = 0; i < nbits; i++) {
+			const char *name = names ? names[i] : NULL;
+
+			if (!__bitmap32_test_bit(mask ?: val, i))
+				continue;
+			attr = nla_nest_start(skb, ETHTOOL_A_BITS_BIT);
+			if (!attr ||
+			    nla_put_u32(skb, ETHTOOL_A_BIT_INDEX, i))
+				goto nla_put_failure;
+			if (name &&
+			    ethnl_put_strz(skb, ETHTOOL_A_BIT_NAME, name))
+				goto nla_put_failure;
+			if (mask && __bitmap32_test_bit(val, i) &&
+			    nla_put_flag(skb, ETHTOOL_A_BIT_VALUE))
+				goto nla_put_failure;
+			nla_nest_end(skb, attr);
+		}
+		nla_nest_end(skb, bits);
+	}
+
+	nla_nest_end(skb, nest);
+	return 0;
+
+nla_put_failure:
+	nla_nest_cancel(skb, nest);
+	return -EMSGSIZE;
+}
+
+static const struct nla_policy bitset_policy[ETHTOOL_A_BITSET_MAX + 1] = {
+	[ETHTOOL_A_BITSET_UNSPEC]	= { .type = NLA_REJECT },
+	[ETHTOOL_A_BITSET_LIST]		= { .type = NLA_FLAG },
+	[ETHTOOL_A_BITSET_SIZE]		= { .type = NLA_U32 },
+	[ETHTOOL_A_BITSET_BITS]		= { .type = NLA_NESTED },
+	[ETHTOOL_A_BITSET_VALUE]	= { .type = NLA_BINARY },
+	[ETHTOOL_A_BITSET_MASK]		= { .type = NLA_BINARY },
+};
+
+static const struct nla_policy bit_policy[ETHTOOL_A_BIT_MAX + 1] = {
+	[ETHTOOL_A_BIT_UNSPEC]		= { .type = NLA_REJECT },
+	[ETHTOOL_A_BIT_INDEX]		= { .type = NLA_U32 },
+	[ETHTOOL_A_BIT_NAME]		= { .type = NLA_NUL_STRING },
+	[ETHTOOL_A_BIT_VALUE]		= { .type = NLA_FLAG },
+};
+
+/**
+ * ethnl_bitset_is_compact() - check if bitset attribute represents a compact
+ *			       bitset
+ * @bitset  - nested attribute representing a bitset
+ * @compact - pointer for return value
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int ethnl_bitset_is_compact(const struct nlattr *bitset, bool *compact)
+{
+	struct nlattr *tb[ETHTOOL_A_BITSET_MAX + 1];
+	int ret;
+
+	ret = nla_parse_nested(tb, ETHTOOL_A_BITSET_MAX, bitset,
+			       bitset_policy, NULL);
+	if (ret < 0)
+		return ret;
+
+	if (tb[ETHTOOL_A_BITSET_BITS]) {
+		if (tb[ETHTOOL_A_BITSET_VALUE] || tb[ETHTOOL_A_BITSET_MASK])
+			return -EINVAL;
+		*compact = false;
+		return 0;
+	}
+	if (!tb[ETHTOOL_A_BITSET_SIZE] || !tb[ETHTOOL_A_BITSET_VALUE])
+		return -EINVAL;
+
+	*compact = true;
+	return 0;
+}
+
+static int ethnl_name_to_idx(ethnl_string_array_t names, unsigned int n_names,
+			     const char *name, unsigned int name_len)
+{
+	unsigned int i;
+
+	if (!names)
+		return n_names;
+
+	for (i = 0; i < n_names; i++) {
+		const char *bname = names[i];
+
+		if (!strncmp(bname, name, name_len) &&
+		    strlen(bname) <= name_len)
+			return i;
+	}
+
+	return n_names;
+}
+
+static int ethnl_parse_bit(unsigned int *index, bool *val, unsigned int nbits,
+			   const struct nlattr *bit_attr, bool is_list,
+			   ethnl_string_array_t names,
+			   struct netlink_ext_ack *extack)
+{
+	struct nlattr *tb[ETHTOOL_A_BIT_MAX + 1];
+	int ret, idx;
+
+	if (nla_type(bit_attr) != ETHTOOL_A_BITS_BIT) {
+		NL_SET_ERR_MSG_ATTR(extack, bit_attr,
+				    "only ETHTOOL_A_BITS_BIT allowed in ETHTOOL_A_BITSET_BITS");
+		return -EINVAL;
+	}
+	ret = nla_parse_nested(tb, ETHTOOL_A_BIT_MAX, bit_attr, bit_policy,
+			       extack);
+	if (ret < 0)
+		return ret;
+
+	if (tb[ETHTOOL_A_BIT_INDEX]) {
+		const char *name;
+
+		idx = nla_get_u32(tb[ETHTOOL_A_BIT_INDEX]);
+		if (idx >= nbits) {
+			NL_SET_ERR_MSG_ATTR(extack,
+					    tb[ETHTOOL_A_BIT_INDEX],
+					    "bit index too high");
+			return -EOPNOTSUPP;
+		}
+		name = names ? names[idx] : NULL;
+		if (tb[ETHTOOL_A_BIT_NAME] && name &&
+		    strncmp(nla_data(tb[ETHTOOL_A_BIT_NAME]), name,
+			    nla_len(tb[ETHTOOL_A_BIT_NAME]))) {
+			NL_SET_ERR_MSG_ATTR(extack, bit_attr,
+					    "bit index and name mismatch");
+			return -EINVAL;
+		}
+	} else if (tb[ETHTOOL_A_BIT_NAME]) {
+		idx = ethnl_name_to_idx(names, nbits,
+					nla_data(tb[ETHTOOL_A_BIT_NAME]),
+					nla_len(tb[ETHTOOL_A_BIT_NAME]));
+		if (idx >= nbits) {
+			NL_SET_ERR_MSG_ATTR(extack,
+					    tb[ETHTOOL_A_BIT_NAME],
+					    "bit name not found");
+			return -EOPNOTSUPP;
+		}
+	} else {
+		NL_SET_ERR_MSG_ATTR(extack, bit_attr,
+				    "neither bit index nor name specified");
+		return -EINVAL;
+	}
+
+	*index = idx;
+	*val = is_list || tb[ETHTOOL_A_BIT_VALUE];
+	return 0;
+}
+
+static int
+ethnl_update_bitset32_verbose(u32 *bitmap, unsigned int nbits,
+			      const struct nlattr *attr, struct nlattr **tb,
+			      ethnl_string_array_t names,
+			      struct netlink_ext_ack *extack, bool *mod)
+{
+	struct nlattr *bit_attr;
+	bool is_list;
+	int rem;
+	int ret;
+
+	if (tb[ETHTOOL_A_BITSET_VALUE]) {
+		NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_BITSET_VALUE],
+				    "value only allowed in compact bitset");
+		return -EINVAL;
+	}
+	if (tb[ETHTOOL_A_BITSET_MASK]) {
+		NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_BITSET_MASK],
+				    "mask only allowed in compact bitset");
+		return -EINVAL;
+	}
+	is_list = (tb[ETHTOOL_A_BITSET_LIST] != NULL);
+
+	nla_for_each_nested(bit_attr, tb[ETHTOOL_A_BITSET_BITS], rem) {
+		bool old_val, new_val;
+		unsigned int idx;
+
+		ret = ethnl_parse_bit(&idx, &new_val, nbits, bit_attr, is_list,
+				      names, extack);
+		if (ret < 0)
+			return ret;
+		old_val = bitmap[idx / 32] & ((u32)1 << (idx % 32));
+		if (new_val != old_val) {
+			if (new_val)
+				bitmap[idx / 32] |= ((u32)1 << (idx % 32));
+			else
+				bitmap[idx / 32] &= ~((u32)1 << (idx % 32));
+			*mod = true;
+		}
+	}
+
+	return 0;
+}
+
+static int ethnl_compact_sanity_checks(unsigned int nbits,
+				       const struct nlattr *nest,
+				       struct nlattr **tb,
+				       struct netlink_ext_ack *extack)
+{
+	bool is_list = (tb[ETHTOOL_A_BITSET_LIST] != NULL);
+	unsigned int attr_nbits, attr_nwords;
+	const struct nlattr *test_attr;
+
+	if (is_list && tb[ETHTOOL_A_BITSET_MASK]) {
+		NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_BITSET_MASK],
+				    "mask not allowed in list bitset");
+		return -EINVAL;
+	}
+	if (!tb[ETHTOOL_A_BITSET_SIZE]) {
+		NL_SET_ERR_MSG_ATTR(extack, nest,
+				    "missing size in compact bitset");
+		return -EINVAL;
+	}
+	if (!tb[ETHTOOL_A_BITSET_VALUE]) {
+		NL_SET_ERR_MSG_ATTR(extack, nest,
+				    "missing value in compact bitset");
+		return -EINVAL;
+	}
+	if (!is_list && !tb[ETHTOOL_A_BITSET_MASK]) {
+		NL_SET_ERR_MSG_ATTR(extack, nest,
+				    "missing mask in compact nonlist bitset");
+		return -EINVAL;
+	}
+
+	attr_nbits = nla_get_u32(tb[ETHTOOL_A_BITSET_SIZE]);
+	attr_nwords = DIV_ROUND_UP(attr_nbits, 32);
+	if (nla_len(tb[ETHTOOL_A_BITSET_VALUE]) != attr_nwords * sizeof(u32)) {
+		NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_BITSET_VALUE],
+				    "bitset value length does not match size");
+		return -EINVAL;
+	}
+	if (tb[ETHTOOL_A_BITSET_MASK] &&
+	    nla_len(tb[ETHTOOL_A_BITSET_MASK]) != attr_nwords * sizeof(u32)) {
+		NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_BITSET_MASK],
+				    "bitset mask length does not match size");
+		return -EINVAL;
+	}
+	if (attr_nbits <= nbits)
+		return 0;
+
+	test_attr = is_list ? tb[ETHTOOL_A_BITSET_VALUE] :
+			      tb[ETHTOOL_A_BITSET_MASK];
+	if (__bitmap32_not_zero(nla_data(test_attr), nbits, attr_nbits)) {
+		NL_SET_ERR_MSG_ATTR(extack, test_attr,
+				    "cannot modify bits past kernel bitset size");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/**
+ * ethnl_update_bitset32() - Apply a bitset nest to a u32 based bitmap
+ * @bitmap:  bitmap to update
+ * @nbits:   size of the updated bitmap in bits
+ * @attr:    nest attribute to parse and apply
+ * @names:   array of bit names; may be null for compact format
+ * @extack:  extack for error reporting
+ * @mod:     set this to true if bitmap is modified, leave as it is if not
+ *
+ * Apply bitset netsted attribute to a bitmap. If the attribute represents
+ * a bit list, @bitmap is set to its contents; otherwise, bits in mask are
+ * set to values from value. Bitmaps in the attribute may be longer than
+ * @nbits but the message must not request modifying any bits past @nbits.
+ *
+ * Return:   negative error code on failure, 0 on success
+ */
+int ethnl_update_bitset32(u32 *bitmap, unsigned int nbits,
+			  const struct nlattr *attr, ethnl_string_array_t names,
+			  struct netlink_ext_ack *extack, bool *mod)
+{
+	struct nlattr *tb[ETHTOOL_A_BITSET_MAX + 1];
+	unsigned int change_bits;
+	bool is_list;
+	int ret;
+
+	if (!attr)
+		return 0;
+	ret = nla_parse_nested(tb, ETHTOOL_A_BITSET_MAX, attr, bitset_policy,
+			       extack);
+	if (ret < 0)
+		return ret;
+
+	if (tb[ETHTOOL_A_BITSET_BITS])
+		return ethnl_update_bitset32_verbose(bitmap, nbits, attr, tb,
+						     names, extack, mod);
+	ret = ethnl_compact_sanity_checks(nbits, attr, tb, extack);
+	if (ret < 0)
+		return ret;
+
+	is_list = (tb[ETHTOOL_A_BITSET_LIST] != NULL);
+	change_bits = min_t(unsigned int,
+			    nla_get_u32(tb[ETHTOOL_A_BITSET_SIZE]), nbits);
+	__bitmap32_update(bitmap, change_bits,
+			  nla_data(tb[ETHTOOL_A_BITSET_VALUE]),
+			  is_list ? NULL : nla_data(tb[ETHTOOL_A_BITSET_MASK]),
+			  mod);
+	if (is_list && change_bits < nbits)
+		__bitmap32_clear(bitmap, change_bits, nbits, mod);
+
+	return 0;
+}
+
+/* 64-bit long endian architecture is the only case when u32 based bitmaps
+ * and unsigned long based bitmaps have different memory layout so that we
+ * cannot simply cast the latter to the former.
+ */
+#if BITS_PER_LONG == 64 && defined(__BIG_ENDIAN)
+
+int ethnl_bitset_size(const unsigned long *val, const unsigned long *mask,
+		      unsigned int nbits, ethnl_string_array_t names,
+		      bool compact)
+{
+	u32 small_mask32[__SMALL_BITMAP_WORDS];
+	u32 small_val32[__SMALL_BITMAP_WORDS];
+	u32 *mask32;
+	u32 *val32;
+	int ret;
+
+	if (nbits > __SMALL_BITMAP_BITS) {
+		unsigned int nwords = DIV_ROUND_UP(nbits, 32);
+
+		val32 = kmalloc_array(2 * nwords, sizeof(u32), GFP_KERNEL);
+		if (!val32)
+			return -ENOMEM;
+		mask32 = val32 + nwords;
+	} else {
+		val32 = small_val32;
+		mask32 = small_mask32;
+	}
+
+	bitmap_to_arr32(val32, val, nbits);
+	if (mask)
+		bitmap_to_arr32(mask32, mask, nbits);
+	else
+		mask32 = NULL;
+	ret = ethnl_bitset32_size(val32, mask32, nbits, names, compact);
+
+	if (nbits > __SMALL_BITMAP_BITS)
+		kfree(val32);
+
+	return ret;
+}
+
+int ethnl_put_bitset(struct sk_buff *skb, int attrtype,
+		     const unsigned long *val, const unsigned long *mask,
+		     unsigned int nbits, ethnl_string_array_t names,
+		     bool compact)
+{
+	u32 small_mask32[__SMALL_BITMAP_WORDS];
+	u32 small_val32[__SMALL_BITMAP_WORDS];
+	u32 *mask32;
+	u32 *val32;
+	int ret;
+
+	if (nbits > __SMALL_BITMAP_BITS) {
+		unsigned int nwords = DIV_ROUND_UP(nbits, 32);
+
+		val32 = kmalloc_array(2 * nwords, sizeof(u32), GFP_KERNEL);
+		if (!val32)
+			return -ENOMEM;
+		mask32 = val32 + nwords;
+	} else {
+		val32 = small_val32;
+		mask32 = small_mask32;
+	}
+
+	bitmap_to_arr32(val32, val, nbits);
+	if (mask)
+		bitmap_to_arr32(mask32, mask, nbits);
+	else
+		mask32 = NULL;
+	ret = ethnl_put_bitset32(skb, attrtype, val32, mask32, nbits, names,
+				 compact);
+
+	if (nbits > __SMALL_BITMAP_BITS)
+		kfree(val32);
+
+	return ret;
+}
+
+int ethnl_update_bitset(unsigned long *bitmap, unsigned int nbits,
+			const struct nlattr *attr, ethnl_string_array_t names,
+			struct netlink_ext_ack *extack, bool *mod)
+{
+	u32 small_bitmap32[__SMALL_BITMAP_WORDS];
+	u32 *bitmap32 = small_bitmap32;
+	bool u32_mod = false;
+	int ret;
+
+	if (nbits > __SMALL_BITMAP_BITS) {
+		unsigned int dst_words = DIV_ROUND_UP(nbits, 32);
+
+		bitmap32 = kmalloc_array(dst_words, sizeof(u32), GFP_KERNEL);
+		if (!bitmap32)
+			return -ENOMEM;
+	}
+
+	bitmap_to_arr32(bitmap32, bitmap, nbits);
+	ret = ethnl_update_bitset32(bitmap32, nbits, attr, names, extack,
+				    &u32_mod);
+	if (ulong_mod) {
+		bitmap_from_arr32(bitmap, bitmap32, nbits);
+		*mod = true;
+	}
+
+	if (size > __SMALL_BITMAP_BITS)
+		kfree(bitmask32);
+
+	return ret;
+}
+
+#else
+
+int ethnl_bitset_size(const unsigned long *val, const unsigned long *mask,
+		      unsigned int nbits, ethnl_string_array_t names,
+		      bool compact)
+{
+	return ethnl_bitset32_size((const u32 *)val, (const u32 *)mask, nbits,
+				   names, compact);
+}
+
+int ethnl_put_bitset(struct sk_buff *skb, int attrtype,
+		     const unsigned long *val, const unsigned long *mask,
+		     unsigned int nbits, ethnl_string_array_t names,
+		     bool compact)
+{
+	return ethnl_put_bitset32(skb, attrtype, (const u32 *)val,
+				  (const u32 *)mask, nbits, names, compact);
+}
+
+int ethnl_update_bitset(unsigned long *bitmap, unsigned int nbits,
+			const struct nlattr *attr, ethnl_string_array_t names,
+			struct netlink_ext_ack *extack, bool *mod)
+{
+	return ethnl_update_bitset32((u32 *)bitmap, nbits, attr, names, extack,
+				     mod);
+}
+
+#endif /* BITS_PER_LONG == 64 && defined(__BIG_ENDIAN) */
diff --git a/net/ethtool/bitset.h b/net/ethtool/bitset.h
new file mode 100644
index 000000000000..cd3d681b4524
--- /dev/null
+++ b/net/ethtool/bitset.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#ifndef _NET_ETHTOOL_BITSET_H
+#define _NET_ETHTOOL_BITSET_H
+
+typedef const char (*const ethnl_string_array_t)[ETH_GSTRING_LEN];
+
+int ethnl_bitset_is_compact(const struct nlattr *bitset, bool *compact);
+int ethnl_bitset_size(const unsigned long *val, const unsigned long *mask,
+		      unsigned int nbits, ethnl_string_array_t names,
+		      bool compact);
+int ethnl_bitset32_size(const u32 *val, const u32 *mask, unsigned int nbits,
+			ethnl_string_array_t names, bool compact);
+int ethnl_put_bitset(struct sk_buff *skb, int attrtype,
+		     const unsigned long *val, const unsigned long *mask,
+		     unsigned int nbits, ethnl_string_array_t names,
+		     bool compact);
+int ethnl_put_bitset32(struct sk_buff *skb, int attrtype, const u32 *val,
+		       const u32 *mask, unsigned int nbits,
+		       ethnl_string_array_t names, bool compact);
+int ethnl_update_bitset(unsigned long *bitmap, unsigned int nbits,
+			const struct nlattr *attr, ethnl_string_array_t names,
+			struct netlink_ext_ack *extack, bool *mod);
+int ethnl_update_bitset32(u32 *bitmap, unsigned int nbits,
+			  const struct nlattr *attr, ethnl_string_array_t names,
+			  struct netlink_ext_ack *extack, bool *mod);
+
+#endif /* _NET_ETHTOOL_BITSET_H */
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index f7c0368a9fa0..4c0b5ca439f8 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -20,6 +20,15 @@ struct sk_buff *ethnl_reply_init(size_t payload, struct net_device *dev, u8 cmd,
 				 u16 hdr_attrtype, struct genl_info *info,
 				 void **ehdrp);
 
+#if BITS_PER_LONG == 64 && defined(__BIG_ENDIAN)
+void ethnl_bitmap_to_u32(unsigned long *bitmap, unsigned int nwords);
+#else
+static inline void ethnl_bitmap_to_u32(unsigned long *bitmap,
+				       unsigned int nwords)
+{
+}
+#endif
+
 /**
  * ethnl_strz_size() - calculate attribute length for fixed size string
  * @s: ETH_GSTRING_LEN sized string (may not be null terminated)
-- 
2.23.0


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

* [PATCH net-next v7 07/17] ethtool: support for netlink notifications
  2019-10-09 20:59 [PATCH net-next v7 00/17] ethtool netlink interface, part 1 Michal Kubecek
                   ` (5 preceding siblings ...)
  2019-10-09 20:59 ` [PATCH net-next v7 06/17] ethtool: netlink bitset handling Michal Kubecek
@ 2019-10-09 20:59 ` Michal Kubecek
  2019-10-09 20:59 ` [PATCH net-next v7 08/17] ethtool: move string arrays into common file Michal Kubecek
                   ` (10 subsequent siblings)
  17 siblings, 0 replies; 46+ messages in thread
From: Michal Kubecek @ 2019-10-09 20:59 UTC (permalink / raw)
  To: David Miller, netdev
  Cc: Jakub Kicinski, Jiri Pirko, Andrew Lunn, Florian Fainelli,
	John Linville, Stephen Hemminger, Johannes Berg, linux-kernel

Add infrastructure for ethtool netlink notifications. There is only one
multicast group "monitor" which is used to notify userspace about changes
and actions performed. Notification messages (types using suffix _NTF)
share the format with replies to GET requests.

Notifications are supposed to be broadcasted on every configuration change,
whether it is done using the netlink interface or ioctl one. Netlink SET
requests only trigger a notification if some data is actually changed.

To trigger an ethtool notification, both ethtool netlink and external code
use ethtool_notify() helper. This helper requires RTNL to be held and may
sleep. Handlers sending messages for specific notification message types
are registered in ethnl_notify_handlers array. As notifications can be
triggered from other code, ethnl_ok flag is used to prevent an attempt to
send notification before genetlink family is registered.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 include/linux/ethtool_netlink.h      |  5 +++++
 include/linux/netdevice.h            |  9 ++++++++
 include/uapi/linux/ethtool_netlink.h |  2 ++
 net/ethtool/netlink.c                | 32 ++++++++++++++++++++++++++++
 4 files changed, 48 insertions(+)

diff --git a/include/linux/ethtool_netlink.h b/include/linux/ethtool_netlink.h
index 0412adb4f42f..2a15e64a16f3 100644
--- a/include/linux/ethtool_netlink.h
+++ b/include/linux/ethtool_netlink.h
@@ -5,5 +5,10 @@
 
 #include <uapi/linux/ethtool_netlink.h>
 #include <linux/ethtool.h>
+#include <linux/netdevice.h>
+
+enum ethtool_multicast_groups {
+	ETHNL_MCGRP_MONITOR,
+};
 
 #endif /* _LINUX_ETHTOOL_NETLINK_H_ */
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 3207e0b9ec4e..a50b3c6e5b2e 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -4377,6 +4377,15 @@ struct netdev_notifier_bonding_info {
 void netdev_bonding_info_change(struct net_device *dev,
 				struct netdev_bonding_info *bonding_info);
 
+#if IS_ENABLED(CONFIG_ETHTOOL_NETLINK)
+void ethtool_notify(struct net_device *dev, unsigned int cmd, const void *data);
+#else
+static inline void ethtool_notify(struct net_device *dev, unsigned int cmd,
+				  const void *data)
+{
+}
+#endif
+
 static inline
 struct sk_buff *skb_gso_segment(struct sk_buff *skb, netdev_features_t features)
 {
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index 418f28965a04..c3d2e950e728 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -90,4 +90,6 @@ enum {
 #define ETHTOOL_GENL_NAME "ethtool"
 #define ETHTOOL_GENL_VERSION 1
 
+#define ETHTOOL_MCGRP_MONITOR_NAME "monitor"
+
 #endif /* _UAPI_LINUX_ETHTOOL_NETLINK_H_ */
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 71145dace79a..ed9c0f82aca4 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -6,6 +6,8 @@
 
 static struct genl_family ethtool_genl_family;
 
+static bool ethnl_ok __read_mostly;
+
 static const struct nla_policy dflt_header_policy[ETHTOOL_A_HEADER_MAX + 1] = {
 	[ETHTOOL_A_HEADER_UNSPEC]	= { .type = NLA_REJECT },
 	[ETHTOOL_A_HEADER_DEV_INDEX]	= { .type = NLA_U32 },
@@ -175,11 +177,38 @@ struct sk_buff *ethnl_reply_init(size_t payload, struct net_device *dev, u8 cmd,
 	return NULL;
 }
 
+/* notifications */
+
+typedef void (*ethnl_notify_handler_t)(struct net_device *dev, unsigned int cmd,
+				       const void *data);
+
+static const ethnl_notify_handler_t ethnl_notify_handlers[] = {
+};
+
+void ethtool_notify(struct net_device *dev, unsigned int cmd, const void *data)
+{
+	if (unlikely(!ethnl_ok))
+		return;
+	ASSERT_RTNL();
+
+	if (likely(cmd < ARRAY_SIZE(ethnl_notify_handlers) &&
+		   ethnl_notify_handlers[cmd]))
+		ethnl_notify_handlers[cmd](dev, cmd, data);
+	else
+		WARN_ONCE(1, "notification %u not implemented (dev=%s)\n",
+			  cmd, netdev_name(dev));
+}
+EXPORT_SYMBOL(ethtool_notify);
+
 /* genetlink setup */
 
 static const struct genl_ops ethtool_genl_ops[] = {
 };
 
+static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
+	[ETHNL_MCGRP_MONITOR] = { .name = ETHTOOL_MCGRP_MONITOR_NAME },
+};
+
 static struct genl_family ethtool_genl_family = {
 	.name		= ETHTOOL_GENL_NAME,
 	.version	= ETHTOOL_GENL_VERSION,
@@ -187,6 +216,8 @@ static struct genl_family ethtool_genl_family = {
 	.parallel_ops	= true,
 	.ops		= ethtool_genl_ops,
 	.n_ops		= ARRAY_SIZE(ethtool_genl_ops),
+	.mcgrps		= ethtool_nl_mcgrps,
+	.n_mcgrps	= ARRAY_SIZE(ethtool_nl_mcgrps),
 };
 
 /* module setup */
@@ -198,6 +229,7 @@ static int __init ethnl_init(void)
 	ret = genl_register_family(&ethtool_genl_family);
 	if (WARN(ret < 0, "ethtool: genetlink family registration failed"))
 		return ret;
+	ethnl_ok = true;
 
 	return 0;
 }
-- 
2.23.0


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

* [PATCH net-next v7 08/17] ethtool: move string arrays into common file
  2019-10-09 20:59 [PATCH net-next v7 00/17] ethtool netlink interface, part 1 Michal Kubecek
                   ` (6 preceding siblings ...)
  2019-10-09 20:59 ` [PATCH net-next v7 07/17] ethtool: support for netlink notifications Michal Kubecek
@ 2019-10-09 20:59 ` Michal Kubecek
  2019-10-10 13:27   ` Jiri Pirko
  2019-10-09 20:59 ` [PATCH net-next v7 09/17] ethtool: generic handlers for GET requests Michal Kubecek
                   ` (9 subsequent siblings)
  17 siblings, 1 reply; 46+ messages in thread
From: Michal Kubecek @ 2019-10-09 20:59 UTC (permalink / raw)
  To: David Miller, netdev
  Cc: Jakub Kicinski, Jiri Pirko, Andrew Lunn, Florian Fainelli,
	John Linville, Stephen Hemminger, Johannes Berg, linux-kernel

Introduce file net/ethtool/common.c for code shared by ioctl and netlink
ethtool interface. Move name tables of features, RSS hash functions,
tunables and PHY tunables into this file.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 net/ethtool/Makefile |  2 +-
 net/ethtool/common.c | 85 ++++++++++++++++++++++++++++++++++++++++++++
 net/ethtool/common.h | 17 +++++++++
 net/ethtool/ioctl.c  | 84 ++-----------------------------------------
 4 files changed, 105 insertions(+), 83 deletions(-)
 create mode 100644 net/ethtool/common.c
 create mode 100644 net/ethtool/common.h

diff --git a/net/ethtool/Makefile b/net/ethtool/Makefile
index 482fdb9380fa..11782306593b 100644
--- a/net/ethtool/Makefile
+++ b/net/ethtool/Makefile
@@ -1,6 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0
 
-obj-y				+= ioctl.o
+obj-y				+= ioctl.o common.o
 
 obj-$(CONFIG_ETHTOOL_NETLINK)	+= ethtool_nl.o
 
diff --git a/net/ethtool/common.c b/net/ethtool/common.c
new file mode 100644
index 000000000000..220d6b539180
--- /dev/null
+++ b/net/ethtool/common.c
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
+
+#include "common.h"
+
+const char netdev_features_strings[NETDEV_FEATURE_COUNT][ETH_GSTRING_LEN] = {
+	[NETIF_F_SG_BIT] =               "tx-scatter-gather",
+	[NETIF_F_IP_CSUM_BIT] =          "tx-checksum-ipv4",
+	[NETIF_F_HW_CSUM_BIT] =          "tx-checksum-ip-generic",
+	[NETIF_F_IPV6_CSUM_BIT] =        "tx-checksum-ipv6",
+	[NETIF_F_HIGHDMA_BIT] =          "highdma",
+	[NETIF_F_FRAGLIST_BIT] =         "tx-scatter-gather-fraglist",
+	[NETIF_F_HW_VLAN_CTAG_TX_BIT] =  "tx-vlan-hw-insert",
+
+	[NETIF_F_HW_VLAN_CTAG_RX_BIT] =  "rx-vlan-hw-parse",
+	[NETIF_F_HW_VLAN_CTAG_FILTER_BIT] = "rx-vlan-filter",
+	[NETIF_F_HW_VLAN_STAG_TX_BIT] =  "tx-vlan-stag-hw-insert",
+	[NETIF_F_HW_VLAN_STAG_RX_BIT] =  "rx-vlan-stag-hw-parse",
+	[NETIF_F_HW_VLAN_STAG_FILTER_BIT] = "rx-vlan-stag-filter",
+	[NETIF_F_VLAN_CHALLENGED_BIT] =  "vlan-challenged",
+	[NETIF_F_GSO_BIT] =              "tx-generic-segmentation",
+	[NETIF_F_LLTX_BIT] =             "tx-lockless",
+	[NETIF_F_NETNS_LOCAL_BIT] =      "netns-local",
+	[NETIF_F_GRO_BIT] =              "rx-gro",
+	[NETIF_F_GRO_HW_BIT] =           "rx-gro-hw",
+	[NETIF_F_LRO_BIT] =              "rx-lro",
+
+	[NETIF_F_TSO_BIT] =              "tx-tcp-segmentation",
+	[NETIF_F_GSO_ROBUST_BIT] =       "tx-gso-robust",
+	[NETIF_F_TSO_ECN_BIT] =          "tx-tcp-ecn-segmentation",
+	[NETIF_F_TSO_MANGLEID_BIT] =	 "tx-tcp-mangleid-segmentation",
+	[NETIF_F_TSO6_BIT] =             "tx-tcp6-segmentation",
+	[NETIF_F_FSO_BIT] =              "tx-fcoe-segmentation",
+	[NETIF_F_GSO_GRE_BIT] =		 "tx-gre-segmentation",
+	[NETIF_F_GSO_GRE_CSUM_BIT] =	 "tx-gre-csum-segmentation",
+	[NETIF_F_GSO_IPXIP4_BIT] =	 "tx-ipxip4-segmentation",
+	[NETIF_F_GSO_IPXIP6_BIT] =	 "tx-ipxip6-segmentation",
+	[NETIF_F_GSO_UDP_TUNNEL_BIT] =	 "tx-udp_tnl-segmentation",
+	[NETIF_F_GSO_UDP_TUNNEL_CSUM_BIT] = "tx-udp_tnl-csum-segmentation",
+	[NETIF_F_GSO_PARTIAL_BIT] =	 "tx-gso-partial",
+	[NETIF_F_GSO_SCTP_BIT] =	 "tx-sctp-segmentation",
+	[NETIF_F_GSO_ESP_BIT] =		 "tx-esp-segmentation",
+	[NETIF_F_GSO_UDP_L4_BIT] =	 "tx-udp-segmentation",
+
+	[NETIF_F_FCOE_CRC_BIT] =         "tx-checksum-fcoe-crc",
+	[NETIF_F_SCTP_CRC_BIT] =        "tx-checksum-sctp",
+	[NETIF_F_FCOE_MTU_BIT] =         "fcoe-mtu",
+	[NETIF_F_NTUPLE_BIT] =           "rx-ntuple-filter",
+	[NETIF_F_RXHASH_BIT] =           "rx-hashing",
+	[NETIF_F_RXCSUM_BIT] =           "rx-checksum",
+	[NETIF_F_NOCACHE_COPY_BIT] =     "tx-nocache-copy",
+	[NETIF_F_LOOPBACK_BIT] =         "loopback",
+	[NETIF_F_RXFCS_BIT] =            "rx-fcs",
+	[NETIF_F_RXALL_BIT] =            "rx-all",
+	[NETIF_F_HW_L2FW_DOFFLOAD_BIT] = "l2-fwd-offload",
+	[NETIF_F_HW_TC_BIT] =		 "hw-tc-offload",
+	[NETIF_F_HW_ESP_BIT] =		 "esp-hw-offload",
+	[NETIF_F_HW_ESP_TX_CSUM_BIT] =	 "esp-tx-csum-hw-offload",
+	[NETIF_F_RX_UDP_TUNNEL_PORT_BIT] =	 "rx-udp_tunnel-port-offload",
+	[NETIF_F_HW_TLS_RECORD_BIT] =	"tls-hw-record",
+	[NETIF_F_HW_TLS_TX_BIT] =	 "tls-hw-tx-offload",
+	[NETIF_F_HW_TLS_RX_BIT] =	 "tls-hw-rx-offload",
+};
+
+const char
+rss_hash_func_strings[ETH_RSS_HASH_FUNCS_COUNT][ETH_GSTRING_LEN] = {
+	[ETH_RSS_HASH_TOP_BIT] =	"toeplitz",
+	[ETH_RSS_HASH_XOR_BIT] =	"xor",
+	[ETH_RSS_HASH_CRC32_BIT] =	"crc32",
+};
+
+const char
+tunable_strings[__ETHTOOL_TUNABLE_COUNT][ETH_GSTRING_LEN] = {
+	[ETHTOOL_ID_UNSPEC]     = "Unspec",
+	[ETHTOOL_RX_COPYBREAK]	= "rx-copybreak",
+	[ETHTOOL_TX_COPYBREAK]	= "tx-copybreak",
+	[ETHTOOL_PFC_PREVENTION_TOUT] = "pfc-prevention-tout",
+};
+
+const char
+phy_tunable_strings[__ETHTOOL_PHY_TUNABLE_COUNT][ETH_GSTRING_LEN] = {
+	[ETHTOOL_ID_UNSPEC]     = "Unspec",
+	[ETHTOOL_PHY_DOWNSHIFT]	= "phy-downshift",
+	[ETHTOOL_PHY_FAST_LINK_DOWN] = "phy-fast-link-down",
+	[ETHTOOL_PHY_EDPD]	= "phy-energy-detect-power-down",
+};
diff --git a/net/ethtool/common.h b/net/ethtool/common.h
new file mode 100644
index 000000000000..41b2efc1e4e1
--- /dev/null
+++ b/net/ethtool/common.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#ifndef _ETHTOOL_COMMON_H
+#define _ETHTOOL_COMMON_H
+
+#include <linux/ethtool.h>
+
+extern const char
+netdev_features_strings[NETDEV_FEATURE_COUNT][ETH_GSTRING_LEN];
+extern const char
+rss_hash_func_strings[ETH_RSS_HASH_FUNCS_COUNT][ETH_GSTRING_LEN];
+extern const char
+tunable_strings[__ETHTOOL_TUNABLE_COUNT][ETH_GSTRING_LEN];
+extern const char
+phy_tunable_strings[__ETHTOOL_PHY_TUNABLE_COUNT][ETH_GSTRING_LEN];
+
+#endif /* _ETHTOOL_COMMON_H */
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index c763106c73fc..94307fbba96b 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -27,6 +27,8 @@
 #include <net/xdp_sock.h>
 #include <net/flow_offload.h>
 
+#include "common.h"
+
 /*
  * Some useful ethtool_ops methods that're device independent.
  * If we find that all drivers want to do the same thing here,
@@ -54,88 +56,6 @@ EXPORT_SYMBOL(ethtool_op_get_ts_info);
 
 #define ETHTOOL_DEV_FEATURE_WORDS	((NETDEV_FEATURE_COUNT + 31) / 32)
 
-static const char netdev_features_strings[NETDEV_FEATURE_COUNT][ETH_GSTRING_LEN] = {
-	[NETIF_F_SG_BIT] =               "tx-scatter-gather",
-	[NETIF_F_IP_CSUM_BIT] =          "tx-checksum-ipv4",
-	[NETIF_F_HW_CSUM_BIT] =          "tx-checksum-ip-generic",
-	[NETIF_F_IPV6_CSUM_BIT] =        "tx-checksum-ipv6",
-	[NETIF_F_HIGHDMA_BIT] =          "highdma",
-	[NETIF_F_FRAGLIST_BIT] =         "tx-scatter-gather-fraglist",
-	[NETIF_F_HW_VLAN_CTAG_TX_BIT] =  "tx-vlan-hw-insert",
-
-	[NETIF_F_HW_VLAN_CTAG_RX_BIT] =  "rx-vlan-hw-parse",
-	[NETIF_F_HW_VLAN_CTAG_FILTER_BIT] = "rx-vlan-filter",
-	[NETIF_F_HW_VLAN_STAG_TX_BIT] =  "tx-vlan-stag-hw-insert",
-	[NETIF_F_HW_VLAN_STAG_RX_BIT] =  "rx-vlan-stag-hw-parse",
-	[NETIF_F_HW_VLAN_STAG_FILTER_BIT] = "rx-vlan-stag-filter",
-	[NETIF_F_VLAN_CHALLENGED_BIT] =  "vlan-challenged",
-	[NETIF_F_GSO_BIT] =              "tx-generic-segmentation",
-	[NETIF_F_LLTX_BIT] =             "tx-lockless",
-	[NETIF_F_NETNS_LOCAL_BIT] =      "netns-local",
-	[NETIF_F_GRO_BIT] =              "rx-gro",
-	[NETIF_F_GRO_HW_BIT] =           "rx-gro-hw",
-	[NETIF_F_LRO_BIT] =              "rx-lro",
-
-	[NETIF_F_TSO_BIT] =              "tx-tcp-segmentation",
-	[NETIF_F_GSO_ROBUST_BIT] =       "tx-gso-robust",
-	[NETIF_F_TSO_ECN_BIT] =          "tx-tcp-ecn-segmentation",
-	[NETIF_F_TSO_MANGLEID_BIT] =	 "tx-tcp-mangleid-segmentation",
-	[NETIF_F_TSO6_BIT] =             "tx-tcp6-segmentation",
-	[NETIF_F_FSO_BIT] =              "tx-fcoe-segmentation",
-	[NETIF_F_GSO_GRE_BIT] =		 "tx-gre-segmentation",
-	[NETIF_F_GSO_GRE_CSUM_BIT] =	 "tx-gre-csum-segmentation",
-	[NETIF_F_GSO_IPXIP4_BIT] =	 "tx-ipxip4-segmentation",
-	[NETIF_F_GSO_IPXIP6_BIT] =	 "tx-ipxip6-segmentation",
-	[NETIF_F_GSO_UDP_TUNNEL_BIT] =	 "tx-udp_tnl-segmentation",
-	[NETIF_F_GSO_UDP_TUNNEL_CSUM_BIT] = "tx-udp_tnl-csum-segmentation",
-	[NETIF_F_GSO_PARTIAL_BIT] =	 "tx-gso-partial",
-	[NETIF_F_GSO_SCTP_BIT] =	 "tx-sctp-segmentation",
-	[NETIF_F_GSO_ESP_BIT] =		 "tx-esp-segmentation",
-	[NETIF_F_GSO_UDP_L4_BIT] =	 "tx-udp-segmentation",
-
-	[NETIF_F_FCOE_CRC_BIT] =         "tx-checksum-fcoe-crc",
-	[NETIF_F_SCTP_CRC_BIT] =        "tx-checksum-sctp",
-	[NETIF_F_FCOE_MTU_BIT] =         "fcoe-mtu",
-	[NETIF_F_NTUPLE_BIT] =           "rx-ntuple-filter",
-	[NETIF_F_RXHASH_BIT] =           "rx-hashing",
-	[NETIF_F_RXCSUM_BIT] =           "rx-checksum",
-	[NETIF_F_NOCACHE_COPY_BIT] =     "tx-nocache-copy",
-	[NETIF_F_LOOPBACK_BIT] =         "loopback",
-	[NETIF_F_RXFCS_BIT] =            "rx-fcs",
-	[NETIF_F_RXALL_BIT] =            "rx-all",
-	[NETIF_F_HW_L2FW_DOFFLOAD_BIT] = "l2-fwd-offload",
-	[NETIF_F_HW_TC_BIT] =		 "hw-tc-offload",
-	[NETIF_F_HW_ESP_BIT] =		 "esp-hw-offload",
-	[NETIF_F_HW_ESP_TX_CSUM_BIT] =	 "esp-tx-csum-hw-offload",
-	[NETIF_F_RX_UDP_TUNNEL_PORT_BIT] =	 "rx-udp_tunnel-port-offload",
-	[NETIF_F_HW_TLS_RECORD_BIT] =	"tls-hw-record",
-	[NETIF_F_HW_TLS_TX_BIT] =	 "tls-hw-tx-offload",
-	[NETIF_F_HW_TLS_RX_BIT] =	 "tls-hw-rx-offload",
-};
-
-static const char
-rss_hash_func_strings[ETH_RSS_HASH_FUNCS_COUNT][ETH_GSTRING_LEN] = {
-	[ETH_RSS_HASH_TOP_BIT] =	"toeplitz",
-	[ETH_RSS_HASH_XOR_BIT] =	"xor",
-	[ETH_RSS_HASH_CRC32_BIT] =	"crc32",
-};
-
-static const char
-tunable_strings[__ETHTOOL_TUNABLE_COUNT][ETH_GSTRING_LEN] = {
-	[ETHTOOL_ID_UNSPEC]     = "Unspec",
-	[ETHTOOL_RX_COPYBREAK]	= "rx-copybreak",
-	[ETHTOOL_TX_COPYBREAK]	= "tx-copybreak",
-	[ETHTOOL_PFC_PREVENTION_TOUT] = "pfc-prevention-tout",
-};
-
-static const char
-phy_tunable_strings[__ETHTOOL_PHY_TUNABLE_COUNT][ETH_GSTRING_LEN] = {
-	[ETHTOOL_ID_UNSPEC]     = "Unspec",
-	[ETHTOOL_PHY_DOWNSHIFT]	= "phy-downshift",
-	[ETHTOOL_PHY_FAST_LINK_DOWN] = "phy-fast-link-down",
-	[ETHTOOL_PHY_EDPD]	= "phy-energy-detect-power-down",
-};
-
 static int ethtool_get_features(struct net_device *dev, void __user *useraddr)
 {
 	struct ethtool_gfeatures cmd = {
-- 
2.23.0


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

* [PATCH net-next v7 09/17] ethtool: generic handlers for GET requests
  2019-10-09 20:59 [PATCH net-next v7 00/17] ethtool netlink interface, part 1 Michal Kubecek
                   ` (7 preceding siblings ...)
  2019-10-09 20:59 ` [PATCH net-next v7 08/17] ethtool: move string arrays into common file Michal Kubecek
@ 2019-10-09 20:59 ` Michal Kubecek
  2019-10-10 13:56   ` Jiri Pirko
  2019-10-10 15:23   ` Jiri Pirko
  2019-10-09 20:59 ` [PATCH net-next v7 10/17] ethtool: provide string sets with STRSET_GET request Michal Kubecek
                   ` (8 subsequent siblings)
  17 siblings, 2 replies; 46+ messages in thread
From: Michal Kubecek @ 2019-10-09 20:59 UTC (permalink / raw)
  To: David Miller, netdev
  Cc: Jakub Kicinski, Jiri Pirko, Andrew Lunn, Florian Fainelli,
	John Linville, Stephen Hemminger, Johannes Berg, linux-kernel

Significant part of GET request processing is common for most request
types but unfortunately it cannot be easily separated from type specific
code as we need to alternate between common actions (parsing common request
header, allocating message and filling netlink/genetlink headers etc.) and
specific actions (querying the device, composing the reply). The processing
also happens in three different situations: "do" request, "dump" request
and notification, each doing things in slightly different way.

The request specific code is implemented in four or five callbacks defined
in an instance of struct get_request_ops:

  parse_request() - parse incoming message
  prepare_data()  - retrieve data from driver or NIC
  reply_size()    - estimate reply message size
  fill_reply()    - compose reply message
  cleanup_data()  - (optional) clean up additional data

Other members of struct get_request_ops describe the data structure holding
information from client request and data used to compose the message. The
standard handlers ethnl_get_doit(), ethnl_get_dumpit(), ethnl_get_start()
and ethnl_get_done() can be then used in genl_ops handler. Notification
handler will be introduced in a later patch.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 net/ethtool/netlink.c | 329 ++++++++++++++++++++++++++++++++++++++++++
 net/ethtool/netlink.h | 120 ++++++++++++++-
 2 files changed, 448 insertions(+), 1 deletion(-)

diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index ed9c0f82aca4..e5aade3b69d1 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -177,6 +177,335 @@ struct sk_buff *ethnl_reply_init(size_t payload, struct net_device *dev, u8 cmd,
 	return NULL;
 }
 
+/* GET request helpers */
+
+/**
+ * struct ethnl_dump_ctx - context structure for generic dumpit() callback
+ * @ops:      request ops of currently processed message type
+ * @req_info: parsed request header of processed request
+ * @pos_hash: saved iteration position - hashbucket
+ * @pos_idx:  saved iteration position - index
+ *
+ * These parameters are kept in struct netlink_callback as context preserved
+ * between iterations. They are initialized by ethnl_get_start() and used in
+ * ethnl_get_dumpit() and ethnl_get_done().
+ */
+struct ethnl_dump_ctx {
+	const struct get_request_ops	*ops;
+	struct ethnl_req_info		*req_info;
+	struct ethnl_reply_data		*reply_data;
+	int				pos_hash;
+	int				pos_idx;
+};
+
+static const struct get_request_ops *get_requests[__ETHTOOL_MSG_USER_CNT] = {
+};
+
+static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)
+{
+	return (struct ethnl_dump_ctx *)cb->ctx;
+}
+
+/**
+ * ethnl_std_parse() - Parse request message
+ * @req_info:    pointer to structure to put data into
+ * @nlhdr:       pointer to request message header
+ * @net:         request netns
+ * @request_ops: struct request_ops for request type
+ * @extack:      netlink extack for error reporting
+ * @require_dev: fail if no device identified in header
+ *
+ * Parse universal request header and call request specific ->parse_request()
+ * callback (if defined) to parse the rest of the message.
+ *
+ * Return: 0 on success or negative error code
+ */
+static int ethnl_std_parse(struct ethnl_req_info *req_info,
+			   const struct nlmsghdr *nlhdr, struct net *net,
+			   const struct get_request_ops *request_ops,
+			   struct netlink_ext_ack *extack, bool require_dev)
+{
+	struct nlattr **tb;
+	int ret;
+
+	tb = kmalloc_array(request_ops->max_attr + 1, sizeof(tb[0]),
+			   GFP_KERNEL);
+	if (!tb)
+		return -ENOMEM;
+
+	ret = nlmsg_parse(nlhdr, GENL_HDRLEN, tb, request_ops->max_attr,
+			  request_ops->request_policy, extack);
+	if (ret < 0)
+		goto out;
+	ret = ethnl_parse_header(req_info, tb[request_ops->hdr_attr], net,
+				 extack, request_ops->header_policy,
+				 require_dev);
+	if (ret < 0)
+		goto out;
+
+	if (request_ops->parse_request) {
+		ret = request_ops->parse_request(req_info, tb, extack);
+		if (ret < 0)
+			goto out;
+	}
+
+	if (req_info->req_flags & ~request_ops->all_reqflags) {
+		ret = -EOPNOTSUPP;
+		NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_HEADER_RFLAGS],
+				    "unsupported request specific flags");
+		goto out;
+	}
+
+	ret = 0;
+out:
+	kfree(tb);
+	return ret;
+}
+
+/**
+ * ethnl_init_reply_data() - Initialize reply data for GET request
+ * @req_info: pointer to embedded struct ethnl_req_info
+ * @ops:      instance of struct get_request_ops describing the layout
+ * @dev:      network device to initialize the reply for
+ *
+ * Fills the reply data part with zeros and sets the dev member. Must be called
+ * before calling the ->fill_reply() callback (for each iteration when handling
+ * dump requests).
+ */
+static void ethnl_init_reply_data(struct ethnl_reply_data *reply_data,
+				  const struct get_request_ops *ops,
+				  struct net_device *dev)
+{
+	memset(reply_data, '\0', ops->reply_data_size);
+	reply_data->dev = dev;
+}
+
+/* generic ->doit() handler for GET type requests */
+static int ethnl_get_doit(struct sk_buff *skb, struct genl_info *info)
+{
+	struct ethnl_reply_data *reply_data = NULL;
+	struct ethnl_req_info *req_info = NULL;
+	const u8 cmd = info->genlhdr->cmd;
+	const struct get_request_ops *ops;
+	struct sk_buff *rskb;
+	void *reply_payload;
+	int reply_len;
+	int ret;
+
+	ops = get_requests[cmd];
+	if (WARN_ONCE(!ops, "cmd %u has no get_request_ops\n", cmd))
+		return -EOPNOTSUPP;
+	req_info = kzalloc(ops->req_info_size, GFP_KERNEL);
+	if (!req_info)
+		return -ENOMEM;
+	reply_data = kmalloc(ops->reply_data_size, GFP_KERNEL);
+	if (!reply_data) {
+		kfree(req_info);
+		return -ENOMEM;
+	}
+
+	ret = ethnl_std_parse(req_info, info->nlhdr, genl_info_net(info), ops,
+			      info->extack, !ops->allow_nodev_do);
+	if (ret < 0)
+		goto err_dev;
+	ethnl_init_reply_data(reply_data, ops, req_info->dev);
+
+	rtnl_lock();
+	ret = ops->prepare_data(req_info, reply_data, info);
+	rtnl_unlock();
+	if (ret < 0)
+		goto err_cleanup;
+	reply_len = ops->reply_size(req_info, reply_data);
+	if (ret < 0)
+		goto err_cleanup;
+	ret = -ENOMEM;
+	rskb = ethnl_reply_init(reply_len, req_info->dev, ops->reply_cmd,
+				ops->hdr_attr, info, &reply_payload);
+	if (!rskb)
+		goto err_cleanup;
+	ret = ops->fill_reply(rskb, req_info, reply_data);
+	if (ret < 0)
+		goto err_msg;
+	if (ops->cleanup_data)
+		ops->cleanup_data(reply_data);
+
+	genlmsg_end(rskb, reply_payload);
+	if (req_info->dev)
+		dev_put(req_info->dev);
+	kfree(reply_data);
+	kfree(req_info);
+	return genlmsg_reply(rskb, info);
+
+err_msg:
+	WARN_ONCE(ret == -EMSGSIZE,
+		  "calculated message payload length (%d) not sufficient\n",
+		  reply_len);
+	nlmsg_free(rskb);
+err_cleanup:
+	if (ops->cleanup_data)
+		ops->cleanup_data(reply_data);
+err_dev:
+	if (req_info->dev)
+		dev_put(req_info->dev);
+	kfree(reply_data);
+	kfree(req_info);
+	return ret;
+}
+
+static int ethnl_get_dump_one(struct sk_buff *skb, struct net_device *dev,
+			      const struct ethnl_dump_ctx *ctx)
+{
+	int ret;
+
+	ethnl_init_reply_data(ctx->reply_data, ctx->ops, dev);
+	rtnl_lock();
+	ret = ctx->ops->prepare_data(ctx->req_info, ctx->reply_data, NULL);
+	rtnl_unlock();
+	if (ret < 0)
+		goto out;
+	ret = ethnl_fill_reply_header(skb, dev, ctx->ops->hdr_attr);
+	if (ret < 0)
+		goto out;
+	ret = ctx->ops->fill_reply(skb, ctx->req_info, ctx->reply_data);
+
+out:
+	if (ctx->ops->cleanup_data)
+		ctx->ops->cleanup_data(ctx->reply_data);
+	ctx->reply_data->dev = NULL;
+	return ret;
+}
+
+/* Generic ->dumpit() handler for GET requests. Device iteration copied from
+ * rtnl_dump_ifinfo(); we have to be more careful about device hashtable
+ * persistence as we cannot guarantee to hold RTNL lock through the whole
+ * function as rtnetnlink does.
+ */
+static int ethnl_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
+{
+	struct ethnl_dump_ctx *ctx = ethnl_dump_context(cb);
+	struct net *net = sock_net(skb->sk);
+	int s_idx = ctx->pos_idx;
+	int h, idx = 0;
+	int ret = 0;
+	void *ehdr;
+
+	rtnl_lock();
+	for (h = ctx->pos_hash; h < NETDEV_HASHENTRIES; h++, s_idx = 0) {
+		struct hlist_head *head;
+		struct net_device *dev;
+		unsigned int seq;
+
+		head = &net->dev_index_head[h];
+
+restart_chain:
+		seq = net->dev_base_seq;
+		cb->seq = seq;
+		idx = 0;
+		hlist_for_each_entry(dev, head, index_hlist) {
+			if (idx < s_idx)
+				goto cont;
+			dev_hold(dev);
+			rtnl_unlock();
+
+			ehdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid,
+					   cb->nlh->nlmsg_seq,
+					   &ethtool_genl_family, 0,
+					   ctx->ops->reply_cmd);
+			if (!ehdr) {
+				dev_put(dev);
+				ret = -EMSGSIZE;
+				goto out;
+			}
+			ret = ethnl_get_dump_one(skb, dev, ctx);
+			dev_put(dev);
+			if (ret < 0) {
+				genlmsg_cancel(skb, ehdr);
+				if (ret == -EOPNOTSUPP)
+					goto lock_and_cont;
+				if (likely(skb->len))
+					ret = skb->len;
+				goto out;
+			}
+			genlmsg_end(skb, ehdr);
+lock_and_cont:
+			rtnl_lock();
+			if (net->dev_base_seq != seq) {
+				s_idx = idx + 1;
+				goto restart_chain;
+			}
+cont:
+			idx++;
+		}
+
+	}
+	rtnl_unlock();
+
+out:
+	ctx->pos_hash = h;
+	ctx->pos_idx = idx;
+	nl_dump_check_consistent(cb, nlmsg_hdr(skb));
+
+	return ret;
+}
+
+/* generic ->start() handler for GET requests */
+static int ethnl_get_start(struct netlink_callback *cb)
+{
+	struct ethnl_dump_ctx *ctx = ethnl_dump_context(cb);
+	struct ethnl_reply_data *reply_data;
+	const struct get_request_ops *ops;
+	struct ethnl_req_info *req_info;
+	struct genlmsghdr *ghdr;
+	int ret;
+
+	BUILD_BUG_ON(sizeof(*ctx) > sizeof(cb->ctx));
+
+	ghdr = nlmsg_data(cb->nlh);
+	ops = get_requests[ghdr->cmd];
+	if (WARN_ONCE(!ops, "cmd %u has no get_request_ops\n", ghdr->cmd))
+		return -EOPNOTSUPP;
+	req_info = kzalloc(ops->req_info_size, GFP_KERNEL);
+	if (!req_info)
+		return -ENOMEM;
+	reply_data = kmalloc(ops->reply_data_size, GFP_KERNEL);
+	if (!reply_data) {
+		kfree(req_info);
+		return -ENOMEM;
+	}
+
+	ret = ethnl_std_parse(req_info, cb->nlh, sock_net(cb->skb->sk), ops,
+			      cb->extack, false);
+	if (req_info->dev) {
+		/* We ignore device specification in dump requests but as the
+		 * same parser as for non-dump (doit) requests is used, it
+		 * would take reference to the device if it finds one
+		 */
+		dev_put(req_info->dev);
+		req_info->dev = NULL;
+	}
+	if (ret < 0)
+		return ret;
+
+	ctx->ops = ops;
+	ctx->req_info = req_info;
+	ctx->reply_data = reply_data;
+	ctx->pos_hash = 0;
+	ctx->pos_idx = 0;
+
+	return 0;
+}
+
+/* generic ->done() handler for GET requests */
+static int ethnl_get_done(struct netlink_callback *cb)
+{
+	struct ethnl_dump_ctx *ctx = ethnl_dump_context(cb);
+
+	kfree(ctx->reply_data);
+	kfree(ctx->req_info);
+
+	return 0;
+}
+
 /* notifications */
 
 typedef void (*ethnl_notify_handler_t)(struct net_device *dev, unsigned int cmd,
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 4c0b5ca439f8..077966a33544 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -210,13 +210,30 @@ static inline unsigned int ethnl_reply_header_size(void)
 			      nla_total_size(IFNAMSIZ));
 }
 
+/* GET request handling */
+
+/* Unified processing of GET requests uses two data structures: request info
+ * and reply data. Request info holds information parsed from client request
+ * and its stays constant through all request processing. Reply data holds data
+ * retrieved from ethtool_ops callbacks or other internal sources which is used
+ * to compose the reply. When processing a dump request, request info is filled
+ * only once (when the request message is parsed) but reply data is filled for
+ * each reply message.
+ *
+ * Both structures consist of part common for all request types (struct
+ * ethnl_req_info and struct ethnl_reply_data defined below) and optional
+ * parts specific for each request type. Common part always starts at offset 0.
+ */
+
 /**
  * struct ethnl_req_info - base type of request information for GET requests
  * @dev:          network device the request is for (may be null)
  * @global_flags: request flags common for all request types
  * @req_flags:    request flags specific for each request type
  *
- * This is a common base, additional members may follow after this structure.
+ * This is a common base for request specific structures holding data from
+ * parsed userspace request. These always embed struct ethnl_req_info at
+ * zero offset.
  */
 struct ethnl_req_info {
 	struct net_device		*dev;
@@ -224,4 +241,105 @@ struct ethnl_req_info {
 	u32				req_flags;
 };
 
+/**
+ * struct ethnl_reply_data - base type of reply data for GET requests
+ * @dev:       device for current reply message; in single shot requests it is
+ *             equal to &ethnl_req_info.dev; in dumps it's different for each
+ *             reply message
+ *
+ * This is a common base for request specific structures holding data for
+ * kernel reply message. These always embed struct ethnl_reply_data at zero
+ * offset.
+ */
+struct ethnl_reply_data {
+	struct net_device		*dev;
+};
+
+static inline int ethnl_before_ops(struct net_device *dev)
+{
+	if (dev && dev->ethtool_ops->begin)
+		return dev->ethtool_ops->begin(dev);
+	else
+		return 0;
+}
+
+static inline void ethnl_after_ops(struct net_device *dev)
+{
+	if (dev && dev->ethtool_ops->complete)
+		dev->ethtool_ops->complete(dev);
+}
+
+/**
+ * struct get_request_ops - unified handling of GET requests
+ * @request_cmd:      command id for request (GET)
+ * @reply_cmd:        command id for reply (GET_REPLY)
+ * @hdr_attr:         attribute type for request header
+ * @max_attr:         maximum (top level) attribute type
+ * @req_info_size:    size of request info
+ * @reply_data_size:  size of reply data
+ * @request_policy:   netlink policy for message contents
+ * @header_policy:    (optional) netlink policy for request header
+ * @all_reqflags:     allowed request specific flags
+ * @allow_nodev_do:   allow non-dump request with no device identification
+ * @parse_request:
+ *	Parse request except common header (struct ethnl_req_info). Common
+ *	header is already filled on entry, the rest up to @repdata_offset
+ *	is zero initialized. This callback should only modify type specific
+ *	request info by parsed attributes from request message.
+ * @prepare_data:
+ *	Retrieve and prepare data needed to compose a reply message. Calls to
+ *	ethtool_ops handlers should be limited to this callback. Common reply
+ *	data (struct ethnl_reply_data) is filled on entry, type specific part
+ *	after it is zero initialized. This callback should only modify the
+ *	type specific part of reply data. Device identification from struct
+ *	ethnl_reply_data is to be used as for dump requests, it iterates
+ *	through network devices which common_req_info::dev points to the
+ *	device from client request.
+ * @reply_size:
+ *	Estimate reply message size. Returned value must be sufficient for
+ *	message payload without common reply header. The callback may returned
+ *	estimate higher than actual message size if exact calculation would
+ *	not be worth the saved memory space.
+ * @fill_reply:
+ *	Fill reply message payload (except for common header) from reply data.
+ *	The callback must not generate more payload than previously called
+ *	->reply_size() estimated.
+ * @cleanup_data:
+ *	Optional cleanup called when reply data is no longer needed. Can be
+ *	used e.g. to free any additional data structures outside the main
+ *	structure which were allocated by ->prepare_data(). When processing
+ *	dump requests, ->cleanup() is called for each message.
+ *
+ * Description of variable parts of GET request handling when using the unified
+ * infrastructure. When used, a pointer to an instance of this structure is to
+ * be added to &get_requests array and generic handlers ethnl_get_doit(),
+ * ethnl_get_dumpit(), ethnl_get_start() and ethnl_get_done() used in
+ * @ethnl_genl_ops
+ */
+struct get_request_ops {
+	u8			request_cmd;
+	u8			reply_cmd;
+	u16			hdr_attr;
+	unsigned int		max_attr;
+	unsigned int		req_info_size;
+	unsigned int		reply_data_size;
+	const struct nla_policy *request_policy;
+	const struct nla_policy *header_policy;
+	u32			all_reqflags;
+	bool			allow_nodev_do;
+
+	int (*parse_request)(struct ethnl_req_info *req_info,
+			     struct nlattr **tb,
+			     struct netlink_ext_ack *extack);
+	int (*prepare_data)(const struct ethnl_req_info *req_info,
+			    struct ethnl_reply_data *reply_data,
+			    struct genl_info *info);
+	int (*reply_size)(const struct ethnl_req_info *req_info,
+			  const struct ethnl_reply_data *reply_data);
+	int (*fill_reply)(struct sk_buff *skb,
+			  const struct ethnl_req_info *req_info,
+			  const struct ethnl_reply_data *reply_data);
+	void (*cleanup_data)(struct ethnl_reply_data *reply_data);
+};
+
 #endif /* _NET_ETHTOOL_NETLINK_H */
-- 
2.23.0


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

* [PATCH net-next v7 10/17] ethtool: provide string sets with STRSET_GET request
  2019-10-09 20:59 [PATCH net-next v7 00/17] ethtool netlink interface, part 1 Michal Kubecek
                   ` (8 preceding siblings ...)
  2019-10-09 20:59 ` [PATCH net-next v7 09/17] ethtool: generic handlers for GET requests Michal Kubecek
@ 2019-10-09 20:59 ` Michal Kubecek
  2019-10-10 13:59   ` Jiri Pirko
  2019-10-09 20:59 ` [PATCH net-next v7 11/17] ethtool: provide link mode names as a string set Michal Kubecek
                   ` (7 subsequent siblings)
  17 siblings, 1 reply; 46+ messages in thread
From: Michal Kubecek @ 2019-10-09 20:59 UTC (permalink / raw)
  To: David Miller, netdev
  Cc: Jakub Kicinski, Jiri Pirko, Andrew Lunn, Florian Fainelli,
	John Linville, Stephen Hemminger, Johannes Berg, linux-kernel

Requests a contents of one or more string sets, i.e. indexed arrays of
strings; this information is provided by ETHTOOL_GSSET_INFO and
ETHTOOL_GSTRINGS commands of ioctl interface. Unlike ioctl interface, all
information can be retrieved with one request and mulitple string sets can
be requested at once.

There are three types of requests:

  - no NLM_F_DUMP, no device: get "global" stringsets
  - no NLM_F_DUMP, with device: get string sets related to the device
  - NLM_F_DUMP, no device: get device related string sets for all devices

Client can request either all string sets of given type (global or device
related) or only specific sets. With ETHTOOL_A_STRSET_COUNTS flag set, only
set sizes (numbers of strings) are returned.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Documentation/networking/ethtool-netlink.rst |  77 +++-
 include/uapi/linux/ethtool.h                 |   2 +
 include/uapi/linux/ethtool_netlink.h         |  60 +++
 net/ethtool/Makefile                         |   2 +-
 net/ethtool/netlink.c                        |   8 +
 net/ethtool/netlink.h                        |   4 +
 net/ethtool/strset.c                         | 430 +++++++++++++++++++
 7 files changed, 580 insertions(+), 3 deletions(-)
 create mode 100644 net/ethtool/strset.c

diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst
index 8dda6efee060..d12e0e4f277c 100644
--- a/Documentation/networking/ethtool-netlink.rst
+++ b/Documentation/networking/ethtool-netlink.rst
@@ -163,6 +163,18 @@ according to message purpose:
   ``_NTF``          kernel notification
   ==============    ======================================
 
+Userspace to kernel:
+
+  ===================================== ================================
+  ``ETHTOOL_MSG_STRSET_GET``            get string set
+  ===================================== ================================
+
+Kernel to userspace:
+
+  ===================================== ================================
+  ``ETHTOOL_MSG_STRSET_GET_REPLY``      string set contents
+  ===================================== ================================
+
 ``GET`` requests are sent by userspace applications to retrieve device
 information. They usually do not contain any message specific attributes.
 Kernel replies with corresponding "GET_REPLY" message. For most types, ``GET``
@@ -194,6 +206,67 @@ an ``ACT_REPLY`` message. Performing an action also triggers a notification
 Later sections describe the format and semantics of these messages.
 
 
+STRSET_GET
+==========
+
+Requests contents of a string set as provided by ioctl commands
+``ETHTOOL_GSSET_INFO`` and ``ETHTOOL_GSTRINGS.`` String sets are not user
+writeable so that the corresponding ``STRSET_SET`` message is only used in
+kernel replies. There are two types of string sets: global (independent of
+a device, e.g. device feature names) and device specific (e.g. device private
+flags).
+
+Request contents:
+
+ +---------------------------------------+--------+------------------------+
+ | ``ETHTOOL_A_STRSET_HEADER``           | nested | request header         |
+ +---------------------------------------+--------+------------------------+
+ | ``ETHTOOL_A_STRSET_STRINGSETS``       | nested | string set to request  |
+ +-+-------------------------------------+--------+------------------------+
+ | | ``ETHTOOL_A_STRINGSETS_STRINGSET+`` | nested | one string set         |
+ +-+-+-----------------------------------+--------+------------------------+
+ | | | ``ETHTOOL_A_STRINGSET_ID``        | u32    | set id                 |
+ +-+-+-----------------------------------+--------+------------------------+
+
+Request specific flag:
+
+    ETHTOOL_RFLAG_STRSET_COUNTS_ONLY	send only string counts in reply
+
+Kernel response contents:
+
+ +---------------------------------------+--------+-----------------------+
+ | ``ETHTOOL_A_STRSET_HEADER``           | nested | reply header          |
+ +---------------------------------------+--------+-----------------------+
+ | ``ETHTOOL_A_STRSET_STRINGSETS``       | nested | array of string sets  |
+ +-+-------------------------------------+--------+-----------------------+
+ | | ``ETHTOOL_A_STRINGSETS_STRINGSET+`` | nested | one string set        |
+ +-+-+-----------------------------------+--------+-----------------------+
+ | | | ``ETHTOOL_A_STRINGSET_ID``        | u32    | set id                |
+ +-+-+-----------------------------------+--------+-----------------------+
+ | | | ``ETHTOOL_A_STRINGSET_COUNT``     | u32    | number of strings     |
+ +-+-+-----------------------------------+--------+-----------------------+
+ | | | ``ETHTOOL_A_STRINGSET_STRINGS``   | nested | array of strings      |
+ +-+-+-+---------------------------------+--------+-----------------------+
+ | | | | ``ETHTOOL_A_STRINGS_STRING+``   | nested | one string            |
+ +-+-+-+-+-------------------------------+--------+-----------------------+
+ | | | | | ``ETHTOOL_A_STRING_INDEX``    | u32    | string index          |
+ +-+-+-+-+-------------------------------+--------+-----------------------+
+ | | | | | ``ETHTOOL_A_STRING_VALUE``    | string | string value          |
+ +-+-+-+-+-------------------------------+--------+-----------------------+
+
+Device identification in request header is optional. Depending on its presence
+a and ``NLM_F_DUMP`` flag, there are three type of ``STRSET_GET`` requests:
+
+ - no ``NLM_F_DUMP,`` no device: get "global" stringsets
+ - no ``NLM_F_DUMP``, with device: get string sets related to the device
+ - ``NLM_F_DUMP``, no device: get device related string sets for all devices
+
+If there is no ``ETHTOOL_A_STRSET_STRINGSETS`` array, all string sets of
+requested type are returned, otherwise only those specified in the request.
+Flag ``ETHTOOL_A_STRSET_COUNTS`` tells kernel to only return string counts of
+the sets, not the actual strings.
+
+
 Request translation
 ===================
 
@@ -229,7 +302,7 @@ have their netlink replacement yet.
   ``ETHTOOL_GSG``                     n/a
   ``ETHTOOL_SSG``                     n/a
   ``ETHTOOL_TEST``                    n/a
-  ``ETHTOOL_GSTRINGS``                n/a
+  ``ETHTOOL_GSTRINGS``                ``ETHTOOL_MSG_STRSET_GET``
   ``ETHTOOL_PHYS_ID``                 n/a
   ``ETHTOOL_GSTATS``                  n/a
   ``ETHTOOL_GTSO``                    n/a
@@ -257,7 +330,7 @@ have their netlink replacement yet.
   ``ETHTOOL_RESET``                   n/a
   ``ETHTOOL_SRXNTUPLE``               n/a
   ``ETHTOOL_GRXNTUPLE``               n/a
-  ``ETHTOOL_GSSET_INFO``              n/a
+  ``ETHTOOL_GSSET_INFO``              ``ETHTOOL_MSG_STRSET_GET``
   ``ETHTOOL_GRXFHINDIR``              n/a
   ``ETHTOOL_SRXFHINDIR``              n/a
   ``ETHTOOL_GFEATURES``               n/a
diff --git a/include/uapi/linux/ethtool.h b/include/uapi/linux/ethtool.h
index 8938b76c4ee3..11ac843aa07e 100644
--- a/include/uapi/linux/ethtool.h
+++ b/include/uapi/linux/ethtool.h
@@ -604,6 +604,8 @@ enum ethtool_stringset {
 	ETH_SS_TUNABLES,
 	ETH_SS_PHY_STATS,
 	ETH_SS_PHY_TUNABLES,
+
+	ETH_SS_COUNT
 };
 
 /**
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index c3d2e950e728..751d725866df 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -14,6 +14,7 @@
 /* message types - userspace to kernel */
 enum {
 	ETHTOOL_MSG_USER_NONE,
+	ETHTOOL_MSG_STRSET_GET,
 
 	/* add new constants above here */
 	__ETHTOOL_MSG_USER_CNT,
@@ -23,6 +24,7 @@ enum {
 /* message types - kernel to userspace */
 enum {
 	ETHTOOL_MSG_KERNEL_NONE,
+	ETHTOOL_MSG_STRSET_GET_REPLY,
 
 	/* add new constants above here */
 	__ETHTOOL_MSG_KERNEL_CNT,
@@ -86,6 +88,64 @@ enum {
 	ETHTOOL_A_BITSET_MAX = __ETHTOOL_A_BITSET_CNT - 1
 };
 
+/* string sets */
+
+enum {
+	ETHTOOL_A_STRING_UNSPEC,
+	ETHTOOL_A_STRING_INDEX,			/* u32 */
+	ETHTOOL_A_STRING_VALUE,			/* string */
+
+	/* add new constants above here */
+	__ETHTOOL_A_STRING_CNT,
+	ETHTOOL_A_STRING_MAX = __ETHTOOL_A_STRING_CNT - 1
+};
+
+enum {
+	ETHTOOL_A_STRINGS_UNSPEC,
+	ETHTOOL_A_STRINGS_STRING,		/* nest - _A_STRINGS_* */
+
+	/* add new constants above here */
+	__ETHTOOL_A_STRINGS_CNT,
+	ETHTOOL_A_STRINGS_MAX = __ETHTOOL_A_STRINGS_CNT - 1
+};
+
+enum {
+	ETHTOOL_A_STRINGSET_UNSPEC,
+	ETHTOOL_A_STRINGSET_ID,			/* u32 */
+	ETHTOOL_A_STRINGSET_COUNT,		/* u32 */
+	ETHTOOL_A_STRINGSET_STRINGS,		/* nest - _A_STRINGS_* */
+
+	/* add new constants above here */
+	__ETHTOOL_A_STRINGSET_CNT,
+	ETHTOOL_A_STRINGSET_MAX = __ETHTOOL_A_STRINGSET_CNT - 1
+};
+
+enum {
+	ETHTOOL_A_STRINGSETS_UNSPEC,
+	ETHTOOL_A_STRINGSETS_STRINGSET,		/* nest - _A_STRINGSET_* */
+
+	/* add new constants above here */
+	__ETHTOOL_A_STRINGSETS_CNT,
+	ETHTOOL_A_STRINGSETS_MAX = __ETHTOOL_A_STRINGSETS_CNT - 1
+};
+
+/* STRSET */
+
+enum {
+	ETHTOOL_A_STRSET_UNSPEC,
+	ETHTOOL_A_STRSET_HEADER,		/* nest - _A_HEADER_* */
+	ETHTOOL_A_STRSET_STRINGSETS,		/* nest - _A_STRINGSETS_* */
+
+	/* add new constants above here */
+	__ETHTOOL_A_STRSET_CNT,
+	ETHTOOL_A_STRSET_MAX = __ETHTOOL_A_STRSET_CNT - 1
+};
+
+/* return only string counts, not the strings */
+#define ETHTOOL_RFLAG_STRSET_COUNTS_ONLY	(1 << 0)
+
+#define ETHTOOL_RFLAG_STRSET_ALL (ETHTOOL_RFLAG_STRSET_COUNTS_ONLY)
+
 /* generic netlink info */
 #define ETHTOOL_GENL_NAME "ethtool"
 #define ETHTOOL_GENL_VERSION 1
diff --git a/net/ethtool/Makefile b/net/ethtool/Makefile
index 11782306593b..11ceb00821b3 100644
--- a/net/ethtool/Makefile
+++ b/net/ethtool/Makefile
@@ -4,4 +4,4 @@ obj-y				+= ioctl.o common.o
 
 obj-$(CONFIG_ETHTOOL_NETLINK)	+= ethtool_nl.o
 
-ethtool_nl-y	:= netlink.o bitset.o
+ethtool_nl-y	:= netlink.o bitset.o strset.o
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index e5aade3b69d1..df53999ddb12 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -199,6 +199,7 @@ struct ethnl_dump_ctx {
 };
 
 static const struct get_request_ops *get_requests[__ETHTOOL_MSG_USER_CNT] = {
+	[ETHTOOL_MSG_STRSET_GET]	= &strset_request_ops,
 };
 
 static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)
@@ -532,6 +533,13 @@ EXPORT_SYMBOL(ethtool_notify);
 /* genetlink setup */
 
 static const struct genl_ops ethtool_genl_ops[] = {
+	{
+		.cmd	= ETHTOOL_MSG_STRSET_GET,
+		.doit	= ethnl_get_doit,
+		.start	= ethnl_get_start,
+		.dumpit	= ethnl_get_dumpit,
+		.done	= ethnl_get_done,
+	},
 };
 
 static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 077966a33544..1bd9f0e20429 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -342,4 +342,8 @@ struct get_request_ops {
 	void (*cleanup_data)(struct ethnl_reply_data *reply_data);
 };
 
+/* request handlers */
+
+extern const struct get_request_ops strset_request_ops;
+
 #endif /* _NET_ETHTOOL_NETLINK_H */
diff --git a/net/ethtool/strset.c b/net/ethtool/strset.c
new file mode 100644
index 000000000000..11f2161a0964
--- /dev/null
+++ b/net/ethtool/strset.c
@@ -0,0 +1,430 @@
+// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
+
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+#include "netlink.h"
+#include "common.h"
+
+struct strset_info {
+	bool per_dev;
+	bool free_strings;
+	unsigned int count;
+	const char (*strings)[ETH_GSTRING_LEN];
+};
+
+static const struct strset_info info_template[] = {
+	[ETH_SS_TEST] = {
+		.per_dev	= true,
+	},
+	[ETH_SS_STATS] = {
+		.per_dev	= true,
+	},
+	[ETH_SS_PRIV_FLAGS] = {
+		.per_dev	= true,
+	},
+	[ETH_SS_FEATURES] = {
+		.per_dev	= false,
+		.count		= ARRAY_SIZE(netdev_features_strings),
+		.strings	= netdev_features_strings,
+	},
+	[ETH_SS_RSS_HASH_FUNCS] = {
+		.per_dev	= false,
+		.count		= ARRAY_SIZE(rss_hash_func_strings),
+		.strings	= rss_hash_func_strings,
+	},
+	[ETH_SS_TUNABLES] = {
+		.per_dev	= false,
+		.count		= ARRAY_SIZE(tunable_strings),
+		.strings	= tunable_strings,
+	},
+	[ETH_SS_PHY_STATS] = {
+		.per_dev	= true,
+	},
+	[ETH_SS_PHY_TUNABLES] = {
+		.per_dev	= false,
+		.count		= ARRAY_SIZE(phy_tunable_strings),
+		.strings	= phy_tunable_strings,
+	},
+};
+
+struct strset_req_info {
+	struct ethnl_req_info		base;
+	u32				req_ids;
+};
+
+struct strset_reply_data {
+	struct ethnl_reply_data		base;
+	struct strset_info		sets[ETH_SS_COUNT];
+};
+
+static const struct nla_policy strset_get_policy[ETHTOOL_A_STRSET_MAX + 1] = {
+	[ETHTOOL_A_STRSET_UNSPEC]	= { .type = NLA_REJECT },
+	[ETHTOOL_A_STRSET_HEADER]	= { .type = NLA_NESTED },
+	[ETHTOOL_A_STRSET_STRINGSETS]	= { .type = NLA_NESTED },
+};
+
+static const struct nla_policy
+get_stringset_policy[ETHTOOL_A_STRINGSET_MAX + 1] = {
+	[ETHTOOL_A_STRINGSET_UNSPEC]	= { .type = NLA_REJECT },
+	[ETHTOOL_A_STRINGSET_ID]	= { .type = NLA_U32 },
+	[ETHTOOL_A_STRINGSET_COUNT]	= { .type = NLA_REJECT },
+	[ETHTOOL_A_STRINGSET_STRINGS]	= { .type = NLA_REJECT },
+};
+
+/**
+ * strset_include() - test if a string set should be included in reply
+ * @data: pointer to request data structure
+ * @id:   id of string set to check (ETH_SS_* constants)
+ */
+static bool strset_include(const struct strset_req_info *info,
+			   const struct strset_reply_data *data, u32 id)
+{
+	bool per_dev;
+
+	BUILD_BUG_ON(ETH_SS_COUNT >= BITS_PER_BYTE * sizeof(info->req_ids));
+
+	if (info->req_ids)
+		return info->req_ids & (1U << id);
+	per_dev = data->sets[id].per_dev;
+	if (!per_dev && !data->sets[id].strings)
+		return false;
+
+	return data->base.dev ? per_dev : !per_dev;
+}
+
+static int strset_get_id(const struct nlattr *nest, u32 *val,
+			 struct netlink_ext_ack *extack)
+{
+	struct nlattr *tb[ETHTOOL_A_STRINGSET_MAX + 1];
+	int ret;
+
+	ret = nla_parse_nested(tb, ETHTOOL_A_STRINGSET_MAX, nest,
+			       get_stringset_policy, extack);
+	if (ret < 0)
+		return ret;
+	if (!tb[ETHTOOL_A_STRINGSET_ID])
+		return -EINVAL;
+
+	*val = nla_get_u32(tb[ETHTOOL_A_STRINGSET_ID]);
+	return 0;
+}
+
+static const struct nla_policy
+strset_stringsets_policy[ETHTOOL_A_STRINGSETS_MAX + 1] = {
+	[ETHTOOL_A_STRINGSETS_UNSPEC]		= { .type = NLA_REJECT },
+	[ETHTOOL_A_STRINGSETS_STRINGSET]	= { .type = NLA_NESTED },
+};
+
+/* parse_request() handler */
+static int strset_parse(struct ethnl_req_info *req_base, struct nlattr **tb, struct netlink_ext_ack *extack)
+{
+	struct strset_req_info *req_info =
+		container_of(req_base, struct strset_req_info, base);
+	struct nlattr *nest = tb[ETHTOOL_A_STRSET_STRINGSETS];
+	struct nlattr *attr;
+	int rem, ret;
+
+	if (!nest)
+		return 0;
+	ret = nla_validate_nested(nest, ETHTOOL_A_STRINGSETS_MAX,
+				  strset_stringsets_policy, extack);
+	if (ret < 0)
+		return ret;
+
+	nla_for_each_nested(attr, nest, rem) {
+		u32 id;
+
+		if (WARN_ONCE(nla_type(attr) != ETHTOOL_A_STRINGSETS_STRINGSET,
+			      "unexpected attrtype %u in ETHTOOL_A_STRSET_STRINGSETS\n",
+			      nla_type(attr)))
+			return -EINVAL;
+
+		ret = strset_get_id(attr, &id, extack);
+		if (ret < 0)
+			return ret;
+		if (ret >= ETH_SS_COUNT) {
+			NL_SET_ERR_MSG_ATTR(extack, attr,
+					    "unknown string set id");
+			return -EOPNOTSUPP;
+		}
+
+		req_info->req_ids |= (1U << id);
+	}
+
+	return 0;
+}
+
+/* cleanup_data() handler - free allocated data (if any) */
+static void strset_cleanup(struct ethnl_reply_data *reply_base)
+{
+	struct strset_reply_data *data =
+		container_of(reply_base, struct strset_reply_data, base);
+	unsigned int i;
+
+	for (i = 0; i < ETH_SS_COUNT; i++)
+		if (data->sets[i].free_strings) {
+			kfree(data->sets[i].strings);
+			data->sets[i].strings = NULL;
+			data->sets[i].free_strings = false;
+		}
+}
+
+static int strset_prepare_set(struct strset_info *info, struct net_device *dev,
+			      unsigned int id, bool counts_only)
+{
+	const struct ethtool_ops *ops = dev->ethtool_ops;
+	void *strings;
+	int count, ret;
+
+	if (id == ETH_SS_PHY_STATS && dev->phydev &&
+	    !ops->get_ethtool_phy_stats)
+		ret = phy_ethtool_get_sset_count(dev->phydev);
+	else if (ops->get_sset_count && ops->get_strings)
+		ret = ops->get_sset_count(dev, id);
+	else
+		ret = -EOPNOTSUPP;
+	if (ret <= 0) {
+		info->count = 0;
+		return 0;
+	}
+
+	count = ret;
+	if (!counts_only) {
+		strings = kcalloc(count, ETH_GSTRING_LEN, GFP_KERNEL);
+		if (!strings)
+			return -ENOMEM;
+		if (id == ETH_SS_PHY_STATS && dev->phydev &&
+		    !ops->get_ethtool_phy_stats)
+			phy_ethtool_get_strings(dev->phydev, strings);
+		else
+			ops->get_strings(dev, id, strings);
+		info->strings = strings;
+		info->free_strings = true;
+	}
+	info->count = count;
+
+	return 0;
+}
+
+/* prepare_data() handler */
+static int strset_prepare(const struct ethnl_req_info *req_base,
+			  struct ethnl_reply_data *reply_base,
+			  struct genl_info *info)
+{
+	const struct strset_req_info *req_info =
+		container_of(req_base, struct strset_req_info, base);
+	struct strset_reply_data *data =
+		container_of(reply_base, struct strset_reply_data, base);
+	struct net_device *dev = reply_base->dev;
+	bool counts_only;
+	unsigned int i;
+	int ret;
+
+	counts_only = req_base->req_flags & ETHTOOL_RFLAG_STRSET_COUNTS_ONLY;
+	BUILD_BUG_ON(ARRAY_SIZE(info_template) != ETH_SS_COUNT);
+	memcpy(&data->sets, &info_template, sizeof(data->sets));
+
+	if (!dev) {
+		for (i = 0; i < ETH_SS_COUNT; i++) {
+			if ((req_info->req_ids & (1U << i)) &&
+			    data->sets[i].per_dev) {
+				if (info)
+					GENL_SET_ERR_MSG(info, "requested per device strings without dev");
+				return -EINVAL;
+			}
+		}
+	}
+
+	ret = ethnl_before_ops(dev);
+	if (ret < 0)
+		goto err_strset;
+	for (i = 0; i < ETH_SS_COUNT; i++) {
+		if (!strset_include(req_info, data, i) ||
+		    !data->sets[i].per_dev)
+			continue;
+
+		ret = strset_prepare_set(&data->sets[i], dev, i, counts_only);
+		if (ret < 0)
+			goto err_ops;
+	}
+	ethnl_after_ops(dev);
+
+	return 0;
+err_ops:
+	ethnl_after_ops(dev);
+err_strset:
+	strset_cleanup(reply_base);
+	return ret;
+}
+
+/* calculate size of ETHTOOL_A_STRSET_STRINGSET nest for one string set */
+static int strset_set_size(const struct strset_info *info, bool counts_only)
+{
+	unsigned int len = 0;
+	unsigned int i;
+
+	if (info->count == 0)
+		return 0;
+	if (counts_only)
+		return nla_total_size(2 * nla_total_size(sizeof(u32)));
+
+	for (i = 0; i < info->count; i++) {
+		const char *str = info->strings[i];
+
+		/* ETHTOOL_A_STRING_INDEX, ETHTOOL_A_STRING_VALUE, nest */
+		len += nla_total_size(nla_total_size(sizeof(u32)) +
+				      ethnl_strz_size(str));
+	}
+	/* ETHTOOL_A_STRINGSET_ID, ETHTOOL_A_STRINGSET_COUNT */
+	len = 2 * nla_total_size(sizeof(u32)) + nla_total_size(len);
+
+	return nla_total_size(len);
+}
+
+/* reply_size() handler */
+static int strset_size(const struct ethnl_req_info *req_base,
+		       const struct ethnl_reply_data *reply_base)
+{
+	const struct strset_req_info *req_info =
+		container_of(req_base, struct strset_req_info, base);
+	const struct strset_reply_data *data =
+		container_of(reply_base, struct strset_reply_data, base);
+	bool counts_only;
+	unsigned int i;
+	int len = 0;
+	int ret;
+
+	counts_only = req_base->req_flags & ETHTOOL_RFLAG_STRSET_COUNTS_ONLY;
+
+	len += ethnl_reply_header_size();
+	for (i = 0; i < ETH_SS_COUNT; i++) {
+		const struct strset_info *set_info = &data->sets[i];
+
+		if (!strset_include(req_info, data, i))
+			continue;
+
+		ret = strset_set_size(set_info, counts_only);
+		if (ret < 0)
+			return ret;
+		len += ret;
+	}
+
+	return len;
+}
+
+/* fill one string into reply */
+static int strset_fill_string(struct sk_buff *skb,
+			      const struct strset_info *set_info, u32 idx)
+{
+	struct nlattr *string_attr;
+	const char *value;
+
+	value = set_info->strings[idx];
+
+	string_attr = nla_nest_start(skb, ETHTOOL_A_STRINGS_STRING);
+	if (!string_attr)
+		return -EMSGSIZE;
+	if (nla_put_u32(skb, ETHTOOL_A_STRING_INDEX, idx) ||
+	    ethnl_put_strz(skb, ETHTOOL_A_STRING_VALUE, value))
+		goto nla_put_failure;
+	nla_nest_end(skb, string_attr);
+
+	return 0;
+nla_put_failure:
+	nla_nest_cancel(skb, string_attr);
+	return -EMSGSIZE;
+}
+
+/* fill one string set into reply */
+static int strset_fill_set(struct sk_buff *skb,
+			   const struct strset_info *set_info, u32 id,
+			   bool counts_only)
+{
+	struct nlattr *stringset_attr;
+	struct nlattr *strings_attr;
+	unsigned int i;
+
+	if (!set_info->per_dev && !set_info->strings)
+		return -EOPNOTSUPP;
+	if (set_info->count == 0)
+		return 0;
+	stringset_attr = nla_nest_start(skb, ETHTOOL_A_STRINGSETS_STRINGSET);
+	if (!stringset_attr)
+		return -EMSGSIZE;
+
+	if (nla_put_u32(skb, ETHTOOL_A_STRINGSET_ID, id) ||
+	    nla_put_u32(skb, ETHTOOL_A_STRINGSET_COUNT, set_info->count))
+		goto nla_put_failure;
+
+	if (!counts_only) {
+		strings_attr = nla_nest_start(skb, ETHTOOL_A_STRINGSET_STRINGS);
+		if (!strings_attr)
+			goto nla_put_failure;
+		for (i = 0; i < set_info->count; i++) {
+			if (strset_fill_string(skb, set_info, i) < 0)
+				goto nla_put_failure;
+		}
+		nla_nest_end(skb, strings_attr);
+	}
+
+	nla_nest_end(skb, stringset_attr);
+	return 0;
+
+nla_put_failure:
+	nla_nest_cancel(skb, stringset_attr);
+	return -EMSGSIZE;
+}
+
+/* fill_reply() handler */
+static int strset_fill(struct sk_buff *skb,
+		       const struct ethnl_req_info *req_base,
+		       const struct ethnl_reply_data *reply_base)
+{
+	const struct strset_req_info *req_info =
+		container_of(req_base, struct strset_req_info, base);
+	const struct strset_reply_data *data =
+		container_of(reply_base, struct strset_reply_data, base);
+	bool counts_only =
+		req_base->req_flags & ETHTOOL_RFLAG_STRSET_COUNTS_ONLY;
+	struct nlattr *nest;
+	unsigned int i;
+	int ret;
+
+	nest = nla_nest_start(skb, ETHTOOL_A_STRSET_STRINGSETS);
+	if (!nest)
+		return -EMSGSIZE;
+
+	for (i = 0; i < ETH_SS_COUNT; i++) {
+		if (strset_include(req_info, data, i)) {
+			ret = strset_fill_set(skb, &data->sets[i], i,
+					      counts_only);
+			if (ret < 0)
+				goto nla_put_failure;
+		}
+	}
+
+	nla_nest_end(skb, nest);
+	return 0;
+
+nla_put_failure:
+	nla_nest_cancel(skb, nest);
+	return ret;
+}
+
+const struct get_request_ops strset_request_ops = {
+	.request_cmd		= ETHTOOL_MSG_STRSET_GET,
+	.reply_cmd		= ETHTOOL_MSG_STRSET_GET_REPLY,
+	.hdr_attr		= ETHTOOL_A_STRSET_HEADER,
+	.max_attr		= ETHTOOL_A_STRSET_MAX,
+	.req_info_size		= sizeof(struct strset_req_info),
+	.reply_data_size	= sizeof(struct strset_reply_data),
+	.request_policy		= strset_get_policy,
+	.all_reqflags		= ETHTOOL_RFLAG_STRSET_ALL,
+	.allow_nodev_do		= true,
+
+	.parse_request		= strset_parse,
+	.prepare_data		= strset_prepare,
+	.reply_size		= strset_size,
+	.fill_reply		= strset_fill,
+	.cleanup_data		= strset_cleanup,
+};
-- 
2.23.0


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

* [PATCH net-next v7 11/17] ethtool: provide link mode names as a string set
  2019-10-09 20:59 [PATCH net-next v7 00/17] ethtool netlink interface, part 1 Michal Kubecek
                   ` (9 preceding siblings ...)
  2019-10-09 20:59 ` [PATCH net-next v7 10/17] ethtool: provide string sets with STRSET_GET request Michal Kubecek
@ 2019-10-09 20:59 ` Michal Kubecek
  2019-10-09 20:59 ` [PATCH net-next v7 12/17] ethtool: provide link settings with LINKINFO_GET request Michal Kubecek
                   ` (6 subsequent siblings)
  17 siblings, 0 replies; 46+ messages in thread
From: Michal Kubecek @ 2019-10-09 20:59 UTC (permalink / raw)
  To: David Miller, netdev
  Cc: Jakub Kicinski, Jiri Pirko, Andrew Lunn, Florian Fainelli,
	John Linville, Stephen Hemminger, Johannes Berg, linux-kernel

Unlike e.g. netdev features, the ethtool ioctl interface requires link mode
table to be in sync between kernel and userspace for userspace to be able
to display and set all link modes supported by kernel. The way arbitrary
length bitsets are implemented in netlink interface, this is no longer
needed.

To allow userspace to access all link modes running kernel supports, add
table of ethernet link mode names and make it available as a string set to
userspace GET_STRSET requests. Add build time check to make sure names
are defined for all modes declared in enum ethtool_link_mode_bit_indices.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 include/linux/ethtool.h      |  4 ++
 include/uapi/linux/ethtool.h |  2 +
 net/ethtool/netlink.c        | 83 ++++++++++++++++++++++++++++++++++++
 net/ethtool/netlink.h        |  2 +
 net/ethtool/strset.c         |  5 +++
 5 files changed, 96 insertions(+)

diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h
index 95991e4300bf..5caef65d93d6 100644
--- a/include/linux/ethtool.h
+++ b/include/linux/ethtool.h
@@ -102,6 +102,10 @@ static inline u32 ethtool_rxfh_indir_default(u32 index, u32 n_rx_rings)
 #define __ETHTOOL_DECLARE_LINK_MODE_MASK(name)		\
 	DECLARE_BITMAP(name, __ETHTOOL_LINK_MODE_MASK_NBITS)
 
+/* compose link mode index from speed, type and duplex */
+#define ETHTOOL_LINK_MODE(speed, type, duplex) \
+	ETHTOOL_LINK_MODE_ ## speed ## base ## type ## _ ## duplex ## _BIT
+
 /* drivers must ignore base.cmd and base.link_mode_masks_nwords
  * fields, but they are allowed to overwrite them (will be ignored).
  */
diff --git a/include/uapi/linux/ethtool.h b/include/uapi/linux/ethtool.h
index 11ac843aa07e..53e72dd1c622 100644
--- a/include/uapi/linux/ethtool.h
+++ b/include/uapi/linux/ethtool.h
@@ -593,6 +593,7 @@ struct ethtool_pauseparam {
  * @ETH_SS_RSS_HASH_FUNCS: RSS hush function names
  * @ETH_SS_PHY_STATS: Statistic names, for use with %ETHTOOL_GPHYSTATS
  * @ETH_SS_PHY_TUNABLES: PHY tunable names
+ * @ETH_SS_LINK_MODES: link mode names
  */
 enum ethtool_stringset {
 	ETH_SS_TEST		= 0,
@@ -604,6 +605,7 @@ enum ethtool_stringset {
 	ETH_SS_TUNABLES,
 	ETH_SS_PHY_STATS,
 	ETH_SS_PHY_TUNABLES,
+	ETH_SS_LINK_MODES,
 
 	ETH_SS_COUNT
 };
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index df53999ddb12..bc042502115f 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -8,6 +8,86 @@ static struct genl_family ethtool_genl_family;
 
 static bool ethnl_ok __read_mostly;
 
+#define __LINK_MODE_NAME(speed, type, duplex) \
+	#speed "base" #type "/" #duplex
+#define __DEFINE_LINK_MODE_NAME(speed, type, duplex) \
+	[ETHTOOL_LINK_MODE(speed, type, duplex)] = \
+	__LINK_MODE_NAME(speed, type, duplex)
+#define __DEFINE_SPECIAL_MODE_NAME(_mode, _name) \
+	[ETHTOOL_LINK_MODE_ ## _mode ## _BIT] = _name
+
+const char link_mode_names[][ETH_GSTRING_LEN] = {
+	__DEFINE_LINK_MODE_NAME(10, T, Half),
+	__DEFINE_LINK_MODE_NAME(10, T, Full),
+	__DEFINE_LINK_MODE_NAME(100, T, Half),
+	__DEFINE_LINK_MODE_NAME(100, T, Full),
+	__DEFINE_LINK_MODE_NAME(1000, T, Half),
+	__DEFINE_LINK_MODE_NAME(1000, T, Full),
+	__DEFINE_SPECIAL_MODE_NAME(Autoneg, "Autoneg"),
+	__DEFINE_SPECIAL_MODE_NAME(TP, "TP"),
+	__DEFINE_SPECIAL_MODE_NAME(AUI, "AUI"),
+	__DEFINE_SPECIAL_MODE_NAME(MII, "MII"),
+	__DEFINE_SPECIAL_MODE_NAME(FIBRE, "FIBRE"),
+	__DEFINE_SPECIAL_MODE_NAME(BNC, "BNC"),
+	__DEFINE_LINK_MODE_NAME(10000, T, Full),
+	__DEFINE_SPECIAL_MODE_NAME(Pause, "Pause"),
+	__DEFINE_SPECIAL_MODE_NAME(Asym_Pause, "Asym_Pause"),
+	__DEFINE_LINK_MODE_NAME(2500, X, Full),
+	__DEFINE_SPECIAL_MODE_NAME(Backplane, "Backplane"),
+	__DEFINE_LINK_MODE_NAME(1000, KX, Full),
+	__DEFINE_LINK_MODE_NAME(10000, KX4, Full),
+	__DEFINE_LINK_MODE_NAME(10000, KR, Full),
+	[ETHTOOL_LINK_MODE_10000baseR_FEC_BIT] = "10000baseR_FEC",
+	__DEFINE_LINK_MODE_NAME(20000, MLD2, Full),
+	__DEFINE_LINK_MODE_NAME(20000, KR2, Full),
+	__DEFINE_LINK_MODE_NAME(40000, KR4, Full),
+	__DEFINE_LINK_MODE_NAME(40000, CR4, Full),
+	__DEFINE_LINK_MODE_NAME(40000, SR4, Full),
+	__DEFINE_LINK_MODE_NAME(40000, LR4, Full),
+	__DEFINE_LINK_MODE_NAME(56000, KR4, Full),
+	__DEFINE_LINK_MODE_NAME(56000, CR4, Full),
+	__DEFINE_LINK_MODE_NAME(56000, SR4, Full),
+	__DEFINE_LINK_MODE_NAME(56000, LR4, Full),
+	__DEFINE_LINK_MODE_NAME(25000, CR, Full),
+	__DEFINE_LINK_MODE_NAME(25000, KR, Full),
+	__DEFINE_LINK_MODE_NAME(25000, SR, Full),
+	__DEFINE_LINK_MODE_NAME(50000, CR2, Full),
+	__DEFINE_LINK_MODE_NAME(50000, KR2, Full),
+	__DEFINE_LINK_MODE_NAME(100000, KR4, Full),
+	__DEFINE_LINK_MODE_NAME(100000, SR4, Full),
+	__DEFINE_LINK_MODE_NAME(100000, CR4, Full),
+	__DEFINE_LINK_MODE_NAME(100000, LR4_ER4, Full),
+	__DEFINE_LINK_MODE_NAME(50000, SR2, Full),
+	__DEFINE_LINK_MODE_NAME(1000, X, Full),
+	__DEFINE_LINK_MODE_NAME(10000, CR, Full),
+	__DEFINE_LINK_MODE_NAME(10000, SR, Full),
+	__DEFINE_LINK_MODE_NAME(10000, LR, Full),
+	__DEFINE_LINK_MODE_NAME(10000, LRM, Full),
+	__DEFINE_LINK_MODE_NAME(10000, ER, Full),
+	__DEFINE_LINK_MODE_NAME(2500, T, Full),
+	__DEFINE_LINK_MODE_NAME(5000, T, Full),
+	__DEFINE_SPECIAL_MODE_NAME(FEC_NONE, "None"),
+	__DEFINE_SPECIAL_MODE_NAME(FEC_RS, "RS"),
+	__DEFINE_SPECIAL_MODE_NAME(FEC_BASER, "BASER"),
+	__DEFINE_LINK_MODE_NAME(50000, KR, Full),
+	__DEFINE_LINK_MODE_NAME(50000, SR, Full),
+	__DEFINE_LINK_MODE_NAME(50000, CR, Full),
+	__DEFINE_LINK_MODE_NAME(50000, LR_ER_FR, Full),
+	__DEFINE_LINK_MODE_NAME(50000, DR, Full),
+	__DEFINE_LINK_MODE_NAME(100000, KR2, Full),
+	__DEFINE_LINK_MODE_NAME(100000, SR2, Full),
+	__DEFINE_LINK_MODE_NAME(100000, CR2, Full),
+	__DEFINE_LINK_MODE_NAME(100000, LR2_ER2_FR2, Full),
+	__DEFINE_LINK_MODE_NAME(100000, DR2, Full),
+	__DEFINE_LINK_MODE_NAME(200000, KR4, Full),
+	__DEFINE_LINK_MODE_NAME(200000, SR4, Full),
+	__DEFINE_LINK_MODE_NAME(200000, LR4_ER4_FR4, Full),
+	__DEFINE_LINK_MODE_NAME(200000, DR4, Full),
+	__DEFINE_LINK_MODE_NAME(200000, CR4, Full),
+	__DEFINE_LINK_MODE_NAME(100, T1, Full),
+	__DEFINE_LINK_MODE_NAME(1000, T1, Full),
+};
+
 static const struct nla_policy dflt_header_policy[ETHTOOL_A_HEADER_MAX + 1] = {
 	[ETHTOOL_A_HEADER_UNSPEC]	= { .type = NLA_REJECT },
 	[ETHTOOL_A_HEADER_DEV_INDEX]	= { .type = NLA_U32 },
@@ -563,6 +643,9 @@ static int __init ethnl_init(void)
 {
 	int ret;
 
+	BUILD_BUG_ON(ARRAY_SIZE(link_mode_names) !=
+		     __ETHTOOL_LINK_MODE_MASK_NBITS);
+
 	ret = genl_register_family(&ethtool_genl_family);
 	if (WARN(ret < 0, "ethtool: genetlink family registration failed"))
 		return ret;
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 1bd9f0e20429..07268c916518 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -10,6 +10,8 @@
 
 struct ethnl_req_info;
 
+extern const char link_mode_names[][ETH_GSTRING_LEN];
+
 int ethnl_parse_header(struct ethnl_req_info *req_info,
 		       const struct nlattr *nest, struct net *net,
 		       struct netlink_ext_ack *extack,
diff --git a/net/ethtool/strset.c b/net/ethtool/strset.c
index 11f2161a0964..c4815dadf2bf 100644
--- a/net/ethtool/strset.c
+++ b/net/ethtool/strset.c
@@ -45,6 +45,11 @@ static const struct strset_info info_template[] = {
 		.count		= ARRAY_SIZE(phy_tunable_strings),
 		.strings	= phy_tunable_strings,
 	},
+	[ETH_SS_LINK_MODES] = {
+		.per_dev	= false,
+		.count		= __ETHTOOL_LINK_MODE_MASK_NBITS,
+		.strings	= link_mode_names,
+	},
 };
 
 struct strset_req_info {
-- 
2.23.0


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

* [PATCH net-next v7 12/17] ethtool: provide link settings with LINKINFO_GET request
  2019-10-09 20:59 [PATCH net-next v7 00/17] ethtool netlink interface, part 1 Michal Kubecek
                   ` (10 preceding siblings ...)
  2019-10-09 20:59 ` [PATCH net-next v7 11/17] ethtool: provide link mode names as a string set Michal Kubecek
@ 2019-10-09 20:59 ` Michal Kubecek
  2019-10-10 15:59   ` Jiri Pirko
  2019-10-09 20:59 ` [PATCH net-next v7 13/17] ethtool: add standard notification handler Michal Kubecek
                   ` (5 subsequent siblings)
  17 siblings, 1 reply; 46+ messages in thread
From: Michal Kubecek @ 2019-10-09 20:59 UTC (permalink / raw)
  To: David Miller, netdev
  Cc: Jakub Kicinski, Jiri Pirko, Andrew Lunn, Florian Fainelli,
	John Linville, Stephen Hemminger, Johannes Berg, linux-kernel

Implement LINKINFO_GET netlink request to get basic link settings provided
by ETHTOOL_GLINKSETTINGS and ETHTOOL_GSET ioctl commands.

This request provides settings not directly related to autonegotiation and
link mode selection: physical port, phy MDIO address, MDI(-X) status,
MDI(-X) control and transceiver.

LINKINFO_GET request can be used with NLM_F_DUMP (without device
identification) to request the information for all devices in current
network namespace providing the data.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Documentation/networking/ethtool-netlink.rst | 37 +++++++-
 include/uapi/linux/ethtool_netlink.h         | 20 ++++
 net/ethtool/Makefile                         |  2 +-
 net/ethtool/common.c                         | 48 ++++++++++
 net/ethtool/common.h                         |  4 +
 net/ethtool/ioctl.c                          | 48 ----------
 net/ethtool/linkinfo.c                       | 97 ++++++++++++++++++++
 net/ethtool/netlink.c                        |  8 ++
 net/ethtool/netlink.h                        |  1 +
 9 files changed, 214 insertions(+), 51 deletions(-)
 create mode 100644 net/ethtool/linkinfo.c

diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst
index d12e0e4f277c..0d21469debec 100644
--- a/Documentation/networking/ethtool-netlink.rst
+++ b/Documentation/networking/ethtool-netlink.rst
@@ -167,12 +167,14 @@ Userspace to kernel:
 
   ===================================== ================================
   ``ETHTOOL_MSG_STRSET_GET``            get string set
+  ``ETHTOOL_MSG_LINKINFO_GET``          get link settings
   ===================================== ================================
 
 Kernel to userspace:
 
   ===================================== ================================
   ``ETHTOOL_MSG_STRSET_GET_REPLY``      string set contents
+  ``ETHTOOL_MSG_LINKINFO_GET_REPLY``    link settings
   ===================================== ================================
 
 ``GET`` requests are sent by userspace applications to retrieve device
@@ -267,6 +269,37 @@ Flag ``ETHTOOL_A_STRSET_COUNTS`` tells kernel to only return string counts of
 the sets, not the actual strings.
 
 
+LINKINFO_GET
+============
+
+Requests link settings as provided by ``ETHTOOL_GLINKSETTINGS`` except for
+link modes and autonegotiation related information. The request does not use
+any attributes and does not have any request specific flags.
+
+Request contents:
+
+  ====================================  ======  ==========================
+  ``ETHTOOL_A_LINKINFO_HEADER``         nested  request header
+  ====================================  ======  ==========================
+
+Kernel response contents:
+
+  ====================================  ======  ==========================
+  ``ETHTOOL_A_LINKINFO_HEADER``         nested  reply header
+  ``ETHTOOL_A_LINKINFO_PORT``           u8      physical port
+  ``ETHTOOL_A_LINKINFO_PHYADDR``        u8      phy MDIO address
+  ``ETHTOOL_A_LINKINFO_TP_MDIX``        u8      MDI(-X) status
+  ``ETHTOOL_A_LINKINFO_TP_MDIX_CTRL``   u8      MDI(-X) control
+  ``ETHTOOL_A_LINKINFO_TRANSCEIVER``    u8      transceiver
+  ====================================  ======  ==========================
+
+Attributes and their values have the same meaning as matching members of the
+corresponding ioctl structures.
+
+``LINKINFO_GET`` allows dump requests (kernel returns reply message for all
+devices supporting the request).
+
+
 Request translation
 ===================
 
@@ -277,7 +310,7 @@ have their netlink replacement yet.
   =================================== =====================================
   ioctl command                       netlink command
   =================================== =====================================
-  ``ETHTOOL_GSET``                    n/a
+  ``ETHTOOL_GSET``                    ``ETHTOOL_MSG_LINKINFO_GET``
   ``ETHTOOL_SSET``                    n/a
   ``ETHTOOL_GDRVINFO``                n/a
   ``ETHTOOL_GREGS``                   n/a
@@ -351,7 +384,7 @@ have their netlink replacement yet.
   ``ETHTOOL_STUNABLE``                n/a
   ``ETHTOOL_GPHYSTATS``               n/a
   ``ETHTOOL_PERQUEUE``                n/a
-  ``ETHTOOL_GLINKSETTINGS``           n/a
+  ``ETHTOOL_GLINKSETTINGS``           ``ETHTOOL_MSG_LINKINFO_GET``
   ``ETHTOOL_SLINKSETTINGS``           n/a
   ``ETHTOOL_PHY_GTUNABLE``            n/a
   ``ETHTOOL_PHY_STUNABLE``            n/a
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index 751d725866df..56ab2a530d22 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -15,6 +15,7 @@
 enum {
 	ETHTOOL_MSG_USER_NONE,
 	ETHTOOL_MSG_STRSET_GET,
+	ETHTOOL_MSG_LINKINFO_GET,
 
 	/* add new constants above here */
 	__ETHTOOL_MSG_USER_CNT,
@@ -25,6 +26,7 @@ enum {
 enum {
 	ETHTOOL_MSG_KERNEL_NONE,
 	ETHTOOL_MSG_STRSET_GET_REPLY,
+	ETHTOOL_MSG_LINKINFO_GET_REPLY,
 
 	/* add new constants above here */
 	__ETHTOOL_MSG_KERNEL_CNT,
@@ -146,6 +148,24 @@ enum {
 
 #define ETHTOOL_RFLAG_STRSET_ALL (ETHTOOL_RFLAG_STRSET_COUNTS_ONLY)
 
+/* LINKINFO */
+
+enum {
+	ETHTOOL_A_LINKINFO_UNSPEC,
+	ETHTOOL_A_LINKINFO_HEADER,		/* nest - _A_HEADER_* */
+	ETHTOOL_A_LINKINFO_PORT,		/* u8 */
+	ETHTOOL_A_LINKINFO_PHYADDR,		/* u8 */
+	ETHTOOL_A_LINKINFO_TP_MDIX,		/* u8 */
+	ETHTOOL_A_LINKINFO_TP_MDIX_CTRL,	/* u8 */
+	ETHTOOL_A_LINKINFO_TRANSCEIVER,		/* u8 */
+
+	/* add new constants above here */
+	__ETHTOOL_A_LINKINFO_CNT,
+	ETHTOOL_A_LINKINFO_MAX = __ETHTOOL_A_LINKINFO_CNT - 1
+};
+
+#define ETHTOOL_RFLAG_LINKINFO_ALL 0
+
 /* generic netlink info */
 #define ETHTOOL_GENL_NAME "ethtool"
 #define ETHTOOL_GENL_VERSION 1
diff --git a/net/ethtool/Makefile b/net/ethtool/Makefile
index 11ceb00821b3..28666a0eede8 100644
--- a/net/ethtool/Makefile
+++ b/net/ethtool/Makefile
@@ -4,4 +4,4 @@ obj-y				+= ioctl.o common.o
 
 obj-$(CONFIG_ETHTOOL_NETLINK)	+= ethtool_nl.o
 
-ethtool_nl-y	:= netlink.o bitset.o strset.o
+ethtool_nl-y	:= netlink.o bitset.o strset.o linkinfo.o
diff --git a/net/ethtool/common.c b/net/ethtool/common.c
index 220d6b539180..84fed2eecb55 100644
--- a/net/ethtool/common.c
+++ b/net/ethtool/common.c
@@ -83,3 +83,51 @@ phy_tunable_strings[__ETHTOOL_PHY_TUNABLE_COUNT][ETH_GSTRING_LEN] = {
 	[ETHTOOL_PHY_FAST_LINK_DOWN] = "phy-fast-link-down",
 	[ETHTOOL_PHY_EDPD]	= "phy-energy-detect-power-down",
 };
+
+/* return false if legacy contained non-0 deprecated fields
+ * maxtxpkt/maxrxpkt. rest of ksettings always updated
+ */
+bool
+convert_legacy_settings_to_link_ksettings(
+	struct ethtool_link_ksettings *link_ksettings,
+	const struct ethtool_cmd *legacy_settings)
+{
+	bool retval = true;
+
+	memset(link_ksettings, 0, sizeof(*link_ksettings));
+
+	/* This is used to tell users that driver is still using these
+	 * deprecated legacy fields, and they should not use
+	 * %ETHTOOL_GLINKSETTINGS/%ETHTOOL_SLINKSETTINGS
+	 */
+	if (legacy_settings->maxtxpkt ||
+	    legacy_settings->maxrxpkt)
+		retval = false;
+
+	ethtool_convert_legacy_u32_to_link_mode(
+		link_ksettings->link_modes.supported,
+		legacy_settings->supported);
+	ethtool_convert_legacy_u32_to_link_mode(
+		link_ksettings->link_modes.advertising,
+		legacy_settings->advertising);
+	ethtool_convert_legacy_u32_to_link_mode(
+		link_ksettings->link_modes.lp_advertising,
+		legacy_settings->lp_advertising);
+	link_ksettings->base.speed
+		= ethtool_cmd_speed(legacy_settings);
+	link_ksettings->base.duplex
+		= legacy_settings->duplex;
+	link_ksettings->base.port
+		= legacy_settings->port;
+	link_ksettings->base.phy_address
+		= legacy_settings->phy_address;
+	link_ksettings->base.autoneg
+		= legacy_settings->autoneg;
+	link_ksettings->base.mdio_support
+		= legacy_settings->mdio_support;
+	link_ksettings->base.eth_tp_mdix
+		= legacy_settings->eth_tp_mdix;
+	link_ksettings->base.eth_tp_mdix_ctrl
+		= legacy_settings->eth_tp_mdix_ctrl;
+	return retval;
+}
diff --git a/net/ethtool/common.h b/net/ethtool/common.h
index 41b2efc1e4e1..0381936d8e1e 100644
--- a/net/ethtool/common.h
+++ b/net/ethtool/common.h
@@ -14,4 +14,8 @@ tunable_strings[__ETHTOOL_TUNABLE_COUNT][ETH_GSTRING_LEN];
 extern const char
 phy_tunable_strings[__ETHTOOL_PHY_TUNABLE_COUNT][ETH_GSTRING_LEN];
 
+bool convert_legacy_settings_to_link_ksettings(
+	struct ethtool_link_ksettings *link_ksettings,
+	const struct ethtool_cmd *legacy_settings);
+
 #endif /* _ETHTOOL_COMMON_H */
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index 94307fbba96b..2cd04cb6b4b9 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -352,54 +352,6 @@ bool ethtool_convert_link_mode_to_legacy_u32(u32 *legacy_u32,
 }
 EXPORT_SYMBOL(ethtool_convert_link_mode_to_legacy_u32);
 
-/* return false if legacy contained non-0 deprecated fields
- * maxtxpkt/maxrxpkt. rest of ksettings always updated
- */
-static bool
-convert_legacy_settings_to_link_ksettings(
-	struct ethtool_link_ksettings *link_ksettings,
-	const struct ethtool_cmd *legacy_settings)
-{
-	bool retval = true;
-
-	memset(link_ksettings, 0, sizeof(*link_ksettings));
-
-	/* This is used to tell users that driver is still using these
-	 * deprecated legacy fields, and they should not use
-	 * %ETHTOOL_GLINKSETTINGS/%ETHTOOL_SLINKSETTINGS
-	 */
-	if (legacy_settings->maxtxpkt ||
-	    legacy_settings->maxrxpkt)
-		retval = false;
-
-	ethtool_convert_legacy_u32_to_link_mode(
-		link_ksettings->link_modes.supported,
-		legacy_settings->supported);
-	ethtool_convert_legacy_u32_to_link_mode(
-		link_ksettings->link_modes.advertising,
-		legacy_settings->advertising);
-	ethtool_convert_legacy_u32_to_link_mode(
-		link_ksettings->link_modes.lp_advertising,
-		legacy_settings->lp_advertising);
-	link_ksettings->base.speed
-		= ethtool_cmd_speed(legacy_settings);
-	link_ksettings->base.duplex
-		= legacy_settings->duplex;
-	link_ksettings->base.port
-		= legacy_settings->port;
-	link_ksettings->base.phy_address
-		= legacy_settings->phy_address;
-	link_ksettings->base.autoneg
-		= legacy_settings->autoneg;
-	link_ksettings->base.mdio_support
-		= legacy_settings->mdio_support;
-	link_ksettings->base.eth_tp_mdix
-		= legacy_settings->eth_tp_mdix;
-	link_ksettings->base.eth_tp_mdix_ctrl
-		= legacy_settings->eth_tp_mdix_ctrl;
-	return retval;
-}
-
 /* return false if ksettings link modes had higher bits
  * set. legacy_settings always updated (best effort)
  */
diff --git a/net/ethtool/linkinfo.c b/net/ethtool/linkinfo.c
new file mode 100644
index 000000000000..c28ca4d9dd2a
--- /dev/null
+++ b/net/ethtool/linkinfo.c
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
+
+#include "netlink.h"
+#include "common.h"
+
+struct linkinfo_req_info {
+	struct ethnl_req_info		base;
+};
+
+struct linkinfo_reply_data {
+	struct ethnl_reply_data		base;
+	struct ethtool_link_ksettings	ksettings;
+	struct ethtool_link_settings	*lsettings;
+};
+
+static const struct nla_policy
+linkinfo_get_policy[ETHTOOL_A_LINKINFO_MAX + 1] = {
+	[ETHTOOL_A_LINKINFO_UNSPEC]		= { .type = NLA_REJECT },
+	[ETHTOOL_A_LINKINFO_HEADER]		= { .type = NLA_NESTED },
+	[ETHTOOL_A_LINKINFO_PORT]		= { .type = NLA_REJECT },
+	[ETHTOOL_A_LINKINFO_PHYADDR]		= { .type = NLA_REJECT },
+	[ETHTOOL_A_LINKINFO_TP_MDIX]		= { .type = NLA_REJECT },
+	[ETHTOOL_A_LINKINFO_TP_MDIX_CTRL]	= { .type = NLA_REJECT },
+	[ETHTOOL_A_LINKINFO_TRANSCEIVER]	= { .type = NLA_REJECT },
+};
+
+/* prepare_data() handler */
+static int linkinfo_prepare(const struct ethnl_req_info *req_base,
+			    struct ethnl_reply_data *reply_base,
+			    struct genl_info *info)
+{
+	struct linkinfo_reply_data *data =
+		container_of(reply_base, struct linkinfo_reply_data, base);
+	struct net_device *dev = reply_base->dev;
+	int ret;
+
+	data->lsettings = &data->ksettings.base;
+
+	ret = ethnl_before_ops(dev);
+	if (ret < 0)
+		return ret;
+	ret = __ethtool_get_link_ksettings(dev, &data->ksettings);
+	if (ret < 0 && info)
+		GENL_SET_ERR_MSG(info, "failed to retrieve link settings");
+	ethnl_after_ops(dev);
+
+	return ret;
+}
+
+/* reply_size() handler */
+static int linkinfo_size(const struct ethnl_req_info *req_base,
+			 const struct ethnl_reply_data *reply_base)
+{
+	return nla_total_size(sizeof(u8)) /* LINKINFO_PORT */
+		+ nla_total_size(sizeof(u8)) /* LINKINFO_PHYADDR */
+		+ nla_total_size(sizeof(u8)) /* LINKINFO_TP_MDIX */
+		+ nla_total_size(sizeof(u8)) /* LINKINFO_TP_MDIX_CTRL */
+		+ nla_total_size(sizeof(u8)) /* LINKINFO_TRANSCEIVER */
+		+ 0;
+}
+
+/* fill_reply() handler */
+static int linkinfo_fill(struct sk_buff *skb,
+			 const struct ethnl_req_info *req_base,
+			 const struct ethnl_reply_data *reply_base)
+{
+	const struct linkinfo_reply_data *data =
+		container_of(reply_base, struct linkinfo_reply_data, base);
+
+	if (nla_put_u8(skb, ETHTOOL_A_LINKINFO_PORT, data->lsettings->port) ||
+	    nla_put_u8(skb, ETHTOOL_A_LINKINFO_PHYADDR,
+		       data->lsettings->phy_address) ||
+	    nla_put_u8(skb, ETHTOOL_A_LINKINFO_TP_MDIX,
+		       data->lsettings->eth_tp_mdix) ||
+	    nla_put_u8(skb, ETHTOOL_A_LINKINFO_TP_MDIX_CTRL,
+		       data->lsettings->eth_tp_mdix_ctrl) ||
+	    nla_put_u8(skb, ETHTOOL_A_LINKINFO_TRANSCEIVER,
+		       data->lsettings->transceiver))
+		return -EMSGSIZE;
+
+	return 0;
+}
+
+const struct get_request_ops linkinfo_request_ops = {
+	.request_cmd		= ETHTOOL_MSG_LINKINFO_GET,
+	.reply_cmd		= ETHTOOL_MSG_LINKINFO_GET_REPLY,
+	.hdr_attr		= ETHTOOL_A_LINKINFO_HEADER,
+	.max_attr		= ETHTOOL_A_LINKINFO_MAX,
+	.req_info_size		= sizeof(struct linkinfo_req_info),
+	.reply_data_size	= sizeof(struct linkinfo_reply_data),
+	.request_policy		= linkinfo_get_policy,
+	.all_reqflags		= ETHTOOL_RFLAG_LINKINFO_ALL,
+
+	.prepare_data		= linkinfo_prepare,
+	.reply_size		= linkinfo_size,
+	.fill_reply		= linkinfo_fill,
+};
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index bc042502115f..47b6aefa0bf9 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -280,6 +280,7 @@ struct ethnl_dump_ctx {
 
 static const struct get_request_ops *get_requests[__ETHTOOL_MSG_USER_CNT] = {
 	[ETHTOOL_MSG_STRSET_GET]	= &strset_request_ops,
+	[ETHTOOL_MSG_LINKINFO_GET]	= &linkinfo_request_ops,
 };
 
 static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)
@@ -620,6 +621,13 @@ static const struct genl_ops ethtool_genl_ops[] = {
 		.dumpit	= ethnl_get_dumpit,
 		.done	= ethnl_get_done,
 	},
+	{
+		.cmd	= ETHTOOL_MSG_LINKINFO_GET,
+		.doit	= ethnl_get_doit,
+		.start	= ethnl_get_start,
+		.dumpit	= ethnl_get_dumpit,
+		.done	= ethnl_get_done,
+	},
 };
 
 static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 07268c916518..a0ae47bebe51 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -347,5 +347,6 @@ struct get_request_ops {
 /* request handlers */
 
 extern const struct get_request_ops strset_request_ops;
+extern const struct get_request_ops linkinfo_request_ops;
 
 #endif /* _NET_ETHTOOL_NETLINK_H */
-- 
2.23.0


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

* [PATCH net-next v7 13/17] ethtool: add standard notification handler
  2019-10-09 20:59 [PATCH net-next v7 00/17] ethtool netlink interface, part 1 Michal Kubecek
                   ` (11 preceding siblings ...)
  2019-10-09 20:59 ` [PATCH net-next v7 12/17] ethtool: provide link settings with LINKINFO_GET request Michal Kubecek
@ 2019-10-09 20:59 ` Michal Kubecek
  2019-10-10 15:25   ` Jiri Pirko
  2019-10-09 20:59 ` [PATCH net-next v7 14/17] ethtool: set link settings with LINKINFO_SET request Michal Kubecek
                   ` (4 subsequent siblings)
  17 siblings, 1 reply; 46+ messages in thread
From: Michal Kubecek @ 2019-10-09 20:59 UTC (permalink / raw)
  To: David Miller, netdev
  Cc: Jakub Kicinski, Jiri Pirko, Andrew Lunn, Florian Fainelli,
	John Linville, Stephen Hemminger, Johannes Berg, linux-kernel

The ethtool netlink notifications have the same format as related GET
replies so that if generic GET handling framework is used to process GET
requests, its callbacks and instance of struct get_request_ops can be
also used to compose corresponding notification message.

Provide function ethnl_std_notify() to be used as notification handler in
ethnl_notify_handlers table.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 net/ethtool/netlink.c | 89 +++++++++++++++++++++++++++++++++++++++++++
 net/ethtool/netlink.h |  3 +-
 2 files changed, 91 insertions(+), 1 deletion(-)

diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 47b6aefa0bf9..dc52d912e0dd 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -7,6 +7,7 @@
 static struct genl_family ethtool_genl_family;
 
 static bool ethnl_ok __read_mostly;
+static u32 ethnl_bcast_seq;
 
 #define __LINK_MODE_NAME(speed, type, duplex) \
 	#speed "base" #type "/" #duplex
@@ -257,6 +258,18 @@ struct sk_buff *ethnl_reply_init(size_t payload, struct net_device *dev, u8 cmd,
 	return NULL;
 }
 
+static void *ethnl_bcastmsg_put(struct sk_buff *skb, u8 cmd)
+{
+	return genlmsg_put(skb, 0, ++ethnl_bcast_seq, &ethtool_genl_family, 0,
+			   cmd);
+}
+
+static int ethnl_multicast(struct sk_buff *skb, struct net_device *dev)
+{
+	return genlmsg_multicast_netns(&ethtool_genl_family, dev_net(dev), skb,
+				       0, ETHNL_MCGRP_MONITOR, GFP_KERNEL);
+}
+
 /* GET request helpers */
 
 /**
@@ -588,6 +601,82 @@ static int ethnl_get_done(struct netlink_callback *cb)
 	return 0;
 }
 
+static const struct get_request_ops *ethnl_std_notify_to_ops(unsigned int cmd)
+{
+	WARN_ONCE(1, "unexpected notification type %u\n", cmd);
+	return NULL;
+}
+
+/* generic notification handler */
+static void ethnl_std_notify(struct net_device *dev, unsigned int cmd,
+			     const void *data)
+{
+	struct ethnl_reply_data *reply_data;
+	const struct get_request_ops *ops;
+	struct ethnl_req_info *req_info;
+	struct sk_buff *skb;
+	void *reply_payload;
+	int reply_len;
+	int ret;
+
+	ops = ethnl_std_notify_to_ops(cmd);
+	if (!ops)
+		return;
+	req_info = kzalloc(ops->req_info_size, GFP_KERNEL);
+	if (!req_info)
+		return;
+	reply_data = kmalloc(ops->reply_data_size, GFP_KERNEL);
+	if (!reply_data) {
+		kfree(req_info);
+		return;
+	}
+
+	req_info->dev = dev;
+	req_info->global_flags |= ETHTOOL_GFLAG_COMPACT_BITSETS;
+
+	ethnl_init_reply_data(reply_data, ops, dev);
+	ret = ops->prepare_data(req_info, reply_data, NULL);
+	if (ret < 0)
+		goto err_cleanup;
+	reply_len = ops->reply_size(req_info, reply_data);
+	if (ret < 0)
+		goto err_cleanup;
+	ret = -ENOMEM;
+	skb = genlmsg_new(reply_len, GFP_KERNEL);
+	if (!skb)
+		goto err_cleanup;
+	reply_payload = ethnl_bcastmsg_put(skb, cmd);
+	if (!reply_payload)
+		goto err_skb;
+	ret = ethnl_fill_reply_header(skb, dev, ops->hdr_attr);
+	if (ret < 0)
+		goto err_msg;
+	ret = ops->fill_reply(skb, req_info, reply_data);
+	if (ret < 0)
+		goto err_msg;
+	if (ops->cleanup_data)
+		ops->cleanup_data(reply_data);
+
+	genlmsg_end(skb, reply_payload);
+	kfree(reply_data);
+	kfree(req_info);
+	ethnl_multicast(skb, dev);
+	return;
+
+err_msg:
+	WARN_ONCE(ret == -EMSGSIZE,
+		  "calculated message payload length (%d) not sufficient\n",
+		  reply_len);
+err_skb:
+	nlmsg_free(skb);
+err_cleanup:
+	if (ops->cleanup_data)
+		ops->cleanup_data(reply_data);
+	kfree(reply_data);
+	kfree(req_info);
+	return;
+}
+
 /* notifications */
 
 typedef void (*ethnl_notify_handler_t)(struct net_device *dev, unsigned int cmd,
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index a0ae47bebe51..23e82a4dd265 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -316,7 +316,8 @@ static inline void ethnl_after_ops(struct net_device *dev)
  * infrastructure. When used, a pointer to an instance of this structure is to
  * be added to &get_requests array and generic handlers ethnl_get_doit(),
  * ethnl_get_dumpit(), ethnl_get_start() and ethnl_get_done() used in
- * @ethnl_genl_ops
+ * @ethnl_genl_ops; ethnl_std_notify() can be used in @ethnl_notify_handlers
+ * to send notifications of the corresponding type.
  */
 struct get_request_ops {
 	u8			request_cmd;
-- 
2.23.0


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

* [PATCH net-next v7 14/17] ethtool: set link settings with LINKINFO_SET request
  2019-10-09 20:59 [PATCH net-next v7 00/17] ethtool netlink interface, part 1 Michal Kubecek
                   ` (12 preceding siblings ...)
  2019-10-09 20:59 ` [PATCH net-next v7 13/17] ethtool: add standard notification handler Michal Kubecek
@ 2019-10-09 20:59 ` Michal Kubecek
  2019-10-10 15:37   ` Jiri Pirko
  2019-10-12 16:33   ` Jiri Pirko
  2019-10-09 20:59 ` [PATCH net-next v7 15/17] ethtool: provide link mode information with LINKMODES_GET request Michal Kubecek
                   ` (3 subsequent siblings)
  17 siblings, 2 replies; 46+ messages in thread
From: Michal Kubecek @ 2019-10-09 20:59 UTC (permalink / raw)
  To: David Miller, netdev
  Cc: Jakub Kicinski, Jiri Pirko, Andrew Lunn, Florian Fainelli,
	John Linville, Stephen Hemminger, Johannes Berg, linux-kernel

Implement LINKINFO_SET netlink request to set link settings queried by
LINKINFO_GET message.

Only physical port, phy MDIO address and MDI(-X) control can be set,
attempt to modify MDI(-X) status and transceiver is rejected.

When any data is modified, ETHTOOL_MSG_LINKINFO_NTF message in the same
format as reply to LINKINFO_GET request is sent to notify userspace about
the changes. The same notification is also sent when these settings are
modified using the ioctl interface.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Documentation/networking/ethtool-netlink.rst | 25 +++++-
 include/uapi/linux/ethtool_netlink.h         |  2 +
 net/ethtool/ioctl.c                          | 12 ++-
 net/ethtool/linkinfo.c                       | 83 ++++++++++++++++++++
 net/ethtool/netlink.c                        | 11 +++
 net/ethtool/netlink.h                        |  2 +
 6 files changed, 131 insertions(+), 4 deletions(-)

diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst
index 0d21469debec..48833b6092d9 100644
--- a/Documentation/networking/ethtool-netlink.rst
+++ b/Documentation/networking/ethtool-netlink.rst
@@ -168,6 +168,7 @@ Userspace to kernel:
   ===================================== ================================
   ``ETHTOOL_MSG_STRSET_GET``            get string set
   ``ETHTOOL_MSG_LINKINFO_GET``          get link settings
+  ``ETHTOOL_MSG_LINKINFO_SET``          set link settings
   ===================================== ================================
 
 Kernel to userspace:
@@ -175,6 +176,7 @@ Kernel to userspace:
   ===================================== ================================
   ``ETHTOOL_MSG_STRSET_GET_REPLY``      string set contents
   ``ETHTOOL_MSG_LINKINFO_GET_REPLY``    link settings
+  ``ETHTOOL_MSG_LINKINFO_NTF``          link settings notification
   ===================================== ================================
 
 ``GET`` requests are sent by userspace applications to retrieve device
@@ -300,6 +302,25 @@ corresponding ioctl structures.
 devices supporting the request).
 
 
+LINKINFO_SET
+============
+
+``LINKINFO_SET`` request allows setting some of the attributes reported by
+``LINKINFO_GET``. and The request has no request specific flags.
+
+Request contents:
+
+  ====================================  ======  ==========================
+  ``ETHTOOL_A_LINKINFO_HEADER``         nested  request header
+  ``ETHTOOL_A_LINKINFO_PORT``           u8      physical port
+  ``ETHTOOL_A_LINKINFO_PHYADDR``        u8      phy MDIO address
+  ``ETHTOOL_A_LINKINFO_TP_MDIX_CTRL``   u8      MDI(-X) control
+  ====================================  ======  ==========================
+
+MDI(-X) status and transceiver cannot be set, request with the corresponding
+attributes is rejected.
+
+
 Request translation
 ===================
 
@@ -311,7 +332,7 @@ have their netlink replacement yet.
   ioctl command                       netlink command
   =================================== =====================================
   ``ETHTOOL_GSET``                    ``ETHTOOL_MSG_LINKINFO_GET``
-  ``ETHTOOL_SSET``                    n/a
+  ``ETHTOOL_SSET``                    ``ETHTOOL_MSG_LINKINFO_SET``
   ``ETHTOOL_GDRVINFO``                n/a
   ``ETHTOOL_GREGS``                   n/a
   ``ETHTOOL_GWOL``                    n/a
@@ -385,7 +406,7 @@ have their netlink replacement yet.
   ``ETHTOOL_GPHYSTATS``               n/a
   ``ETHTOOL_PERQUEUE``                n/a
   ``ETHTOOL_GLINKSETTINGS``           ``ETHTOOL_MSG_LINKINFO_GET``
-  ``ETHTOOL_SLINKSETTINGS``           n/a
+  ``ETHTOOL_SLINKSETTINGS``           ``ETHTOOL_MSG_LINKINFO_SET``
   ``ETHTOOL_PHY_GTUNABLE``            n/a
   ``ETHTOOL_PHY_STUNABLE``            n/a
   ``ETHTOOL_GFECPARAM``               n/a
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index 56ab2a530d22..78ee5584d5da 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -16,6 +16,7 @@ enum {
 	ETHTOOL_MSG_USER_NONE,
 	ETHTOOL_MSG_STRSET_GET,
 	ETHTOOL_MSG_LINKINFO_GET,
+	ETHTOOL_MSG_LINKINFO_SET,
 
 	/* add new constants above here */
 	__ETHTOOL_MSG_USER_CNT,
@@ -27,6 +28,7 @@ enum {
 	ETHTOOL_MSG_KERNEL_NONE,
 	ETHTOOL_MSG_STRSET_GET_REPLY,
 	ETHTOOL_MSG_LINKINFO_GET_REPLY,
+	ETHTOOL_MSG_LINKINFO_NTF,
 
 	/* add new constants above here */
 	__ETHTOOL_MSG_KERNEL_CNT,
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index 2cd04cb6b4b9..0747fe6b5f4c 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -26,6 +26,7 @@
 #include <net/devlink.h>
 #include <net/xdp_sock.h>
 #include <net/flow_offload.h>
+#include <linux/ethtool_netlink.h>
 
 #include "common.h"
 
@@ -565,7 +566,10 @@ static int ethtool_set_link_ksettings(struct net_device *dev,
 	    != link_ksettings.base.link_mode_masks_nwords)
 		return -EINVAL;
 
-	return dev->ethtool_ops->set_link_ksettings(dev, &link_ksettings);
+	err = dev->ethtool_ops->set_link_ksettings(dev, &link_ksettings);
+	if (err >= 0)
+		ethtool_notify(dev, ETHTOOL_MSG_LINKINFO_NTF, NULL);
+	return err;
 }
 
 /* Query device for its ethtool_cmd settings.
@@ -614,6 +618,7 @@ static int ethtool_set_settings(struct net_device *dev, void __user *useraddr)
 {
 	struct ethtool_link_ksettings link_ksettings;
 	struct ethtool_cmd cmd;
+	int ret;
 
 	ASSERT_RTNL();
 
@@ -626,7 +631,10 @@ static int ethtool_set_settings(struct net_device *dev, void __user *useraddr)
 		return -EINVAL;
 	link_ksettings.base.link_mode_masks_nwords =
 		__ETHTOOL_LINK_MODE_MASK_NU32;
-	return dev->ethtool_ops->set_link_ksettings(dev, &link_ksettings);
+	ret = dev->ethtool_ops->set_link_ksettings(dev, &link_ksettings);
+	if (ret >= 0)
+		ethtool_notify(dev, ETHTOOL_MSG_LINKINFO_NTF, NULL);
+	return ret;
 }
 
 static noinline_for_stack int ethtool_get_drvinfo(struct net_device *dev,
diff --git a/net/ethtool/linkinfo.c b/net/ethtool/linkinfo.c
index c28ca4d9dd2a..ea1997b154f0 100644
--- a/net/ethtool/linkinfo.c
+++ b/net/ethtool/linkinfo.c
@@ -95,3 +95,86 @@ const struct get_request_ops linkinfo_request_ops = {
 	.reply_size		= linkinfo_size,
 	.fill_reply		= linkinfo_fill,
 };
+
+/* LINKINFO_SET */
+
+static const struct nla_policy linkinfo_hdr_policy[ETHTOOL_A_HEADER_MAX + 1] = {
+	[ETHTOOL_A_HEADER_UNSPEC]		= { .type = NLA_REJECT },
+	[ETHTOOL_A_HEADER_DEV_INDEX]		= { .type = NLA_U32 },
+	[ETHTOOL_A_HEADER_DEV_NAME]		= { .type = NLA_NUL_STRING,
+						    .len = IFNAMSIZ - 1 },
+	[ETHTOOL_A_HEADER_GFLAGS]		= { .type = NLA_U32 },
+	[ETHTOOL_A_HEADER_RFLAGS]		= { .type = NLA_REJECT },
+};
+
+static const struct nla_policy
+linkinfo_set_policy[ETHTOOL_A_LINKINFO_MAX + 1] = {
+	[ETHTOOL_A_LINKINFO_UNSPEC]		= { .type = NLA_REJECT },
+	[ETHTOOL_A_LINKINFO_HEADER]		= { .type = NLA_NESTED },
+	[ETHTOOL_A_LINKINFO_PORT]		= { .type = NLA_U8 },
+	[ETHTOOL_A_LINKINFO_PHYADDR]		= { .type = NLA_U8 },
+	[ETHTOOL_A_LINKINFO_TP_MDIX]		= { .type = NLA_REJECT },
+	[ETHTOOL_A_LINKINFO_TP_MDIX_CTRL]	= { .type = NLA_U8 },
+	[ETHTOOL_A_LINKINFO_TRANSCEIVER]	= { .type = NLA_REJECT },
+};
+
+int ethnl_set_linkinfo(struct sk_buff *skb, struct genl_info *info)
+{
+	struct nlattr *tb[ETHTOOL_A_LINKINFO_MAX + 1];
+	struct ethtool_link_ksettings ksettings = {};
+	struct ethtool_link_settings *lsettings;
+	struct ethnl_req_info req_info = {};
+	struct net_device *dev;
+	bool mod = false;
+	int ret;
+
+	ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb,
+			  ETHTOOL_A_LINKINFO_MAX, linkinfo_set_policy,
+			  info->extack);
+	if (ret < 0)
+		return ret;
+	ret = ethnl_parse_header(&req_info, tb[ETHTOOL_A_LINKINFO_HEADER],
+				 genl_info_net(info), info->extack,
+				 linkinfo_hdr_policy, true);
+	if (ret < 0)
+		return ret;
+	dev = req_info.dev;
+	if (!dev->ethtool_ops->get_link_ksettings ||
+	    !dev->ethtool_ops->set_link_ksettings)
+		return -EOPNOTSUPP;
+
+	rtnl_lock();
+	ret = ethnl_before_ops(dev);
+	if (ret < 0)
+		goto out_rtnl;
+
+	ret = __ethtool_get_link_ksettings(dev, &ksettings);
+	if (ret < 0) {
+		if (info)
+			GENL_SET_ERR_MSG(info, "failed to retrieve link settings");
+		goto out_ops;
+	}
+	lsettings = &ksettings.base;
+
+	ethnl_update_u8(&lsettings->port, tb[ETHTOOL_A_LINKINFO_PORT], &mod);
+	ethnl_update_u8(&lsettings->phy_address, tb[ETHTOOL_A_LINKINFO_PHYADDR],
+			&mod);
+	ethnl_update_u8(&lsettings->eth_tp_mdix_ctrl,
+			tb[ETHTOOL_A_LINKINFO_TP_MDIX_CTRL], &mod);
+
+	ret = 0;
+	if (mod) {
+		ret = dev->ethtool_ops->set_link_ksettings(dev, &ksettings);
+		if (ret < 0)
+			GENL_SET_ERR_MSG(info, "link settings update failed");
+		else
+			ethtool_notify(dev, ETHTOOL_MSG_LINKINFO_NTF, NULL);
+	}
+
+out_ops:
+	ethnl_after_ops(dev);
+out_rtnl:
+	rtnl_unlock();
+	dev_put(dev);
+	return ret;
+}
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index dc52d912e0dd..5b9d12656e97 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -603,6 +603,11 @@ static int ethnl_get_done(struct netlink_callback *cb)
 
 static const struct get_request_ops *ethnl_std_notify_to_ops(unsigned int cmd)
 {
+	switch (cmd) {
+	case ETHTOOL_MSG_LINKINFO_NTF:
+		return &linkinfo_request_ops;
+	};
+
 	WARN_ONCE(1, "unexpected notification type %u\n", cmd);
 	return NULL;
 }
@@ -683,6 +688,7 @@ typedef void (*ethnl_notify_handler_t)(struct net_device *dev, unsigned int cmd,
 				       const void *data);
 
 static const ethnl_notify_handler_t ethnl_notify_handlers[] = {
+	[ETHTOOL_MSG_LINKINFO_NTF]	= ethnl_std_notify,
 };
 
 void ethtool_notify(struct net_device *dev, unsigned int cmd, const void *data)
@@ -717,6 +723,11 @@ static const struct genl_ops ethtool_genl_ops[] = {
 		.dumpit	= ethnl_get_dumpit,
 		.done	= ethnl_get_done,
 	},
+	{
+		.cmd	= ETHTOOL_MSG_LINKINFO_SET,
+		.flags	= GENL_UNS_ADMIN_PERM,
+		.doit	= ethnl_set_linkinfo,
+	},
 };
 
 static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 23e82a4dd265..ca136dd7ea02 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -350,4 +350,6 @@ struct get_request_ops {
 extern const struct get_request_ops strset_request_ops;
 extern const struct get_request_ops linkinfo_request_ops;
 
+int ethnl_set_linkinfo(struct sk_buff *skb, struct genl_info *info);
+
 #endif /* _NET_ETHTOOL_NETLINK_H */
-- 
2.23.0


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

* [PATCH net-next v7 15/17] ethtool: provide link mode information with LINKMODES_GET request
  2019-10-09 20:59 [PATCH net-next v7 00/17] ethtool netlink interface, part 1 Michal Kubecek
                   ` (13 preceding siblings ...)
  2019-10-09 20:59 ` [PATCH net-next v7 14/17] ethtool: set link settings with LINKINFO_SET request Michal Kubecek
@ 2019-10-09 20:59 ` Michal Kubecek
  2019-10-09 20:59 ` [PATCH net-next v7 16/17] ethtool: set link modes related data with LINKMODES_SET request Michal Kubecek
                   ` (2 subsequent siblings)
  17 siblings, 0 replies; 46+ messages in thread
From: Michal Kubecek @ 2019-10-09 20:59 UTC (permalink / raw)
  To: David Miller, netdev
  Cc: Jakub Kicinski, Jiri Pirko, Andrew Lunn, Florian Fainelli,
	John Linville, Stephen Hemminger, Johannes Berg, linux-kernel

Implement LINKMODES_GET netlink request to get link modes related
information provided by ETHTOOL_GLINKSETTINGS and ETHTOOL_GSET ioctl
commands.

This request provides supported, advertised and peer advertised link modes,
autonegotiation flag, speed and duplex.

LINKMODES_GET request can be used with NLM_F_DUMP (without device
identification) to request the information for all devices in current
network namespace providing the data.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Documentation/networking/ethtool-netlink.rst |  37 +++++
 include/linux/ethtool_netlink.h              |   3 +
 include/uapi/linux/ethtool_netlink.h         |  20 +++
 net/ethtool/Makefile                         |   2 +-
 net/ethtool/linkmodes.c                      | 163 +++++++++++++++++++
 net/ethtool/netlink.c                        |   8 +
 net/ethtool/netlink.h                        |   1 +
 7 files changed, 233 insertions(+), 1 deletion(-)
 create mode 100644 net/ethtool/linkmodes.c

diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst
index 48833b6092d9..e04c90ee526e 100644
--- a/Documentation/networking/ethtool-netlink.rst
+++ b/Documentation/networking/ethtool-netlink.rst
@@ -169,6 +169,7 @@ Userspace to kernel:
   ``ETHTOOL_MSG_STRSET_GET``            get string set
   ``ETHTOOL_MSG_LINKINFO_GET``          get link settings
   ``ETHTOOL_MSG_LINKINFO_SET``          set link settings
+  ``ETHTOOL_MSG_LINKMODES_GET``         get link modes info
   ===================================== ================================
 
 Kernel to userspace:
@@ -177,6 +178,7 @@ Kernel to userspace:
   ``ETHTOOL_MSG_STRSET_GET_REPLY``      string set contents
   ``ETHTOOL_MSG_LINKINFO_GET_REPLY``    link settings
   ``ETHTOOL_MSG_LINKINFO_NTF``          link settings notification
+  ``ETHTOOL_MSG_LINKMODES_GET_REPLY``   link modes info
   ===================================== ================================
 
 ``GET`` requests are sent by userspace applications to retrieve device
@@ -321,6 +323,39 @@ MDI(-X) status and transceiver cannot be set, request with the corresponding
 attributes is rejected.
 
 
+LINKMODES_GET
+=============
+
+Requests link modes (supported, advertised and peer advertised) and related
+information (autonegotiation status, link speed and duplex) as provided by
+``ETHTOOL_GLINKSETTINGS``. The request does not use any attributes and does
+not have any request specific flags.
+
+Request contents:
+
+  ====================================  ======  ==========================
+  ``ETHTOOL_A_LINKMODES_HEADER``        nested  request header
+  ====================================  ======  ==========================
+
+Kernel response contents:
+
+  ====================================  ======  ==========================
+  ``ETHTOOL_A_LINKMODES_HEADER``        nested  reply header
+  ``ETHTOOL_A_LINKMODES_AUTONEG``       u8      autonegotiation status
+  ``ETHTOOL_A_LINKMODES_OURS``          bitset  advertised link modes
+  ``ETHTOOL_A_LINKMODES_PEER``          bitset  partner link modes
+  ``ETHTOOL_A_LINKMODES_SPEED``         u32     link speed (Mb/s)
+  ``ETHTOOL_A_LINKMODES_DUPLEX``        u8      duplex mode
+  ====================================  ======  ==========================
+
+For ``ETHTOOL_A_LINKMODES_OURS``, value represents advertised modes and mask
+represents supported modes. ``ETHTOOL_A_LINKMODES_PEER`` in the reply is a bit
+list.
+
+``LINKMODES_GET`` allows dump requests (kernel returns reply messages for all
+devices supporting the request).
+
+
 Request translation
 ===================
 
@@ -332,6 +367,7 @@ have their netlink replacement yet.
   ioctl command                       netlink command
   =================================== =====================================
   ``ETHTOOL_GSET``                    ``ETHTOOL_MSG_LINKINFO_GET``
+                                      ``ETHTOOL_MSG_LINKMODES_GET``
   ``ETHTOOL_SSET``                    ``ETHTOOL_MSG_LINKINFO_SET``
   ``ETHTOOL_GDRVINFO``                n/a
   ``ETHTOOL_GREGS``                   n/a
@@ -406,6 +442,7 @@ have their netlink replacement yet.
   ``ETHTOOL_GPHYSTATS``               n/a
   ``ETHTOOL_PERQUEUE``                n/a
   ``ETHTOOL_GLINKSETTINGS``           ``ETHTOOL_MSG_LINKINFO_GET``
+                                      ``ETHTOOL_MSG_LINKMODES_GET``
   ``ETHTOOL_SLINKSETTINGS``           ``ETHTOOL_MSG_LINKINFO_SET``
   ``ETHTOOL_PHY_GTUNABLE``            n/a
   ``ETHTOOL_PHY_STUNABLE``            n/a
diff --git a/include/linux/ethtool_netlink.h b/include/linux/ethtool_netlink.h
index 2a15e64a16f3..e770e6e9acca 100644
--- a/include/linux/ethtool_netlink.h
+++ b/include/linux/ethtool_netlink.h
@@ -7,6 +7,9 @@
 #include <linux/ethtool.h>
 #include <linux/netdevice.h>
 
+#define __ETHTOOL_LINK_MODE_MASK_NWORDS \
+	DIV_ROUND_UP(__ETHTOOL_LINK_MODE_MASK_NBITS, 32)
+
 enum ethtool_multicast_groups {
 	ETHNL_MCGRP_MONITOR,
 };
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index 78ee5584d5da..d8ec49aebe48 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -17,6 +17,7 @@ enum {
 	ETHTOOL_MSG_STRSET_GET,
 	ETHTOOL_MSG_LINKINFO_GET,
 	ETHTOOL_MSG_LINKINFO_SET,
+	ETHTOOL_MSG_LINKMODES_GET,
 
 	/* add new constants above here */
 	__ETHTOOL_MSG_USER_CNT,
@@ -29,6 +30,7 @@ enum {
 	ETHTOOL_MSG_STRSET_GET_REPLY,
 	ETHTOOL_MSG_LINKINFO_GET_REPLY,
 	ETHTOOL_MSG_LINKINFO_NTF,
+	ETHTOOL_MSG_LINKMODES_GET_REPLY,
 
 	/* add new constants above here */
 	__ETHTOOL_MSG_KERNEL_CNT,
@@ -168,6 +170,24 @@ enum {
 
 #define ETHTOOL_RFLAG_LINKINFO_ALL 0
 
+/* LINKMODES */
+
+enum {
+	ETHTOOL_A_LINKMODES_UNSPEC,
+	ETHTOOL_A_LINKMODES_HEADER,		/* nest - _A_HEADER_* */
+	ETHTOOL_A_LINKMODES_AUTONEG,		/* u8 */
+	ETHTOOL_A_LINKMODES_OURS,		/* bitset */
+	ETHTOOL_A_LINKMODES_PEER,		/* bitset */
+	ETHTOOL_A_LINKMODES_SPEED,		/* u32 */
+	ETHTOOL_A_LINKMODES_DUPLEX,		/* u8 */
+
+	/* add new constants above here */
+	__ETHTOOL_A_LINKMODES_CNT,
+	ETHTOOL_A_LINKMODES_MAX = __ETHTOOL_A_LINKMODES_CNT - 1
+};
+
+#define ETHTOOL_RFLAG_LINKMODES_ALL 0
+
 /* generic netlink info */
 #define ETHTOOL_GENL_NAME "ethtool"
 #define ETHTOOL_GENL_VERSION 1
diff --git a/net/ethtool/Makefile b/net/ethtool/Makefile
index 28666a0eede8..9ae0786c343b 100644
--- a/net/ethtool/Makefile
+++ b/net/ethtool/Makefile
@@ -4,4 +4,4 @@ obj-y				+= ioctl.o common.o
 
 obj-$(CONFIG_ETHTOOL_NETLINK)	+= ethtool_nl.o
 
-ethtool_nl-y	:= netlink.o bitset.o strset.o linkinfo.o
+ethtool_nl-y	:= netlink.o bitset.o strset.o linkinfo.o linkmodes.o
diff --git a/net/ethtool/linkmodes.c b/net/ethtool/linkmodes.c
new file mode 100644
index 000000000000..3441f29b8e67
--- /dev/null
+++ b/net/ethtool/linkmodes.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
+
+#include "netlink.h"
+#include "common.h"
+#include "bitset.h"
+
+struct linkmodes_req_info {
+	struct ethnl_req_info		base;
+};
+
+struct linkmodes_reply_data {
+	struct ethnl_reply_data		base;
+	struct ethtool_link_ksettings	ksettings;
+	struct ethtool_link_settings	*lsettings;
+	bool				peer_empty;
+};
+
+static const struct nla_policy
+linkmodes_get_policy[ETHTOOL_A_LINKMODES_MAX + 1] = {
+	[ETHTOOL_A_LINKMODES_UNSPEC]		= { .type = NLA_REJECT },
+	[ETHTOOL_A_LINKMODES_HEADER]		= { .type = NLA_NESTED },
+	[ETHTOOL_A_LINKMODES_AUTONEG]		= { .type = NLA_REJECT },
+	[ETHTOOL_A_LINKMODES_OURS]		= { .type = NLA_REJECT },
+	[ETHTOOL_A_LINKMODES_PEER]		= { .type = NLA_REJECT },
+	[ETHTOOL_A_LINKMODES_SPEED]		= { .type = NLA_REJECT },
+	[ETHTOOL_A_LINKMODES_DUPLEX]		= { .type = NLA_REJECT },
+};
+
+/* prepare_data() handler */
+static int linkmodes_prepare(const struct ethnl_req_info *req_base,
+			     struct ethnl_reply_data *reply_base,
+			     struct genl_info *info)
+{
+	struct linkmodes_reply_data *data =
+		container_of(reply_base, struct linkmodes_reply_data, base);
+	struct net_device *dev = reply_base->dev;
+	int ret;
+
+	data->lsettings = &data->ksettings.base;
+
+	ret = ethnl_before_ops(dev);
+	if (ret < 0)
+		return ret;
+
+	ret = __ethtool_get_link_ksettings(dev, &data->ksettings);
+	if (ret < 0 && info) {
+		GENL_SET_ERR_MSG(info, "failed to retrieve link settings");
+		goto out;
+	}
+
+	data->peer_empty =
+		bitmap_empty(data->ksettings.link_modes.lp_advertising,
+			     __ETHTOOL_LINK_MODE_MASK_NBITS);
+	ethnl_bitmap_to_u32(data->ksettings.link_modes.supported,
+			    __ETHTOOL_LINK_MODE_MASK_NWORDS);
+	ethnl_bitmap_to_u32(data->ksettings.link_modes.advertising,
+			    __ETHTOOL_LINK_MODE_MASK_NWORDS);
+	ethnl_bitmap_to_u32(data->ksettings.link_modes.lp_advertising,
+			    __ETHTOOL_LINK_MODE_MASK_NWORDS);
+
+out:
+	ethnl_after_ops(dev);
+	return ret;
+}
+
+/* reply_size() handler */
+static int linkmodes_size(const struct ethnl_req_info *req_base,
+			  const struct ethnl_reply_data *reply_base)
+{
+	const struct linkmodes_reply_data *data =
+		container_of(reply_base, struct linkmodes_reply_data, base);
+	const struct ethtool_link_ksettings *ksettings = &data->ksettings;
+	const u32 *advertising;
+	const u32 *supported;
+	const u32 *lp_adv;
+	bool compact;
+	int len, ret;
+
+	supported = (const u32 *)ksettings->link_modes.supported;
+	advertising = (const u32 *)ksettings->link_modes.advertising;
+	lp_adv = (const u32 *)ksettings->link_modes.lp_advertising;
+	compact = req_base->global_flags & ETHTOOL_GFLAG_COMPACT_BITSETS;
+
+	len = nla_total_size(sizeof(u8)) /* LINKMODES_AUTONEG */
+		+ nla_total_size(sizeof(u32)) /* LINKMODES_SPEED */
+		+ nla_total_size(sizeof(u8)) /* LINKMODES_DUPLEX */
+		+ 0;
+	ret = ethnl_bitset32_size(advertising, supported,
+				  __ETHTOOL_LINK_MODE_MASK_NBITS,
+				  link_mode_names, compact);
+	if (ret < 0)
+		return ret;
+	len += ret;
+	if (!data->peer_empty) {
+		ret = ethnl_bitset32_size(lp_adv, NULL,
+					  __ETHTOOL_LINK_MODE_MASK_NBITS,
+					  link_mode_names, compact);
+		if (ret < 0)
+			return ret;
+		len += ret;
+	}
+
+	return len;
+}
+
+/* fill_reply() handler */
+static int linkmodes_fill(struct sk_buff *skb,
+			  const struct ethnl_req_info *req_base,
+			  const struct ethnl_reply_data *reply_base)
+{
+	const struct linkmodes_reply_data *data =
+		container_of(reply_base, struct linkmodes_reply_data, base);
+	const struct ethtool_link_ksettings *ksettings = &data->ksettings;
+	const struct ethtool_link_settings *lsettings = &ksettings->base;
+	const u32 *advertising;
+	const u32 *supported;
+	const u32 *lp_adv;
+	bool compact;
+	int ret;
+
+	supported = (const u32 *)ksettings->link_modes.supported;
+	advertising = (const u32 *)ksettings->link_modes.advertising;
+	lp_adv = (const u32 *)ksettings->link_modes.lp_advertising;
+	compact = req_base->global_flags & ETHTOOL_GFLAG_COMPACT_BITSETS;
+
+	if (nla_put_u8(skb, ETHTOOL_A_LINKMODES_AUTONEG, lsettings->autoneg))
+		return -EMSGSIZE;
+
+	ret = ethnl_put_bitset32(skb, ETHTOOL_A_LINKMODES_OURS, advertising,
+				 supported, __ETHTOOL_LINK_MODE_MASK_NBITS,
+				 link_mode_names, compact);
+	if (ret < 0)
+		return -EMSGSIZE;
+	if (!data->peer_empty) {
+		ret = ethnl_put_bitset32(skb, ETHTOOL_A_LINKMODES_PEER,
+					 lp_adv, NULL,
+					 __ETHTOOL_LINK_MODE_MASK_NBITS,
+					 link_mode_names, compact);
+		if (ret < 0)
+			return -EMSGSIZE;
+	}
+
+	if (nla_put_u32(skb, ETHTOOL_A_LINKMODES_SPEED, lsettings->speed) ||
+	    nla_put_u8(skb, ETHTOOL_A_LINKMODES_DUPLEX, lsettings->duplex))
+		return -EMSGSIZE;
+
+	return 0;
+}
+
+const struct get_request_ops linkmodes_request_ops = {
+	.request_cmd		= ETHTOOL_MSG_LINKMODES_GET,
+	.reply_cmd		= ETHTOOL_MSG_LINKMODES_GET_REPLY,
+	.hdr_attr		= ETHTOOL_A_LINKMODES_HEADER,
+	.max_attr		= ETHTOOL_A_LINKMODES_MAX,
+	.req_info_size		= sizeof(struct linkmodes_req_info),
+	.reply_data_size	= sizeof(struct linkmodes_reply_data),
+	.request_policy		= linkmodes_get_policy,
+	.all_reqflags		= ETHTOOL_RFLAG_LINKMODES_ALL,
+
+	.prepare_data		= linkmodes_prepare,
+	.reply_size		= linkmodes_size,
+	.fill_reply		= linkmodes_fill,
+};
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 5b9d12656e97..73d990b4d5c1 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -294,6 +294,7 @@ struct ethnl_dump_ctx {
 static const struct get_request_ops *get_requests[__ETHTOOL_MSG_USER_CNT] = {
 	[ETHTOOL_MSG_STRSET_GET]	= &strset_request_ops,
 	[ETHTOOL_MSG_LINKINFO_GET]	= &linkinfo_request_ops,
+	[ETHTOOL_MSG_LINKMODES_GET]	= &linkmodes_request_ops,
 };
 
 static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)
@@ -728,6 +729,13 @@ static const struct genl_ops ethtool_genl_ops[] = {
 		.flags	= GENL_UNS_ADMIN_PERM,
 		.doit	= ethnl_set_linkinfo,
 	},
+	{
+		.cmd	= ETHTOOL_MSG_LINKMODES_GET,
+		.doit	= ethnl_get_doit,
+		.start	= ethnl_get_start,
+		.dumpit	= ethnl_get_dumpit,
+		.done	= ethnl_get_done,
+	},
 };
 
 static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index ca136dd7ea02..b330b29fe927 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -349,6 +349,7 @@ struct get_request_ops {
 
 extern const struct get_request_ops strset_request_ops;
 extern const struct get_request_ops linkinfo_request_ops;
+extern const struct get_request_ops linkmodes_request_ops;
 
 int ethnl_set_linkinfo(struct sk_buff *skb, struct genl_info *info);
 
-- 
2.23.0


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

* [PATCH net-next v7 16/17] ethtool: set link modes related data with LINKMODES_SET request
  2019-10-09 20:59 [PATCH net-next v7 00/17] ethtool netlink interface, part 1 Michal Kubecek
                   ` (14 preceding siblings ...)
  2019-10-09 20:59 ` [PATCH net-next v7 15/17] ethtool: provide link mode information with LINKMODES_GET request Michal Kubecek
@ 2019-10-09 20:59 ` Michal Kubecek
  2019-10-09 20:59 ` [PATCH net-next v7 17/17] ethtool: provide link state with LINKSTATE_GET request Michal Kubecek
  2019-10-11  0:48 ` [PATCH net-next v7 00/17] ethtool netlink interface, part 1 Jakub Kicinski
  17 siblings, 0 replies; 46+ messages in thread
From: Michal Kubecek @ 2019-10-09 20:59 UTC (permalink / raw)
  To: David Miller, netdev
  Cc: Jakub Kicinski, Jiri Pirko, Andrew Lunn, Florian Fainelli,
	John Linville, Stephen Hemminger, Johannes Berg, linux-kernel

Implement LINKMODES_SET netlink request to set advertised linkmodes and
related attributes as ETHTOOL_SLINKSETTINGS and ETHTOOL_SSET commands do.

The request allows setting autonegotiation flag, speed, duplex and
advertised link modes.

When any data is modified, ETHTOOL_MSG_LINKMODES_NTF message in the same
format as reply to LINKINFO_GET request is sent to notify userspace about
the changes. The same notification is also sent when these settings are
modified using the ioctl interface.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Documentation/networking/ethtool-netlink.rst |  28 +++
 include/uapi/linux/ethtool_netlink.h         |   2 +
 net/ethtool/ioctl.c                          |   8 +-
 net/ethtool/linkmodes.c                      | 243 +++++++++++++++++++
 net/ethtool/netlink.c                        |   8 +
 net/ethtool/netlink.h                        |   1 +
 6 files changed, 288 insertions(+), 2 deletions(-)

diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst
index e04c90ee526e..ff5a607ca182 100644
--- a/Documentation/networking/ethtool-netlink.rst
+++ b/Documentation/networking/ethtool-netlink.rst
@@ -170,6 +170,7 @@ Userspace to kernel:
   ``ETHTOOL_MSG_LINKINFO_GET``          get link settings
   ``ETHTOOL_MSG_LINKINFO_SET``          set link settings
   ``ETHTOOL_MSG_LINKMODES_GET``         get link modes info
+  ``ETHTOOL_MSG_LINKMODES_SET``         set link modes info
   ===================================== ================================
 
 Kernel to userspace:
@@ -179,6 +180,7 @@ Kernel to userspace:
   ``ETHTOOL_MSG_LINKINFO_GET_REPLY``    link settings
   ``ETHTOOL_MSG_LINKINFO_NTF``          link settings notification
   ``ETHTOOL_MSG_LINKMODES_GET_REPLY``   link modes info
+  ``ETHTOOL_MSG_LINKMODES_NTF``         link modes notification
   ===================================== ================================
 
 ``GET`` requests are sent by userspace applications to retrieve device
@@ -356,6 +358,30 @@ list.
 devices supporting the request).
 
 
+LINKMODES_SET
+=============
+
+Request contents:
+
+  ====================================  ======  ==========================
+  ``ETHTOOL_A_LINKMODES_HEADER``        nested  request header
+  ``ETHTOOL_A_LINKMODES_AUTONEG``       u8      autonegotiation status
+  ``ETHTOOL_A_LINKMODES_OURS``          bitset  advertised link modes
+  ``ETHTOOL_A_LINKMODES_PEER``          bitset  partner link modes
+  ``ETHTOOL_A_LINKMODES_SPEED``         u32     link speed (Mb/s)
+  ``ETHTOOL_A_LINKMODES_DUPLEX``        u8      duplex mode
+  ====================================  ======  ==========================
+
+``ETHTOOL_A_LINKMODES_OURS`` bit set allows setting advertised link modes. If
+autonegotiation is on (either set now or kept from before), advertised modes
+are not changed (no ``ETHTOOL_A_LINKMODES_OURS`` attribute) and at least one
+of speed and duplex is specified, kernel adjusts advertised modes to all
+supported modes matching speed, duplex or both (whatever is specified). This
+autoselection is done on ethtool side with ioctl interface, netlink interface
+is supposed to allow requesting changes without knowing what exactly kernel
+supports.
+
+
 Request translation
 ===================
 
@@ -369,6 +395,7 @@ have their netlink replacement yet.
   ``ETHTOOL_GSET``                    ``ETHTOOL_MSG_LINKINFO_GET``
                                       ``ETHTOOL_MSG_LINKMODES_GET``
   ``ETHTOOL_SSET``                    ``ETHTOOL_MSG_LINKINFO_SET``
+                                      ``ETHTOOL_MSG_LINKMODES_SET``
   ``ETHTOOL_GDRVINFO``                n/a
   ``ETHTOOL_GREGS``                   n/a
   ``ETHTOOL_GWOL``                    n/a
@@ -444,6 +471,7 @@ have their netlink replacement yet.
   ``ETHTOOL_GLINKSETTINGS``           ``ETHTOOL_MSG_LINKINFO_GET``
                                       ``ETHTOOL_MSG_LINKMODES_GET``
   ``ETHTOOL_SLINKSETTINGS``           ``ETHTOOL_MSG_LINKINFO_SET``
+                                      ``ETHTOOL_MSG_LINKMODES_SET``
   ``ETHTOOL_PHY_GTUNABLE``            n/a
   ``ETHTOOL_PHY_STUNABLE``            n/a
   ``ETHTOOL_GFECPARAM``               n/a
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index d8ec49aebe48..19c7c55c6483 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -18,6 +18,7 @@ enum {
 	ETHTOOL_MSG_LINKINFO_GET,
 	ETHTOOL_MSG_LINKINFO_SET,
 	ETHTOOL_MSG_LINKMODES_GET,
+	ETHTOOL_MSG_LINKMODES_SET,
 
 	/* add new constants above here */
 	__ETHTOOL_MSG_USER_CNT,
@@ -31,6 +32,7 @@ enum {
 	ETHTOOL_MSG_LINKINFO_GET_REPLY,
 	ETHTOOL_MSG_LINKINFO_NTF,
 	ETHTOOL_MSG_LINKMODES_GET_REPLY,
+	ETHTOOL_MSG_LINKMODES_NTF,
 
 	/* add new constants above here */
 	__ETHTOOL_MSG_KERNEL_CNT,
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index 0747fe6b5f4c..7768c2e99a29 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -567,8 +567,10 @@ static int ethtool_set_link_ksettings(struct net_device *dev,
 		return -EINVAL;
 
 	err = dev->ethtool_ops->set_link_ksettings(dev, &link_ksettings);
-	if (err >= 0)
+	if (err >= 0) {
 		ethtool_notify(dev, ETHTOOL_MSG_LINKINFO_NTF, NULL);
+		ethtool_notify(dev, ETHTOOL_MSG_LINKMODES_NTF, NULL);
+	}
 	return err;
 }
 
@@ -632,8 +634,10 @@ static int ethtool_set_settings(struct net_device *dev, void __user *useraddr)
 	link_ksettings.base.link_mode_masks_nwords =
 		__ETHTOOL_LINK_MODE_MASK_NU32;
 	ret = dev->ethtool_ops->set_link_ksettings(dev, &link_ksettings);
-	if (ret >= 0)
+	if (ret >= 0) {
 		ethtool_notify(dev, ETHTOOL_MSG_LINKINFO_NTF, NULL);
+		ethtool_notify(dev, ETHTOOL_MSG_LINKMODES_NTF, NULL);
+	}
 	return ret;
 }
 
diff --git a/net/ethtool/linkmodes.c b/net/ethtool/linkmodes.c
index 3441f29b8e67..1bfe53a43e69 100644
--- a/net/ethtool/linkmodes.c
+++ b/net/ethtool/linkmodes.c
@@ -161,3 +161,246 @@ const struct get_request_ops linkmodes_request_ops = {
 	.reply_size		= linkmodes_size,
 	.fill_reply		= linkmodes_fill,
 };
+
+/* LINKMODES_SET */
+
+struct link_mode_info {
+	int				speed;
+	u8				duplex;
+};
+
+#define __DEFINE_LINK_MODE_PARAMS(_speed, _type, _duplex) \
+	[ETHTOOL_LINK_MODE(_speed, _type, _duplex)] = { \
+		.speed	= SPEED_ ## _speed, \
+		.duplex	= __DUPLEX_ ## _duplex \
+	}
+#define __DUPLEX_Half DUPLEX_HALF
+#define __DUPLEX_Full DUPLEX_FULL
+#define __DEFINE_SPECIAL_MODE_PARAMS(_mode) \
+	[ETHTOOL_LINK_MODE_ ## _mode ## _BIT] = { \
+		.speed	= SPEED_UNKNOWN, \
+		.duplex	= DUPLEX_UNKNOWN, \
+	}
+
+static const struct link_mode_info link_mode_params[] = {
+	__DEFINE_LINK_MODE_PARAMS(10, T, Half),
+	__DEFINE_LINK_MODE_PARAMS(10, T, Full),
+	__DEFINE_LINK_MODE_PARAMS(100, T, Half),
+	__DEFINE_LINK_MODE_PARAMS(100, T, Full),
+	__DEFINE_LINK_MODE_PARAMS(1000, T, Half),
+	__DEFINE_LINK_MODE_PARAMS(1000, T, Full),
+	__DEFINE_SPECIAL_MODE_PARAMS(Autoneg),
+	__DEFINE_SPECIAL_MODE_PARAMS(TP),
+	__DEFINE_SPECIAL_MODE_PARAMS(AUI),
+	__DEFINE_SPECIAL_MODE_PARAMS(MII),
+	__DEFINE_SPECIAL_MODE_PARAMS(FIBRE),
+	__DEFINE_SPECIAL_MODE_PARAMS(BNC),
+	__DEFINE_LINK_MODE_PARAMS(10000, T, Full),
+	__DEFINE_SPECIAL_MODE_PARAMS(Pause),
+	__DEFINE_SPECIAL_MODE_PARAMS(Asym_Pause),
+	__DEFINE_LINK_MODE_PARAMS(2500, X, Full),
+	__DEFINE_SPECIAL_MODE_PARAMS(Backplane),
+	__DEFINE_LINK_MODE_PARAMS(1000, KX, Full),
+	__DEFINE_LINK_MODE_PARAMS(10000, KX4, Full),
+	__DEFINE_LINK_MODE_PARAMS(10000, KR, Full),
+	[ETHTOOL_LINK_MODE_10000baseR_FEC_BIT] = {
+		.speed	= SPEED_10000,
+		.duplex = DUPLEX_FULL,
+	},
+	__DEFINE_LINK_MODE_PARAMS(20000, MLD2, Full),
+	__DEFINE_LINK_MODE_PARAMS(20000, KR2, Full),
+	__DEFINE_LINK_MODE_PARAMS(40000, KR4, Full),
+	__DEFINE_LINK_MODE_PARAMS(40000, CR4, Full),
+	__DEFINE_LINK_MODE_PARAMS(40000, SR4, Full),
+	__DEFINE_LINK_MODE_PARAMS(40000, LR4, Full),
+	__DEFINE_LINK_MODE_PARAMS(56000, KR4, Full),
+	__DEFINE_LINK_MODE_PARAMS(56000, CR4, Full),
+	__DEFINE_LINK_MODE_PARAMS(56000, SR4, Full),
+	__DEFINE_LINK_MODE_PARAMS(56000, LR4, Full),
+	__DEFINE_LINK_MODE_PARAMS(25000, CR, Full),
+	__DEFINE_LINK_MODE_PARAMS(25000, KR, Full),
+	__DEFINE_LINK_MODE_PARAMS(25000, SR, Full),
+	__DEFINE_LINK_MODE_PARAMS(50000, CR2, Full),
+	__DEFINE_LINK_MODE_PARAMS(50000, KR2, Full),
+	__DEFINE_LINK_MODE_PARAMS(100000, KR4, Full),
+	__DEFINE_LINK_MODE_PARAMS(100000, SR4, Full),
+	__DEFINE_LINK_MODE_PARAMS(100000, CR4, Full),
+	__DEFINE_LINK_MODE_PARAMS(100000, LR4_ER4, Full),
+	__DEFINE_LINK_MODE_PARAMS(50000, SR2, Full),
+	__DEFINE_LINK_MODE_PARAMS(1000, X, Full),
+	__DEFINE_LINK_MODE_PARAMS(10000, CR, Full),
+	__DEFINE_LINK_MODE_PARAMS(10000, SR, Full),
+	__DEFINE_LINK_MODE_PARAMS(10000, LR, Full),
+	__DEFINE_LINK_MODE_PARAMS(10000, LRM, Full),
+	__DEFINE_LINK_MODE_PARAMS(10000, ER, Full),
+	__DEFINE_LINK_MODE_PARAMS(2500, T, Full),
+	__DEFINE_LINK_MODE_PARAMS(5000, T, Full),
+	__DEFINE_SPECIAL_MODE_PARAMS(FEC_NONE),
+	__DEFINE_SPECIAL_MODE_PARAMS(FEC_RS),
+	__DEFINE_SPECIAL_MODE_PARAMS(FEC_BASER),
+	__DEFINE_LINK_MODE_PARAMS(50000, KR, Full),
+	__DEFINE_LINK_MODE_PARAMS(50000, SR, Full),
+	__DEFINE_LINK_MODE_PARAMS(50000, CR, Full),
+	__DEFINE_LINK_MODE_PARAMS(50000, LR_ER_FR, Full),
+	__DEFINE_LINK_MODE_PARAMS(50000, DR, Full),
+	__DEFINE_LINK_MODE_PARAMS(100000, KR2, Full),
+	__DEFINE_LINK_MODE_PARAMS(100000, SR2, Full),
+	__DEFINE_LINK_MODE_PARAMS(100000, CR2, Full),
+	__DEFINE_LINK_MODE_PARAMS(100000, LR2_ER2_FR2, Full),
+	__DEFINE_LINK_MODE_PARAMS(100000, DR2, Full),
+	__DEFINE_LINK_MODE_PARAMS(200000, KR4, Full),
+	__DEFINE_LINK_MODE_PARAMS(200000, SR4, Full),
+	__DEFINE_LINK_MODE_PARAMS(200000, LR4_ER4_FR4, Full),
+	__DEFINE_LINK_MODE_PARAMS(200000, DR4, Full),
+	__DEFINE_LINK_MODE_PARAMS(200000, CR4, Full),
+	__DEFINE_LINK_MODE_PARAMS(100, T1, Full),
+	__DEFINE_LINK_MODE_PARAMS(1000, T1, Full),
+};
+
+static const struct nla_policy
+linkmodes_hdr_policy[ETHTOOL_A_HEADER_MAX + 1] = {
+	[ETHTOOL_A_HEADER_UNSPEC]		= { .type = NLA_REJECT },
+	[ETHTOOL_A_HEADER_DEV_INDEX]		= { .type = NLA_U32 },
+	[ETHTOOL_A_HEADER_DEV_NAME]		= { .type = NLA_NUL_STRING,
+						    .len = IFNAMSIZ - 1 },
+	[ETHTOOL_A_HEADER_GFLAGS]		= { .type = NLA_U32 },
+	[ETHTOOL_A_HEADER_RFLAGS]		= { .type = NLA_REJECT },
+};
+
+static const struct nla_policy
+linkmodes_set_policy[ETHTOOL_A_LINKMODES_MAX + 1] = {
+	[ETHTOOL_A_LINKMODES_UNSPEC]		= { .type = NLA_REJECT },
+	[ETHTOOL_A_LINKMODES_HEADER]		= { .type = NLA_NESTED },
+	[ETHTOOL_A_LINKMODES_AUTONEG]		= { .type = NLA_U8 },
+	[ETHTOOL_A_LINKMODES_OURS]		= { .type = NLA_NESTED },
+	[ETHTOOL_A_LINKMODES_PEER]		= { .type = NLA_REJECT },
+	[ETHTOOL_A_LINKMODES_SPEED]		= { .type = NLA_U32 },
+	[ETHTOOL_A_LINKMODES_DUPLEX]		= { .type = NLA_U8 },
+};
+
+/* Set advertised link modes to all supported modes matching requested speed
+ * and duplex values. Called when autonegotiation is on, speed or duplex is
+ * requested but no link mode change. This is done in userspace with ioctl()
+ * interface, move it into kernel for netlink.
+ * Returns true if advertised modes bitmap was modified.
+ */
+static bool ethnl_auto_linkmodes(struct ethtool_link_ksettings *ksettings,
+				 bool req_speed, bool req_duplex)
+{
+	unsigned long *advertising = ksettings->link_modes.advertising;
+	unsigned long *supported = ksettings->link_modes.supported;
+	DECLARE_BITMAP(old_adv, __ETHTOOL_LINK_MODE_MASK_NBITS);
+	unsigned int i;
+
+	BUILD_BUG_ON(ARRAY_SIZE(link_mode_params) !=
+		     __ETHTOOL_LINK_MODE_MASK_NBITS);
+
+	bitmap_copy(old_adv, advertising, __ETHTOOL_LINK_MODE_MASK_NBITS);
+
+	for (i = 0; i < __ETHTOOL_LINK_MODE_MASK_NBITS; i++) {
+		const struct link_mode_info *info = &link_mode_params[i];
+
+		if (info->speed == SPEED_UNKNOWN)
+			continue;
+		if (test_bit(i, supported) &&
+		    (!req_speed || info->speed == ksettings->base.speed) &&
+		    (!req_duplex || info->duplex == ksettings->base.duplex))
+			set_bit(i, advertising);
+		else
+			clear_bit(i, advertising);
+	}
+
+	return !bitmap_equal(old_adv, advertising,
+			     __ETHTOOL_LINK_MODE_MASK_NBITS);
+}
+
+static int ethnl_update_linkmodes(struct genl_info *info, struct nlattr **tb,
+				  struct ethtool_link_ksettings *ksettings,
+				  bool *mod)
+{
+	struct ethtool_link_settings *lsettings = &ksettings->base;
+	bool req_speed, req_duplex;
+	int ret;
+
+	*mod = false;
+	req_speed = tb[ETHTOOL_A_LINKMODES_SPEED];
+	req_duplex = tb[ETHTOOL_A_LINKMODES_DUPLEX];
+
+	ethnl_update_u8(&lsettings->autoneg, tb[ETHTOOL_A_LINKMODES_AUTONEG],
+			mod);
+	ret = ethnl_update_bitset(ksettings->link_modes.advertising,
+				  __ETHTOOL_LINK_MODE_MASK_NBITS,
+				  tb[ETHTOOL_A_LINKMODES_OURS], link_mode_names,
+				  info->extack, mod);
+	if (ret < 0)
+		return ret;
+	ethnl_update_u32(&lsettings->speed, tb[ETHTOOL_A_LINKMODES_SPEED],
+			 mod);
+	ethnl_update_u8(&lsettings->duplex, tb[ETHTOOL_A_LINKMODES_DUPLEX],
+			mod);
+
+	if (!tb[ETHTOOL_A_LINKMODES_OURS] && lsettings->autoneg &&
+	    (req_speed || req_duplex) &&
+	    ethnl_auto_linkmodes(ksettings, req_speed, req_duplex))
+		*mod = true;
+
+	return 0;
+}
+
+int ethnl_set_linkmodes(struct sk_buff *skb, struct genl_info *info)
+{
+	struct nlattr *tb[ETHTOOL_A_LINKMODES_MAX + 1];
+	struct ethtool_link_ksettings ksettings = {};
+	struct ethtool_link_settings *lsettings;
+	struct ethnl_req_info req_info = {};
+	struct net_device *dev;
+	bool mod = false;
+	int ret;
+
+	ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb,
+			  ETHTOOL_A_LINKMODES_MAX, linkmodes_set_policy,
+			  info->extack);
+	if (ret < 0)
+		return ret;
+	ret = ethnl_parse_header(&req_info, tb[ETHTOOL_A_LINKMODES_HEADER],
+				 genl_info_net(info), info->extack,
+				 linkmodes_hdr_policy, true);
+	if (ret < 0)
+		return ret;
+	dev = req_info.dev;
+	if (!dev->ethtool_ops->get_link_ksettings ||
+	    !dev->ethtool_ops->set_link_ksettings)
+		return -EOPNOTSUPP;
+
+	rtnl_lock();
+	ret = ethnl_before_ops(dev);
+	if (ret < 0)
+		goto out_rtnl;
+
+	ret = __ethtool_get_link_ksettings(dev, &ksettings);
+	if (ret < 0) {
+		if (info)
+			GENL_SET_ERR_MSG(info, "failed to retrieve link settings");
+		goto out_ops;
+	}
+	lsettings = &ksettings.base;
+
+	ret = ethnl_update_linkmodes(info, tb, &ksettings, &mod);
+	if (ret < 0)
+		goto out_ops;
+
+	if (mod) {
+		ret = dev->ethtool_ops->set_link_ksettings(dev, &ksettings);
+		if (ret < 0)
+			GENL_SET_ERR_MSG(info, "link settings update failed");
+		else
+			ethtool_notify(dev, ETHTOOL_MSG_LINKMODES_NTF, NULL);
+	}
+
+out_ops:
+	ethnl_after_ops(dev);
+out_rtnl:
+	rtnl_unlock();
+	dev_put(dev);
+	return ret;
+}
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 73d990b4d5c1..7976200e5e35 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -607,6 +607,8 @@ static const struct get_request_ops *ethnl_std_notify_to_ops(unsigned int cmd)
 	switch (cmd) {
 	case ETHTOOL_MSG_LINKINFO_NTF:
 		return &linkinfo_request_ops;
+	case ETHTOOL_MSG_LINKMODES_NTF:
+		return &linkmodes_request_ops;
 	};
 
 	WARN_ONCE(1, "unexpected notification type %u\n", cmd);
@@ -690,6 +692,7 @@ typedef void (*ethnl_notify_handler_t)(struct net_device *dev, unsigned int cmd,
 
 static const ethnl_notify_handler_t ethnl_notify_handlers[] = {
 	[ETHTOOL_MSG_LINKINFO_NTF]	= ethnl_std_notify,
+	[ETHTOOL_MSG_LINKMODES_NTF]	= ethnl_std_notify,
 };
 
 void ethtool_notify(struct net_device *dev, unsigned int cmd, const void *data)
@@ -736,6 +739,11 @@ static const struct genl_ops ethtool_genl_ops[] = {
 		.dumpit	= ethnl_get_dumpit,
 		.done	= ethnl_get_done,
 	},
+	{
+		.cmd	= ETHTOOL_MSG_LINKMODES_SET,
+		.flags	= GENL_UNS_ADMIN_PERM,
+		.doit	= ethnl_set_linkmodes,
+	},
 };
 
 static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index b330b29fe927..88026572567c 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -352,5 +352,6 @@ extern const struct get_request_ops linkinfo_request_ops;
 extern const struct get_request_ops linkmodes_request_ops;
 
 int ethnl_set_linkinfo(struct sk_buff *skb, struct genl_info *info);
+int ethnl_set_linkmodes(struct sk_buff *skb, struct genl_info *info);
 
 #endif /* _NET_ETHTOOL_NETLINK_H */
-- 
2.23.0


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

* [PATCH net-next v7 17/17] ethtool: provide link state with LINKSTATE_GET request
  2019-10-09 20:59 [PATCH net-next v7 00/17] ethtool netlink interface, part 1 Michal Kubecek
                   ` (15 preceding siblings ...)
  2019-10-09 20:59 ` [PATCH net-next v7 16/17] ethtool: set link modes related data with LINKMODES_SET request Michal Kubecek
@ 2019-10-09 20:59 ` Michal Kubecek
  2019-10-11  0:48 ` [PATCH net-next v7 00/17] ethtool netlink interface, part 1 Jakub Kicinski
  17 siblings, 0 replies; 46+ messages in thread
From: Michal Kubecek @ 2019-10-09 20:59 UTC (permalink / raw)
  To: David Miller, netdev
  Cc: Jakub Kicinski, Jiri Pirko, Andrew Lunn, Florian Fainelli,
	John Linville, Stephen Hemminger, Johannes Berg, linux-kernel

Implement LINKSTATE_GET netlink request to get link state information.

At the moment, only link up flag as provided by ETHTOOL_GLINK ioctl command
is returned.

LINKSTATE_GET request can be used with NLM_F_DUMP (without device
identification) to request the information for all devices in current
network namespace providing the data.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Documentation/networking/ethtool-netlink.rst | 33 ++++++++-
 include/uapi/linux/ethtool_netlink.h         | 16 ++++
 net/ethtool/Makefile                         |  3 +-
 net/ethtool/common.c                         |  8 ++
 net/ethtool/common.h                         |  3 +
 net/ethtool/ioctl.c                          |  8 +-
 net/ethtool/linkstate.c                      | 77 ++++++++++++++++++++
 net/ethtool/netlink.c                        |  8 ++
 net/ethtool/netlink.h                        |  1 +
 9 files changed, 151 insertions(+), 6 deletions(-)
 create mode 100644 net/ethtool/linkstate.c

diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst
index ff5a607ca182..48ace5e97bbe 100644
--- a/Documentation/networking/ethtool-netlink.rst
+++ b/Documentation/networking/ethtool-netlink.rst
@@ -171,6 +171,7 @@ Userspace to kernel:
   ``ETHTOOL_MSG_LINKINFO_SET``          set link settings
   ``ETHTOOL_MSG_LINKMODES_GET``         get link modes info
   ``ETHTOOL_MSG_LINKMODES_SET``         set link modes info
+  ``ETHTOOL_MSG_LINKSTATE_GET``         get link state
   ===================================== ================================
 
 Kernel to userspace:
@@ -181,6 +182,7 @@ Kernel to userspace:
   ``ETHTOOL_MSG_LINKINFO_NTF``          link settings notification
   ``ETHTOOL_MSG_LINKMODES_GET_REPLY``   link modes info
   ``ETHTOOL_MSG_LINKMODES_NTF``         link modes notification
+  ``ETHTOOL_MSG_LINKSTATE_GET_REPLY``   link state info
   ===================================== ================================
 
 ``GET`` requests are sent by userspace applications to retrieve device
@@ -382,6 +384,35 @@ is supposed to allow requesting changes without knowing what exactly kernel
 supports.
 
 
+LINKSTATE_GET
+=============
+
+Requests link state information. At the moment, only link up/down flag (as
+provided by ``ETHTOOL_GLINK`` ioctl command) is provided but some future
+extensions are planned (e.g. link down reason). This request does not have any
+attributes or request specific flags.
+
+Request contents:
+
+  ====================================  ======  ==========================
+  ``ETHTOOL_A_LINKSTATE_HEADER``        nested  request header
+  ====================================  ======  ==========================
+
+Kernel response contents:
+
+  ====================================  ======  ==========================
+  ``ETHTOOL_A_LINKSTATE_HEADER``        nested  reply header
+  ``ETHTOOL_A_LINKSTATE_LINK``          u8      autonegotiation status
+  ====================================  ======  ==========================
+
+For most NIC drivers, the value of ``ETHTOOL_A_LINKSTATE_LINK`` returns
+carrier flag provided by ``netif_carrier_ok()`` but there are drivers which
+define their own handler.
+
+``LINKSTATE_GET`` allows dump requests (kernel returns reply messages for all
+devices supporting the request).
+
+
 Request translation
 ===================
 
@@ -403,7 +434,7 @@ have their netlink replacement yet.
   ``ETHTOOL_GMSGLVL``                 n/a
   ``ETHTOOL_SMSGLVL``                 n/a
   ``ETHTOOL_NWAY_RST``                n/a
-  ``ETHTOOL_GLINK``                   n/a
+  ``ETHTOOL_GLINK``                   ``ETHTOOL_MSG_LINKSTATE_GET``
   ``ETHTOOL_GEEPROM``                 n/a
   ``ETHTOOL_SEEPROM``                 n/a
   ``ETHTOOL_GCOALESCE``               n/a
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index 19c7c55c6483..fdb87548c3cc 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -19,6 +19,7 @@ enum {
 	ETHTOOL_MSG_LINKINFO_SET,
 	ETHTOOL_MSG_LINKMODES_GET,
 	ETHTOOL_MSG_LINKMODES_SET,
+	ETHTOOL_MSG_LINKSTATE_GET,
 
 	/* add new constants above here */
 	__ETHTOOL_MSG_USER_CNT,
@@ -33,6 +34,7 @@ enum {
 	ETHTOOL_MSG_LINKINFO_NTF,
 	ETHTOOL_MSG_LINKMODES_GET_REPLY,
 	ETHTOOL_MSG_LINKMODES_NTF,
+	ETHTOOL_MSG_LINKSTATE_GET_REPLY,
 
 	/* add new constants above here */
 	__ETHTOOL_MSG_KERNEL_CNT,
@@ -190,6 +192,20 @@ enum {
 
 #define ETHTOOL_RFLAG_LINKMODES_ALL 0
 
+/* LINKSTATE */
+
+enum {
+	ETHTOOL_A_LINKSTATE_UNSPEC,
+	ETHTOOL_A_LINKSTATE_HEADER,		/* nest - _A_HEADER_* */
+	ETHTOOL_A_LINKSTATE_LINK,		/* u8 */
+
+	/* add new constants above here */
+	__ETHTOOL_A_LINKSTATE_CNT,
+	ETHTOOL_A_LINKSTATE_MAX = __ETHTOOL_A_LINKSTATE_CNT - 1
+};
+
+#define ETHTOOL_RFLAG_LINKSTATE_ALL 0
+
 /* generic netlink info */
 #define ETHTOOL_GENL_NAME "ethtool"
 #define ETHTOOL_GENL_VERSION 1
diff --git a/net/ethtool/Makefile b/net/ethtool/Makefile
index 9ae0786c343b..520a39c9875b 100644
--- a/net/ethtool/Makefile
+++ b/net/ethtool/Makefile
@@ -4,4 +4,5 @@ obj-y				+= ioctl.o common.o
 
 obj-$(CONFIG_ETHTOOL_NETLINK)	+= ethtool_nl.o
 
-ethtool_nl-y	:= netlink.o bitset.o strset.o linkinfo.o linkmodes.o
+ethtool_nl-y	:= netlink.o bitset.o strset.o linkinfo.o linkmodes.o \
+		   linkstate.o
diff --git a/net/ethtool/common.c b/net/ethtool/common.c
index 84fed2eecb55..d1fb035394f3 100644
--- a/net/ethtool/common.c
+++ b/net/ethtool/common.c
@@ -131,3 +131,11 @@ convert_legacy_settings_to_link_ksettings(
 		= legacy_settings->eth_tp_mdix_ctrl;
 	return retval;
 }
+
+int __ethtool_get_link(struct net_device *dev)
+{
+	if (!dev->ethtool_ops->get_link)
+		return -EOPNOTSUPP;
+
+	return netif_running(dev) && dev->ethtool_ops->get_link(dev);
+}
diff --git a/net/ethtool/common.h b/net/ethtool/common.h
index 0381936d8e1e..a2c1504576c2 100644
--- a/net/ethtool/common.h
+++ b/net/ethtool/common.h
@@ -3,6 +3,7 @@
 #ifndef _ETHTOOL_COMMON_H
 #define _ETHTOOL_COMMON_H
 
+#include <linux/netdevice.h>
 #include <linux/ethtool.h>
 
 extern const char
@@ -14,6 +15,8 @@ tunable_strings[__ETHTOOL_TUNABLE_COUNT][ETH_GSTRING_LEN];
 extern const char
 phy_tunable_strings[__ETHTOOL_PHY_TUNABLE_COUNT][ETH_GSTRING_LEN];
 
+int __ethtool_get_link(struct net_device *dev);
+
 bool convert_legacy_settings_to_link_ksettings(
 	struct ethtool_link_ksettings *link_ksettings,
 	const struct ethtool_cmd *legacy_settings);
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index 7768c2e99a29..da5ce57869f4 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -1357,12 +1357,12 @@ static int ethtool_nway_reset(struct net_device *dev)
 static int ethtool_get_link(struct net_device *dev, char __user *useraddr)
 {
 	struct ethtool_value edata = { .cmd = ETHTOOL_GLINK };
+	int link = __ethtool_get_link(dev);
 
-	if (!dev->ethtool_ops->get_link)
-		return -EOPNOTSUPP;
-
-	edata.data = netif_running(dev) && dev->ethtool_ops->get_link(dev);
+	if (link < 0)
+		return link;
 
+	edata.data = link;
 	if (copy_to_user(useraddr, &edata, sizeof(edata)))
 		return -EFAULT;
 	return 0;
diff --git a/net/ethtool/linkstate.c b/net/ethtool/linkstate.c
new file mode 100644
index 000000000000..8459295abfdf
--- /dev/null
+++ b/net/ethtool/linkstate.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
+
+#include "netlink.h"
+#include "common.h"
+
+struct linkstate_req_info {
+	struct ethnl_req_info		base;
+};
+
+struct linkstate_reply_data {
+	struct ethnl_reply_data		base;
+	int				link;
+};
+
+static const struct nla_policy
+linkstate_get_policy[ETHTOOL_A_LINKSTATE_MAX + 1] = {
+	[ETHTOOL_A_LINKSTATE_UNSPEC]		= { .type = NLA_REJECT },
+	[ETHTOOL_A_LINKSTATE_HEADER]		= { .type = NLA_NESTED },
+	[ETHTOOL_A_LINKSTATE_LINK]		= { .type = NLA_REJECT },
+};
+
+/* prepare_data() handler */
+static int linkstate_prepare(const struct ethnl_req_info *req_base,
+			     struct ethnl_reply_data *reply_base,
+			     struct genl_info *info)
+{
+	struct linkstate_reply_data *data =
+		container_of(reply_base, struct linkstate_reply_data, base);
+	struct net_device *dev = reply_base->dev;
+	int ret;
+
+	ret = ethnl_before_ops(dev);
+	if (ret < 0)
+		return ret;
+	data->link = __ethtool_get_link(dev);
+	ethnl_after_ops(dev);
+
+	return 0;
+}
+
+/* reply_size() handler */
+static int linkstate_size(const struct ethnl_req_info *req_base,
+			  const struct ethnl_reply_data *reply_base)
+{
+	return nla_total_size(sizeof(u8)) /* LINKSTATE_LINK */
+		+ 0;
+}
+
+/* fill_reply() handler */
+static int linkstate_fill(struct sk_buff *skb,
+			  const struct ethnl_req_info *req_base,
+			  const struct ethnl_reply_data *reply_base)
+{
+	struct linkstate_reply_data *data =
+		container_of(reply_base, struct linkstate_reply_data, base);
+
+	if (data->link >= 0 &&
+	    nla_put_u8(skb, ETHTOOL_A_LINKSTATE_LINK, !!data->link))
+		return -EMSGSIZE;
+
+	return 0;
+}
+
+const struct get_request_ops linkstate_request_ops = {
+	.request_cmd		= ETHTOOL_MSG_LINKSTATE_GET,
+	.reply_cmd		= ETHTOOL_MSG_LINKSTATE_GET_REPLY,
+	.hdr_attr		= ETHTOOL_A_LINKSTATE_HEADER,
+	.max_attr		= ETHTOOL_A_LINKSTATE_MAX,
+	.req_info_size		= sizeof(struct linkstate_req_info),
+	.reply_data_size	= sizeof(struct linkstate_reply_data),
+	.request_policy		= linkstate_get_policy,
+	.all_reqflags		= ETHTOOL_RFLAG_LINKSTATE_ALL,
+
+	.prepare_data		= linkstate_prepare,
+	.reply_size		= linkstate_size,
+	.fill_reply		= linkstate_fill,
+};
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 7976200e5e35..4ee8e066065c 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -295,6 +295,7 @@ static const struct get_request_ops *get_requests[__ETHTOOL_MSG_USER_CNT] = {
 	[ETHTOOL_MSG_STRSET_GET]	= &strset_request_ops,
 	[ETHTOOL_MSG_LINKINFO_GET]	= &linkinfo_request_ops,
 	[ETHTOOL_MSG_LINKMODES_GET]	= &linkmodes_request_ops,
+	[ETHTOOL_MSG_LINKSTATE_GET]	= &linkstate_request_ops,
 };
 
 static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)
@@ -744,6 +745,13 @@ static const struct genl_ops ethtool_genl_ops[] = {
 		.flags	= GENL_UNS_ADMIN_PERM,
 		.doit	= ethnl_set_linkmodes,
 	},
+	{
+		.cmd	= ETHTOOL_MSG_LINKSTATE_GET,
+		.doit	= ethnl_get_doit,
+		.start	= ethnl_get_start,
+		.dumpit	= ethnl_get_dumpit,
+		.done	= ethnl_get_done,
+	},
 };
 
 static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 88026572567c..3608c852fcb3 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -350,6 +350,7 @@ struct get_request_ops {
 extern const struct get_request_ops strset_request_ops;
 extern const struct get_request_ops linkinfo_request_ops;
 extern const struct get_request_ops linkmodes_request_ops;
+extern const struct get_request_ops linkstate_request_ops;
 
 int ethnl_set_linkinfo(struct sk_buff *skb, struct genl_info *info);
 int ethnl_set_linkmodes(struct sk_buff *skb, struct genl_info *info);
-- 
2.23.0


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

* Re: [PATCH net-next v7 08/17] ethtool: move string arrays into common file
  2019-10-09 20:59 ` [PATCH net-next v7 08/17] ethtool: move string arrays into common file Michal Kubecek
@ 2019-10-10 13:27   ` Jiri Pirko
  0 siblings, 0 replies; 46+ messages in thread
From: Jiri Pirko @ 2019-10-10 13:27 UTC (permalink / raw)
  To: Michal Kubecek
  Cc: David Miller, netdev, Jakub Kicinski, Andrew Lunn,
	Florian Fainelli, John Linville, Stephen Hemminger,
	Johannes Berg, linux-kernel

Wed, Oct 09, 2019 at 10:59:24PM CEST, mkubecek@suse.cz wrote:
>Introduce file net/ethtool/common.c for code shared by ioctl and netlink
>ethtool interface. Move name tables of features, RSS hash functions,
>tunables and PHY tunables into this file.
>
>Signed-off-by: Michal Kubecek <mkubecek@suse.cz>

Reviewed-by: Jiri Pirko <jiri@mellanox.com>


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

* Re: [PATCH net-next v7 05/17] ethtool: helper functions for netlink interface
  2019-10-09 20:59 ` [PATCH net-next v7 05/17] ethtool: helper functions for " Michal Kubecek
@ 2019-10-10 13:42   ` Jiri Pirko
  2019-10-10 17:13     ` Michal Kubecek
  0 siblings, 1 reply; 46+ messages in thread
From: Jiri Pirko @ 2019-10-10 13:42 UTC (permalink / raw)
  To: Michal Kubecek
  Cc: David Miller, netdev, Jakub Kicinski, Andrew Lunn,
	Florian Fainelli, John Linville, Stephen Hemminger,
	Johannes Berg, linux-kernel

Wed, Oct 09, 2019 at 10:59:15PM CEST, mkubecek@suse.cz wrote:

[...]


>+/**
>+ * ethnl_parse_header() - parse request header
>+ * @req_info:    structure to put results into
>+ * @header:      nest attribute with request header
>+ * @net:         request netns
>+ * @extack:      netlink extack for error reporting
>+ * @policy:      netlink attribute policy to validate header; use
>+ *               @dflt_header_policy (all attributes allowed) if null
>+ * @require_dev: fail if no device identiified in header

s/identiified/identified/


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

* Re: [PATCH net-next v7 09/17] ethtool: generic handlers for GET requests
  2019-10-09 20:59 ` [PATCH net-next v7 09/17] ethtool: generic handlers for GET requests Michal Kubecek
@ 2019-10-10 13:56   ` Jiri Pirko
  2019-10-10 18:04     ` Michal Kubecek
  2019-10-10 15:23   ` Jiri Pirko
  1 sibling, 1 reply; 46+ messages in thread
From: Jiri Pirko @ 2019-10-10 13:56 UTC (permalink / raw)
  To: Michal Kubecek
  Cc: David Miller, netdev, Jakub Kicinski, Andrew Lunn,
	Florian Fainelli, John Linville, Stephen Hemminger,
	Johannes Berg, linux-kernel

Wed, Oct 09, 2019 at 10:59:27PM CEST, mkubecek@suse.cz wrote:
>Significant part of GET request processing is common for most request
>types but unfortunately it cannot be easily separated from type specific
>code as we need to alternate between common actions (parsing common request
>header, allocating message and filling netlink/genetlink headers etc.) and
>specific actions (querying the device, composing the reply). The processing
>also happens in three different situations: "do" request, "dump" request
>and notification, each doing things in slightly different way.
>
>The request specific code is implemented in four or five callbacks defined
>in an instance of struct get_request_ops:
>
>  parse_request() - parse incoming message
>  prepare_data()  - retrieve data from driver or NIC
>  reply_size()    - estimate reply message size
>  fill_reply()    - compose reply message
>  cleanup_data()  - (optional) clean up additional data
>
>Other members of struct get_request_ops describe the data structure holding
>information from client request and data used to compose the message. The
>standard handlers ethnl_get_doit(), ethnl_get_dumpit(), ethnl_get_start()
>and ethnl_get_done() can be then used in genl_ops handler. Notification
>handler will be introduced in a later patch.
>
>Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
>---
> net/ethtool/netlink.c | 329 ++++++++++++++++++++++++++++++++++++++++++
> net/ethtool/netlink.h | 120 ++++++++++++++-
> 2 files changed, 448 insertions(+), 1 deletion(-)
>
>diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
>index ed9c0f82aca4..e5aade3b69d1 100644
>--- a/net/ethtool/netlink.c
>+++ b/net/ethtool/netlink.c
>@@ -177,6 +177,335 @@ struct sk_buff *ethnl_reply_init(size_t payload, struct net_device *dev, u8 cmd,
> 	return NULL;
> }
> 
>+/* GET request helpers */
>+
>+/**
>+ * struct ethnl_dump_ctx - context structure for generic dumpit() callback
>+ * @ops:      request ops of currently processed message type
>+ * @req_info: parsed request header of processed request
>+ * @pos_hash: saved iteration position - hashbucket
>+ * @pos_idx:  saved iteration position - index
>+ *
>+ * These parameters are kept in struct netlink_callback as context preserved
>+ * between iterations. They are initialized by ethnl_get_start() and used in
>+ * ethnl_get_dumpit() and ethnl_get_done().
>+ */
>+struct ethnl_dump_ctx {
>+	const struct get_request_ops	*ops;
>+	struct ethnl_req_info		*req_info;
>+	struct ethnl_reply_data		*reply_data;
>+	int				pos_hash;
>+	int				pos_idx;
>+};
>+
>+static const struct get_request_ops *get_requests[__ETHTOOL_MSG_USER_CNT] = {
>+};
>+
>+static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)
>+{
>+	return (struct ethnl_dump_ctx *)cb->ctx;
>+}
>+
>+/**
>+ * ethnl_std_parse() - Parse request message
>+ * @req_info:    pointer to structure to put data into
>+ * @nlhdr:       pointer to request message header
>+ * @net:         request netns
>+ * @request_ops: struct request_ops for request type
>+ * @extack:      netlink extack for error reporting
>+ * @require_dev: fail if no device identified in header
>+ *
>+ * Parse universal request header and call request specific ->parse_request()
>+ * callback (if defined) to parse the rest of the message.
>+ *
>+ * Return: 0 on success or negative error code
>+ */
>+static int ethnl_std_parse(struct ethnl_req_info *req_info,

"std" sounds a bit odd. Perhaps "common"?


>+			   const struct nlmsghdr *nlhdr, struct net *net,
>+			   const struct get_request_ops *request_ops,
>+			   struct netlink_ext_ack *extack, bool require_dev)
>+{
>+	struct nlattr **tb;
>+	int ret;
>+
>+	tb = kmalloc_array(request_ops->max_attr + 1, sizeof(tb[0]),
>+			   GFP_KERNEL);
>+	if (!tb)
>+		return -ENOMEM;
>+
>+	ret = nlmsg_parse(nlhdr, GENL_HDRLEN, tb, request_ops->max_attr,
>+			  request_ops->request_policy, extack);
>+	if (ret < 0)
>+		goto out;
>+	ret = ethnl_parse_header(req_info, tb[request_ops->hdr_attr], net,
>+				 extack, request_ops->header_policy,
>+				 require_dev);

This is odd. It's the other way around in compare what I would expect.
There is a request-specific header attr that contains common header
attributes parsed in ethnl_parse_header.

Why don't you have the common header as a root then then have one nested
attr that would carry the request-specific attrs?

Similar to how it is done in rtnl IFLA_INFO_KIND.

You can parse the common stuff in pre_doit/start genl ops and you
don't have to explicitly call ethnl_parse_header.
Also, that would allow you to benefit from the genl doit/dumpit initial
attr parsing and save basically this whole function (alloc,parse).

Code would be much more simple to follow then.

Still seems to me that you use the generic netlink but you don't like
the infra too much so you make it up yourself again in parallel - that is
my feeling reading the code. I get the argument about the similarities
of the individual requests and why you have this request_ops (alhough I
don't like it too much).


>+	if (ret < 0)
>+		goto out;
>+
>+	if (request_ops->parse_request) {
>+		ret = request_ops->parse_request(req_info, tb, extack);
>+		if (ret < 0)
>+			goto out;
>+	}
>+
>+	if (req_info->req_flags & ~request_ops->all_reqflags) {
>+		ret = -EOPNOTSUPP;
>+		NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_HEADER_RFLAGS],
>+				    "unsupported request specific flags");
>+		goto out;
>+	}
>+
>+	ret = 0;
>+out:
>+	kfree(tb);
>+	return ret;
>+}
>+
>+/**
>+ * ethnl_init_reply_data() - Initialize reply data for GET request
>+ * @req_info: pointer to embedded struct ethnl_req_info
>+ * @ops:      instance of struct get_request_ops describing the layout
>+ * @dev:      network device to initialize the reply for
>+ *
>+ * Fills the reply data part with zeros and sets the dev member. Must be called
>+ * before calling the ->fill_reply() callback (for each iteration when handling
>+ * dump requests).
>+ */
>+static void ethnl_init_reply_data(struct ethnl_reply_data *reply_data,
>+				  const struct get_request_ops *ops,
>+				  struct net_device *dev)
>+{
>+	memset(reply_data, '\0', ops->reply_data_size);

Just "0" would do too.


>+	reply_data->dev = dev;
>+}
>+
>+/* generic ->doit() handler for GET type requests */
>+static int ethnl_get_doit(struct sk_buff *skb, struct genl_info *info)
>+{
>+	struct ethnl_reply_data *reply_data = NULL;
>+	struct ethnl_req_info *req_info = NULL;
>+	const u8 cmd = info->genlhdr->cmd;
>+	const struct get_request_ops *ops;
>+	struct sk_buff *rskb;
>+	void *reply_payload;
>+	int reply_len;
>+	int ret;
>+
>+	ops = get_requests[cmd];
>+	if (WARN_ONCE(!ops, "cmd %u has no get_request_ops\n", cmd))
>+		return -EOPNOTSUPP;
>+	req_info = kzalloc(ops->req_info_size, GFP_KERNEL);
>+	if (!req_info)
>+		return -ENOMEM;
>+	reply_data = kmalloc(ops->reply_data_size, GFP_KERNEL);
>+	if (!reply_data) {
>+		kfree(req_info);
>+		return -ENOMEM;
>+	}
>+
>+	ret = ethnl_std_parse(req_info, info->nlhdr, genl_info_net(info), ops,
>+			      info->extack, !ops->allow_nodev_do);
>+	if (ret < 0)
>+		goto err_dev;
>+	ethnl_init_reply_data(reply_data, ops, req_info->dev);
>+
>+	rtnl_lock();
>+	ret = ops->prepare_data(req_info, reply_data, info);
>+	rtnl_unlock();
>+	if (ret < 0)
>+		goto err_cleanup;
>+	reply_len = ops->reply_size(req_info, reply_data);
>+	if (ret < 0)
>+		goto err_cleanup;
>+	ret = -ENOMEM;
>+	rskb = ethnl_reply_init(reply_len, req_info->dev, ops->reply_cmd,
>+				ops->hdr_attr, info, &reply_payload);
>+	if (!rskb)
>+		goto err_cleanup;
>+	ret = ops->fill_reply(rskb, req_info, reply_data);
>+	if (ret < 0)
>+		goto err_msg;
>+	if (ops->cleanup_data)
>+		ops->cleanup_data(reply_data);
>+
>+	genlmsg_end(rskb, reply_payload);
>+	if (req_info->dev)
>+		dev_put(req_info->dev);
>+	kfree(reply_data);
>+	kfree(req_info);
>+	return genlmsg_reply(rskb, info);
>+
>+err_msg:
>+	WARN_ONCE(ret == -EMSGSIZE,

No need to wrap here (and in other similar cases)


>+		  "calculated message payload length (%d) not sufficient\n",
>+		  reply_len);
>+	nlmsg_free(rskb);
>+err_cleanup:
>+	if (ops->cleanup_data)
>+		ops->cleanup_data(reply_data);
>+err_dev:
>+	if (req_info->dev)
>+		dev_put(req_info->dev);
>+	kfree(reply_data);
>+	kfree(req_info);
>+	return ret;
>+}
>+
>+static int ethnl_get_dump_one(struct sk_buff *skb, struct net_device *dev,
>+			      const struct ethnl_dump_ctx *ctx)
>+{
>+	int ret;
>+
>+	ethnl_init_reply_data(ctx->reply_data, ctx->ops, dev);
>+	rtnl_lock();
>+	ret = ctx->ops->prepare_data(ctx->req_info, ctx->reply_data, NULL);
>+	rtnl_unlock();
>+	if (ret < 0)
>+		goto out;
>+	ret = ethnl_fill_reply_header(skb, dev, ctx->ops->hdr_attr);
>+	if (ret < 0)
>+		goto out;
>+	ret = ctx->ops->fill_reply(skb, ctx->req_info, ctx->reply_data);
>+
>+out:
>+	if (ctx->ops->cleanup_data)
>+		ctx->ops->cleanup_data(ctx->reply_data);
>+	ctx->reply_data->dev = NULL;
>+	return ret;
>+}
>+
>+/* Generic ->dumpit() handler for GET requests. Device iteration copied from
>+ * rtnl_dump_ifinfo(); we have to be more careful about device hashtable
>+ * persistence as we cannot guarantee to hold RTNL lock through the whole
>+ * function as rtnetnlink does.
>+ */
>+static int ethnl_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
>+{
>+	struct ethnl_dump_ctx *ctx = ethnl_dump_context(cb);
>+	struct net *net = sock_net(skb->sk);
>+	int s_idx = ctx->pos_idx;
>+	int h, idx = 0;
>+	int ret = 0;
>+	void *ehdr;
>+
>+	rtnl_lock();
>+	for (h = ctx->pos_hash; h < NETDEV_HASHENTRIES; h++, s_idx = 0) {
>+		struct hlist_head *head;
>+		struct net_device *dev;
>+		unsigned int seq;
>+
>+		head = &net->dev_index_head[h];
>+
>+restart_chain:
>+		seq = net->dev_base_seq;
>+		cb->seq = seq;
>+		idx = 0;
>+		hlist_for_each_entry(dev, head, index_hlist) {
>+			if (idx < s_idx)
>+				goto cont;
>+			dev_hold(dev);
>+			rtnl_unlock();
>+
>+			ehdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid,
>+					   cb->nlh->nlmsg_seq,
>+					   &ethtool_genl_family, 0,
>+					   ctx->ops->reply_cmd);
>+			if (!ehdr) {
>+				dev_put(dev);
>+				ret = -EMSGSIZE;
>+				goto out;
>+			}
>+			ret = ethnl_get_dump_one(skb, dev, ctx);
>+			dev_put(dev);
>+			if (ret < 0) {
>+				genlmsg_cancel(skb, ehdr);
>+				if (ret == -EOPNOTSUPP)
>+					goto lock_and_cont;
>+				if (likely(skb->len))
>+					ret = skb->len;
>+				goto out;
>+			}
>+			genlmsg_end(skb, ehdr);
>+lock_and_cont:
>+			rtnl_lock();
>+			if (net->dev_base_seq != seq) {
>+				s_idx = idx + 1;
>+				goto restart_chain;
>+			}
>+cont:
>+			idx++;
>+		}
>+
>+	}
>+	rtnl_unlock();
>+
>+out:
>+	ctx->pos_hash = h;
>+	ctx->pos_idx = idx;
>+	nl_dump_check_consistent(cb, nlmsg_hdr(skb));
>+
>+	return ret;
>+}
>+
>+/* generic ->start() handler for GET requests */
>+static int ethnl_get_start(struct netlink_callback *cb)
>+{
>+	struct ethnl_dump_ctx *ctx = ethnl_dump_context(cb);
>+	struct ethnl_reply_data *reply_data;
>+	const struct get_request_ops *ops;
>+	struct ethnl_req_info *req_info;
>+	struct genlmsghdr *ghdr;
>+	int ret;
>+
>+	BUILD_BUG_ON(sizeof(*ctx) > sizeof(cb->ctx));
>+
>+	ghdr = nlmsg_data(cb->nlh);
>+	ops = get_requests[ghdr->cmd];
>+	if (WARN_ONCE(!ops, "cmd %u has no get_request_ops\n", ghdr->cmd))
>+		return -EOPNOTSUPP;
>+	req_info = kzalloc(ops->req_info_size, GFP_KERNEL);
>+	if (!req_info)
>+		return -ENOMEM;
>+	reply_data = kmalloc(ops->reply_data_size, GFP_KERNEL);
>+	if (!reply_data) {
>+		kfree(req_info);
>+		return -ENOMEM;
>+	}
>+
>+	ret = ethnl_std_parse(req_info, cb->nlh, sock_net(cb->skb->sk), ops,
>+			      cb->extack, false);
>+	if (req_info->dev) {
>+		/* We ignore device specification in dump requests but as the
>+		 * same parser as for non-dump (doit) requests is used, it
>+		 * would take reference to the device if it finds one
>+		 */
>+		dev_put(req_info->dev);
>+		req_info->dev = NULL;
>+	}
>+	if (ret < 0)
>+		return ret;
>+
>+	ctx->ops = ops;
>+	ctx->req_info = req_info;
>+	ctx->reply_data = reply_data;
>+	ctx->pos_hash = 0;
>+	ctx->pos_idx = 0;
>+
>+	return 0;
>+}
>+
>+/* generic ->done() handler for GET requests */
>+static int ethnl_get_done(struct netlink_callback *cb)
>+{
>+	struct ethnl_dump_ctx *ctx = ethnl_dump_context(cb);
>+
>+	kfree(ctx->reply_data);
>+	kfree(ctx->req_info);
>+
>+	return 0;
>+}
>+
> /* notifications */
> 
> typedef void (*ethnl_notify_handler_t)(struct net_device *dev, unsigned int cmd,
>diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
>index 4c0b5ca439f8..077966a33544 100644
>--- a/net/ethtool/netlink.h
>+++ b/net/ethtool/netlink.h
>@@ -210,13 +210,30 @@ static inline unsigned int ethnl_reply_header_size(void)
> 			      nla_total_size(IFNAMSIZ));
> }
> 
>+/* GET request handling */
>+
>+/* Unified processing of GET requests uses two data structures: request info
>+ * and reply data. Request info holds information parsed from client request
>+ * and its stays constant through all request processing. Reply data holds data
>+ * retrieved from ethtool_ops callbacks or other internal sources which is used
>+ * to compose the reply. When processing a dump request, request info is filled
>+ * only once (when the request message is parsed) but reply data is filled for
>+ * each reply message.
>+ *
>+ * Both structures consist of part common for all request types (struct
>+ * ethnl_req_info and struct ethnl_reply_data defined below) and optional
>+ * parts specific for each request type. Common part always starts at offset 0.
>+ */
>+
> /**
>  * struct ethnl_req_info - base type of request information for GET requests
>  * @dev:          network device the request is for (may be null)
>  * @global_flags: request flags common for all request types
>  * @req_flags:    request flags specific for each request type
>  *
>- * This is a common base, additional members may follow after this structure.
>+ * This is a common base for request specific structures holding data from
>+ * parsed userspace request. These always embed struct ethnl_req_info at
>+ * zero offset.
>  */
> struct ethnl_req_info {
> 	struct net_device		*dev;
>@@ -224,4 +241,105 @@ struct ethnl_req_info {
> 	u32				req_flags;
> };
> 
>+/**
>+ * struct ethnl_reply_data - base type of reply data for GET requests
>+ * @dev:       device for current reply message; in single shot requests it is
>+ *             equal to &ethnl_req_info.dev; in dumps it's different for each
>+ *             reply message
>+ *
>+ * This is a common base for request specific structures holding data for
>+ * kernel reply message. These always embed struct ethnl_reply_data at zero
>+ * offset.
>+ */
>+struct ethnl_reply_data {
>+	struct net_device		*dev;
>+};
>+
>+static inline int ethnl_before_ops(struct net_device *dev)
>+{
>+	if (dev && dev->ethtool_ops->begin)
>+		return dev->ethtool_ops->begin(dev);
>+	else
>+		return 0;
>+}
>+
>+static inline void ethnl_after_ops(struct net_device *dev)
>+{
>+	if (dev && dev->ethtool_ops->complete)
>+		dev->ethtool_ops->complete(dev);
>+}
>+
>+/**
>+ * struct get_request_ops - unified handling of GET requests
>+ * @request_cmd:      command id for request (GET)
>+ * @reply_cmd:        command id for reply (GET_REPLY)
>+ * @hdr_attr:         attribute type for request header
>+ * @max_attr:         maximum (top level) attribute type
>+ * @req_info_size:    size of request info
>+ * @reply_data_size:  size of reply data
>+ * @request_policy:   netlink policy for message contents
>+ * @header_policy:    (optional) netlink policy for request header
>+ * @all_reqflags:     allowed request specific flags
>+ * @allow_nodev_do:   allow non-dump request with no device identification
>+ * @parse_request:
>+ *	Parse request except common header (struct ethnl_req_info). Common
>+ *	header is already filled on entry, the rest up to @repdata_offset
>+ *	is zero initialized. This callback should only modify type specific
>+ *	request info by parsed attributes from request message.
>+ * @prepare_data:
>+ *	Retrieve and prepare data needed to compose a reply message. Calls to
>+ *	ethtool_ops handlers should be limited to this callback. Common reply
>+ *	data (struct ethnl_reply_data) is filled on entry, type specific part
>+ *	after it is zero initialized. This callback should only modify the
>+ *	type specific part of reply data. Device identification from struct
>+ *	ethnl_reply_data is to be used as for dump requests, it iterates
>+ *	through network devices which common_req_info::dev points to the

First time I see this notation. Is "::" something common in kernel code?


>+ *	device from client request.
>+ * @reply_size:
>+ *	Estimate reply message size. Returned value must be sufficient for
>+ *	message payload without common reply header. The callback may returned
>+ *	estimate higher than actual message size if exact calculation would
>+ *	not be worth the saved memory space.
>+ * @fill_reply:
>+ *	Fill reply message payload (except for common header) from reply data.
>+ *	The callback must not generate more payload than previously called
>+ *	->reply_size() estimated.
>+ * @cleanup_data:
>+ *	Optional cleanup called when reply data is no longer needed. Can be
>+ *	used e.g. to free any additional data structures outside the main
>+ *	structure which were allocated by ->prepare_data(). When processing
>+ *	dump requests, ->cleanup() is called for each message.
>+ *
>+ * Description of variable parts of GET request handling when using the unified
>+ * infrastructure. When used, a pointer to an instance of this structure is to
>+ * be added to &get_requests array and generic handlers ethnl_get_doit(),
>+ * ethnl_get_dumpit(), ethnl_get_start() and ethnl_get_done() used in
>+ * @ethnl_genl_ops
>+ */
>+struct get_request_ops {

Could you please have "ethnl_" prefix for things like this.
"get_request_ops" sounds way to generic.


>+	u8			request_cmd;
>+	u8			reply_cmd;
>+	u16			hdr_attr;
>+	unsigned int		max_attr;
>+	unsigned int		req_info_size;
>+	unsigned int		reply_data_size;
>+	const struct nla_policy *request_policy;
>+	const struct nla_policy *header_policy;
>+	u32			all_reqflags;
>+	bool			allow_nodev_do;
>+
>+	int (*parse_request)(struct ethnl_req_info *req_info,
>+			     struct nlattr **tb,
>+			     struct netlink_ext_ack *extack);
>+	int (*prepare_data)(const struct ethnl_req_info *req_info,
>+			    struct ethnl_reply_data *reply_data,
>+			    struct genl_info *info);
>+	int (*reply_size)(const struct ethnl_req_info *req_info,
>+			  const struct ethnl_reply_data *reply_data);
>+	int (*fill_reply)(struct sk_buff *skb,
>+			  const struct ethnl_req_info *req_info,
>+			  const struct ethnl_reply_data *reply_data);
>+	void (*cleanup_data)(struct ethnl_reply_data *reply_data);
>+};
>+
> #endif /* _NET_ETHTOOL_NETLINK_H */
>-- 
>2.23.0
>

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

* Re: [PATCH net-next v7 10/17] ethtool: provide string sets with STRSET_GET request
  2019-10-09 20:59 ` [PATCH net-next v7 10/17] ethtool: provide string sets with STRSET_GET request Michal Kubecek
@ 2019-10-10 13:59   ` Jiri Pirko
  2019-10-10 18:05     ` Michal Kubecek
  0 siblings, 1 reply; 46+ messages in thread
From: Jiri Pirko @ 2019-10-10 13:59 UTC (permalink / raw)
  To: Michal Kubecek
  Cc: David Miller, netdev, Jakub Kicinski, Andrew Lunn,
	Florian Fainelli, John Linville, Stephen Hemminger,
	Johannes Berg, linux-kernel

Wed, Oct 09, 2019 at 10:59:30PM CEST, mkubecek@suse.cz wrote:

[...]

>+const struct get_request_ops strset_request_ops = {

I think when var is leaving context of single file, it should have
prefix:
ethnl_strset_request_ops

[...]

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

* Re: [PATCH net-next v7 09/17] ethtool: generic handlers for GET requests
  2019-10-09 20:59 ` [PATCH net-next v7 09/17] ethtool: generic handlers for GET requests Michal Kubecek
  2019-10-10 13:56   ` Jiri Pirko
@ 2019-10-10 15:23   ` Jiri Pirko
  1 sibling, 0 replies; 46+ messages in thread
From: Jiri Pirko @ 2019-10-10 15:23 UTC (permalink / raw)
  To: Michal Kubecek
  Cc: David Miller, netdev, Jakub Kicinski, Andrew Lunn,
	Florian Fainelli, John Linville, Stephen Hemminger,
	Johannes Berg, linux-kernel

Wed, Oct 09, 2019 at 10:59:27PM CEST, mkubecek@suse.cz wrote:

[...]


>+static const struct get_request_ops *get_requests[__ETHTOOL_MSG_USER_CNT] = {

I think that prefix would be good here as well:

+static const struct ethnl_get_request_ops *
ethnl_get_requests[__ETHTOOL_MSG_USER_CNT] = {

[...]

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

* Re: [PATCH net-next v7 13/17] ethtool: add standard notification handler
  2019-10-09 20:59 ` [PATCH net-next v7 13/17] ethtool: add standard notification handler Michal Kubecek
@ 2019-10-10 15:25   ` Jiri Pirko
  2019-10-10 18:17     ` Michal Kubecek
  0 siblings, 1 reply; 46+ messages in thread
From: Jiri Pirko @ 2019-10-10 15:25 UTC (permalink / raw)
  To: Michal Kubecek
  Cc: David Miller, netdev, Jakub Kicinski, Andrew Lunn,
	Florian Fainelli, John Linville, Stephen Hemminger,
	Johannes Berg, linux-kernel

Wed, Oct 09, 2019 at 10:59:40PM CEST, mkubecek@suse.cz wrote:
>The ethtool netlink notifications have the same format as related GET
>replies so that if generic GET handling framework is used to process GET
>requests, its callbacks and instance of struct get_request_ops can be
>also used to compose corresponding notification message.
>
>Provide function ethnl_std_notify() to be used as notification handler in
>ethnl_notify_handlers table.
>
>Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
>---
> net/ethtool/netlink.c | 89 +++++++++++++++++++++++++++++++++++++++++++
> net/ethtool/netlink.h |  3 +-
> 2 files changed, 91 insertions(+), 1 deletion(-)
>
>diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
>index 47b6aefa0bf9..dc52d912e0dd 100644
>--- a/net/ethtool/netlink.c
>+++ b/net/ethtool/netlink.c
>@@ -7,6 +7,7 @@
> static struct genl_family ethtool_genl_family;
> 
> static bool ethnl_ok __read_mostly;
>+static u32 ethnl_bcast_seq;
> 
> #define __LINK_MODE_NAME(speed, type, duplex) \
> 	#speed "base" #type "/" #duplex
>@@ -257,6 +258,18 @@ struct sk_buff *ethnl_reply_init(size_t payload, struct net_device *dev, u8 cmd,
> 	return NULL;
> }
> 
>+static void *ethnl_bcastmsg_put(struct sk_buff *skb, u8 cmd)
>+{
>+	return genlmsg_put(skb, 0, ++ethnl_bcast_seq, &ethtool_genl_family, 0,
>+			   cmd);
>+}
>+
>+static int ethnl_multicast(struct sk_buff *skb, struct net_device *dev)
>+{
>+	return genlmsg_multicast_netns(&ethtool_genl_family, dev_net(dev), skb,
>+				       0, ETHNL_MCGRP_MONITOR, GFP_KERNEL);
>+}

No need for these 2 helpers. Just put the code directly into
ethnl_std_notify() and make the code easier to read.


>+
> /* GET request helpers */
> 
> /**
>@@ -588,6 +601,82 @@ static int ethnl_get_done(struct netlink_callback *cb)
> 	return 0;
> }
> 
>+static const struct get_request_ops *ethnl_std_notify_to_ops(unsigned int cmd)
>+{
>+	WARN_ONCE(1, "unexpected notification type %u\n", cmd);
>+	return NULL;
>+}

Why this isn't a table similar to get_requests ?


>+
>+/* generic notification handler */
>+static void ethnl_std_notify(struct net_device *dev, unsigned int cmd,

Better "common" comparing to "standard", I believe.


>+			     const void *data)
>+{
>+	struct ethnl_reply_data *reply_data;
>+	const struct get_request_ops *ops;
>+	struct ethnl_req_info *req_info;
>+	struct sk_buff *skb;
>+	void *reply_payload;
>+	int reply_len;
>+	int ret;
>+
>+	ops = ethnl_std_notify_to_ops(cmd);
>+	if (!ops)
>+		return;
>+	req_info = kzalloc(ops->req_info_size, GFP_KERNEL);
>+	if (!req_info)
>+		return;
>+	reply_data = kmalloc(ops->reply_data_size, GFP_KERNEL);
>+	if (!reply_data) {
>+		kfree(req_info);
>+		return;
>+	}
>+
>+	req_info->dev = dev;
>+	req_info->global_flags |= ETHTOOL_GFLAG_COMPACT_BITSETS;
>+
>+	ethnl_init_reply_data(reply_data, ops, dev);
>+	ret = ops->prepare_data(req_info, reply_data, NULL);
>+	if (ret < 0)
>+		goto err_cleanup;
>+	reply_len = ops->reply_size(req_info, reply_data);
>+	if (ret < 0)
>+		goto err_cleanup;
>+	ret = -ENOMEM;
>+	skb = genlmsg_new(reply_len, GFP_KERNEL);
>+	if (!skb)
>+		goto err_cleanup;
>+	reply_payload = ethnl_bcastmsg_put(skb, cmd);
>+	if (!reply_payload)
>+		goto err_skb;
>+	ret = ethnl_fill_reply_header(skb, dev, ops->hdr_attr);
>+	if (ret < 0)
>+		goto err_msg;
>+	ret = ops->fill_reply(skb, req_info, reply_data);
>+	if (ret < 0)
>+		goto err_msg;
>+	if (ops->cleanup_data)
>+		ops->cleanup_data(reply_data);
>+
>+	genlmsg_end(skb, reply_payload);
>+	kfree(reply_data);
>+	kfree(req_info);
>+	ethnl_multicast(skb, dev);
>+	return;
>+
>+err_msg:
>+	WARN_ONCE(ret == -EMSGSIZE,
>+		  "calculated message payload length (%d) not sufficient\n",
>+		  reply_len);
>+err_skb:
>+	nlmsg_free(skb);
>+err_cleanup:
>+	if (ops->cleanup_data)
>+		ops->cleanup_data(reply_data);
>+	kfree(reply_data);
>+	kfree(req_info);
>+	return;
>+}
>+
> /* notifications */
> 
> typedef void (*ethnl_notify_handler_t)(struct net_device *dev, unsigned int cmd,
>diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
>index a0ae47bebe51..23e82a4dd265 100644
>--- a/net/ethtool/netlink.h
>+++ b/net/ethtool/netlink.h
>@@ -316,7 +316,8 @@ static inline void ethnl_after_ops(struct net_device *dev)
>  * infrastructure. When used, a pointer to an instance of this structure is to
>  * be added to &get_requests array and generic handlers ethnl_get_doit(),
>  * ethnl_get_dumpit(), ethnl_get_start() and ethnl_get_done() used in
>- * @ethnl_genl_ops
>+ * @ethnl_genl_ops; ethnl_std_notify() can be used in @ethnl_notify_handlers
>+ * to send notifications of the corresponding type.
>  */
> struct get_request_ops {
> 	u8			request_cmd;
>-- 
>2.23.0
>

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

* Re: [PATCH net-next v7 14/17] ethtool: set link settings with LINKINFO_SET request
  2019-10-09 20:59 ` [PATCH net-next v7 14/17] ethtool: set link settings with LINKINFO_SET request Michal Kubecek
@ 2019-10-10 15:37   ` Jiri Pirko
  2019-10-10 19:30     ` Michal Kubecek
  2019-10-12 16:33   ` Jiri Pirko
  1 sibling, 1 reply; 46+ messages in thread
From: Jiri Pirko @ 2019-10-10 15:37 UTC (permalink / raw)
  To: Michal Kubecek
  Cc: David Miller, netdev, Jakub Kicinski, Andrew Lunn,
	Florian Fainelli, John Linville, Stephen Hemminger,
	Johannes Berg, linux-kernel

Wed, Oct 09, 2019 at 10:59:43PM CEST, mkubecek@suse.cz wrote:
>Implement LINKINFO_SET netlink request to set link settings queried by
>LINKINFO_GET message.
>
>Only physical port, phy MDIO address and MDI(-X) control can be set,
>attempt to modify MDI(-X) status and transceiver is rejected.
>
>When any data is modified, ETHTOOL_MSG_LINKINFO_NTF message in the same
>format as reply to LINKINFO_GET request is sent to notify userspace about
>the changes. The same notification is also sent when these settings are
>modified using the ioctl interface.
>

It is a bit confusing and harder to follow when you have set and notify
code in the same patch. Could you please split?

[...]


>+/* LINKINFO_SET */
>+
>+static const struct nla_policy linkinfo_hdr_policy[ETHTOOL_A_HEADER_MAX + 1] = {
>+	[ETHTOOL_A_HEADER_UNSPEC]		= { .type = NLA_REJECT },
>+	[ETHTOOL_A_HEADER_DEV_INDEX]		= { .type = NLA_U32 },
>+	[ETHTOOL_A_HEADER_DEV_NAME]		= { .type = NLA_NUL_STRING,
>+						    .len = IFNAMSIZ - 1 },
>+	[ETHTOOL_A_HEADER_GFLAGS]		= { .type = NLA_U32 },
>+	[ETHTOOL_A_HEADER_RFLAGS]		= { .type = NLA_REJECT },
>+};

This is what I was talking about in the other email. These common attrs
should have common policy and should be parsed by generic netlink code
by default and be available for ethnl_set_linkinfo() in info->attrs.


>+
>+static const struct nla_policy
>+linkinfo_set_policy[ETHTOOL_A_LINKINFO_MAX + 1] = {
>+	[ETHTOOL_A_LINKINFO_UNSPEC]		= { .type = NLA_REJECT },
>+	[ETHTOOL_A_LINKINFO_HEADER]		= { .type = NLA_NESTED },
>+	[ETHTOOL_A_LINKINFO_PORT]		= { .type = NLA_U8 },
>+	[ETHTOOL_A_LINKINFO_PHYADDR]		= { .type = NLA_U8 },
>+	[ETHTOOL_A_LINKINFO_TP_MDIX]		= { .type = NLA_REJECT },
>+	[ETHTOOL_A_LINKINFO_TP_MDIX_CTRL]	= { .type = NLA_U8 },
>+	[ETHTOOL_A_LINKINFO_TRANSCEIVER]	= { .type = NLA_REJECT },
>+};
>+
>+int ethnl_set_linkinfo(struct sk_buff *skb, struct genl_info *info)
>+{
>+	struct nlattr *tb[ETHTOOL_A_LINKINFO_MAX + 1];
>+	struct ethtool_link_ksettings ksettings = {};
>+	struct ethtool_link_settings *lsettings;
>+	struct ethnl_req_info req_info = {};
>+	struct net_device *dev;
>+	bool mod = false;
>+	int ret;
>+
>+	ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb,
>+			  ETHTOOL_A_LINKINFO_MAX, linkinfo_set_policy,
>+			  info->extack);

Yeah, genl code should do this parse..


>+	if (ret < 0)
>+		return ret;
>+	ret = ethnl_parse_header(&req_info, tb[ETHTOOL_A_LINKINFO_HEADER],
>+				 genl_info_net(info), info->extack,
>+				 linkinfo_hdr_policy, true);

and pre_doit should do this one.


>+	if (ret < 0)
>+		return ret;
>+	dev = req_info.dev;
>+	if (!dev->ethtool_ops->get_link_ksettings ||
>+	    !dev->ethtool_ops->set_link_ksettings)
>+		return -EOPNOTSUPP;
>+
>+	rtnl_lock();
>+	ret = ethnl_before_ops(dev);
>+	if (ret < 0)
>+		goto out_rtnl;
>+
>+	ret = __ethtool_get_link_ksettings(dev, &ksettings);
>+	if (ret < 0) {
>+		if (info)
>+			GENL_SET_ERR_MSG(info, "failed to retrieve link settings");
>+		goto out_ops;
>+	}
>+	lsettings = &ksettings.base;
>+
>+	ethnl_update_u8(&lsettings->port, tb[ETHTOOL_A_LINKINFO_PORT], &mod);
>+	ethnl_update_u8(&lsettings->phy_address, tb[ETHTOOL_A_LINKINFO_PHYADDR],
>+			&mod);
>+	ethnl_update_u8(&lsettings->eth_tp_mdix_ctrl,
>+			tb[ETHTOOL_A_LINKINFO_TP_MDIX_CTRL], &mod);
>+
>+	ret = 0;
>+	if (mod) {

	if (!mod)
		goto out_ops;

?


>+		ret = dev->ethtool_ops->set_link_ksettings(dev, &ksettings);
>+		if (ret < 0)
>+			GENL_SET_ERR_MSG(info, "link settings update failed");
>+		else
>+			ethtool_notify(dev, ETHTOOL_MSG_LINKINFO_NTF, NULL);
>+	}
>+
>+out_ops:
>+	ethnl_after_ops(dev);
>+out_rtnl:
>+	rtnl_unlock();
>+	dev_put(dev);
>+	return ret;
>+}
>diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
>index dc52d912e0dd..5b9d12656e97 100644
>--- a/net/ethtool/netlink.c
>+++ b/net/ethtool/netlink.c
>@@ -603,6 +603,11 @@ static int ethnl_get_done(struct netlink_callback *cb)
> 
> static const struct get_request_ops *ethnl_std_notify_to_ops(unsigned int cmd)
> {
>+	switch (cmd) {
>+	case ETHTOOL_MSG_LINKINFO_NTF:
>+		return &linkinfo_request_ops;
>+	};
>+
> 	WARN_ONCE(1, "unexpected notification type %u\n", cmd);
> 	return NULL;
> }
>@@ -683,6 +688,7 @@ typedef void (*ethnl_notify_handler_t)(struct net_device *dev, unsigned int cmd,
> 				       const void *data);
> 
> static const ethnl_notify_handler_t ethnl_notify_handlers[] = {
>+	[ETHTOOL_MSG_LINKINFO_NTF]	= ethnl_std_notify,

Correct me if I'm wrong, but this is the only notification I found in
this patchset. Do you expect other then ethnl_std_notify() handler?
Bacause otherwise this can ba simplified down to just a single table
similar you have for GET.


> };
> 
> void ethtool_notify(struct net_device *dev, unsigned int cmd, const void *data)
>@@ -717,6 +723,11 @@ static const struct genl_ops ethtool_genl_ops[] = {
> 		.dumpit	= ethnl_get_dumpit,
> 		.done	= ethnl_get_done,
> 	},
>+	{
>+		.cmd	= ETHTOOL_MSG_LINKINFO_SET,
>+		.flags	= GENL_UNS_ADMIN_PERM,
>+		.doit	= ethnl_set_linkinfo,
>+	},
> };
> 
> static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
>diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
>index 23e82a4dd265..ca136dd7ea02 100644
>--- a/net/ethtool/netlink.h
>+++ b/net/ethtool/netlink.h
>@@ -350,4 +350,6 @@ struct get_request_ops {
> extern const struct get_request_ops strset_request_ops;
> extern const struct get_request_ops linkinfo_request_ops;
> 
>+int ethnl_set_linkinfo(struct sk_buff *skb, struct genl_info *info);
>+
> #endif /* _NET_ETHTOOL_NETLINK_H */
>-- 
>2.23.0
>

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

* Re: [PATCH net-next v7 12/17] ethtool: provide link settings with LINKINFO_GET request
  2019-10-09 20:59 ` [PATCH net-next v7 12/17] ethtool: provide link settings with LINKINFO_GET request Michal Kubecek
@ 2019-10-10 15:59   ` Jiri Pirko
  2019-10-10 20:15     ` Michal Kubecek
  0 siblings, 1 reply; 46+ messages in thread
From: Jiri Pirko @ 2019-10-10 15:59 UTC (permalink / raw)
  To: Michal Kubecek
  Cc: David Miller, netdev, Jakub Kicinski, Andrew Lunn,
	Florian Fainelli, John Linville, Stephen Hemminger,
	Johannes Berg, linux-kernel

Wed, Oct 09, 2019 at 10:59:37PM CEST, mkubecek@suse.cz wrote:

[...]

>+/* prepare_data() handler */

Not sure how valuable are comments like this...


>+static int linkinfo_prepare(const struct ethnl_req_info *req_base,
>+			    struct ethnl_reply_data *reply_base,
>+			    struct genl_info *info)
>+{
>+	struct linkinfo_reply_data *data =
>+		container_of(reply_base, struct linkinfo_reply_data, base);

A helper would be nice for this. For req_info too.


>+	struct net_device *dev = reply_base->dev;
>+	int ret;
>+
>+	data->lsettings = &data->ksettings.base;
>+
>+	ret = ethnl_before_ops(dev);

"before_ops"/"after_ops" sounds odd. Maybe:
ethnl_ops_begin
ethnl_ops_complete

To me in-line with ethtool_ops names?

I guess you don't want the caller (ethnl_get_doit/ethnl_get_dumpit)
to call this because it might not be needed down in prepare_data()
callback, right?


>+	if (ret < 0)
>+		return ret;
>+	ret = __ethtool_get_link_ksettings(dev, &data->ksettings);
>+	if (ret < 0 && info)
>+		GENL_SET_ERR_MSG(info, "failed to retrieve link settings");
>+	ethnl_after_ops(dev);
>+
>+	return ret;
>+}

[...]


>+const struct get_request_ops linkinfo_request_ops = {
>+	.request_cmd		= ETHTOOL_MSG_LINKINFO_GET,
>+	.reply_cmd		= ETHTOOL_MSG_LINKINFO_GET_REPLY,
>+	.hdr_attr		= ETHTOOL_A_LINKINFO_HEADER,
>+	.max_attr		= ETHTOOL_A_LINKINFO_MAX,
>+	.req_info_size		= sizeof(struct linkinfo_req_info),
>+	.reply_data_size	= sizeof(struct linkinfo_reply_data),
>+	.request_policy		= linkinfo_get_policy,
>+	.all_reqflags		= ETHTOOL_RFLAG_LINKINFO_ALL,
>+
>+	.prepare_data		= linkinfo_prepare,

Please have the ops with the same name/suffix:
	.request_policy		= linkinfo_reques_policy,
	.prepare_data		= linkinfo_prepare_data,
	.reply_size		= linkinfo_reply_size,
	.fill_reply		= linkinfo_fill_reply,

Same applies of course to the other patches.


>+	.reply_size		= linkinfo_size,
>+	.fill_reply		= linkinfo_fill,
>+};

[...]

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

* Re: [PATCH net-next v7 05/17] ethtool: helper functions for netlink interface
  2019-10-10 13:42   ` Jiri Pirko
@ 2019-10-10 17:13     ` Michal Kubecek
  0 siblings, 0 replies; 46+ messages in thread
From: Michal Kubecek @ 2019-10-10 17:13 UTC (permalink / raw)
  To: netdev
  Cc: Jiri Pirko, David Miller, Jakub Kicinski, Andrew Lunn,
	Florian Fainelli, John Linville, Stephen Hemminger,
	Johannes Berg, linux-kernel

On Thu, Oct 10, 2019 at 03:42:03PM +0200, Jiri Pirko wrote:
> Wed, Oct 09, 2019 at 10:59:15PM CEST, mkubecek@suse.cz wrote:
> 
> [...]
> 
> 
> >+/**
> >+ * ethnl_parse_header() - parse request header
> >+ * @req_info:    structure to put results into
> >+ * @header:      nest attribute with request header
> >+ * @net:         request netns
> >+ * @extack:      netlink extack for error reporting
> >+ * @policy:      netlink attribute policy to validate header; use
> >+ *               @dflt_header_policy (all attributes allowed) if null
> >+ * @require_dev: fail if no device identiified in header
> 
> s/identiified/identified/

Will fix.

Michal

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

* Re: [PATCH net-next v7 09/17] ethtool: generic handlers for GET requests
  2019-10-10 13:56   ` Jiri Pirko
@ 2019-10-10 18:04     ` Michal Kubecek
  2019-10-10 18:18       ` Johannes Berg
  2019-10-11  6:06       ` Jiri Pirko
  0 siblings, 2 replies; 46+ messages in thread
From: Michal Kubecek @ 2019-10-10 18:04 UTC (permalink / raw)
  To: netdev
  Cc: Jiri Pirko, David Miller, Jakub Kicinski, Andrew Lunn,
	Florian Fainelli, John Linville, Stephen Hemminger,
	Johannes Berg, linux-kernel

On Thu, Oct 10, 2019 at 03:56:39PM +0200, Jiri Pirko wrote:
> Wed, Oct 09, 2019 at 10:59:27PM CEST, mkubecek@suse.cz wrote:
> >+/**
> >+ * ethnl_std_parse() - Parse request message
> >+ * @req_info:    pointer to structure to put data into
> >+ * @nlhdr:       pointer to request message header
> >+ * @net:         request netns
> >+ * @request_ops: struct request_ops for request type
> >+ * @extack:      netlink extack for error reporting
> >+ * @require_dev: fail if no device identified in header
> >+ *
> >+ * Parse universal request header and call request specific ->parse_request()
> >+ * callback (if defined) to parse the rest of the message.
> >+ *
> >+ * Return: 0 on success or negative error code
> >+ */
> >+static int ethnl_std_parse(struct ethnl_req_info *req_info,
> 
> "std" sounds a bit odd. Perhaps "common"?

It could be "common". The reason I used "standard" was that this parser
is only used by (GET) request types which use the standard doit/dumpit
handlers. Request types using their own (nonstandard) handlers will also
do parsing on their own.

> >+			   const struct nlmsghdr *nlhdr, struct net *net,
> >+			   const struct get_request_ops *request_ops,
> >+			   struct netlink_ext_ack *extack, bool require_dev)
> >+{
> >+	struct nlattr **tb;
> >+	int ret;
> >+
> >+	tb = kmalloc_array(request_ops->max_attr + 1, sizeof(tb[0]),
> >+			   GFP_KERNEL);
> >+	if (!tb)
> >+		return -ENOMEM;
> >+
> >+	ret = nlmsg_parse(nlhdr, GENL_HDRLEN, tb, request_ops->max_attr,
> >+			  request_ops->request_policy, extack);
> >+	if (ret < 0)
> >+		goto out;
> >+	ret = ethnl_parse_header(req_info, tb[request_ops->hdr_attr], net,
> >+				 extack, request_ops->header_policy,
> >+				 require_dev);
> 
> This is odd. It's the other way around in compare what I would expect.
> There is a request-specific header attr that contains common header
> attributes parsed in ethnl_parse_header.
> 
> Why don't you have the common header as a root then then have one nested
> attr that would carry the request-specific attrs?
> 
> Similar to how it is done in rtnl IFLA_INFO_KIND.

To me, what you suggest feels much more odd. I thought about it last
time, I thought about it now and the only reason for such layout I could
come with would be to work around the unfortunate design flaw of the way
validation and parsing is done in genetlink (see below).

The situation with IFLA_INFO_KIND is a bit different, what you suggest
would rather correspond to having only attributes common for all RTNL on
top level and hiding all IFLA_* attributes into a nest (and the same
with attributes specific to "ip addr", "ip route", "ip rule" etc.)

> You can parse the common stuff in pre_doit/start genl ops and you
> don't have to explicitly call ethnl_parse_header.
> Also, that would allow you to benefit from the genl doit/dumpit initial
> attr parsing and save basically this whole function (alloc,parse).
> 
> Code would be much more simple to follow then.
> 
> Still seems to me that you use the generic netlink but you don't like
> the infra too much so you make it up yourself again in parallel - that is
> my feeling reading the code. I get the argument about the similarities
> of the individual requests and why you have this request_ops (alhough I
> don't like it too much).

The only thing I don't like about the genetlink infrastructure is the
design decision that policy and corresponding maxattr is an attribute of
the family rather than a command. This forces anyone who wants to use it
to essentially have one common message format for all commands and if
that is not possible, to do what you suggest above, hide the actual
request into a nest.

Whether you use one common attribute type for "command specific nest" or
different attribute for each request type, you do not actually make
things simpler, you just move the complexity one level lower. You will
still have to do your own (per request) parsing of the actual request,
the only difference is that you will do it in a different place and use
nla_parse_nested() rather than nlmsg_parse().

Rather than bending the message layout to fit into the limitations of
unified genetlink parsing, I prefer to keep the logical message
structure and do the parsing on my own.

> >+static void ethnl_init_reply_data(struct ethnl_reply_data *reply_data,
> >+				  const struct get_request_ops *ops,
> >+				  struct net_device *dev)
> >+{
> >+	memset(reply_data, '\0', ops->reply_data_size);
> 
> Just "0" would do too.

OK

> >+
> >+err_msg:
> >+	WARN_ONCE(ret == -EMSGSIZE,
> 
> No need to wrap here (and in other similar cases)

OK 

> >+		  "calculated message payload length (%d) not sufficient\n",
> >+		  reply_len);
...
> >+ * @prepare_data:
> >+ *	Retrieve and prepare data needed to compose a reply message. Calls to
> >+ *	ethtool_ops handlers should be limited to this callback. Common reply
> >+ *	data (struct ethnl_reply_data) is filled on entry, type specific part
> >+ *	after it is zero initialized. This callback should only modify the
> >+ *	type specific part of reply data. Device identification from struct
> >+ *	ethnl_reply_data is to be used as for dump requests, it iterates
> >+ *	through network devices which common_req_info::dev points to the
> 
> First time I see this notation. Is "::" something common in kernel code?

It's sometimes used to denote a struct member but I don't know if it's
common in kernel documentation. I'll write it explicitly.

> >+struct get_request_ops {
> 
> Could you please have "ethnl_" prefix for things like this.
> "get_request_ops" sounds way to generic.

OK

Michal

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

* Re: [PATCH net-next v7 10/17] ethtool: provide string sets with STRSET_GET request
  2019-10-10 13:59   ` Jiri Pirko
@ 2019-10-10 18:05     ` Michal Kubecek
  0 siblings, 0 replies; 46+ messages in thread
From: Michal Kubecek @ 2019-10-10 18:05 UTC (permalink / raw)
  To: netdev
  Cc: Jiri Pirko, David Miller, Jakub Kicinski, Andrew Lunn,
	Florian Fainelli, John Linville, Stephen Hemminger,
	Johannes Berg, linux-kernel

On Thu, Oct 10, 2019 at 03:59:17PM +0200, Jiri Pirko wrote:
> Wed, Oct 09, 2019 at 10:59:30PM CEST, mkubecek@suse.cz wrote:
> 
> [...]
> 
> >+const struct get_request_ops strset_request_ops = {
> 
> I think when var is leaving context of single file, it should have
> prefix:
> ethnl_strset_request_ops

OK

Michal

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

* Re: [PATCH net-next v7 13/17] ethtool: add standard notification handler
  2019-10-10 15:25   ` Jiri Pirko
@ 2019-10-10 18:17     ` Michal Kubecek
  2019-10-11  5:56       ` Jiri Pirko
  0 siblings, 1 reply; 46+ messages in thread
From: Michal Kubecek @ 2019-10-10 18:17 UTC (permalink / raw)
  To: netdev
  Cc: Jiri Pirko, David Miller, Jakub Kicinski, Andrew Lunn,
	Florian Fainelli, John Linville, Stephen Hemminger,
	Johannes Berg, linux-kernel

On Thu, Oct 10, 2019 at 05:25:59PM +0200, Jiri Pirko wrote:
> Wed, Oct 09, 2019 at 10:59:40PM CEST, mkubecek@suse.cz wrote:
> >+static void *ethnl_bcastmsg_put(struct sk_buff *skb, u8 cmd)
> >+{
> >+	return genlmsg_put(skb, 0, ++ethnl_bcast_seq, &ethtool_genl_family, 0,
> >+			   cmd);
> >+}
> >+
> >+static int ethnl_multicast(struct sk_buff *skb, struct net_device *dev)
> >+{
> >+	return genlmsg_multicast_netns(&ethtool_genl_family, dev_net(dev), skb,
> >+				       0, ETHNL_MCGRP_MONITOR, GFP_KERNEL);
> >+}
> 
> No need for these 2 helpers. Just put the code directly into
> ethnl_std_notify() and make the code easier to read.

In later patches (not submitted yet), these two will be also called by
other notification handlers.

> >+static const struct get_request_ops *ethnl_std_notify_to_ops(unsigned int cmd)
> >+{
> >+	WARN_ONCE(1, "unexpected notification type %u\n", cmd);
> >+	return NULL;
> >+}
> 
> Why this isn't a table similar to get_requests ?

It's a relic of earlier version before splitting the complex message
types when the table was rather sparse. I'll change it to a lookup table
to make it consistent with the rest of the code.

> >+
> >+/* generic notification handler */
> >+static void ethnl_std_notify(struct net_device *dev, unsigned int cmd,
> 
> Better "common" comparing to "standard", I believe.

That's similar to ethnl_std_parse(), the idea is that this is the
standard handler for notifications which are triggered without
additional data and the message is the same as reply to corresponding
"GET" request (which is generated by the standard ethnl_get_doit()
handler). Notifications for actions and notifications for SET commands
which cannot be generated this standard way will have to use their own
(nonstandard) handler.

Michal

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

* Re: [PATCH net-next v7 09/17] ethtool: generic handlers for GET requests
  2019-10-10 18:04     ` Michal Kubecek
@ 2019-10-10 18:18       ` Johannes Berg
  2019-10-10 20:00         ` Michal Kubecek
  2019-10-11  6:06       ` Jiri Pirko
  1 sibling, 1 reply; 46+ messages in thread
From: Johannes Berg @ 2019-10-10 18:18 UTC (permalink / raw)
  To: Michal Kubecek, netdev
  Cc: Jiri Pirko, David Miller, Jakub Kicinski, Andrew Lunn,
	Florian Fainelli, John Linville, Stephen Hemminger, linux-kernel

On Thu, 2019-10-10 at 20:04 +0200, Michal Kubecek wrote:
> 
> The only thing I don't like about the genetlink infrastructure is the
> design decision that policy and corresponding maxattr is an attribute of
> the family rather than a command. This forces anyone who wants to use it
> to essentially have one common message format for all commands and if
> that is not possible, to do what you suggest above, hide the actual
> request into a nest.
> 
> Whether you use one common attribute type for "command specific nest" or
> different attribute for each request type, you do not actually make
> things simpler, you just move the complexity one level lower. You will
> still have to do your own (per request) parsing of the actual request,
> the only difference is that you will do it in a different place and use
> nla_parse_nested() rather than nlmsg_parse().
> 
> Rather than bending the message layout to fit into the limitations of
> unified genetlink parsing, I prefer to keep the logical message
> structure and do the parsing on my own.

I can't really agree with this.

Having a common format is way more accessible. Generic netlink (now)
even exposes the policy (if set) and all of its nested sub-policies to
userspace (if you use NLA_POLICY_NESTED), so it's very easy to discover
what's in the policy and how it'll be interpreted.

This makes it really easy to have tools for introspection, or have
common debugging tools that just understand the message format based on
the kernel's policy.

It's also much easier this way to not mess up things like "attribute # 7
always means a netdev index". You solved that by nesting the common
bits, though the part about ETHTOOL_A_HEADER_RFLAGS actually seems ...
wrong? Shouldn't that have been somewhere else? Or does that mean each
and every request_policy has to have this at the same index? That sounds
error prone ...

But you even have *two* policies for each kind of message, one for the
content and one for the header...?


It almost seems though that your argument isn't so much on the actual
hierarchy/nesting structure of the message itself, but the easy of
parsing it?

I have thought previous that it might make sense to create a
hierarchical representation of the message, with the nested TBs pre-
parsed too in generic netlink, so you wouldn't just have a common
attrbuf but (optionally) allocate nested attrbufs for those nested
attributes that are present, and give a way of accessing those.


I really do think that a single policy that's exposed for introspection
and links its nested sub-policies for the different sub-commands (which
are then also exposed to introspection) is much superior to having it
all just driven by the code like this.

johannes


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

* Re: [PATCH net-next v7 14/17] ethtool: set link settings with LINKINFO_SET request
  2019-10-10 15:37   ` Jiri Pirko
@ 2019-10-10 19:30     ` Michal Kubecek
  2019-10-11  5:59       ` Jiri Pirko
  0 siblings, 1 reply; 46+ messages in thread
From: Michal Kubecek @ 2019-10-10 19:30 UTC (permalink / raw)
  To: netdev
  Cc: Jiri Pirko, David Miller, Jakub Kicinski, Andrew Lunn,
	Florian Fainelli, John Linville, Stephen Hemminger,
	Johannes Berg, linux-kernel

On Thu, Oct 10, 2019 at 05:37:54PM +0200, Jiri Pirko wrote:
> Wed, Oct 09, 2019 at 10:59:43PM CEST, mkubecek@suse.cz wrote:
> >Implement LINKINFO_SET netlink request to set link settings queried by
> >LINKINFO_GET message.
> >
> >Only physical port, phy MDIO address and MDI(-X) control can be set,
> >attempt to modify MDI(-X) status and transceiver is rejected.
> >
> >When any data is modified, ETHTOOL_MSG_LINKINFO_NTF message in the same
> >format as reply to LINKINFO_GET request is sent to notify userspace about
> >the changes. The same notification is also sent when these settings are
> >modified using the ioctl interface.
> >
> 
> It is a bit confusing and harder to follow when you have set and notify
> code in the same patch. Could you please split?

As the notification is composed and sent by ethnl_std_notify() with help
of the callback functions used to generate the reply to GET request, the
only notification related changes in this patch are the three calls to
ethtool_notify() (one in netlink code, two in ioctl code) and the entry
added to ethnl_notify_handlers[].

But I have no objection to splitting these out into a separate patch,
except for having sacrifice some of the patches actually implementing
something so that the series doesn't get too long.

> 
> [...]
> 
> 
> >+/* LINKINFO_SET */
> >+
> >+static const struct nla_policy linkinfo_hdr_policy[ETHTOOL_A_HEADER_MAX + 1] = {
> >+	[ETHTOOL_A_HEADER_UNSPEC]		= { .type = NLA_REJECT },
> >+	[ETHTOOL_A_HEADER_DEV_INDEX]		= { .type = NLA_U32 },
> >+	[ETHTOOL_A_HEADER_DEV_NAME]		= { .type = NLA_NUL_STRING,
> >+						    .len = IFNAMSIZ - 1 },
> >+	[ETHTOOL_A_HEADER_GFLAGS]		= { .type = NLA_U32 },
> >+	[ETHTOOL_A_HEADER_RFLAGS]		= { .type = NLA_REJECT },
> >+};
> 
> This is what I was talking about in the other email. These common attrs
> should have common policy and should be parsed by generic netlink code
> by default and be available for ethnl_set_linkinfo() in info->attrs.

NLA_REJECT for ETHTOOL_A_HEADER_RFLAGS is probably an overkill here. If
I just check that client does not set flags we do not know, I can have
one universal header policy as well. I'll probably do that.

> >+int ethnl_set_linkinfo(struct sk_buff *skb, struct genl_info *info)
> >+{
> >+	struct nlattr *tb[ETHTOOL_A_LINKINFO_MAX + 1];
> >+	struct ethtool_link_ksettings ksettings = {};
> >+	struct ethtool_link_settings *lsettings;
> >+	struct ethnl_req_info req_info = {};
> >+	struct net_device *dev;
> >+	bool mod = false;
> >+	int ret;
> >+
> >+	ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb,
> >+			  ETHTOOL_A_LINKINFO_MAX, linkinfo_set_policy,
> >+			  info->extack);
> 
> Yeah, genl code should do this parse..

Not really. It would only parse the top level - which, in your design,
would only be the common header. In other words, it would do what is now
done by the call to nla_parse_nested() inside ethnl_parse_header(). For
equivalent of this parse, you would still have to call your own
nla_parse_nested() on the "request specific data" nested attribute.

> >+	if (ret < 0)
> >+		return ret;
> >+	ret = ethnl_parse_header(&req_info, tb[ETHTOOL_A_LINKINFO_HEADER],
> >+				 genl_info_net(info), info->extack,
> >+				 linkinfo_hdr_policy, true);
> 
> and pre_doit should do this one.

...and also (each) start(). Which means you would either duplicate the
code or introduce the same helper. All you would save would be that one
call of nla_parse_nested() in ethnl_parse_header().

> >+
> >+	ret = 0;
> >+	if (mod) {
> 
> 	if (!mod)
> 		goto out_ops;
> 
> ?

OK

> >+		ret = dev->ethtool_ops->set_link_ksettings(dev, &ksettings);
> >+		if (ret < 0)
> >+			GENL_SET_ERR_MSG(info, "link settings update failed");
> >+		else
> >+			ethtool_notify(dev, ETHTOOL_MSG_LINKINFO_NTF, NULL);
> >+	}
> >+
> >+out_ops:
> >+	ethnl_after_ops(dev);
> >+out_rtnl:
> >+	rtnl_unlock();
> >+	dev_put(dev);
> >+	return ret;
> >+}
...
> >@@ -683,6 +688,7 @@ typedef void (*ethnl_notify_handler_t)(struct net_device *dev, unsigned int cmd,
> > 				       const void *data);
> > 
> > static const ethnl_notify_handler_t ethnl_notify_handlers[] = {
> >+	[ETHTOOL_MSG_LINKINFO_NTF]	= ethnl_std_notify,
> 
> Correct me if I'm wrong, but this is the only notification I found in
> this patchset. Do you expect other then ethnl_std_notify() handler?
> Bacause otherwise this can ba simplified down to just a single table
> similar you have for GET.

Yes, there will be other handlers; ethnl_std_notify() can only handle
the simplest (even if most common) type of notification where caller
does not pass any information except the device, the notification
message is exactly the same as reply to corresponding GET request would
be and that GET request does not have any attributes (so that it can be
handled with ethnl_get_doit()).

There will be notifications which will need their own handlers, e.g. all
notifications triggered by an action request (e.g. renegotiation or
device reset) or notifications triggered by "ethtool -X".

Michal

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

* Re: [PATCH net-next v7 09/17] ethtool: generic handlers for GET requests
  2019-10-10 18:18       ` Johannes Berg
@ 2019-10-10 20:00         ` Michal Kubecek
  2019-10-11  8:08           ` Johannes Berg
  0 siblings, 1 reply; 46+ messages in thread
From: Michal Kubecek @ 2019-10-10 20:00 UTC (permalink / raw)
  To: netdev
  Cc: Johannes Berg, Jiri Pirko, David Miller, Jakub Kicinski,
	Andrew Lunn, Florian Fainelli, John Linville, Stephen Hemminger,
	linux-kernel

On Thu, Oct 10, 2019 at 08:18:05PM +0200, Johannes Berg wrote:
> On Thu, 2019-10-10 at 20:04 +0200, Michal Kubecek wrote:
> > 
> > The only thing I don't like about the genetlink infrastructure is the
> > design decision that policy and corresponding maxattr is an attribute of
> > the family rather than a command. This forces anyone who wants to use it
> > to essentially have one common message format for all commands and if
> > that is not possible, to do what you suggest above, hide the actual
> > request into a nest.
> > 
> > Whether you use one common attribute type for "command specific nest" or
> > different attribute for each request type, you do not actually make
> > things simpler, you just move the complexity one level lower. You will
> > still have to do your own (per request) parsing of the actual request,
> > the only difference is that you will do it in a different place and use
> > nla_parse_nested() rather than nlmsg_parse().
> > 
> > Rather than bending the message layout to fit into the limitations of
> > unified genetlink parsing, I prefer to keep the logical message
> > structure and do the parsing on my own.
> 
> I can't really agree with this.
> 
> Having a common format is way more accessible. Generic netlink (now)
> even exposes the policy (if set) and all of its nested sub-policies to
> userspace (if you use NLA_POLICY_NESTED), so it's very easy to discover
> what's in the policy and how it'll be interpreted.

This, however, would require a different design that Jiri proposed. What
he proposed was one attribute type for "request specific attributes".
But to be able to perform nested validation of the whole message and
export all policies would, with current genetlink design, require having
one such attribute type for each request type (command).

But that would also require an extra check that the request message
contains only the attribute matching its command (request type) so that
the validation performed by genetlink would still be incomplete (it will
always be incomplete as there are lots of strange constraints which
cannot be described by a policy).

Unless you suggest to effectively have just one command and determine
the request type based on which of these request specific attributes is
present (and define what to do if there is more than one).

> This makes it really easy to have tools for introspection, or have
> common debugging tools that just understand the message format based on
> the kernel's policy.
> 
> It's also much easier this way to not mess up things like "attribute # 7
> always means a netdev index". You solved that by nesting the common
> bits, though the part about ETHTOOL_A_HEADER_RFLAGS actually seems ...
> wrong? Shouldn't that have been somewhere else? Or does that mean each
> and every request_policy has to have this at the same index? That sounds
> error prone ...

ETHTOOL_A_HEADER_RFLAGS is a constant, it's always the same. Yes,
logically it would rather belong outside header and maybe should be
replaced by a (possibly empty) set of NLA_FLAG attributes. If having it
in the common header is such a big problem, I'll move it out.

> But you even have *two* policies for each kind of message, one for the
> content and one for the header...?

As I said in reply to another patch, it turns out that the only reason
for having a per request header policy was rejecting
ETHTOOL_A_HEADER_RFLAGS for requests which do not define any request
flags but that's probably an overkill so that one universal header
policy would be sufficient.

> It almost seems though that your argument isn't so much on the actual
> hierarchy/nesting structure of the message itself, but the easy of
> parsing it?

It's both. I still feel that from logical point of view it makes much
more sense to use top level attributes for what the message is actually
about. Nothing you said convinced me otherwise, rather the opposite: it
only confirmed that the only reason for hiding the actual request
contents one level below is to work around the consequences of the
decision to make policy in genetlink per family rather than per command.

> I have thought previous that it might make sense to create a
> hierarchical representation of the message, with the nested TBs pre-
> parsed too in generic netlink, so you wouldn't just have a common
> attrbuf but (optionally) allocate nested attrbufs for those nested
> attributes that are present, and give a way of accessing those.
> 
> I really do think that a single policy that's exposed for introspection
> and links its nested sub-policies for the different sub-commands (which
> are then also exposed to introspection) is much superior to having it
> all just driven by the code like this.

I still don't see any reason why all this could not work with per
command policies and would be principially dependent on having one
universal policy for the whole family.

Michal Kubecek

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

* Re: [PATCH net-next v7 12/17] ethtool: provide link settings with LINKINFO_GET request
  2019-10-10 15:59   ` Jiri Pirko
@ 2019-10-10 20:15     ` Michal Kubecek
  0 siblings, 0 replies; 46+ messages in thread
From: Michal Kubecek @ 2019-10-10 20:15 UTC (permalink / raw)
  To: netdev
  Cc: Jiri Pirko, David Miller, Jakub Kicinski, Andrew Lunn,
	Florian Fainelli, John Linville, Stephen Hemminger,
	Johannes Berg, linux-kernel

On Thu, Oct 10, 2019 at 05:59:55PM +0200, Jiri Pirko wrote:
> Wed, Oct 09, 2019 at 10:59:37PM CEST, mkubecek@suse.cz wrote:
> 
> [...]
> 
> >+/* prepare_data() handler */
> 
> Not sure how valuable are comments like this...

I'll drop them.

> >+static int linkinfo_prepare(const struct ethnl_req_info *req_base,
> >+			    struct ethnl_reply_data *reply_base,
> >+			    struct genl_info *info)
> >+{
> >+	struct linkinfo_reply_data *data =
> >+		container_of(reply_base, struct linkinfo_reply_data, base);
> 
> A helper would be nice for this. For req_info too.

Good point.

> >+	struct net_device *dev = reply_base->dev;
> >+	int ret;
> >+
> >+	data->lsettings = &data->ksettings.base;
> >+
> >+	ret = ethnl_before_ops(dev);
> 
> "before_ops"/"after_ops" sounds odd. Maybe:
> ethnl_ops_begin
> ethnl_ops_complete
> 
> To me in-line with ethtool_ops names?

OK

> I guess you don't want the caller (ethnl_get_doit/ethnl_get_dumpit)
> to call this because it might not be needed down in prepare_data()
> callback, right?

Yes, there are some which do not call any ethtool_ops callbacks, e.g.
netdev features (ethtool -k / -K).

> >+const struct get_request_ops linkinfo_request_ops = {
> >+	.request_cmd		= ETHTOOL_MSG_LINKINFO_GET,
> >+	.reply_cmd		= ETHTOOL_MSG_LINKINFO_GET_REPLY,
> >+	.hdr_attr		= ETHTOOL_A_LINKINFO_HEADER,
> >+	.max_attr		= ETHTOOL_A_LINKINFO_MAX,
> >+	.req_info_size		= sizeof(struct linkinfo_req_info),
> >+	.reply_data_size	= sizeof(struct linkinfo_reply_data),
> >+	.request_policy		= linkinfo_get_policy,
> >+	.all_reqflags		= ETHTOOL_RFLAG_LINKINFO_ALL,
> >+
> >+	.prepare_data		= linkinfo_prepare,
> 
> Please have the ops with the same name/suffix:
> 	.request_policy		= linkinfo_reques_policy,
> 	.prepare_data		= linkinfo_prepare_data,
> 	.reply_size		= linkinfo_reply_size,
> 	.fill_reply		= linkinfo_fill_reply,
> 
> Same applies of course to the other patches.

OK

Michal

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

* Re: [PATCH net-next v7 00/17] ethtool netlink interface, part 1
  2019-10-09 20:59 [PATCH net-next v7 00/17] ethtool netlink interface, part 1 Michal Kubecek
                   ` (16 preceding siblings ...)
  2019-10-09 20:59 ` [PATCH net-next v7 17/17] ethtool: provide link state with LINKSTATE_GET request Michal Kubecek
@ 2019-10-11  0:48 ` Jakub Kicinski
  2019-10-11  6:46   ` Johannes Berg
  17 siblings, 1 reply; 46+ messages in thread
From: Jakub Kicinski @ 2019-10-11  0:48 UTC (permalink / raw)
  To: Michal Kubecek
  Cc: David Miller, netdev, Jiri Pirko, Andrew Lunn, Florian Fainelli,
	John Linville, Stephen Hemminger, Johannes Berg, linux-kernel

On Wed,  9 Oct 2019 22:59:00 +0200 (CEST), Michal Kubecek wrote:
> This is first part of netlink based alternative userspace interface for
> ethtool. It aims to address some long known issues with the ioctl
> interface, mainly lack of extensibility, raciness, limited error reporting
> and absence of notifications. The goal is to allow userspace ethtool
> utility to provide all features it currently does but without using the
> ioctl interface. However, some features provided by ethtool ioctl API will
> be available through other netlink interfaces (rtnetlink, devlink) if it's
> more appropriate.

Looks like perhaps with ETHTOOL_A_HEADER_RFLAGS checking taken out of
the picture we can proceed as is and potentially work on defining some
best practices around attrs and nesting for the future generations? :)

I was trying to find a way to perhaps start merging something.. Would
it make sense to spin out patches 1, 2, 3, and 8 as they seem to be
ready (possibly 11, too if the reorder isn't painful)?

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

* Re: [PATCH net-next v7 13/17] ethtool: add standard notification handler
  2019-10-10 18:17     ` Michal Kubecek
@ 2019-10-11  5:56       ` Jiri Pirko
  2019-10-11  5:59         ` Michal Kubecek
  0 siblings, 1 reply; 46+ messages in thread
From: Jiri Pirko @ 2019-10-11  5:56 UTC (permalink / raw)
  To: Michal Kubecek
  Cc: netdev, David Miller, Jakub Kicinski, Andrew Lunn,
	Florian Fainelli, John Linville, Stephen Hemminger,
	Johannes Berg, linux-kernel

Thu, Oct 10, 2019 at 08:17:43PM CEST, mkubecek@suse.cz wrote:
>On Thu, Oct 10, 2019 at 05:25:59PM +0200, Jiri Pirko wrote:
>> Wed, Oct 09, 2019 at 10:59:40PM CEST, mkubecek@suse.cz wrote:

[...]


>> >+
>> >+/* generic notification handler */
>> >+static void ethnl_std_notify(struct net_device *dev, unsigned int cmd,
>> 
>> Better "common" comparing to "standard", I believe.
>
>That's similar to ethnl_std_parse(), the idea is that this is the
>standard handler for notifications which are triggered without
>additional data and the message is the same as reply to corresponding
>"GET" request (which is generated by the standard ethnl_get_doit()
>handler). Notifications for actions and notifications for SET commands
>which cannot be generated this standard way will have to use their own
>(nonstandard) handler.

So "default"? The "standard" sounds rather weird to me. It isn't any
"standard" :)

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

* Re: [PATCH net-next v7 13/17] ethtool: add standard notification handler
  2019-10-11  5:56       ` Jiri Pirko
@ 2019-10-11  5:59         ` Michal Kubecek
  0 siblings, 0 replies; 46+ messages in thread
From: Michal Kubecek @ 2019-10-11  5:59 UTC (permalink / raw)
  To: netdev
  Cc: Jiri Pirko, David Miller, Jakub Kicinski, Andrew Lunn,
	Florian Fainelli, John Linville, Stephen Hemminger,
	Johannes Berg, linux-kernel

On Friday, 11 October 2019 7:56 Jiri Pirko wrote:
> Thu, Oct 10, 2019 at 08:17:43PM CEST, mkubecek@suse.cz wrote:
> >On Thu, Oct 10, 2019 at 05:25:59PM +0200, Jiri Pirko wrote:
> >> Wed, Oct 09, 2019 at 10:59:40PM CEST, mkubecek@suse.cz wrote:
> [...]
> 
> >> >+
> >> >+/* generic notification handler */
> >> >+static void ethnl_std_notify(struct net_device *dev, unsigned int
> >> >cmd,>> 
> >> Better "common" comparing to "standard", I believe.
> >
> >That's similar to ethnl_std_parse(), the idea is that this is the
> >standard handler for notifications which are triggered without
> >additional data and the message is the same as reply to corresponding
> >"GET" request (which is generated by the standard ethnl_get_doit()
> >handler). Notifications for actions and notifications for SET
> >commands which cannot be generated this standard way will have to
> >use their own (nonstandard) handler.
> 
> So "default"? The "standard" sounds rather weird to me. It isn't any
> "standard" :)

Yes, "default" sounds fine.

Michal



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

* Re: [PATCH net-next v7 14/17] ethtool: set link settings with LINKINFO_SET request
  2019-10-10 19:30     ` Michal Kubecek
@ 2019-10-11  5:59       ` Jiri Pirko
  0 siblings, 0 replies; 46+ messages in thread
From: Jiri Pirko @ 2019-10-11  5:59 UTC (permalink / raw)
  To: Michal Kubecek
  Cc: netdev, David Miller, Jakub Kicinski, Andrew Lunn,
	Florian Fainelli, John Linville, Stephen Hemminger,
	Johannes Berg, linux-kernel

Thu, Oct 10, 2019 at 09:30:44PM CEST, mkubecek@suse.cz wrote:
>On Thu, Oct 10, 2019 at 05:37:54PM +0200, Jiri Pirko wrote:
>> Wed, Oct 09, 2019 at 10:59:43PM CEST, mkubecek@suse.cz wrote:
>> >Implement LINKINFO_SET netlink request to set link settings queried by
>> >LINKINFO_GET message.
>> >
>> >Only physical port, phy MDIO address and MDI(-X) control can be set,
>> >attempt to modify MDI(-X) status and transceiver is rejected.
>> >
>> >When any data is modified, ETHTOOL_MSG_LINKINFO_NTF message in the same
>> >format as reply to LINKINFO_GET request is sent to notify userspace about
>> >the changes. The same notification is also sent when these settings are
>> >modified using the ioctl interface.
>> >
>> 
>> It is a bit confusing and harder to follow when you have set and notify
>> code in the same patch. Could you please split?
>
>As the notification is composed and sent by ethnl_std_notify() with help
>of the callback functions used to generate the reply to GET request, the
>only notification related changes in this patch are the three calls to
>ethtool_notify() (one in netlink code, two in ioctl code) and the entry
>added to ethnl_notify_handlers[].
>
>But I have no objection to splitting these out into a separate patch,
>except for having sacrifice some of the patches actually implementing
>something so that the series doesn't get too long.

Please split.


>
>> 
>> [...]
>> 
>> 
>> >+/* LINKINFO_SET */
>> >+
>> >+static const struct nla_policy linkinfo_hdr_policy[ETHTOOL_A_HEADER_MAX + 1] = {
>> >+	[ETHTOOL_A_HEADER_UNSPEC]		= { .type = NLA_REJECT },
>> >+	[ETHTOOL_A_HEADER_DEV_INDEX]		= { .type = NLA_U32 },
>> >+	[ETHTOOL_A_HEADER_DEV_NAME]		= { .type = NLA_NUL_STRING,
>> >+						    .len = IFNAMSIZ - 1 },
>> >+	[ETHTOOL_A_HEADER_GFLAGS]		= { .type = NLA_U32 },
>> >+	[ETHTOOL_A_HEADER_RFLAGS]		= { .type = NLA_REJECT },
>> >+};
>> 
>> This is what I was talking about in the other email. These common attrs
>> should have common policy and should be parsed by generic netlink code
>> by default and be available for ethnl_set_linkinfo() in info->attrs.
>
>NLA_REJECT for ETHTOOL_A_HEADER_RFLAGS is probably an overkill here. If
>I just check that client does not set flags we do not know, I can have
>one universal header policy as well. I'll probably do that.
>
>> >+int ethnl_set_linkinfo(struct sk_buff *skb, struct genl_info *info)
>> >+{
>> >+	struct nlattr *tb[ETHTOOL_A_LINKINFO_MAX + 1];
>> >+	struct ethtool_link_ksettings ksettings = {};
>> >+	struct ethtool_link_settings *lsettings;
>> >+	struct ethnl_req_info req_info = {};
>> >+	struct net_device *dev;
>> >+	bool mod = false;
>> >+	int ret;
>> >+
>> >+	ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb,
>> >+			  ETHTOOL_A_LINKINFO_MAX, linkinfo_set_policy,
>> >+			  info->extack);
>> 
>> Yeah, genl code should do this parse..
>
>Not really. It would only parse the top level - which, in your design,
>would only be the common header. In other words, it would do what is now
>done by the call to nla_parse_nested() inside ethnl_parse_header(). For
>equivalent of this parse, you would still have to call your own
>nla_parse_nested() on the "request specific data" nested attribute.

Okay, the parse would stay, you are correct.


>
>> >+	if (ret < 0)
>> >+		return ret;
>> >+	ret = ethnl_parse_header(&req_info, tb[ETHTOOL_A_LINKINFO_HEADER],
>> >+				 genl_info_net(info), info->extack,
>> >+				 linkinfo_hdr_policy, true);
>> 
>> and pre_doit should do this one.
>
>...and also (each) start(). Which means you would either duplicate the
>code or introduce the same helper. All you would save would be that one
>call of nla_parse_nested() in ethnl_parse_header().

Sure, it could be a same helper called fro pre_doit and start. No
problem.


>
>> >+
>> >+	ret = 0;
>> >+	if (mod) {
>> 
>> 	if (!mod)
>> 		goto out_ops;
>> 
>> ?
>
>OK
>
>> >+		ret = dev->ethtool_ops->set_link_ksettings(dev, &ksettings);
>> >+		if (ret < 0)
>> >+			GENL_SET_ERR_MSG(info, "link settings update failed");
>> >+		else
>> >+			ethtool_notify(dev, ETHTOOL_MSG_LINKINFO_NTF, NULL);
>> >+	}
>> >+
>> >+out_ops:
>> >+	ethnl_after_ops(dev);
>> >+out_rtnl:
>> >+	rtnl_unlock();
>> >+	dev_put(dev);
>> >+	return ret;
>> >+}
>...
>> >@@ -683,6 +688,7 @@ typedef void (*ethnl_notify_handler_t)(struct net_device *dev, unsigned int cmd,
>> > 				       const void *data);
>> > 
>> > static const ethnl_notify_handler_t ethnl_notify_handlers[] = {
>> >+	[ETHTOOL_MSG_LINKINFO_NTF]	= ethnl_std_notify,
>> 
>> Correct me if I'm wrong, but this is the only notification I found in
>> this patchset. Do you expect other then ethnl_std_notify() handler?
>> Bacause otherwise this can ba simplified down to just a single table
>> similar you have for GET.
>
>Yes, there will be other handlers; ethnl_std_notify() can only handle
>the simplest (even if most common) type of notification where caller
>does not pass any information except the device, the notification
>message is exactly the same as reply to corresponding GET request would
>be and that GET request does not have any attributes (so that it can be
>handled with ethnl_get_doit()).
>
>There will be notifications which will need their own handlers, e.g. all
>notifications triggered by an action request (e.g. renegotiation or
>device reset) or notifications triggered by "ethtool -X".
>
>Michal

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

* Re: [PATCH net-next v7 09/17] ethtool: generic handlers for GET requests
  2019-10-10 18:04     ` Michal Kubecek
  2019-10-10 18:18       ` Johannes Berg
@ 2019-10-11  6:06       ` Jiri Pirko
  1 sibling, 0 replies; 46+ messages in thread
From: Jiri Pirko @ 2019-10-11  6:06 UTC (permalink / raw)
  To: Michal Kubecek
  Cc: netdev, David Miller, Jakub Kicinski, Andrew Lunn,
	Florian Fainelli, John Linville, Stephen Hemminger,
	Johannes Berg, linux-kernel

Thu, Oct 10, 2019 at 08:04:01PM CEST, mkubecek@suse.cz wrote:
>On Thu, Oct 10, 2019 at 03:56:39PM +0200, Jiri Pirko wrote:
>> Wed, Oct 09, 2019 at 10:59:27PM CEST, mkubecek@suse.cz wrote:

[...]


>> >+			   const struct nlmsghdr *nlhdr, struct net *net,
>> >+			   const struct get_request_ops *request_ops,
>> >+			   struct netlink_ext_ack *extack, bool require_dev)
>> >+{
>> >+	struct nlattr **tb;
>> >+	int ret;
>> >+
>> >+	tb = kmalloc_array(request_ops->max_attr + 1, sizeof(tb[0]),
>> >+			   GFP_KERNEL);
>> >+	if (!tb)
>> >+		return -ENOMEM;
>> >+
>> >+	ret = nlmsg_parse(nlhdr, GENL_HDRLEN, tb, request_ops->max_attr,
>> >+			  request_ops->request_policy, extack);
>> >+	if (ret < 0)
>> >+		goto out;
>> >+	ret = ethnl_parse_header(req_info, tb[request_ops->hdr_attr], net,
>> >+				 extack, request_ops->header_policy,
>> >+				 require_dev);
>> 
>> This is odd. It's the other way around in compare what I would expect.
>> There is a request-specific header attr that contains common header
>> attributes parsed in ethnl_parse_header.
>> 
>> Why don't you have the common header as a root then then have one nested
>> attr that would carry the request-specific attrs?
>> 
>> Similar to how it is done in rtnl IFLA_INFO_KIND.
>
>To me, what you suggest feels much more odd. I thought about it last
>time, I thought about it now and the only reason for such layout I could
>come with would be to work around the unfortunate design flaw of the way
>validation and parsing is done in genetlink (see below).
>
>The situation with IFLA_INFO_KIND is a bit different, what you suggest
>would rather correspond to having only attributes common for all RTNL on
>top level and hiding all IFLA_* attributes into a nest (and the same
>with attributes specific to "ip addr", "ip route", "ip rule" etc.)
>
>> You can parse the common stuff in pre_doit/start genl ops and you
>> don't have to explicitly call ethnl_parse_header.
>> Also, that would allow you to benefit from the genl doit/dumpit initial
>> attr parsing and save basically this whole function (alloc,parse).
>> 
>> Code would be much more simple to follow then.
>> 
>> Still seems to me that you use the generic netlink but you don't like
>> the infra too much so you make it up yourself again in parallel - that is
>> my feeling reading the code. I get the argument about the similarities
>> of the individual requests and why you have this request_ops (alhough I
>> don't like it too much).
>
>The only thing I don't like about the genetlink infrastructure is the
>design decision that policy and corresponding maxattr is an attribute of
>the family rather than a command. This forces anyone who wants to use it
>to essentially have one common message format for all commands and if
>that is not possible, to do what you suggest above, hide the actual
>request into a nest.

But that is fine, the genetlink code would parse the common attributes
for you according to the family, then you inside ethnl_get_doit prepare
(alloc, parse) data for ops->prepare_data and other callbacks, according
to per-request ops->policy and ops->maxattr.

Then the request callbacks would get parsed attrs according to their
type. And you can use similar technique for set dumpit/ops. Would be
neat.


>
>Whether you use one common attribute type for "command specific nest" or
>different attribute for each request type, you do not actually make
>things simpler, you just move the complexity one level lower. You will
>still have to do your own (per request) parsing of the actual request,
>the only difference is that you will do it in a different place and use
>nla_parse_nested() rather than nlmsg_parse().
>
>Rather than bending the message layout to fit into the limitations of
>unified genetlink parsing, I prefer to keep the logical message
>structure and do the parsing on my own.

You are going to still have it but the person looking at the traffic by
nlmon would know what is happening and also you are going to use
genetlink in non-abusive way :)

>

[...]

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

* Re: [PATCH net-next v7 00/17] ethtool netlink interface, part 1
  2019-10-11  0:48 ` [PATCH net-next v7 00/17] ethtool netlink interface, part 1 Jakub Kicinski
@ 2019-10-11  6:46   ` Johannes Berg
  0 siblings, 0 replies; 46+ messages in thread
From: Johannes Berg @ 2019-10-11  6:46 UTC (permalink / raw)
  To: Jakub Kicinski, Michal Kubecek
  Cc: David Miller, netdev, Jiri Pirko, Andrew Lunn, Florian Fainelli,
	John Linville, Stephen Hemminger, linux-kernel

On Thu, 2019-10-10 at 17:48 -0700, Jakub Kicinski wrote:
> On Wed,  9 Oct 2019 22:59:00 +0200 (CEST), Michal Kubecek wrote:
> > This is first part of netlink based alternative userspace interface for
> > ethtool. It aims to address some long known issues with the ioctl
> > interface, mainly lack of extensibility, raciness, limited error reporting
> > and absence of notifications. The goal is to allow userspace ethtool
> > utility to provide all features it currently does but without using the
> > ioctl interface. However, some features provided by ethtool ioctl API will
> > be available through other netlink interfaces (rtnetlink, devlink) if it's
> > more appropriate.
> 
> Looks like perhaps with ETHTOOL_A_HEADER_RFLAGS checking taken out of
> the picture we can proceed as is and potentially work on defining some
> best practices around attrs and nesting for the future generations? :)
> 
> I was trying to find a way to perhaps start merging something.. Would
> it make sense to spin out patches 1, 2, 3, and 8 as they seem to be
> ready (possibly 11, too if the reorder isn't painful)?

I was surprised 3 didn't go in even last time this was posted, it seems
such an obvious thing and not necessary just for ethtool :)

johannes


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

* Re: [PATCH net-next v7 09/17] ethtool: generic handlers for GET requests
  2019-10-10 20:00         ` Michal Kubecek
@ 2019-10-11  8:08           ` Johannes Berg
  0 siblings, 0 replies; 46+ messages in thread
From: Johannes Berg @ 2019-10-11  8:08 UTC (permalink / raw)
  To: Michal Kubecek, netdev
  Cc: Jiri Pirko, David Miller, Jakub Kicinski, Andrew Lunn,
	Florian Fainelli, John Linville, Stephen Hemminger, linux-kernel

On Thu, 2019-10-10 at 22:00 +0200, Michal Kubecek wrote:
> 
> > Having a common format is way more accessible. Generic netlink (now)
> > even exposes the policy (if set) and all of its nested sub-policies to
> > userspace (if you use NLA_POLICY_NESTED), so it's very easy to discover
> > what's in the policy and how it'll be interpreted.
> 
> This, however, would require a different design that Jiri proposed. What
> he proposed was one attribute type for "request specific attributes".
> But to be able to perform nested validation of the whole message and
> export all policies would, with current genetlink design, require having
> one such attribute type for each request type (command).

Yes, indeed, that's true.

We actually used to have per-command policy in genetlink, but I removed
it as there was only one user, and it would've made the introspection
stuff a lot more complicated and it wasted quite a bit of memory in the
ops tables for everyone else.

Maybe we *do* want this back, with a nested policy pointing to the
common like you have. Then you'd have the option to do exactly what you
do here, but have introspection & common validation.

Actually, it's only half true that we had this - it was that the
*policy* was in the op, but the *maxattr* was in the family. This never
really makes any sense.

(IMHO we really should try to find a way to embed the maxattr into the
family, passing both always is very tiring. Perhaps just having a
terminator entry would be sufficient, or using a trick similar to what I
did with strict_start_type, and putting it into the 0th entry that's
usually unused? Even where the 0th entry *is* used, as long as the type
isn't something that relies on the validation data, we could do that.
But this is totally an aside...)

> But that would also require an extra check that the request message
> contains only the attribute matching its command (request type) so that
> the validation performed by genetlink would still be incomplete (it will
> always be incomplete as there are lots of strange constraints which
> cannot be described by a policy).

Also true, yes.

> Unless you suggest to effectively have just one command and determine
> the request type based on which of these request specific attributes is
> present (and define what to do if there is more than one).

Also possible, I guess, I can't say that's much better.

> ETHTOOL_A_HEADER_RFLAGS is a constant, it's always the same. Yes,
> logically it would rather belong outside header and maybe should be
> replaced by a (possibly empty) set of NLA_FLAG attributes. If having it
> in the common header is such a big problem, I'll move it out.

NLA_FLAG tends to be large - I think having a bitmap is fine.

Btw, you can also have a common *fixed* header in the genetlink family.
I don't think anyone does that, but it's possible.

> > But you even have *two* policies for each kind of message, one for the
> > content and one for the header...?
> 
> As I said in reply to another patch, it turns out that the only reason
> for having a per request header policy was rejecting
> ETHTOOL_A_HEADER_RFLAGS for requests which do not define any request
> flags but that's probably an overkill so that one universal header
> policy would be sufficient.

Missed that, OK.

> > It almost seems though that your argument isn't so much on the actual
> > hierarchy/nesting structure of the message itself, but the easy of
> > parsing it?
> 
> It's both. I still feel that from logical point of view it makes much
> more sense to use top level attributes for what the message is actually
> about. Nothing you said convinced me otherwise, rather the opposite: it
> only confirmed that the only reason for hiding the actual request
> contents one level below is to work around the consequences of the
> decision to make policy in genetlink per family rather than per command.

As I said above, it was actually the other way around and I changed it
relatively recently.

I don't have any strong objections to changing that really, it just
wasn't really used by anyone.

> I still don't see any reason why all this could not work with per
> command policies and would be principially dependent on having one
> universal policy for the whole family.

True, it just makes the code to expose it more complex (and uses more
space in the ops tables.)

If we do bring that back then IMHO it should be done properly and not
have a maxattr in the family, but with each policy. There could still
be a single maxattr (that must be >= max of all maxattrs) for the whole
family to ease allocation, but that could also just be derived as the
max(maxattr of all possible policies) at family registration time.

I'm almost thinking if we do that we should have a "struct
genl_ops_with_policy" and a second ops pointer in the family, so that
families not using this don't have to have a policy pointer & maxattr
for each op, the op struct now is 5 pointers long, so adding
policy/maxattr would cost us an increase of 20% on 64-bit and 40% on 32-
bit ... For families that don't need it, that's pretty large.

johannes


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

* Re: [PATCH net-next v7 06/17] ethtool: netlink bitset handling
  2019-10-09 20:59 ` [PATCH net-next v7 06/17] ethtool: netlink bitset handling Michal Kubecek
@ 2019-10-11 13:34   ` Jiri Pirko
  2019-10-14 11:18     ` Michal Kubecek
  0 siblings, 1 reply; 46+ messages in thread
From: Jiri Pirko @ 2019-10-11 13:34 UTC (permalink / raw)
  To: Michal Kubecek
  Cc: David Miller, netdev, Jakub Kicinski, Andrew Lunn,
	Florian Fainelli, John Linville, Stephen Hemminger,
	Johannes Berg, linux-kernel

Wed, Oct 09, 2019 at 10:59:18PM CEST, mkubecek@suse.cz wrote:
>The ethtool netlink code uses common framework for passing arbitrary
>length bit sets to allow future extensions. A bitset can be a list (only
>one bitmap) or can consist of value and mask pair (used e.g. when client
>want to modify only some bits). A bitset can use one of two formats:
>verbose (bit by bit) or compact.
>
>Verbose format consists of bitset size (number of bits), list flag and
>an array of bit nests, telling which bits are part of the list or which
>bits are in the mask and which of them are to be set. In requests, bits
>can be identified by index (position) or by name. In replies, kernel
>provides both index and name. Verbose format is suitable for "one shot"
>applications like standard ethtool command as it avoids the need to
>either keep bit names (e.g. link modes) in sync with kernel or having to
>add an extra roundtrip for string set request (e.g. for private flags).
>
>Compact format uses one (list) or two (value/mask) arrays of 32-bit
>words to store the bitmap(s). It is more suitable for long running
>applications (ethtool in monitor mode or network management daemons)
>which can retrieve the names once and then pass only compact bitmaps to
>save space.
>
>Userspace requests can use either format; ETHTOOL_GFLAG_COMPACT_BITSETS
>flag in request header tells kernel which format to use in reply.
>Notifications always use compact format.
>
>Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
>---
> Documentation/networking/ethtool-netlink.rst |  68 ++
> include/uapi/linux/ethtool_netlink.h         |  35 +
> net/ethtool/Makefile                         |   2 +-
> net/ethtool/bitset.c                         | 714 +++++++++++++++++++
> net/ethtool/bitset.h                         |  28 +
> net/ethtool/netlink.h                        |   9 +
> 6 files changed, 855 insertions(+), 1 deletion(-)
> create mode 100644 net/ethtool/bitset.c
> create mode 100644 net/ethtool/bitset.h
>
>diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst
>index 3e9680b63afa..8dda6efee060 100644
>--- a/Documentation/networking/ethtool-netlink.rst
>+++ b/Documentation/networking/ethtool-netlink.rst
>@@ -79,6 +79,74 @@ clients not aware of the flag should be interpreted the way the client
> expects. A client must not set flags it does not understand.
> 
> 
>+Bit sets
>+========
>+
>+For short bitmaps of (reasonably) fixed length, standard ``NLA_BITFIELD32``
>+type is used. For arbitrary length bitmaps, ethtool netlink uses a nested
>+attribute with contents of one of two forms: compact (two binary bitmaps
>+representing bit values and mask of affected bits) and bit-by-bit (list of
>+bits identified by either index or name).
>+
>+Compact form: nested (bitset) atrribute contents:
>+
>+  ============================  ======  ============================
>+  ``ETHTOOL_A_BITSET_LIST``     flag    no mask, only a list

I find "list" a bit confusing name of a flag. Perhaps better to stick
with the "compact" terminology and make this "ETHTOOL_A_BITSET_COMPACT"?
Then in the code you can have var "is_compact", which makes the code a
bit easier to read I believe.


>+  ``ETHTOOL_A_BITSET_SIZE``     u32     number of significant bits
>+  ``ETHTOOL_A_BITSET_VALUE``    binary  bitmap of bit values
>+  ``ETHTOOL_A_BITSET_MASK``     binary  bitmap of valid bits

Couple of times the NLA_BITFIELD32 limitation was discussed, so isn't
this the time to introduce generic NLA_BITFIELD with variable len and
use it here? This is exactly job for it. As this is UAPI, I believe it
should be done now cause later won't work.



>+  ============================  ======  ============================
>+
>+Value and mask must have length at least ``ETHTOOL_A_BITSET_SIZE`` bits
>+rounded up to a multiple of 32 bits. They consist of 32-bit words in host byte
>+order, words ordered from least significant to most significant (i.e. the same
>+way as bitmaps are passed with ioctl interface).
>+
>+For compact form, ``ETHTOOL_A_BITSET_SIZE`` and ``ETHTOOL_A_BITSET_VALUE`` are
>+mandatory.  Similar to ``NLA_BITFIELD32``, a compact form bit set requests to
>+set bits in the mask to 1 (if the bit is set in value) or 0 (if not) and
>+preserve the rest. If ``ETHTOOL_A_BITSET_LIST`` is present, there is no mask
>+and bitset represents a simple list of bits.
>+
>+Kernel bit set length may differ from userspace length if older application is
>+used on newer kernel or vice versa. If userspace bitmap is longer, an error is
>+issued only if the request actually tries to set values of some bits not
>+recognized by kernel.
>+
>+Bit-by-bit form: nested (bitset) attribute contents:
>+
>+ +---------------------------------+--------+-----------------------------+
>+ | ``ETHTOOL_A_BITSET_LIST``       | flag   | no mask, only a list        |
>+ +---------------------------------+--------+-----------------------------+
>+ | ``ETHTOOL_A_BITSET_SIZE``       | u32    | number of significant bits  |
>+ +---------------------------------+--------+-----------------------------+
>+ | ``ETHTOOL_A_BITSET_BIT``        | nested | array of bits               |

"ETHTOOL_A_BITSET_BIT" does not exist in the code. I believe you ment
"ETHTOOL_A_BITSET_BITS"


>+ +-+-------------------------------+--------+-----------------------------+
>+ |   ``ETHTOOL_A_BITSET_BIT+``     | nested | one bit                     |

You seem to be missing "|" here.
Also "ETHTOOL_A_BITSET_BIT" does not exist. I believe you ment
"ETHTOOL_A_BITS_BIT"


>+ +-+-+-----------------------------+--------+-----------------------------+
>+ | | | ``ETHTOOL_A_BIT_INDEX``     | u32    | bit index (0 for LSB)       |
>+ +-+-+-----------------------------+--------+-----------------------------+
>+ | | | ``ETHTOOL_A_BIT_NAME``      | string | bit name                    |
>+ +-+-+-----------------------------+--------+-----------------------------+
>+ | | | ``ETHTOOL_A_BIT_VALUE``     | flag   | present if bit is set       |
>+ +-+-+-----------------------------+--------+-----------------------------+
>+
>+Bit size is optional for bit-by-bit form. ``ETHTOOL_A_BITSET_BITS`` nest can
>+only contain ``ETHTOOL_A_BITS_BIT`` attributes but there can be an arbitrary
>+number of them.  A bit may be identified by its index or by its name. When
>+used in requests, listed bits are set to 0 or 1 according to
>+``ETHTOOL_A_BIT_VALUE``, the rest is preserved. A request fails if index
>+exceeds kernel bit length or if name is not recognized.
>+
>+When ``ETHTOOL_A_BITSET_LIST`` flag is present, bitset is interpreted as a
>+simple bit list. ``ETHTOOL_A_BIT_VALUE`` attributes are not used in such case.
>+Bit list represents a bitmap with listed bits set and the rest zero.
>+
>+In requests, application can use either form. Form used by kernel in reply is
>+determined by a flag in flags field of request header. Semantics of value and
>+mask depends on the attribute.
>+
>+
> List of message types
> =====================
> 
>diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
>index c58d9fd52ffc..418f28965a04 100644
>--- a/include/uapi/linux/ethtool_netlink.h
>+++ b/include/uapi/linux/ethtool_netlink.h
>@@ -51,6 +51,41 @@ enum {
> 	ETHTOOL_A_HEADER_MAX = __ETHTOOL_A_HEADER_CNT - 1
> };
> 
>+/* bit sets */
>+
>+enum {
>+	ETHTOOL_A_BIT_UNSPEC,
>+	ETHTOOL_A_BIT_INDEX,			/* u32 */
>+	ETHTOOL_A_BIT_NAME,			/* string */
>+	ETHTOOL_A_BIT_VALUE,			/* flag */
>+
>+	/* add new constants above here */
>+	__ETHTOOL_A_BIT_CNT,
>+	ETHTOOL_A_BIT_MAX = __ETHTOOL_A_BIT_CNT - 1
>+};
>+
>+enum {
>+	ETHTOOL_A_BITS_UNSPEC,
>+	ETHTOOL_A_BITS_BIT,
>+
>+	/* add new constants above here */
>+	__ETHTOOL_A_BITS_CNT,
>+	ETHTOOL_A_BITS_MAX = __ETHTOOL_A_BITS_CNT - 1
>+};

I think it would be good to have this named with "_BITSET_" in it so it
is crystal clear this is part of the bitset UAPI.


>+
>+enum {
>+	ETHTOOL_A_BITSET_UNSPEC,
>+	ETHTOOL_A_BITSET_LIST,			/* flag */
>+	ETHTOOL_A_BITSET_SIZE,			/* u32 */
>+	ETHTOOL_A_BITSET_BITS,			/* nest - _A_BITS_* */
>+	ETHTOOL_A_BITSET_VALUE,			/* binary */
>+	ETHTOOL_A_BITSET_MASK,			/* binary */
>+
>+	/* add new constants above here */
>+	__ETHTOOL_A_BITSET_CNT,
>+	ETHTOOL_A_BITSET_MAX = __ETHTOOL_A_BITSET_CNT - 1
>+};
>+
> /* generic netlink info */
> #define ETHTOOL_GENL_NAME "ethtool"
> #define ETHTOOL_GENL_VERSION 1
>diff --git a/net/ethtool/Makefile b/net/ethtool/Makefile
>index f30e0da88be5..482fdb9380fa 100644
>--- a/net/ethtool/Makefile
>+++ b/net/ethtool/Makefile
>@@ -4,4 +4,4 @@ obj-y				+= ioctl.o
> 
> obj-$(CONFIG_ETHTOOL_NETLINK)	+= ethtool_nl.o
> 
>-ethtool_nl-y	:= netlink.o
>+ethtool_nl-y	:= netlink.o bitset.o
>diff --git a/net/ethtool/bitset.c b/net/ethtool/bitset.c
>new file mode 100644
>index 000000000000..aff6413d6bcc
>--- /dev/null
>+++ b/net/ethtool/bitset.c
>@@ -0,0 +1,714 @@
>+// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
>+
>+#include <linux/ethtool_netlink.h>
>+#include <linux/bitmap.h>
>+#include "netlink.h"
>+#include "bitset.h"
>+
>+/* To reduce the number of slab allocations, the wrappers use fixed size local
>+ * variables for bitmaps up to __SMALL_BITMAP_BITS bits which is the majority
>+ * of bitmaps used by ethtool.
>+ */
>+#define __SMALL_BITMAP_BITS 128
>+#define __SMALL_BITMAP_WORDS DIV_ROUND_UP(__SMALL_BITMAP_BITS, 32)
>+
>+static u32 __lower_bits(unsigned int n)
>+{
>+	return ~(u32)0 >> (32 - n % 32);
>+}
>+
>+static u32 __upper_bits(unsigned int n)
>+{
>+	return ~(u32)0 << (n % 32);
>+}
>+
>+/**
>+ * __bitmap32_clear() - Clear u32 based bitmap
>+ * @dst:   bitmap to clear
>+ * @start: beginning of the interval
>+ * @end:   end of the interval
>+ * @mod:   set if bitmap was modified
>+ *
>+ * Clear @nbits bits of a bitmap with indices @start <= i < @end
>+ */
>+static void __bitmap32_clear(u32 *dst, unsigned int start, unsigned int end,
>+			     bool *mod)
>+{
>+	unsigned int start_word = start / 32;
>+	unsigned int end_word = end / 32;
>+	unsigned int i;
>+	u32 mask;
>+
>+	if (end <= start)
>+		return;
>+
>+	if (start % 32) {
>+		mask = __upper_bits(start);
>+		if (end_word == start_word) {
>+			mask &= __lower_bits(end);
>+			if (dst[start_word] & mask) {
>+				dst[start_word] &= ~mask;
>+				*mod = true;
>+			}
>+			return;
>+		}
>+		if (dst[start_word] & mask) {
>+			dst[start_word] &= ~mask;
>+			*mod = true;
>+		}
>+		start_word++;
>+	}
>+
>+	for (i = start_word; i < end_word; i++) {
>+		if (dst[i]) {
>+			dst[i] = 0;
>+			*mod = true;
>+		}
>+	}
>+	if (end % 32) {
>+		mask = __lower_bits(end);
>+		if (dst[end_word] & mask) {
>+			dst[end_word] &= ~mask;
>+			*mod = true;
>+		}
>+	}
>+}
>+
>+/**
>+ * __bitmap32_no_zero() - Check if any bit is set in an interval
>+ * @map:   bitmap to test
>+ * @start: beginning of the interval
>+ * @end:   end of the interval
>+ *
>+ * Return: true if there is non-zero bit with  index @start <= i < @end,
>+ *         false if the whole interval is zero
>+ */
>+static bool __bitmap32_not_zero(const u32 *map, unsigned int start,
>+				unsigned int end)
>+{
>+	unsigned int start_word = start / 32;
>+	unsigned int end_word = end / 32;
>+	u32 mask;
>+
>+	if (end <= start)
>+		return true;
>+
>+	if (start % 32) {
>+		mask = __upper_bits(start);
>+		if (end_word == start_word) {
>+			mask &= __lower_bits(end);
>+			return map[start_word] & mask;
>+		}
>+		if (map[start_word] & mask)
>+			return true;
>+		start_word++;
>+	}
>+
>+	if (!memchr_inv(map + start_word, '\0',
>+			(end_word - start_word) * sizeof(u32)))
>+		return true;
>+	if (end % 32 == 0)
>+		return true;
>+	return map[end_word] & __lower_bits(end);
>+}
>+
>+/**
>+ * __bitmap32_update() - Modify u32 based bitmap according to value/mask pair
>+ * @dst:   bitmap to update
>+ * @nbits: bit size of the bitmap
>+ * @value: values to set
>+ * @mask:  mask of bits to set
>+ * @mod:   set to true if bitmap is modified, preserve if not
>+ *
>+ * Set bits in @dst bitmap which are set in @mask to values from @value, leave
>+ * the rest untouched. If destination bitmap was modified, set @mod to true,
>+ * leave as it is if not.
>+ */
>+static void __bitmap32_update(u32 *dst, unsigned int nbits, const u32 *value,
>+			      const u32 *mask, bool *mod)
>+{
>+	while (nbits > 0) {
>+		u32 real_mask = mask ? *mask : ~(u32)0;
>+		u32 new_value;
>+
>+		if (nbits < 32)
>+			real_mask &= __lower_bits(nbits);
>+		new_value = (*dst & ~real_mask) | (*value & real_mask);
>+		if (new_value != *dst) {
>+			*dst = new_value;
>+			*mod = true;
>+		}
>+
>+		if (nbits <= 32)
>+			break;
>+		dst++;
>+		nbits -= 32;
>+		value++;
>+		if (mask)
>+			mask++;
>+	}
>+}
>+
>+static bool __bitmap32_test_bit(const u32 *map, unsigned int index)
>+{
>+	return map[index / 32] & (1U << (index % 32));
>+}
>+
>+/**
>+ * ethnl_bitset32_size() - Calculate size of bitset nested attribute
>+ * @val:     value bitmap (u32 based)
>+ * @mask:    mask bitmap (u32 based, optional)
>+ * @nbits:   bit length of the bitset
>+ * @names:   array of bit names (optional)
>+ * @compact: assume compact format for output
>+ *
>+ * Estimate length of netlink attribute composed by a later call to
>+ * ethnl_put_bitset32() call with the same arguments.
>+ *
>+ * Return: negative error code or attribute length estimate
>+ */
>+int ethnl_bitset32_size(const u32 *val, const u32 *mask, unsigned int nbits,
>+			ethnl_string_array_t names, bool compact)
>+{
>+	unsigned int len = 0;
>+
>+	/* list flag */
>+	if (!mask)
>+		len += nla_total_size(sizeof(u32));
>+	/* size */
>+	len += nla_total_size(sizeof(u32));
>+
>+	if (compact) {
>+		unsigned int nwords = DIV_ROUND_UP(nbits, 32);
>+
>+		/* value, mask */
>+		len += (mask ? 2 : 1) * nla_total_size(nwords * sizeof(u32));
>+	} else {
>+		unsigned int bits_len = 0;
>+		unsigned int bit_len, i;
>+
>+		for (i = 0; i < nbits; i++) {
>+			const char *name = names ? names[i] : NULL;
>+
>+			if (!__bitmap32_test_bit(mask ?: val, i))
>+				continue;
>+			/* index */
>+			bit_len = nla_total_size(sizeof(u32));
>+			/* name */
>+			if (name)
>+				bit_len += ethnl_strz_size(name);
>+			/* value */
>+			if (mask && __bitmap32_test_bit(val, i))
>+				bit_len += nla_total_size(0);
>+
>+			/* bit nest */
>+			bits_len += nla_total_size(bit_len);
>+		}
>+		/* bits nest */
>+		len += nla_total_size(bits_len);
>+	}
>+
>+	/* outermost nest */
>+	return nla_total_size(len);
>+}
>+
>+/**
>+ * ethnl_put_bitset32() - Put a bitset nest into a message
>+ * @skb:      skb with the message
>+ * @attrtype: attribute type for the bitset nest
>+ * @val:      value bitmap (u32 based)
>+ * @mask:     mask bitmap (u32 based, optional)
>+ * @nbits:    bit length of the bitset
>+ * @names:    array of bit names (optional)
>+ * @compact:  use compact format for the output
>+ *
>+ * Compose a nested attribute representing a bitset. If @mask is null, simple
>+ * bitmap (bit list) is created, if @mask is provided, represent a value/mask
>+ * pair. Bit names are only used in verbose mode and when provided by calller.
>+ *
>+ * Return:    0 on success, negative error value on error

Remove the spaces.


>+ */
>+int ethnl_put_bitset32(struct sk_buff *skb, int attrtype, const u32 *val,
>+		       const u32 *mask, unsigned int nbits,
>+		       ethnl_string_array_t names, bool compact)
>+{
>+	struct nlattr *nest;
>+	struct nlattr *attr;
>+
>+	nest = nla_nest_start(skb, attrtype);
>+	if (!nest)
>+		return -EMSGSIZE;
>+
>+	if (!mask && nla_put_flag(skb, ETHTOOL_A_BITSET_LIST))

Wait, shouldn't you rather check "!compact" ?

and WARN_ON in case compact == true && mask == NULL?


>+		goto nla_put_failure;
>+	if (nla_put_u32(skb, ETHTOOL_A_BITSET_SIZE, nbits))
>+		goto nla_put_failure;
>+	if (compact) {
>+		unsigned int nwords = DIV_ROUND_UP(nbits, 32);
>+		unsigned int nbytes = nwords * sizeof(u32);
>+		u32 *dst;
>+
>+		attr = nla_reserve(skb, ETHTOOL_A_BITSET_VALUE, nbytes);
>+		if (!attr)
>+			goto nla_put_failure;
>+		dst = nla_data(attr);
>+		memcpy(dst, val, nbytes);
>+		if (nbits % 32)
>+			dst[nwords - 1] &= __lower_bits(nbits);
>+
>+		if (mask) {
>+			attr = nla_reserve(skb, ETHTOOL_A_BITSET_MASK, nbytes);
>+			if (!attr)
>+				goto nla_put_failure;
>+			dst = nla_data(attr);
>+			memcpy(dst, mask, nbytes);
>+			if (nbits % 32)
>+				dst[nwords - 1] &= __lower_bits(nbits);
>+		}
>+	} else {
>+		struct nlattr *bits;
>+		unsigned int i;
>+
>+		bits = nla_nest_start(skb, ETHTOOL_A_BITSET_BITS);
>+		if (!bits)
>+			goto nla_put_failure;
>+		for (i = 0; i < nbits; i++) {
>+			const char *name = names ? names[i] : NULL;
>+
>+			if (!__bitmap32_test_bit(mask ?: val, i))

A) this __bitmap32_test_bit() looks like something generic, yet it is
   not. Perhaps you would want to add this helper to
   include/linux/bitmap.h?
B) Why don't you do bitmap_to_arr32 conversion in this function just
   before val/mask put. Then you can use normal test_bit() here.


>+				continue;
>+			attr = nla_nest_start(skb, ETHTOOL_A_BITS_BIT);
>+			if (!attr ||
>+			    nla_put_u32(skb, ETHTOOL_A_BIT_INDEX, i))

You mix these 2 in 1 if which are not related. Better keep them separate
in two ifs.
Or you can put the rest of the puts in the same if too.


>+				goto nla_put_failure;
>+			if (name &&
>+			    ethnl_put_strz(skb, ETHTOOL_A_BIT_NAME, name))
>+				goto nla_put_failure;
>+			if (mask && __bitmap32_test_bit(val, i) &&
>+			    nla_put_flag(skb, ETHTOOL_A_BIT_VALUE))
>+				goto nla_put_failure;
>+			nla_nest_end(skb, attr);
>+		}
>+		nla_nest_end(skb, bits);
>+	}
>+
>+	nla_nest_end(skb, nest);
>+	return 0;
>+
>+nla_put_failure:
>+	nla_nest_cancel(skb, nest);
>+	return -EMSGSIZE;
>+}
>+
>+static const struct nla_policy bitset_policy[ETHTOOL_A_BITSET_MAX + 1] = {
>+	[ETHTOOL_A_BITSET_UNSPEC]	= { .type = NLA_REJECT },
>+	[ETHTOOL_A_BITSET_LIST]		= { .type = NLA_FLAG },
>+	[ETHTOOL_A_BITSET_SIZE]		= { .type = NLA_U32 },
>+	[ETHTOOL_A_BITSET_BITS]		= { .type = NLA_NESTED },
>+	[ETHTOOL_A_BITSET_VALUE]	= { .type = NLA_BINARY },
>+	[ETHTOOL_A_BITSET_MASK]		= { .type = NLA_BINARY },
>+};
>+
>+static const struct nla_policy bit_policy[ETHTOOL_A_BIT_MAX + 1] = {
>+	[ETHTOOL_A_BIT_UNSPEC]		= { .type = NLA_REJECT },
>+	[ETHTOOL_A_BIT_INDEX]		= { .type = NLA_U32 },
>+	[ETHTOOL_A_BIT_NAME]		= { .type = NLA_NUL_STRING },
>+	[ETHTOOL_A_BIT_VALUE]		= { .type = NLA_FLAG },
>+};
>+
>+/**
>+ * ethnl_bitset_is_compact() - check if bitset attribute represents a compact
>+ *			       bitset
>+ * @bitset  - nested attribute representing a bitset
>+ * @compact - pointer for return value

In the rest of the code, you use
@name: description


>+ *
>+ * Return: 0 on success, negative error code on failure
>+ */
>+int ethnl_bitset_is_compact(const struct nlattr *bitset, bool *compact)
>+{
>+	struct nlattr *tb[ETHTOOL_A_BITSET_MAX + 1];
>+	int ret;
>+
>+	ret = nla_parse_nested(tb, ETHTOOL_A_BITSET_MAX, bitset,
>+			       bitset_policy, NULL);
>+	if (ret < 0)
>+		return ret;
>+
>+	if (tb[ETHTOOL_A_BITSET_BITS]) {
>+		if (tb[ETHTOOL_A_BITSET_VALUE] || tb[ETHTOOL_A_BITSET_MASK])
>+			return -EINVAL;
>+		*compact = false;
>+		return 0;
>+	}
>+	if (!tb[ETHTOOL_A_BITSET_SIZE] || !tb[ETHTOOL_A_BITSET_VALUE])
>+		return -EINVAL;
>+
>+	*compact = true;
>+	return 0;
>+}
>+
>+static int ethnl_name_to_idx(ethnl_string_array_t names, unsigned int n_names,
>+			     const char *name, unsigned int name_len)
>+{
>+	unsigned int i;
>+
>+	if (!names)
>+		return n_names;
>+
>+	for (i = 0; i < n_names; i++) {
>+		const char *bname = names[i];
>+
>+		if (!strncmp(bname, name, name_len) &&
>+		    strlen(bname) <= name_len)
>+			return i;
>+	}
>+
>+	return n_names;

Maybe bettet to stick with -ERRNO?


>+}
>+
>+static int ethnl_parse_bit(unsigned int *index, bool *val, unsigned int nbits,
>+			   const struct nlattr *bit_attr, bool is_list,
>+			   ethnl_string_array_t names,
>+			   struct netlink_ext_ack *extack)
>+{
>+	struct nlattr *tb[ETHTOOL_A_BIT_MAX + 1];
>+	int ret, idx;
>+
>+	if (nla_type(bit_attr) != ETHTOOL_A_BITS_BIT) {
>+		NL_SET_ERR_MSG_ATTR(extack, bit_attr,
>+				    "only ETHTOOL_A_BITS_BIT allowed in ETHTOOL_A_BITSET_BITS");
>+		return -EINVAL;
>+	}

Probably it makes sense the caller does this check. Later on, if there
is another possible value, the check would have to go there anyway.


>+	ret = nla_parse_nested(tb, ETHTOOL_A_BIT_MAX, bit_attr, bit_policy,
>+			       extack);
>+	if (ret < 0)
>+		return ret;
>+
>+	if (tb[ETHTOOL_A_BIT_INDEX]) {
>+		const char *name;
>+
>+		idx = nla_get_u32(tb[ETHTOOL_A_BIT_INDEX]);
>+		if (idx >= nbits) {
>+			NL_SET_ERR_MSG_ATTR(extack,
>+					    tb[ETHTOOL_A_BIT_INDEX],
>+					    "bit index too high");
>+			return -EOPNOTSUPP;
>+		}
>+		name = names ? names[idx] : NULL;
>+		if (tb[ETHTOOL_A_BIT_NAME] && name &&
>+		    strncmp(nla_data(tb[ETHTOOL_A_BIT_NAME]), name,
>+			    nla_len(tb[ETHTOOL_A_BIT_NAME]))) {
>+			NL_SET_ERR_MSG_ATTR(extack, bit_attr,
>+					    "bit index and name mismatch");
>+			return -EINVAL;
>+		}
>+	} else if (tb[ETHTOOL_A_BIT_NAME]) {
>+		idx = ethnl_name_to_idx(names, nbits,
>+					nla_data(tb[ETHTOOL_A_BIT_NAME]),
>+					nla_len(tb[ETHTOOL_A_BIT_NAME]));

It's a string? Policy validation should take care if it is correctly
terminated by '\0'. Then you don't need to pass len down. Anyone who is
interested in length can use strlen().


>+		if (idx >= nbits) {
>+			NL_SET_ERR_MSG_ATTR(extack,
>+					    tb[ETHTOOL_A_BIT_NAME],
>+					    "bit name not found");
>+			return -EOPNOTSUPP;
>+		}
>+	} else {
>+		NL_SET_ERR_MSG_ATTR(extack, bit_attr,
>+				    "neither bit index nor name specified");
>+		return -EINVAL;
>+	}
>+
>+	*index = idx;
>+	*val = is_list || tb[ETHTOOL_A_BIT_VALUE];
>+	return 0;
>+}
>+
>+static int
>+ethnl_update_bitset32_verbose(u32 *bitmap, unsigned int nbits,
>+			      const struct nlattr *attr, struct nlattr **tb,
>+			      ethnl_string_array_t names,
>+			      struct netlink_ext_ack *extack, bool *mod)
>+{
>+	struct nlattr *bit_attr;
>+	bool is_list;
>+	int rem;
>+	int ret;
>+
>+	if (tb[ETHTOOL_A_BITSET_VALUE]) {
>+		NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_BITSET_VALUE],
>+				    "value only allowed in compact bitset");
>+		return -EINVAL;
>+	}
>+	if (tb[ETHTOOL_A_BITSET_MASK]) {
>+		NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_BITSET_MASK],
>+				    "mask only allowed in compact bitset");
>+		return -EINVAL;
>+	}
>+	is_list = (tb[ETHTOOL_A_BITSET_LIST] != NULL);

just:
	is_list = tb[ETHTOOL_A_BITSET_LIST]
is enough.



>+
>+	nla_for_each_nested(bit_attr, tb[ETHTOOL_A_BITSET_BITS], rem) {
>+		bool old_val, new_val;
>+		unsigned int idx;
>+
>+		ret = ethnl_parse_bit(&idx, &new_val, nbits, bit_attr, is_list,
>+				      names, extack);
>+		if (ret < 0)
>+			return ret;
>+		old_val = bitmap[idx / 32] & ((u32)1 << (idx % 32));
>+		if (new_val != old_val) {
>+			if (new_val)
>+				bitmap[idx / 32] |= ((u32)1 << (idx % 32));
>+			else
>+				bitmap[idx / 32] &= ~((u32)1 << (idx % 32));
>+			*mod = true;
>+		}
>+	}
>+
>+	return 0;
>+}
>+
>+static int ethnl_compact_sanity_checks(unsigned int nbits,
>+				       const struct nlattr *nest,
>+				       struct nlattr **tb,
>+				       struct netlink_ext_ack *extack)
>+{
>+	bool is_list = (tb[ETHTOOL_A_BITSET_LIST] != NULL);

Same here.


>+	unsigned int attr_nbits, attr_nwords;
>+	const struct nlattr *test_attr;
>+
>+	if (is_list && tb[ETHTOOL_A_BITSET_MASK]) {
>+		NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_BITSET_MASK],
>+				    "mask not allowed in list bitset");
>+		return -EINVAL;
>+	}
>+	if (!tb[ETHTOOL_A_BITSET_SIZE]) {
>+		NL_SET_ERR_MSG_ATTR(extack, nest,
>+				    "missing size in compact bitset");
>+		return -EINVAL;
>+	}
>+	if (!tb[ETHTOOL_A_BITSET_VALUE]) {
>+		NL_SET_ERR_MSG_ATTR(extack, nest,
>+				    "missing value in compact bitset");
>+		return -EINVAL;
>+	}
>+	if (!is_list && !tb[ETHTOOL_A_BITSET_MASK]) {
>+		NL_SET_ERR_MSG_ATTR(extack, nest,
>+				    "missing mask in compact nonlist bitset");
>+		return -EINVAL;
>+	}
>+
>+	attr_nbits = nla_get_u32(tb[ETHTOOL_A_BITSET_SIZE]);
>+	attr_nwords = DIV_ROUND_UP(attr_nbits, 32);
>+	if (nla_len(tb[ETHTOOL_A_BITSET_VALUE]) != attr_nwords * sizeof(u32)) {
>+		NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_BITSET_VALUE],
>+				    "bitset value length does not match size");
>+		return -EINVAL;
>+	}
>+	if (tb[ETHTOOL_A_BITSET_MASK] &&
>+	    nla_len(tb[ETHTOOL_A_BITSET_MASK]) != attr_nwords * sizeof(u32)) {
>+		NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_BITSET_MASK],
>+				    "bitset mask length does not match size");
>+		return -EINVAL;
>+	}
>+	if (attr_nbits <= nbits)
>+		return 0;
>+
>+	test_attr = is_list ? tb[ETHTOOL_A_BITSET_VALUE] :
>+			      tb[ETHTOOL_A_BITSET_MASK];
>+	if (__bitmap32_not_zero(nla_data(test_attr), nbits, attr_nbits)) {
>+		NL_SET_ERR_MSG_ATTR(extack, test_attr,
>+				    "cannot modify bits past kernel bitset size");
>+		return -EINVAL;
>+	}
>+	return 0;
>+}
>+
>+/**
>+ * ethnl_update_bitset32() - Apply a bitset nest to a u32 based bitmap
>+ * @bitmap:  bitmap to update
>+ * @nbits:   size of the updated bitmap in bits
>+ * @attr:    nest attribute to parse and apply
>+ * @names:   array of bit names; may be null for compact format
>+ * @extack:  extack for error reporting
>+ * @mod:     set this to true if bitmap is modified, leave as it is if not
>+ *
>+ * Apply bitset netsted attribute to a bitmap. If the attribute represents
>+ * a bit list, @bitmap is set to its contents; otherwise, bits in mask are
>+ * set to values from value. Bitmaps in the attribute may be longer than
>+ * @nbits but the message must not request modifying any bits past @nbits.
>+ *
>+ * Return:   negative error code on failure, 0 on success
>+ */
>+int ethnl_update_bitset32(u32 *bitmap, unsigned int nbits,
>+			  const struct nlattr *attr, ethnl_string_array_t names,
>+			  struct netlink_ext_ack *extack, bool *mod)
>+{
>+	struct nlattr *tb[ETHTOOL_A_BITSET_MAX + 1];
>+	unsigned int change_bits;
>+	bool is_list;
>+	int ret;
>+
>+	if (!attr)
>+		return 0;
>+	ret = nla_parse_nested(tb, ETHTOOL_A_BITSET_MAX, attr, bitset_policy,
>+			       extack);
>+	if (ret < 0)
>+		return ret;
>+
>+	if (tb[ETHTOOL_A_BITSET_BITS])
>+		return ethnl_update_bitset32_verbose(bitmap, nbits, attr, tb,
>+						     names, extack, mod);
>+	ret = ethnl_compact_sanity_checks(nbits, attr, tb, extack);
>+	if (ret < 0)
>+		return ret;
>+
>+	is_list = (tb[ETHTOOL_A_BITSET_LIST] != NULL);

And here.


>+	change_bits = min_t(unsigned int,
>+			    nla_get_u32(tb[ETHTOOL_A_BITSET_SIZE]), nbits);
>+	__bitmap32_update(bitmap, change_bits,
>+			  nla_data(tb[ETHTOOL_A_BITSET_VALUE]),
>+			  is_list ? NULL : nla_data(tb[ETHTOOL_A_BITSET_MASK]),
>+			  mod);
>+	if (is_list && change_bits < nbits)
>+		__bitmap32_clear(bitmap, change_bits, nbits, mod);
>+
>+	return 0;
>+}
>+
>+/* 64-bit long endian architecture is the only case when u32 based bitmaps
>+ * and unsigned long based bitmaps have different memory layout so that we
>+ * cannot simply cast the latter to the former.
>+ */
>+#if BITS_PER_LONG == 64 && defined(__BIG_ENDIAN)
>+
>+int ethnl_bitset_size(const unsigned long *val, const unsigned long *mask,
>+		      unsigned int nbits, ethnl_string_array_t names,
>+		      bool compact)
>+{
>+	u32 small_mask32[__SMALL_BITMAP_WORDS];
>+	u32 small_val32[__SMALL_BITMAP_WORDS];
>+	u32 *mask32;
>+	u32 *val32;
>+	int ret;
>+
>+	if (nbits > __SMALL_BITMAP_BITS) {
>+		unsigned int nwords = DIV_ROUND_UP(nbits, 32);
>+
>+		val32 = kmalloc_array(2 * nwords, sizeof(u32), GFP_KERNEL);
>+		if (!val32)
>+			return -ENOMEM;
>+		mask32 = val32 + nwords;
>+	} else {
>+		val32 = small_val32;
>+		mask32 = small_mask32;
>+	}
>+
>+	bitmap_to_arr32(val32, val, nbits);
>+	if (mask)
>+		bitmap_to_arr32(mask32, mask, nbits);
>+	else
>+		mask32 = NULL;
>+	ret = ethnl_bitset32_size(val32, mask32, nbits, names, compact);
>+
>+	if (nbits > __SMALL_BITMAP_BITS)
>+		kfree(val32);
>+
>+	return ret;
>+}
>+
>+int ethnl_put_bitset(struct sk_buff *skb, int attrtype,
>+		     const unsigned long *val, const unsigned long *mask,
>+		     unsigned int nbits, ethnl_string_array_t names,
>+		     bool compact)
>+{
>+	u32 small_mask32[__SMALL_BITMAP_WORDS];
>+	u32 small_val32[__SMALL_BITMAP_WORDS];
>+	u32 *mask32;
>+	u32 *val32;
>+	int ret;
>+
>+	if (nbits > __SMALL_BITMAP_BITS) {
>+		unsigned int nwords = DIV_ROUND_UP(nbits, 32);
>+
>+		val32 = kmalloc_array(2 * nwords, sizeof(u32), GFP_KERNEL);
>+		if (!val32)
>+			return -ENOMEM;
>+		mask32 = val32 + nwords;
>+	} else {
>+		val32 = small_val32;
>+		mask32 = small_mask32;
>+	}
>+
>+	bitmap_to_arr32(val32, val, nbits);
>+	if (mask)
>+		bitmap_to_arr32(mask32, mask, nbits);
>+	else
>+		mask32 = NULL;
>+	ret = ethnl_put_bitset32(skb, attrtype, val32, mask32, nbits, names,
>+				 compact);
>+
>+	if (nbits > __SMALL_BITMAP_BITS)
>+		kfree(val32);
>+
>+	return ret;
>+}
>+
>+int ethnl_update_bitset(unsigned long *bitmap, unsigned int nbits,
>+			const struct nlattr *attr, ethnl_string_array_t names,
>+			struct netlink_ext_ack *extack, bool *mod)
>+{
>+	u32 small_bitmap32[__SMALL_BITMAP_WORDS];
>+	u32 *bitmap32 = small_bitmap32;
>+	bool u32_mod = false;
>+	int ret;
>+
>+	if (nbits > __SMALL_BITMAP_BITS) {
>+		unsigned int dst_words = DIV_ROUND_UP(nbits, 32);
>+
>+		bitmap32 = kmalloc_array(dst_words, sizeof(u32), GFP_KERNEL);
>+		if (!bitmap32)
>+			return -ENOMEM;
>+	}
>+
>+	bitmap_to_arr32(bitmap32, bitmap, nbits);
>+	ret = ethnl_update_bitset32(bitmap32, nbits, attr, names, extack,
>+				    &u32_mod);
>+	if (ulong_mod) {
>+		bitmap_from_arr32(bitmap, bitmap32, nbits);
>+		*mod = true;
>+	}
>+
>+	if (size > __SMALL_BITMAP_BITS)
>+		kfree(bitmask32);
>+
>+	return ret;
>+}
>+
>+#else
>+
>+int ethnl_bitset_size(const unsigned long *val, const unsigned long *mask,
>+		      unsigned int nbits, ethnl_string_array_t names,
>+		      bool compact)
>+{
>+	return ethnl_bitset32_size((const u32 *)val, (const u32 *)mask, nbits,
>+				   names, compact);
>+}
>+
>+int ethnl_put_bitset(struct sk_buff *skb, int attrtype,
>+		     const unsigned long *val, const unsigned long *mask,
>+		     unsigned int nbits, ethnl_string_array_t names,
>+		     bool compact)
>+{
>+	return ethnl_put_bitset32(skb, attrtype, (const u32 *)val,
>+				  (const u32 *)mask, nbits, names, compact);
>+}
>+
>+int ethnl_update_bitset(unsigned long *bitmap, unsigned int nbits,
>+			const struct nlattr *attr, ethnl_string_array_t names,
>+			struct netlink_ext_ack *extack, bool *mod)
>+{
>+	return ethnl_update_bitset32((u32 *)bitmap, nbits, attr, names, extack,
>+				     mod);
>+}
>+
>+#endif /* BITS_PER_LONG == 64 && defined(__BIG_ENDIAN) */
>diff --git a/net/ethtool/bitset.h b/net/ethtool/bitset.h
>new file mode 100644
>index 000000000000..cd3d681b4524
>--- /dev/null
>+++ b/net/ethtool/bitset.h
>@@ -0,0 +1,28 @@
>+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
>+
>+#ifndef _NET_ETHTOOL_BITSET_H
>+#define _NET_ETHTOOL_BITSET_H
>+
>+typedef const char (*const ethnl_string_array_t)[ETH_GSTRING_LEN];
>+
>+int ethnl_bitset_is_compact(const struct nlattr *bitset, bool *compact);
>+int ethnl_bitset_size(const unsigned long *val, const unsigned long *mask,
>+		      unsigned int nbits, ethnl_string_array_t names,
>+		      bool compact);
>+int ethnl_bitset32_size(const u32 *val, const u32 *mask, unsigned int nbits,
>+			ethnl_string_array_t names, bool compact);
>+int ethnl_put_bitset(struct sk_buff *skb, int attrtype,
>+		     const unsigned long *val, const unsigned long *mask,
>+		     unsigned int nbits, ethnl_string_array_t names,
>+		     bool compact);
>+int ethnl_put_bitset32(struct sk_buff *skb, int attrtype, const u32 *val,
>+		       const u32 *mask, unsigned int nbits,
>+		       ethnl_string_array_t names, bool compact);
>+int ethnl_update_bitset(unsigned long *bitmap, unsigned int nbits,
>+			const struct nlattr *attr, ethnl_string_array_t names,
>+			struct netlink_ext_ack *extack, bool *mod);
>+int ethnl_update_bitset32(u32 *bitmap, unsigned int nbits,
>+			  const struct nlattr *attr, ethnl_string_array_t names,
>+			  struct netlink_ext_ack *extack, bool *mod);

Hmm, I wonder why user needs to work with the 32 variants..


>+
>+#endif /* _NET_ETHTOOL_BITSET_H */
>diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
>index f7c0368a9fa0..4c0b5ca439f8 100644
>--- a/net/ethtool/netlink.h
>+++ b/net/ethtool/netlink.h
>@@ -20,6 +20,15 @@ struct sk_buff *ethnl_reply_init(size_t payload, struct net_device *dev, u8 cmd,
> 				 u16 hdr_attrtype, struct genl_info *info,
> 				 void **ehdrp);
> 
>+#if BITS_PER_LONG == 64 && defined(__BIG_ENDIAN)
>+void ethnl_bitmap_to_u32(unsigned long *bitmap, unsigned int nwords);
>+#else
>+static inline void ethnl_bitmap_to_u32(unsigned long *bitmap,
>+				       unsigned int nwords)
>+{
>+}
>+#endif
>+
> /**
>  * ethnl_strz_size() - calculate attribute length for fixed size string
>  * @s: ETH_GSTRING_LEN sized string (may not be null terminated)
>-- 
>2.23.0
>

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

* Re: [PATCH net-next v7 14/17] ethtool: set link settings with LINKINFO_SET request
  2019-10-09 20:59 ` [PATCH net-next v7 14/17] ethtool: set link settings with LINKINFO_SET request Michal Kubecek
  2019-10-10 15:37   ` Jiri Pirko
@ 2019-10-12 16:33   ` Jiri Pirko
  2019-10-14  8:48     ` Michal Kubecek
  1 sibling, 1 reply; 46+ messages in thread
From: Jiri Pirko @ 2019-10-12 16:33 UTC (permalink / raw)
  To: Michal Kubecek
  Cc: David Miller, netdev, Jakub Kicinski, Andrew Lunn,
	Florian Fainelli, John Linville, Stephen Hemminger,
	Johannes Berg, linux-kernel

Wed, Oct 09, 2019 at 10:59:43PM CEST, mkubecek@suse.cz wrote:

[...]

>+static const struct nla_policy linkinfo_hdr_policy[ETHTOOL_A_HEADER_MAX + 1] = {
>+	[ETHTOOL_A_HEADER_UNSPEC]		= { .type = NLA_REJECT },
>+	[ETHTOOL_A_HEADER_DEV_INDEX]		= { .type = NLA_U32 },
>+	[ETHTOOL_A_HEADER_DEV_NAME]		= { .type = NLA_NUL_STRING,
>+						    .len = IFNAMSIZ - 1 },

Please make ETHTOOL_A_HEADER_DEV_NAME accept alternative names as well.
Just s/IFNAMSIZ/ALTIFNAMSIZ should be enough.

>+	[ETHTOOL_A_HEADER_GFLAGS]		= { .type = NLA_U32 },
>+	[ETHTOOL_A_HEADER_RFLAGS]		= { .type = NLA_REJECT },
>+};

[...]

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

* Re: [PATCH net-next v7 14/17] ethtool: set link settings with LINKINFO_SET request
  2019-10-12 16:33   ` Jiri Pirko
@ 2019-10-14  8:48     ` Michal Kubecek
  0 siblings, 0 replies; 46+ messages in thread
From: Michal Kubecek @ 2019-10-14  8:48 UTC (permalink / raw)
  To: netdev
  Cc: Jiri Pirko, David Miller, Jakub Kicinski, Andrew Lunn,
	Florian Fainelli, John Linville, Stephen Hemminger,
	Johannes Berg, linux-kernel

On Sat, Oct 12, 2019 at 06:33:09PM +0200, Jiri Pirko wrote:
> Wed, Oct 09, 2019 at 10:59:43PM CEST, mkubecek@suse.cz wrote:
> 
> [...]
> 
> >+static const struct nla_policy linkinfo_hdr_policy[ETHTOOL_A_HEADER_MAX + 1] = {
> >+	[ETHTOOL_A_HEADER_UNSPEC]		= { .type = NLA_REJECT },
> >+	[ETHTOOL_A_HEADER_DEV_INDEX]		= { .type = NLA_U32 },
> >+	[ETHTOOL_A_HEADER_DEV_NAME]		= { .type = NLA_NUL_STRING,
> >+						    .len = IFNAMSIZ - 1 },
> 
> Please make ETHTOOL_A_HEADER_DEV_NAME accept alternative names as well.
> Just s/IFNAMSIZ/ALTIFNAMSIZ should be enough.

Yes, definitely. I focused on (finally) submitting v7 so I didn't left
testing how it plays with altnames for later.

Michal 

> >+	[ETHTOOL_A_HEADER_GFLAGS]		= { .type = NLA_U32 },
> >+	[ETHTOOL_A_HEADER_RFLAGS]		= { .type = NLA_REJECT },
> >+};
> 
> [...]

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

* Re: [PATCH net-next v7 06/17] ethtool: netlink bitset handling
  2019-10-11 13:34   ` Jiri Pirko
@ 2019-10-14 11:18     ` Michal Kubecek
  2019-10-14 13:02       ` Jiri Pirko
  0 siblings, 1 reply; 46+ messages in thread
From: Michal Kubecek @ 2019-10-14 11:18 UTC (permalink / raw)
  To: netdev
  Cc: Jiri Pirko, David Miller, Jakub Kicinski, Andrew Lunn,
	Florian Fainelli, John Linville, Stephen Hemminger,
	Johannes Berg, linux-kernel

On Fri, Oct 11, 2019 at 03:34:29PM +0200, Jiri Pirko wrote:
> Wed, Oct 09, 2019 at 10:59:18PM CEST, mkubecek@suse.cz wrote:
> >+Bit sets
> >+========
> >+
> >+For short bitmaps of (reasonably) fixed length, standard ``NLA_BITFIELD32``
> >+type is used. For arbitrary length bitmaps, ethtool netlink uses a nested
> >+attribute with contents of one of two forms: compact (two binary bitmaps
> >+representing bit values and mask of affected bits) and bit-by-bit (list of
> >+bits identified by either index or name).
> >+
> >+Compact form: nested (bitset) atrribute contents:
> >+
> >+  ============================  ======  ============================
> >+  ``ETHTOOL_A_BITSET_LIST``     flag    no mask, only a list
> 
> I find "list" a bit confusing name of a flag. Perhaps better to stick
> with the "compact" terminology and make this "ETHTOOL_A_BITSET_COMPACT"?
> Then in the code you can have var "is_compact", which makes the code a
> bit easier to read I believe.

This is not the same as "compact", "list" flag means that the bit set
does not represent a value/mask pair but only a single bitmap (which can
be understood as a list or subset of possible values).

This saves some space in kernel replies where there is no natural mask
so that we would have to invent one (usually all possible bits) but it
is more important in request where some request want to modify a subset
of bits (set some, unset some) while some requests pass a list of bits
to be set after the operation (i.e. "I want exactly these to be
enabled").

The flag could be omitted for compact form where we could simply say
that if there is no mask, it's a list, but we need it for verbose form.

> >+  ``ETHTOOL_A_BITSET_SIZE``     u32     number of significant bits
> >+  ``ETHTOOL_A_BITSET_VALUE``    binary  bitmap of bit values
> >+  ``ETHTOOL_A_BITSET_MASK``     binary  bitmap of valid bits
> 
> Couple of times the NLA_BITFIELD32 limitation was discussed, so isn't
> this the time to introduce generic NLA_BITFIELD with variable len and
> use it here? This is exactly job for it. As this is UAPI, I believe it
> should be done now cause later won't work.

As discussed before, we would lose the option to omit mask when it's not
needed.

> >+Bit-by-bit form: nested (bitset) attribute contents:
> >+
> >+ +---------------------------------+--------+-----------------------------+
> >+ | ``ETHTOOL_A_BITSET_LIST``       | flag   | no mask, only a list        |
> >+ +---------------------------------+--------+-----------------------------+
> >+ | ``ETHTOOL_A_BITSET_SIZE``       | u32    | number of significant bits  |
> >+ +---------------------------------+--------+-----------------------------+
> >+ | ``ETHTOOL_A_BITSET_BIT``        | nested | array of bits               |
> 
> "ETHTOOL_A_BITSET_BIT" does not exist in the code. I believe you ment
> "ETHTOOL_A_BITSET_BITS"
> 
> 
> >+ +-+-------------------------------+--------+-----------------------------+
> >+ |   ``ETHTOOL_A_BITSET_BIT+``     | nested | one bit                     |
> 
> You seem to be missing "|" here.
> Also "ETHTOOL_A_BITSET_BIT" does not exist. I believe you ment
> "ETHTOOL_A_BITS_BIT"

Yes on both, thanks.

> >+/* bit sets */
> >+
> >+enum {
> >+	ETHTOOL_A_BIT_UNSPEC,
> >+	ETHTOOL_A_BIT_INDEX,			/* u32 */
> >+	ETHTOOL_A_BIT_NAME,			/* string */
> >+	ETHTOOL_A_BIT_VALUE,			/* flag */
> >+
> >+	/* add new constants above here */
> >+	__ETHTOOL_A_BIT_CNT,
> >+	ETHTOOL_A_BIT_MAX = __ETHTOOL_A_BIT_CNT - 1
> >+};
> >+
> >+enum {
> >+	ETHTOOL_A_BITS_UNSPEC,
> >+	ETHTOOL_A_BITS_BIT,
> >+
> >+	/* add new constants above here */
> >+	__ETHTOOL_A_BITS_CNT,
> >+	ETHTOOL_A_BITS_MAX = __ETHTOOL_A_BITS_CNT - 1
> >+};
> 
> I think it would be good to have this named with "_BITSET_" in it so it
> is crystal clear this is part of the bitset UAPI.

I guess we can add "_BITSET" (e.g. ETHTOOL_A_BITSET_BIT_VALUE). These
constants shouldn't be used outside bitset.c (and some isolated part of
the userspace code) so the length is not so much of an issue.

> >+/**
> >+ * ethnl_put_bitset32() - Put a bitset nest into a message
> >+ * @skb:      skb with the message
> >+ * @attrtype: attribute type for the bitset nest
> >+ * @val:      value bitmap (u32 based)
> >+ * @mask:     mask bitmap (u32 based, optional)
> >+ * @nbits:    bit length of the bitset
> >+ * @names:    array of bit names (optional)
> >+ * @compact:  use compact format for the output
> >+ *
> >+ * Compose a nested attribute representing a bitset. If @mask is null, simple
> >+ * bitmap (bit list) is created, if @mask is provided, represent a value/mask
> >+ * pair. Bit names are only used in verbose mode and when provided by calller.
> >+ *
> >+ * Return:    0 on success, negative error value on error
> 
> Remove the spaces.

OK

> >+ */
> >+int ethnl_put_bitset32(struct sk_buff *skb, int attrtype, const u32 *val,
> >+		       const u32 *mask, unsigned int nbits,
> >+		       ethnl_string_array_t names, bool compact)
> >+{
> >+	struct nlattr *nest;
> >+	struct nlattr *attr;
> >+
> >+	nest = nla_nest_start(skb, attrtype);
> >+	if (!nest)
> >+		return -EMSGSIZE;
> >+
> >+	if (!mask && nla_put_flag(skb, ETHTOOL_A_BITSET_LIST))
> 
> Wait, shouldn't you rather check "!compact" ?
> 
> and WARN_ON in case compact == true && mask == NULL?

The "compact" and "list" flags are orthogonal. In this function, caller
passes null @mask if it wants to generated a list (as documented in the
function description above). In some older version I had "bool is_list"
which was set to "!mask" but I felt it didn't really make the code any
simpler; I can return to that if you think it will make the code easier
to read.

> 
> 
> >+		goto nla_put_failure;
> >+	if (nla_put_u32(skb, ETHTOOL_A_BITSET_SIZE, nbits))
> >+		goto nla_put_failure;
> >+	if (compact) {
> >+		unsigned int nwords = DIV_ROUND_UP(nbits, 32);
> >+		unsigned int nbytes = nwords * sizeof(u32);
> >+		u32 *dst;
> >+
> >+		attr = nla_reserve(skb, ETHTOOL_A_BITSET_VALUE, nbytes);
> >+		if (!attr)
> >+			goto nla_put_failure;
> >+		dst = nla_data(attr);
> >+		memcpy(dst, val, nbytes);
> >+		if (nbits % 32)
> >+			dst[nwords - 1] &= __lower_bits(nbits);
> >+
> >+		if (mask) {
> >+			attr = nla_reserve(skb, ETHTOOL_A_BITSET_MASK, nbytes);
> >+			if (!attr)
> >+				goto nla_put_failure;
> >+			dst = nla_data(attr);
> >+			memcpy(dst, mask, nbytes);
> >+			if (nbits % 32)
> >+				dst[nwords - 1] &= __lower_bits(nbits);
> >+		}
> >+	} else {
> >+		struct nlattr *bits;
> >+		unsigned int i;
> >+
> >+		bits = nla_nest_start(skb, ETHTOOL_A_BITSET_BITS);
> >+		if (!bits)
> >+			goto nla_put_failure;
> >+		for (i = 0; i < nbits; i++) {
> >+			const char *name = names ? names[i] : NULL;
> >+
> >+			if (!__bitmap32_test_bit(mask ?: val, i))
> 
> A) this __bitmap32_test_bit() looks like something generic, yet it is
>    not. Perhaps you would want to add this helper to
>    include/linux/bitmap.h?

I'm not sure it would be appreciated there as the whole header file is
for functions handling unsigned long based bitmaps. I'll rename it to
make it obvious it's a local helper.

> B) Why don't you do bitmap_to_arr32 conversion in this function just
>    before val/mask put. Then you can use normal test_bit() here.

This relates to the question (below) why we need two versions of the
functions, one for unsigned long based bitmaps, one for u32 based ones.
The reason is that both are used internally by existing code. So if we
had only one set of bitset functions, callers using the other format
would have to do the wrapping themselves.

There are two reasons why u32 versions are implemented directly and
usingned long ones as wrappers. First, u32 based bitmaps are more
frequent in existing code. Second, when we can get away with a cast
(i.e. anywhere exect 64-bit big endian), unsigned long based bitmap can
be always interpreted as u32 based bitmap but if we tried it the other
way, we would need a special handling of the last word when the number
of 32-bit words is odd.

> >+				continue;
> >+			attr = nla_nest_start(skb, ETHTOOL_A_BITS_BIT);
> >+			if (!attr ||
> >+			    nla_put_u32(skb, ETHTOOL_A_BIT_INDEX, i))
> 
> You mix these 2 in 1 if which are not related. Better keep them separate
> in two ifs.
> Or you can put the rest of the puts in the same if too.

OK

> >+				goto nla_put_failure;
> >+			if (name &&
> >+			    ethnl_put_strz(skb, ETHTOOL_A_BIT_NAME, name))
> >+				goto nla_put_failure;
> >+			if (mask && __bitmap32_test_bit(val, i) &&
> >+			    nla_put_flag(skb, ETHTOOL_A_BIT_VALUE))
> >+				goto nla_put_failure;
> >+			nla_nest_end(skb, attr);
> >+		}
> >+		nla_nest_end(skb, bits);
> >+	}
> >+
> >+	nla_nest_end(skb, nest);
> >+	return 0;
> >+
> >+nla_put_failure:
> >+	nla_nest_cancel(skb, nest);
> >+	return -EMSGSIZE;
> >+}
> >+
> >+static const struct nla_policy bitset_policy[ETHTOOL_A_BITSET_MAX + 1] = {
> >+	[ETHTOOL_A_BITSET_UNSPEC]	= { .type = NLA_REJECT },
> >+	[ETHTOOL_A_BITSET_LIST]		= { .type = NLA_FLAG },
> >+	[ETHTOOL_A_BITSET_SIZE]		= { .type = NLA_U32 },
> >+	[ETHTOOL_A_BITSET_BITS]		= { .type = NLA_NESTED },
> >+	[ETHTOOL_A_BITSET_VALUE]	= { .type = NLA_BINARY },
> >+	[ETHTOOL_A_BITSET_MASK]		= { .type = NLA_BINARY },
> >+};
> >+
> >+static const struct nla_policy bit_policy[ETHTOOL_A_BIT_MAX + 1] = {
> >+	[ETHTOOL_A_BIT_UNSPEC]		= { .type = NLA_REJECT },
> >+	[ETHTOOL_A_BIT_INDEX]		= { .type = NLA_U32 },
> >+	[ETHTOOL_A_BIT_NAME]		= { .type = NLA_NUL_STRING },
> >+	[ETHTOOL_A_BIT_VALUE]		= { .type = NLA_FLAG },
> >+};
> >+
> >+/**
> >+ * ethnl_bitset_is_compact() - check if bitset attribute represents a compact
> >+ *			       bitset
> >+ * @bitset  - nested attribute representing a bitset
> >+ * @compact - pointer for return value
> 
> In the rest of the code, you use
> @name: description

Right, I'll fix this.

> >+ *
> >+ * Return: 0 on success, negative error code on failure
> >+ */
> >+int ethnl_bitset_is_compact(const struct nlattr *bitset, bool *compact)
> >+{
> >+	struct nlattr *tb[ETHTOOL_A_BITSET_MAX + 1];
> >+	int ret;
> >+
> >+	ret = nla_parse_nested(tb, ETHTOOL_A_BITSET_MAX, bitset,
> >+			       bitset_policy, NULL);
> >+	if (ret < 0)
> >+		return ret;
> >+
> >+	if (tb[ETHTOOL_A_BITSET_BITS]) {
> >+		if (tb[ETHTOOL_A_BITSET_VALUE] || tb[ETHTOOL_A_BITSET_MASK])
> >+			return -EINVAL;
> >+		*compact = false;
> >+		return 0;
> >+	}
> >+	if (!tb[ETHTOOL_A_BITSET_SIZE] || !tb[ETHTOOL_A_BITSET_VALUE])
> >+		return -EINVAL;
> >+
> >+	*compact = true;
> >+	return 0;
> >+}
> >+
> >+static int ethnl_name_to_idx(ethnl_string_array_t names, unsigned int n_names,
> >+			     const char *name, unsigned int name_len)
> >+{
> >+	unsigned int i;
> >+
> >+	if (!names)
> >+		return n_names;
> >+
> >+	for (i = 0; i < n_names; i++) {
> >+		const char *bname = names[i];
> >+
> >+		if (!strncmp(bname, name, name_len) &&
> >+		    strlen(bname) <= name_len)
> >+			return i;
> >+	}
> >+
> >+	return n_names;
> 
> Maybe bettet to stick with -ERRNO?

OK

> >+}
> >+
> >+static int ethnl_parse_bit(unsigned int *index, bool *val, unsigned int nbits,
> >+			   const struct nlattr *bit_attr, bool is_list,
> >+			   ethnl_string_array_t names,
> >+			   struct netlink_ext_ack *extack)
> >+{
> >+	struct nlattr *tb[ETHTOOL_A_BIT_MAX + 1];
> >+	int ret, idx;
> >+
> >+	if (nla_type(bit_attr) != ETHTOOL_A_BITS_BIT) {
> >+		NL_SET_ERR_MSG_ATTR(extack, bit_attr,
> >+				    "only ETHTOOL_A_BITS_BIT allowed in ETHTOOL_A_BITSET_BITS");
> >+		return -EINVAL;
> >+	}
> 
> Probably it makes sense the caller does this check. Later on, if there
> is another possible value, the check would have to go there anyway.

OK

> >+	ret = nla_parse_nested(tb, ETHTOOL_A_BIT_MAX, bit_attr, bit_policy,
> >+			       extack);
> >+	if (ret < 0)
> >+		return ret;
> >+
> >+	if (tb[ETHTOOL_A_BIT_INDEX]) {
> >+		const char *name;
> >+
> >+		idx = nla_get_u32(tb[ETHTOOL_A_BIT_INDEX]);
> >+		if (idx >= nbits) {
> >+			NL_SET_ERR_MSG_ATTR(extack,
> >+					    tb[ETHTOOL_A_BIT_INDEX],
> >+					    "bit index too high");
> >+			return -EOPNOTSUPP;
> >+		}
> >+		name = names ? names[idx] : NULL;
> >+		if (tb[ETHTOOL_A_BIT_NAME] && name &&
> >+		    strncmp(nla_data(tb[ETHTOOL_A_BIT_NAME]), name,
> >+			    nla_len(tb[ETHTOOL_A_BIT_NAME]))) {
> >+			NL_SET_ERR_MSG_ATTR(extack, bit_attr,
> >+					    "bit index and name mismatch");
> >+			return -EINVAL;
> >+		}
> >+	} else if (tb[ETHTOOL_A_BIT_NAME]) {
> >+		idx = ethnl_name_to_idx(names, nbits,
> >+					nla_data(tb[ETHTOOL_A_BIT_NAME]),
> >+					nla_len(tb[ETHTOOL_A_BIT_NAME]));
> 
> It's a string? Policy validation should take care if it is correctly
> terminated by '\0'. Then you don't need to pass len down. Anyone who is
> interested in length can use strlen().

OK

> >+	is_list = (tb[ETHTOOL_A_BITSET_LIST] != NULL);
> 
> just:
> 	is_list = tb[ETHTOOL_A_BITSET_LIST]
> is enough.

Assignment from pointer to a bool felt a bit weird but if you find it
acceptable, I have no problem with it.

> >+int ethnl_bitset_is_compact(const struct nlattr *bitset, bool *compact);
> >+int ethnl_bitset_size(const unsigned long *val, const unsigned long *mask,
> >+		      unsigned int nbits, ethnl_string_array_t names,
> >+		      bool compact);
> >+int ethnl_bitset32_size(const u32 *val, const u32 *mask, unsigned int nbits,
> >+			ethnl_string_array_t names, bool compact);
> >+int ethnl_put_bitset(struct sk_buff *skb, int attrtype,
> >+		     const unsigned long *val, const unsigned long *mask,
> >+		     unsigned int nbits, ethnl_string_array_t names,
> >+		     bool compact);
> >+int ethnl_put_bitset32(struct sk_buff *skb, int attrtype, const u32 *val,
> >+		       const u32 *mask, unsigned int nbits,
> >+		       ethnl_string_array_t names, bool compact);
> >+int ethnl_update_bitset(unsigned long *bitmap, unsigned int nbits,
> >+			const struct nlattr *attr, ethnl_string_array_t names,
> >+			struct netlink_ext_ack *extack, bool *mod);
> >+int ethnl_update_bitset32(u32 *bitmap, unsigned int nbits,
> >+			  const struct nlattr *attr, ethnl_string_array_t names,
> >+			  struct netlink_ext_ack *extack, bool *mod);
> 
> Hmm, I wonder why user needs to work with the 32 variants..

See above.

Michal

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

* Re: [PATCH net-next v7 06/17] ethtool: netlink bitset handling
  2019-10-14 11:18     ` Michal Kubecek
@ 2019-10-14 13:02       ` Jiri Pirko
  0 siblings, 0 replies; 46+ messages in thread
From: Jiri Pirko @ 2019-10-14 13:02 UTC (permalink / raw)
  To: Michal Kubecek
  Cc: netdev, David Miller, Jakub Kicinski, Andrew Lunn,
	Florian Fainelli, John Linville, Stephen Hemminger,
	Johannes Berg, linux-kernel

Mon, Oct 14, 2019 at 01:18:47PM CEST, mkubecek@suse.cz wrote:
>On Fri, Oct 11, 2019 at 03:34:29PM +0200, Jiri Pirko wrote:
>> Wed, Oct 09, 2019 at 10:59:18PM CEST, mkubecek@suse.cz wrote:
>> >+Bit sets
>> >+========
>> >+
>> >+For short bitmaps of (reasonably) fixed length, standard ``NLA_BITFIELD32``
>> >+type is used. For arbitrary length bitmaps, ethtool netlink uses a nested
>> >+attribute with contents of one of two forms: compact (two binary bitmaps
>> >+representing bit values and mask of affected bits) and bit-by-bit (list of
>> >+bits identified by either index or name).
>> >+
>> >+Compact form: nested (bitset) atrribute contents:
>> >+
>> >+  ============================  ======  ============================
>> >+  ``ETHTOOL_A_BITSET_LIST``     flag    no mask, only a list
>> 
>> I find "list" a bit confusing name of a flag. Perhaps better to stick
>> with the "compact" terminology and make this "ETHTOOL_A_BITSET_COMPACT"?
>> Then in the code you can have var "is_compact", which makes the code a
>> bit easier to read I believe.
>
>This is not the same as "compact", "list" flag means that the bit set
>does not represent a value/mask pair but only a single bitmap (which can
>be understood as a list or subset of possible values).

Okay, this is confusing. So you say that the "LIST" may be on and
ETHTOOL_A_BITSET_VALUE present, but ETHTOOL_A_BITSET_MASK not?
I thought that whtn "LIST" is on, no "VALUE" nor "MASK" should be here.


>
>This saves some space in kernel replies where there is no natural mask
>so that we would have to invent one (usually all possible bits) but it

Do you have an example?


>is more important in request where some request want to modify a subset
>of bits (set some, unset some) while some requests pass a list of bits
>to be set after the operation (i.e. "I want exactly these to be
>enabled").

Hmm, it's a different type of bitset then. Wouldn't it be better to have
ETHTOOL_A_BITSET_TYPE
and enum:
ETHTOOL_A_BITSET_TYPE_LIST
ETHTOOL_A_BITSET_TYPE_MASKED
or something like that?
Or maybe just NLA_FLAG called "MASKED". I don't know, "list" has a
specific meaning and this isn't that...


>
>The flag could be omitted for compact form where we could simply say
>that if there is no mask, it's a list, but we need it for verbose form.
>
>> >+  ``ETHTOOL_A_BITSET_SIZE``     u32     number of significant bits
>> >+  ``ETHTOOL_A_BITSET_VALUE``    binary  bitmap of bit values
>> >+  ``ETHTOOL_A_BITSET_MASK``     binary  bitmap of valid bits
>> 
>> Couple of times the NLA_BITFIELD32 limitation was discussed, so isn't
>> this the time to introduce generic NLA_BITFIELD with variable len and
>> use it here? This is exactly job for it. As this is UAPI, I believe it
>> should be done now cause later won't work.
>
>As discussed before, we would lose the option to omit mask when it's not
>needed.

Sorry, it's been couple of months already :/


>
>> >+Bit-by-bit form: nested (bitset) attribute contents:
>> >+
>> >+ +---------------------------------+--------+-----------------------------+
>> >+ | ``ETHTOOL_A_BITSET_LIST``       | flag   | no mask, only a list        |
>> >+ +---------------------------------+--------+-----------------------------+
>> >+ | ``ETHTOOL_A_BITSET_SIZE``       | u32    | number of significant bits  |
>> >+ +---------------------------------+--------+-----------------------------+
>> >+ | ``ETHTOOL_A_BITSET_BIT``        | nested | array of bits               |
>> 
>> "ETHTOOL_A_BITSET_BIT" does not exist in the code. I believe you ment
>> "ETHTOOL_A_BITSET_BITS"
>> 
>> 
>> >+ +-+-------------------------------+--------+-----------------------------+
>> >+ |   ``ETHTOOL_A_BITSET_BIT+``     | nested | one bit                     |
>> 
>> You seem to be missing "|" here.
>> Also "ETHTOOL_A_BITSET_BIT" does not exist. I believe you ment
>> "ETHTOOL_A_BITS_BIT"
>
>Yes on both, thanks.
>
>> >+/* bit sets */
>> >+
>> >+enum {
>> >+	ETHTOOL_A_BIT_UNSPEC,
>> >+	ETHTOOL_A_BIT_INDEX,			/* u32 */
>> >+	ETHTOOL_A_BIT_NAME,			/* string */
>> >+	ETHTOOL_A_BIT_VALUE,			/* flag */
>> >+
>> >+	/* add new constants above here */
>> >+	__ETHTOOL_A_BIT_CNT,
>> >+	ETHTOOL_A_BIT_MAX = __ETHTOOL_A_BIT_CNT - 1
>> >+};
>> >+
>> >+enum {
>> >+	ETHTOOL_A_BITS_UNSPEC,
>> >+	ETHTOOL_A_BITS_BIT,
>> >+
>> >+	/* add new constants above here */
>> >+	__ETHTOOL_A_BITS_CNT,
>> >+	ETHTOOL_A_BITS_MAX = __ETHTOOL_A_BITS_CNT - 1
>> >+};
>> 
>> I think it would be good to have this named with "_BITSET_" in it so it
>> is crystal clear this is part of the bitset UAPI.
>
>I guess we can add "_BITSET" (e.g. ETHTOOL_A_BITSET_BIT_VALUE). These
>constants shouldn't be used outside bitset.c (and some isolated part of
>the userspace code) so the length is not so much of an issue.

Great.


>
>> >+/**
>> >+ * ethnl_put_bitset32() - Put a bitset nest into a message
>> >+ * @skb:      skb with the message
>> >+ * @attrtype: attribute type for the bitset nest
>> >+ * @val:      value bitmap (u32 based)
>> >+ * @mask:     mask bitmap (u32 based, optional)
>> >+ * @nbits:    bit length of the bitset
>> >+ * @names:    array of bit names (optional)
>> >+ * @compact:  use compact format for the output
>> >+ *
>> >+ * Compose a nested attribute representing a bitset. If @mask is null, simple
>> >+ * bitmap (bit list) is created, if @mask is provided, represent a value/mask
>> >+ * pair. Bit names are only used in verbose mode and when provided by calller.
>> >+ *
>> >+ * Return:    0 on success, negative error value on error
>> 
>> Remove the spaces.
>
>OK
>
>> >+ */
>> >+int ethnl_put_bitset32(struct sk_buff *skb, int attrtype, const u32 *val,
>> >+		       const u32 *mask, unsigned int nbits,
>> >+		       ethnl_string_array_t names, bool compact)
>> >+{
>> >+	struct nlattr *nest;
>> >+	struct nlattr *attr;
>> >+
>> >+	nest = nla_nest_start(skb, attrtype);
>> >+	if (!nest)
>> >+		return -EMSGSIZE;
>> >+
>> >+	if (!mask && nla_put_flag(skb, ETHTOOL_A_BITSET_LIST))
>> 
>> Wait, shouldn't you rather check "!compact" ?
>> 
>> and WARN_ON in case compact == true && mask == NULL?
>
>The "compact" and "list" flags are orthogonal. In this function, caller
>passes null @mask if it wants to generated a list (as documented in the
>function description above). In some older version I had "bool is_list"
>which was set to "!mask" but I felt it didn't really make the code any
>simpler; I can return to that if you think it will make the code easier
>to read.
>
>> 
>> 
>> >+		goto nla_put_failure;
>> >+	if (nla_put_u32(skb, ETHTOOL_A_BITSET_SIZE, nbits))
>> >+		goto nla_put_failure;
>> >+	if (compact) {
>> >+		unsigned int nwords = DIV_ROUND_UP(nbits, 32);
>> >+		unsigned int nbytes = nwords * sizeof(u32);
>> >+		u32 *dst;
>> >+
>> >+		attr = nla_reserve(skb, ETHTOOL_A_BITSET_VALUE, nbytes);
>> >+		if (!attr)
>> >+			goto nla_put_failure;
>> >+		dst = nla_data(attr);
>> >+		memcpy(dst, val, nbytes);
>> >+		if (nbits % 32)
>> >+			dst[nwords - 1] &= __lower_bits(nbits);
>> >+
>> >+		if (mask) {
>> >+			attr = nla_reserve(skb, ETHTOOL_A_BITSET_MASK, nbytes);
>> >+			if (!attr)
>> >+				goto nla_put_failure;
>> >+			dst = nla_data(attr);
>> >+			memcpy(dst, mask, nbytes);
>> >+			if (nbits % 32)
>> >+				dst[nwords - 1] &= __lower_bits(nbits);
>> >+		}
>> >+	} else {
>> >+		struct nlattr *bits;
>> >+		unsigned int i;
>> >+
>> >+		bits = nla_nest_start(skb, ETHTOOL_A_BITSET_BITS);
>> >+		if (!bits)
>> >+			goto nla_put_failure;
>> >+		for (i = 0; i < nbits; i++) {
>> >+			const char *name = names ? names[i] : NULL;
>> >+
>> >+			if (!__bitmap32_test_bit(mask ?: val, i))
>> 
>> A) this __bitmap32_test_bit() looks like something generic, yet it is
>>    not. Perhaps you would want to add this helper to
>>    include/linux/bitmap.h?
>
>I'm not sure it would be appreciated there as the whole header file is
>for functions handling unsigned long based bitmaps. I'll rename it to
>make it obvious it's a local helper.
>
>> B) Why don't you do bitmap_to_arr32 conversion in this function just
>>    before val/mask put. Then you can use normal test_bit() here.
>
>This relates to the question (below) why we need two versions of the
>functions, one for unsigned long based bitmaps, one for u32 based ones.
>The reason is that both are used internally by existing code. So if we
>had only one set of bitset functions, callers using the other format
>would have to do the wrapping themselves.
>
>There are two reasons why u32 versions are implemented directly and
>usingned long ones as wrappers. First, u32 based bitmaps are more
>frequent in existing code. Second, when we can get away with a cast
>(i.e. anywhere exect 64-bit big endian), unsigned long based bitmap can
>be always interpreted as u32 based bitmap but if we tried it the other
>way, we would need a special handling of the last word when the number
>of 32-bit words is odd.

Okay. Perhaps you can add it as a comment so it is clear what is going
on?


>
>> >+				continue;
>> >+			attr = nla_nest_start(skb, ETHTOOL_A_BITS_BIT);
>> >+			if (!attr ||
>> >+			    nla_put_u32(skb, ETHTOOL_A_BIT_INDEX, i))
>> 
>> You mix these 2 in 1 if which are not related. Better keep them separate
>> in two ifs.
>> Or you can put the rest of the puts in the same if too.
>
>OK
>
>> >+				goto nla_put_failure;
>> >+			if (name &&
>> >+			    ethnl_put_strz(skb, ETHTOOL_A_BIT_NAME, name))
>> >+				goto nla_put_failure;
>> >+			if (mask && __bitmap32_test_bit(val, i) &&
>> >+			    nla_put_flag(skb, ETHTOOL_A_BIT_VALUE))
>> >+				goto nla_put_failure;
>> >+			nla_nest_end(skb, attr);
>> >+		}
>> >+		nla_nest_end(skb, bits);
>> >+	}
>> >+
>> >+	nla_nest_end(skb, nest);
>> >+	return 0;
>> >+
>> >+nla_put_failure:
>> >+	nla_nest_cancel(skb, nest);
>> >+	return -EMSGSIZE;
>> >+}
>> >+
>> >+static const struct nla_policy bitset_policy[ETHTOOL_A_BITSET_MAX + 1] = {
>> >+	[ETHTOOL_A_BITSET_UNSPEC]	= { .type = NLA_REJECT },
>> >+	[ETHTOOL_A_BITSET_LIST]		= { .type = NLA_FLAG },
>> >+	[ETHTOOL_A_BITSET_SIZE]		= { .type = NLA_U32 },
>> >+	[ETHTOOL_A_BITSET_BITS]		= { .type = NLA_NESTED },
>> >+	[ETHTOOL_A_BITSET_VALUE]	= { .type = NLA_BINARY },
>> >+	[ETHTOOL_A_BITSET_MASK]		= { .type = NLA_BINARY },
>> >+};
>> >+
>> >+static const struct nla_policy bit_policy[ETHTOOL_A_BIT_MAX + 1] = {
>> >+	[ETHTOOL_A_BIT_UNSPEC]		= { .type = NLA_REJECT },
>> >+	[ETHTOOL_A_BIT_INDEX]		= { .type = NLA_U32 },
>> >+	[ETHTOOL_A_BIT_NAME]		= { .type = NLA_NUL_STRING },
>> >+	[ETHTOOL_A_BIT_VALUE]		= { .type = NLA_FLAG },
>> >+};
>> >+
>> >+/**
>> >+ * ethnl_bitset_is_compact() - check if bitset attribute represents a compact
>> >+ *			       bitset
>> >+ * @bitset  - nested attribute representing a bitset
>> >+ * @compact - pointer for return value
>> 
>> In the rest of the code, you use
>> @name: description
>
>Right, I'll fix this.
>
>> >+ *
>> >+ * Return: 0 on success, negative error code on failure
>> >+ */
>> >+int ethnl_bitset_is_compact(const struct nlattr *bitset, bool *compact)
>> >+{
>> >+	struct nlattr *tb[ETHTOOL_A_BITSET_MAX + 1];
>> >+	int ret;
>> >+
>> >+	ret = nla_parse_nested(tb, ETHTOOL_A_BITSET_MAX, bitset,
>> >+			       bitset_policy, NULL);
>> >+	if (ret < 0)
>> >+		return ret;
>> >+
>> >+	if (tb[ETHTOOL_A_BITSET_BITS]) {
>> >+		if (tb[ETHTOOL_A_BITSET_VALUE] || tb[ETHTOOL_A_BITSET_MASK])
>> >+			return -EINVAL;
>> >+		*compact = false;
>> >+		return 0;
>> >+	}
>> >+	if (!tb[ETHTOOL_A_BITSET_SIZE] || !tb[ETHTOOL_A_BITSET_VALUE])
>> >+		return -EINVAL;
>> >+
>> >+	*compact = true;
>> >+	return 0;
>> >+}
>> >+
>> >+static int ethnl_name_to_idx(ethnl_string_array_t names, unsigned int n_names,
>> >+			     const char *name, unsigned int name_len)
>> >+{
>> >+	unsigned int i;
>> >+
>> >+	if (!names)
>> >+		return n_names;
>> >+
>> >+	for (i = 0; i < n_names; i++) {
>> >+		const char *bname = names[i];
>> >+
>> >+		if (!strncmp(bname, name, name_len) &&
>> >+		    strlen(bname) <= name_len)
>> >+			return i;
>> >+	}
>> >+
>> >+	return n_names;
>> 
>> Maybe bettet to stick with -ERRNO?
>
>OK
>
>> >+}
>> >+
>> >+static int ethnl_parse_bit(unsigned int *index, bool *val, unsigned int nbits,
>> >+			   const struct nlattr *bit_attr, bool is_list,
>> >+			   ethnl_string_array_t names,
>> >+			   struct netlink_ext_ack *extack)
>> >+{
>> >+	struct nlattr *tb[ETHTOOL_A_BIT_MAX + 1];
>> >+	int ret, idx;
>> >+
>> >+	if (nla_type(bit_attr) != ETHTOOL_A_BITS_BIT) {
>> >+		NL_SET_ERR_MSG_ATTR(extack, bit_attr,
>> >+				    "only ETHTOOL_A_BITS_BIT allowed in ETHTOOL_A_BITSET_BITS");
>> >+		return -EINVAL;
>> >+	}
>> 
>> Probably it makes sense the caller does this check. Later on, if there
>> is another possible value, the check would have to go there anyway.
>
>OK
>
>> >+	ret = nla_parse_nested(tb, ETHTOOL_A_BIT_MAX, bit_attr, bit_policy,
>> >+			       extack);
>> >+	if (ret < 0)
>> >+		return ret;
>> >+
>> >+	if (tb[ETHTOOL_A_BIT_INDEX]) {
>> >+		const char *name;
>> >+
>> >+		idx = nla_get_u32(tb[ETHTOOL_A_BIT_INDEX]);
>> >+		if (idx >= nbits) {
>> >+			NL_SET_ERR_MSG_ATTR(extack,
>> >+					    tb[ETHTOOL_A_BIT_INDEX],
>> >+					    "bit index too high");
>> >+			return -EOPNOTSUPP;
>> >+		}
>> >+		name = names ? names[idx] : NULL;
>> >+		if (tb[ETHTOOL_A_BIT_NAME] && name &&
>> >+		    strncmp(nla_data(tb[ETHTOOL_A_BIT_NAME]), name,
>> >+			    nla_len(tb[ETHTOOL_A_BIT_NAME]))) {
>> >+			NL_SET_ERR_MSG_ATTR(extack, bit_attr,
>> >+					    "bit index and name mismatch");
>> >+			return -EINVAL;
>> >+		}
>> >+	} else if (tb[ETHTOOL_A_BIT_NAME]) {
>> >+		idx = ethnl_name_to_idx(names, nbits,
>> >+					nla_data(tb[ETHTOOL_A_BIT_NAME]),
>> >+					nla_len(tb[ETHTOOL_A_BIT_NAME]));
>> 
>> It's a string? Policy validation should take care if it is correctly
>> terminated by '\0'. Then you don't need to pass len down. Anyone who is
>> interested in length can use strlen().
>
>OK
>
>> >+	is_list = (tb[ETHTOOL_A_BITSET_LIST] != NULL);
>> 
>> just:
>> 	is_list = tb[ETHTOOL_A_BITSET_LIST]
>> is enough.
>
>Assignment from pointer to a bool felt a bit weird but if you find it
>acceptable, I have no problem with it.
>
>> >+int ethnl_bitset_is_compact(const struct nlattr *bitset, bool *compact);
>> >+int ethnl_bitset_size(const unsigned long *val, const unsigned long *mask,
>> >+		      unsigned int nbits, ethnl_string_array_t names,
>> >+		      bool compact);
>> >+int ethnl_bitset32_size(const u32 *val, const u32 *mask, unsigned int nbits,
>> >+			ethnl_string_array_t names, bool compact);
>> >+int ethnl_put_bitset(struct sk_buff *skb, int attrtype,
>> >+		     const unsigned long *val, const unsigned long *mask,
>> >+		     unsigned int nbits, ethnl_string_array_t names,
>> >+		     bool compact);
>> >+int ethnl_put_bitset32(struct sk_buff *skb, int attrtype, const u32 *val,
>> >+		       const u32 *mask, unsigned int nbits,
>> >+		       ethnl_string_array_t names, bool compact);
>> >+int ethnl_update_bitset(unsigned long *bitmap, unsigned int nbits,
>> >+			const struct nlattr *attr, ethnl_string_array_t names,
>> >+			struct netlink_ext_ack *extack, bool *mod);
>> >+int ethnl_update_bitset32(u32 *bitmap, unsigned int nbits,
>> >+			  const struct nlattr *attr, ethnl_string_array_t names,
>> >+			  struct netlink_ext_ack *extack, bool *mod);
>> 
>> Hmm, I wonder why user needs to work with the 32 variants..
>
>See above.
>
>Michal

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

end of thread, back to index

Thread overview: 46+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-10-09 20:59 [PATCH net-next v7 00/17] ethtool netlink interface, part 1 Michal Kubecek
2019-10-09 20:59 ` [PATCH net-next v7 01/17] rtnetlink: provide permanent hardware address in RTM_NEWLINK Michal Kubecek
2019-10-09 20:59 ` [PATCH net-next v7 02/17] netlink: rename nl80211_validate_nested() to nla_validate_nested() Michal Kubecek
2019-10-09 20:59 ` [PATCH net-next v7 03/17] ethtool: move to its own directory Michal Kubecek
2019-10-09 20:59 ` [PATCH net-next v7 04/17] ethtool: introduce ethtool netlink interface Michal Kubecek
2019-10-09 20:59 ` [PATCH net-next v7 05/17] ethtool: helper functions for " Michal Kubecek
2019-10-10 13:42   ` Jiri Pirko
2019-10-10 17:13     ` Michal Kubecek
2019-10-09 20:59 ` [PATCH net-next v7 06/17] ethtool: netlink bitset handling Michal Kubecek
2019-10-11 13:34   ` Jiri Pirko
2019-10-14 11:18     ` Michal Kubecek
2019-10-14 13:02       ` Jiri Pirko
2019-10-09 20:59 ` [PATCH net-next v7 07/17] ethtool: support for netlink notifications Michal Kubecek
2019-10-09 20:59 ` [PATCH net-next v7 08/17] ethtool: move string arrays into common file Michal Kubecek
2019-10-10 13:27   ` Jiri Pirko
2019-10-09 20:59 ` [PATCH net-next v7 09/17] ethtool: generic handlers for GET requests Michal Kubecek
2019-10-10 13:56   ` Jiri Pirko
2019-10-10 18:04     ` Michal Kubecek
2019-10-10 18:18       ` Johannes Berg
2019-10-10 20:00         ` Michal Kubecek
2019-10-11  8:08           ` Johannes Berg
2019-10-11  6:06       ` Jiri Pirko
2019-10-10 15:23   ` Jiri Pirko
2019-10-09 20:59 ` [PATCH net-next v7 10/17] ethtool: provide string sets with STRSET_GET request Michal Kubecek
2019-10-10 13:59   ` Jiri Pirko
2019-10-10 18:05     ` Michal Kubecek
2019-10-09 20:59 ` [PATCH net-next v7 11/17] ethtool: provide link mode names as a string set Michal Kubecek
2019-10-09 20:59 ` [PATCH net-next v7 12/17] ethtool: provide link settings with LINKINFO_GET request Michal Kubecek
2019-10-10 15:59   ` Jiri Pirko
2019-10-10 20:15     ` Michal Kubecek
2019-10-09 20:59 ` [PATCH net-next v7 13/17] ethtool: add standard notification handler Michal Kubecek
2019-10-10 15:25   ` Jiri Pirko
2019-10-10 18:17     ` Michal Kubecek
2019-10-11  5:56       ` Jiri Pirko
2019-10-11  5:59         ` Michal Kubecek
2019-10-09 20:59 ` [PATCH net-next v7 14/17] ethtool: set link settings with LINKINFO_SET request Michal Kubecek
2019-10-10 15:37   ` Jiri Pirko
2019-10-10 19:30     ` Michal Kubecek
2019-10-11  5:59       ` Jiri Pirko
2019-10-12 16:33   ` Jiri Pirko
2019-10-14  8:48     ` Michal Kubecek
2019-10-09 20:59 ` [PATCH net-next v7 15/17] ethtool: provide link mode information with LINKMODES_GET request Michal Kubecek
2019-10-09 20:59 ` [PATCH net-next v7 16/17] ethtool: set link modes related data with LINKMODES_SET request Michal Kubecek
2019-10-09 20:59 ` [PATCH net-next v7 17/17] ethtool: provide link state with LINKSTATE_GET request Michal Kubecek
2019-10-11  0:48 ` [PATCH net-next v7 00/17] ethtool netlink interface, part 1 Jakub Kicinski
2019-10-11  6:46   ` Johannes Berg

Netdev Archive on lore.kernel.org

Archives are clonable:
	git clone --mirror https://lore.kernel.org/netdev/0 netdev/git/0.git
	git clone --mirror https://lore.kernel.org/netdev/1 netdev/git/1.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 netdev netdev/ https://lore.kernel.org/netdev \
		netdev@vger.kernel.org netdev@archiver.kernel.org
	public-inbox-index netdev

Example config snippet for mirrors

Newsgroup available over NNTP:
	nntp://nntp.lore.kernel.org/org.kernel.vger.netdev


AGPL code for this site: git clone https://public-inbox.org/ public-inbox