diff mbox series

[RFC,net-next,v2,10/17] ethtool: implement GET_SETTINGS message

Message ID 67d3b68a50e95db9612cc96e42a52ce332f716a9.1532953989.git.mkubecek@suse.cz
State RFC, archived
Delegated to: David Miller
Headers show
Series ethtool netlink interface (WiP) | expand

Commit Message

Michal Kubecek July 30, 2018, 12:53 p.m. UTC
Requests the information provided by ETHTOOL_GLINKSETTINGS, ETHTOOL_GWOL
and ETHTOOL_GMSGLVL. The info_mask header field can be used to request only
part of the information. Flag ETH_SETTINGS_RF_COMPACT_BITSETS switches
between flag-by-flag list and compact bitmaps for link modes in the reply.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Documentation/networking/ethtool-netlink.txt |  69 ++-
 include/linux/ethtool_netlink.h              |   3 +
 include/linux/netdevice.h                    |   2 +
 include/uapi/linux/ethtool.h                 |   3 +
 include/uapi/linux/ethtool_netlink.h         |  37 ++
 net/ethtool/Makefile                         |   2 +-
 net/ethtool/common.c                         | 112 +++++
 net/ethtool/common.h                         |   8 +
 net/ethtool/ioctl.c                          | 108 +----
 net/ethtool/netlink.c                        |  69 +++
 net/ethtool/netlink.h                        |   2 +
 net/ethtool/settings.c                       | 416 +++++++++++++++++++
 12 files changed, 725 insertions(+), 106 deletions(-)
 create mode 100644 net/ethtool/settings.c

Comments

Andrew Lunn July 30, 2018, 6:54 p.m. UTC | #1
> +/* Internal kernel helper to query a device ethtool_link_settings.
> + *
> + * Backward compatibility note: for compatibility with legacy drivers
> + * that implement only the ethtool_cmd API, this has to work with both
> + * drivers implementing get_link_ksettings API and drivers
> + * implementing get_settings API. When drivers implement get_settings
> + * and report ethtool_cmd deprecated fields
> + * (transceiver/maxrxpkt/maxtxpkt), these fields are silently ignored
> + * because the resulting struct ethtool_link_settings does not report them.

~/linux/drivers$ grep -r [.]get_settings *
net/ethernet/8390/etherh.c:		 .get_settings	= etherh_get_settings,

I don't think it is worth adding support for .get_settings for just
one driver. It is better to just convert that driver to the new API.

    Andrew
Andrew Lunn July 30, 2018, 7:06 p.m. UTC | #2
> +    ETHA_SETTINGS_MDIO_SUPPORT	(bitfield32)	MDIO support flags

Now might be a good time to drop this. The mdio drivers don't have
this information. There is no API between the MAC driver and the MDIO
driver to ask for it. Only a couple of drivers which ignore the kernel
MDIO code set this.

     Andrew
Andrew Lunn July 30, 2018, 7:09 p.m. UTC | #3
> +Response contents:
> +
> +    ETHA_SETTINGS_LINK		(u32)		link state

> +	[ETHA_SETTINGS_LINK]		= { .type = NLA_FLAG },

Is this correct?

The link is either up or down. So a u32 seems a bit big.

    Andrew
Michal Kubecek July 30, 2018, 7:42 p.m. UTC | #4
On Mon, Jul 30, 2018 at 09:09:41PM +0200, Andrew Lunn wrote:
> > +Response contents:
> > +
> > +    ETHA_SETTINGS_LINK		(u32)		link state
> 
> > +	[ETHA_SETTINGS_LINK]		= { .type = NLA_FLAG },
> 
> Is this correct?

NLA_FLAG is wrong, we need three states: on/off/unknown for "get"
replies and on/off/keep for "set" requests.

> The link is either up or down. So a u32 seems a bit big.

I tend to use u32 everywhere with some obvious exceptions. The reason is
that netlink attributes are padded to 32 bits so that no matter if you
use u8, u16 or u32, the attribute still ends up taking 8 bytes. But yes,
this looks like an obvious exception where u8 wouldn't mean any risk of
running out of values one day.

Michal Kubecek
Michal Kubecek Aug. 21, 2018, 9:32 a.m. UTC | #5
On Mon, Jul 30, 2018 at 08:54:55PM +0200, Andrew Lunn wrote:
> > +/* Internal kernel helper to query a device ethtool_link_settings.
> > + *
> > + * Backward compatibility note: for compatibility with legacy drivers
> > + * that implement only the ethtool_cmd API, this has to work with both
> > + * drivers implementing get_link_ksettings API and drivers
> > + * implementing get_settings API. When drivers implement get_settings
> > + * and report ethtool_cmd deprecated fields
> > + * (transceiver/maxrxpkt/maxtxpkt), these fields are silently ignored
> > + * because the resulting struct ethtool_link_settings does not report them.
> 
> ~/linux/drivers$ grep -r [.]get_settings *
> net/ethernet/8390/etherh.c:		 .get_settings	= etherh_get_settings,
> 
> I don't think it is worth adding support for .get_settings for just
> one driver. It is better to just convert that driver to the new API.

I have prepared a patch converting 8390/etherh driver to use
{g,s}et_link_ksettings and I'm going to submit it when net-next opens.
Do you think we can then drop {g,s}et_settings callbacks completely
(i.e. also from ioctl() code and ethtool_ops)?  Do we care about
unconverted out of tree drivers?

Michal Kubecek
Andrew Lunn Aug. 21, 2018, 2:10 p.m. UTC | #6
On Tue, Aug 21, 2018 at 11:32:46AM +0200, Michal Kubecek wrote:
> On Mon, Jul 30, 2018 at 08:54:55PM +0200, Andrew Lunn wrote:
> > > +/* Internal kernel helper to query a device ethtool_link_settings.
> > > + *
> > > + * Backward compatibility note: for compatibility with legacy drivers
> > > + * that implement only the ethtool_cmd API, this has to work with both
> > > + * drivers implementing get_link_ksettings API and drivers
> > > + * implementing get_settings API. When drivers implement get_settings
> > > + * and report ethtool_cmd deprecated fields
> > > + * (transceiver/maxrxpkt/maxtxpkt), these fields are silently ignored
> > > + * because the resulting struct ethtool_link_settings does not report them.
> > 
> > ~/linux/drivers$ grep -r [.]get_settings *
> > net/ethernet/8390/etherh.c:		 .get_settings	= etherh_get_settings,
> > 
> > I don't think it is worth adding support for .get_settings for just
> > one driver. It is better to just convert that driver to the new API.
> 
> I have prepared a patch converting 8390/etherh driver to use
> {g,s}et_link_ksettings and I'm going to submit it when net-next opens.
> Do you think we can then drop {g,s}et_settings callbacks completely
> (i.e. also from ioctl() code and ethtool_ops)?  Do we care about
> unconverted out of tree drivers?

Hi Michal

We cannot break ethtool, the ABI it uses. But there is already code to
use get_link_ksettings() and only fall back to get_settings if it does
not exist. So we can clean up all the fallback code, remove the
ethtool_ops, etc.

I personally don't care about out of tree drivers. They have had over
2 years to change to the new API.

	     Andrew
Michal Kubecek Aug. 21, 2018, 2:52 p.m. UTC | #7
On Tue, Aug 21, 2018 at 04:10:22PM +0200, Andrew Lunn wrote:
> On Tue, Aug 21, 2018 at 11:32:46AM +0200, Michal Kubecek wrote:
> > I have prepared a patch converting 8390/etherh driver to use
> > {g,s}et_link_ksettings and I'm going to submit it when net-next opens.
> > Do you think we can then drop {g,s}et_settings callbacks completely
> > (i.e. also from ioctl() code and ethtool_ops)?  Do we care about
> > unconverted out of tree drivers?
> 
> We cannot break ethtool, the ABI it uses. But there is already code to
> use get_link_ksettings() and only fall back to get_settings if it does
> not exist. So we can clean up all the fallback code, remove the
> ethtool_ops, etc.

Yes, that's what I have in mind: keep the ethtool interface as is (both
ETHTOOL_{G,S}SET and ETHTOOL_{G,S}LINKSETTINGS) but drop get_settings
and set_settings from ethtool_ops and the code that falls back to them.
This way both old and new versions of ethtool would still work and the
only problem would be with out of tree drivers not converted to
{s,g}et_link_ksettings (which won't build so that it won't be silent
breakage).

Michal Kubecek
diff mbox series

Patch

diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt
index 1e3d5ffc97ab..60d426cf6dad 100644
--- a/Documentation/networking/ethtool-netlink.txt
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -123,6 +123,8 @@  List of message types
     ETHNL_CMD_SET_STRSET		response only
     ETHNL_CMD_GET_DRVINFO
     ETHNL_CMD_SET_DRVINFO		response only
+    ETHNL_CMD_GET_SETTINGS
+    ETHNL_CMD_SET_SETTINGS		response only (for now)
 
 All constants use ETHNL_CMD_ prefix, usually followed by "GET", "SET" or "ACT"
 to indicate the type.
@@ -192,6 +194,63 @@  GET_DRVINFO requests).
 GET_DRVINFO requests allow dumps.
 
 
+GET_SETTINGS
+------------
+
+GET_SETTINGS request retrieves information provided by ETHTOOL_GLINKSETTINGS,
+ETHTOOL_GWOL, ETHTOOL_GMSGLVL and ETHTOOL_GLINK ioctl requests. The request
+doesn't use any attributes.
+
+Request attributes:
+
+    ETHA_SETTINGS_DEV		(nested)	device identification
+    ETHA_SETTINGS_INFOMASK	(u32)		info mask
+    ETHA_SETTINGS_COMPACT	(flag)		request compact bitsets
+
+Info mask bits meaning:
+
+    ETH_SETTINGS_IM_LINKINFO		link_ksettings except link modes
+    ETH_SETTINGS_IM_LINKMODES		link modes from link_ksettings
+    ETH_SETTINGS_IM_MSGLEVEL		msglevel
+    ETH_SETTINGS_IM_WOLINFO		struct ethtool_wolinfo
+    ETH_SETTINGS_IM_LINK		link state
+
+Response contents:
+
+    ETHA_SETTINGS_DEV		(nested)	device identification
+    ETHA_SETTINGS_SPEED		(u32)		link speed (Mb/s)
+    ETHA_SETTINGS_DUPLEX	(u8)		duplex mode
+    ETHA_SETTINGS_PORT		(u8)		physical port
+    ETHA_SETTINGS_PHYADDR	(u8)		MDIO address of phy
+    ETHA_SETTINGS_AUTONEG	(u8)		autoneotiation status
+    ETHA_SETTINGS_MDIO_SUPPORT	(bitfield32)	MDIO support flags
+    ETHA_SETTINGS_TP_MDIX	(u8)		MDI(-X) status
+    ETHA_SETTINGS_TP_MDIX_CTRL	(u8)		MDI(-X) control
+    ETHA_SETTINGS_TRANSCEIVER	(u8)		transceiver
+    ETHA_SETTINGS_WOL_MODES	(bitfield32)	wake-on-lan modes
+    ETHA_SETTINGS_SOPASS	(binary)	SecureOn(tm) password
+    ETHA_SETTINGS_MSGLVL	(bitfield32)	debug level
+    ETHA_SETTINGS_LINK_MODES	(bitset)	device link modes
+    ETHA_SETTINGS_PEER_MODES	(bitset)	link partner link modes
+    ETHA_SETTINGS_LINK		(u32)		link state
+
+Most of the attributes have the same meaning (including values) as
+corresponding members of ioctl structures. For ETHA_SETTINGS_MDIO_SUPPORT and
+ETHA_SETTINGS_MSGLVL, selector reports flags supported by kernel. For
+ETHA_SETTINGS_WOL_MODES it reports flags supported by the device. For
+ETHA_SETTINGS_LINK_MODES, value represent advertised modes and mask represents
+supported modes. For ETHA_SETTINGS_PEER_MODES, both value and mask represent
+partner advertised link modes.
+
+GET_SETTINGS request is allowed for unprivileged user but ETHA_SETTINGS_SOPASS
+is only provided by kernel in response to privileged (netns CAP_NET_ADMIN)
+requests.
+
+GET_SETTINGS requests allow dumps and messages in the same format as response
+to them are broadcasted as notifications on change of these settings using
+netlink or ioctl ethtool interface.
+
+
 Request translation
 -------------------
 
@@ -201,16 +260,16 @@  have their netlink replacement yet.
 
 ioctl command			netlink command
 ---------------------------------------------------------------------
-ETHTOOL_GSET			n/a
+ETHTOOL_GSET			ETHNL_CMD_GET_SETTINGS
 ETHTOOL_SSET			n/a
 ETHTOOL_GDRVINFO		ETHNL_CMD_GET_DRVINFO
 ETHTOOL_GREGS			n/a
-ETHTOOL_GWOL			n/a
+ETHTOOL_GWOL			ETHNL_CMD_GET_SETTINGS
 ETHTOOL_SWOL			n/a
-ETHTOOL_GMSGLVL			n/a
+ETHTOOL_GMSGLVL			ETHNL_CMD_GET_SETTINGS
 ETHTOOL_SMSGLVL			n/a
 ETHTOOL_NWAY_RST		n/a
-ETHTOOL_GLINK			n/a
+ETHTOOL_GLINK			ETHNL_CMD_GET_SETTINGS
 ETHTOOL_GEEPROM			n/a
 ETHTOOL_SEEPROM			n/a
 ETHTOOL_GCOALESCE		n/a
@@ -275,7 +334,7 @@  ETHTOOL_GTUNABLE		n/a
 ETHTOOL_STUNABLE		n/a
 ETHTOOL_GPHYSTATS		n/a
 ETHTOOL_PERQUEUE		n/a
-ETHTOOL_GLINKSETTINGS		n/a
+ETHTOOL_GLINKSETTINGS		ETHNL_CMD_GET_SETTINGS
 ETHTOOL_SLINKSETTINGS		n/a
 ETHTOOL_PHY_GTUNABLE		n/a
 ETHTOOL_PHY_STUNABLE		n/a
diff --git a/include/linux/ethtool_netlink.h b/include/linux/ethtool_netlink.h
index 2a15e64a16f3..fba8ff961887 100644
--- a/include/linux/ethtool_netlink.h
+++ b/include/linux/ethtool_netlink.h
@@ -7,6 +7,9 @@ 
 #include <linux/ethtool.h>
 #include <linux/netdevice.h>
 
+#define __ETHTOOL_LINK_MODE_MASK_NWORDS \
+	((__ETHTOOL_LINK_MODE_MASK_NBITS + 31) / 32)
+
 enum ethtool_multicast_groups {
 	ETHNL_MCGRP_MONITOR,
 };
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index c4b0c575d57e..93071380558c 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -3738,6 +3738,8 @@  enum {
 	NETIF_MSG_PKTDATA	= 0x1000,
 	NETIF_MSG_HW		= 0x2000,
 	NETIF_MSG_WOL		= 0x4000,
+
+	NETIF_MSG_ALL		= 0x7fff,
 };
 
 #define netif_msg_drv(p)	((p)->msg_enable & NETIF_MSG_DRV)
diff --git a/include/uapi/linux/ethtool.h b/include/uapi/linux/ethtool.h
index 2ae393963704..98f645a38168 100644
--- a/include/uapi/linux/ethtool.h
+++ b/include/uapi/linux/ethtool.h
@@ -143,6 +143,9 @@  static inline __u32 ethtool_cmd_speed(const struct ethtool_cmd *ep)
  */
 #define ETH_MDIO_SUPPORTS_C45	2
 
+/* All defined ETH_MDIO_SUPPORTS_* flags */
+#define ETH_MDIO_SUPPORTS_ALL (ETH_MDIO_SUPPORTS_C22 | ETH_MDIO_SUPPORTS_C45)
+
 #define ETHTOOL_FWVERS_LEN	32
 #define ETHTOOL_BUSINFO_LEN	32
 #define ETHTOOL_EROMVERS_LEN	32
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index df4de61fac48..66df44aa7226 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -12,6 +12,8 @@  enum {
 	ETHNL_CMD_SET_STRSET,		/* only for reply */
 	ETHNL_CMD_GET_DRVINFO,
 	ETHNL_CMD_SET_DRVINFO,		/* only for reply */
+	ETHNL_CMD_GET_SETTINGS,
+	ETHNL_CMD_SET_SETTINGS,
 
 	__ETHNL_CMD_MAX,
 	ETHNL_CMD_MAX = (__ETHNL_CMD_MAX - 1)
@@ -146,6 +148,41 @@  enum {
 	ETHA_DRVINFO_MAX = (__ETHA_DRVINFO_MAX - 1)
 };
 
+/* GET_SETTINGS / SET_SETTINGS */
+
+enum {
+	ETHA_SETTINGS_UNSPEC,
+	ETHA_SETTINGS_DEV,			/* nest - ETHA_DEV_* */
+	ETHA_SETTINGS_INFOMASK,			/* u32 */
+	ETHA_SETTINGS_COMPACT,			/* flag */
+	ETHA_SETTINGS_SPEED,			/* u32 */
+	ETHA_SETTINGS_DUPLEX,			/* u8 */
+	ETHA_SETTINGS_PORT,			/* u8 */
+	ETHA_SETTINGS_PHYADDR,			/* u8 */
+	ETHA_SETTINGS_AUTONEG,			/* u8 */
+	ETHA_SETTINGS_MDIO_SUPPORT,		/* bitfield32 */
+	ETHA_SETTINGS_TP_MDIX,			/* u8 */
+	ETHA_SETTINGS_TP_MDIX_CTRL,		/* u8 */
+	ETHA_SETTINGS_TRANSCEIVER,		/* u8 */
+	ETHA_SETTINGS_WOL_MODES,		/* bitfield32 */
+	ETHA_SETTINGS_SOPASS,			/* binary */
+	ETHA_SETTINGS_MSGLVL,			/* bitfield32 */
+	ETHA_SETTINGS_LINK_MODES,		/* bitset */
+	ETHA_SETTINGS_PEER_MODES,		/* bitset */
+	ETHA_SETTINGS_LINK,			/* u32 */
+
+	__ETHA_SETTINGS_MAX,
+	ETHA_SETTINGS_MAX = (__ETHA_SETTINGS_MAX - 1)
+};
+
+#define ETH_SETTINGS_IM_LINKINFO		0x01
+#define ETH_SETTINGS_IM_LINKMODES		0x02
+#define ETH_SETTINGS_IM_MSGLEVEL		0x04
+#define ETH_SETTINGS_IM_WOLINFO			0x08
+#define ETH_SETTINGS_IM_LINK			0x10
+
+#define ETH_SETTINGS_IM_DEFAULT			0x1f
+
 /* generic netlink info */
 #define ETHTOOL_GENL_NAME "ethtool"
 #define ETHTOOL_GENL_VERSION 1
diff --git a/net/ethtool/Makefile b/net/ethtool/Makefile
index 2e840ae0ba1e..8dd98310ed6f 100644
--- a/net/ethtool/Makefile
+++ b/net/ethtool/Makefile
@@ -4,4 +4,4 @@  obj-y				+= ioctl.o common.o
 
 obj-$(CONFIG_ETHTOOL_NETLINK)	+= ethtool_nl.o
 
-ethtool_nl-y	:= netlink.o strset.o drvinfo.o
+ethtool_nl-y	:= netlink.o strset.o drvinfo.o settings.o
diff --git a/net/ethtool/common.c b/net/ethtool/common.c
index 1dc4a6515996..986e82664447 100644
--- a/net/ethtool/common.c
+++ b/net/ethtool/common.c
@@ -128,3 +128,115 @@  int __ethtool_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info)
 	return 0;
 }
 EXPORT_SYMBOL(__ethtool_get_drvinfo);
+
+/* return false if legacy contained non-0 deprecated fields
+ * maxtxpkt/maxrxpkt. rest of ksettings always updated
+ */
+bool
+convert_legacy_settings_to_link_ksettings(
+	struct ethtool_link_ksettings *link_ksettings,
+	const struct ethtool_cmd *legacy_settings)
+{
+	bool retval = true;
+
+	memset(link_ksettings, 0, sizeof(*link_ksettings));
+
+	/* This is used to tell users that driver is still using these
+	 * deprecated legacy fields, and they should not use
+	 * %ETHTOOL_GLINKSETTINGS/%ETHTOOL_SLINKSETTINGS
+	 */
+	if (legacy_settings->maxtxpkt ||
+	    legacy_settings->maxrxpkt)
+		retval = false;
+
+	ethtool_convert_legacy_u32_to_link_mode(
+		link_ksettings->link_modes.supported,
+		legacy_settings->supported);
+	ethtool_convert_legacy_u32_to_link_mode(
+		link_ksettings->link_modes.advertising,
+		legacy_settings->advertising);
+	ethtool_convert_legacy_u32_to_link_mode(
+		link_ksettings->link_modes.lp_advertising,
+		legacy_settings->lp_advertising);
+	link_ksettings->base.speed
+		= ethtool_cmd_speed(legacy_settings);
+	link_ksettings->base.duplex
+		= legacy_settings->duplex;
+	link_ksettings->base.port
+		= legacy_settings->port;
+	link_ksettings->base.phy_address
+		= legacy_settings->phy_address;
+	link_ksettings->base.autoneg
+		= legacy_settings->autoneg;
+	link_ksettings->base.mdio_support
+		= legacy_settings->mdio_support;
+	link_ksettings->base.eth_tp_mdix
+		= legacy_settings->eth_tp_mdix;
+	link_ksettings->base.eth_tp_mdix_ctrl
+		= legacy_settings->eth_tp_mdix_ctrl;
+	return retval;
+}
+
+/* Internal kernel helper to query a device ethtool_link_settings.
+ *
+ * Backward compatibility note: for compatibility with legacy drivers
+ * that implement only the ethtool_cmd API, this has to work with both
+ * drivers implementing get_link_ksettings API and drivers
+ * implementing get_settings API. When drivers implement get_settings
+ * and report ethtool_cmd deprecated fields
+ * (transceiver/maxrxpkt/maxtxpkt), these fields are silently ignored
+ * because the resulting struct ethtool_link_settings does not report them.
+ */
+int __ethtool_get_link_ksettings(struct net_device *dev,
+				 struct ethtool_link_ksettings *link_ksettings)
+{
+	int err;
+	struct ethtool_cmd cmd;
+
+	ASSERT_RTNL();
+
+	if (dev->ethtool_ops->get_link_ksettings) {
+		memset(link_ksettings, 0, sizeof(*link_ksettings));
+		return dev->ethtool_ops->get_link_ksettings(dev,
+							    link_ksettings);
+	}
+
+	/* driver doesn't support %ethtool_link_ksettings API. revert to
+	 * legacy %ethtool_cmd API, unless it's not supported either.
+	 * TODO: remove when ethtool_ops::get_settings disappears internally
+	 */
+	if (!dev->ethtool_ops->get_settings)
+		return -EOPNOTSUPP;
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.cmd = ETHTOOL_GSET;
+	err = dev->ethtool_ops->get_settings(dev, &cmd);
+	if (err < 0)
+		return err;
+
+	/* we ignore deprecated fields transceiver/maxrxpkt/maxtxpkt
+	 */
+	convert_legacy_settings_to_link_ksettings(link_ksettings, &cmd);
+	return err;
+}
+EXPORT_SYMBOL(__ethtool_get_link_ksettings);
+
+int __ethtool_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
+{
+	if (!dev->ethtool_ops->get_wol)
+		return -EOPNOTSUPP;
+
+	dev->ethtool_ops->get_wol(dev, wol);
+
+	return 0;
+}
+EXPORT_SYMBOL(__ethtool_get_wol);
+
+int __ethtool_get_link(struct net_device *dev)
+{
+	if (!dev->ethtool_ops->get_link)
+		return -EOPNOTSUPP;
+
+	return netif_running(dev) && dev->ethtool_ops->get_link(dev);
+}
+EXPORT_SYMBOL(__ethtool_get_link);
diff --git a/net/ethtool/common.h b/net/ethtool/common.h
index 0f768c1be527..ec90d4ccddf7 100644
--- a/net/ethtool/common.h
+++ b/net/ethtool/common.h
@@ -12,5 +12,13 @@  extern const char tunable_strings[__ETHTOOL_TUNABLE_COUNT][ETH_GSTRING_LEN];
 extern const char phy_tunable_strings[__ETHTOOL_PHY_TUNABLE_COUNT][ETH_GSTRING_LEN];
 
 int __ethtool_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info);
+int __ethtool_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol);
+int __ethtool_get_link_ksettings(struct net_device *dev,
+				 struct ethtool_link_ksettings *link_ksettings);
+int __ethtool_get_link(struct net_device *dev);
+
+bool convert_legacy_settings_to_link_ksettings(
+	struct ethtool_link_ksettings *link_ksettings,
+	const struct ethtool_cmd *legacy_settings);
 
 #endif /* _ETHTOOL_COMMON_H */
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index 7b5831d35bca..8613434b6fc0 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -352,54 +352,6 @@  bool ethtool_convert_link_mode_to_legacy_u32(u32 *legacy_u32,
 }
 EXPORT_SYMBOL(ethtool_convert_link_mode_to_legacy_u32);
 
-/* return false if legacy contained non-0 deprecated fields
- * maxtxpkt/maxrxpkt. rest of ksettings always updated
- */
-static bool
-convert_legacy_settings_to_link_ksettings(
-	struct ethtool_link_ksettings *link_ksettings,
-	const struct ethtool_cmd *legacy_settings)
-{
-	bool retval = true;
-
-	memset(link_ksettings, 0, sizeof(*link_ksettings));
-
-	/* This is used to tell users that driver is still using these
-	 * deprecated legacy fields, and they should not use
-	 * %ETHTOOL_GLINKSETTINGS/%ETHTOOL_SLINKSETTINGS
-	 */
-	if (legacy_settings->maxtxpkt ||
-	    legacy_settings->maxrxpkt)
-		retval = false;
-
-	ethtool_convert_legacy_u32_to_link_mode(
-		link_ksettings->link_modes.supported,
-		legacy_settings->supported);
-	ethtool_convert_legacy_u32_to_link_mode(
-		link_ksettings->link_modes.advertising,
-		legacy_settings->advertising);
-	ethtool_convert_legacy_u32_to_link_mode(
-		link_ksettings->link_modes.lp_advertising,
-		legacy_settings->lp_advertising);
-	link_ksettings->base.speed
-		= ethtool_cmd_speed(legacy_settings);
-	link_ksettings->base.duplex
-		= legacy_settings->duplex;
-	link_ksettings->base.port
-		= legacy_settings->port;
-	link_ksettings->base.phy_address
-		= legacy_settings->phy_address;
-	link_ksettings->base.autoneg
-		= legacy_settings->autoneg;
-	link_ksettings->base.mdio_support
-		= legacy_settings->mdio_support;
-	link_ksettings->base.eth_tp_mdix
-		= legacy_settings->eth_tp_mdix;
-	link_ksettings->base.eth_tp_mdix_ctrl
-		= legacy_settings->eth_tp_mdix_ctrl;
-	return retval;
-}
-
 /* return false if ksettings link modes had higher bits
  * set. legacy_settings always updated (best effort)
  */
@@ -460,50 +412,6 @@  struct ethtool_link_usettings {
 	} link_modes;
 };
 
-/* Internal kernel helper to query a device ethtool_link_settings.
- *
- * Backward compatibility note: for compatibility with legacy drivers
- * that implement only the ethtool_cmd API, this has to work with both
- * drivers implementing get_link_ksettings API and drivers
- * implementing get_settings API. When drivers implement get_settings
- * and report ethtool_cmd deprecated fields
- * (transceiver/maxrxpkt/maxtxpkt), these fields are silently ignored
- * because the resulting struct ethtool_link_settings does not report them.
- */
-int __ethtool_get_link_ksettings(struct net_device *dev,
-				 struct ethtool_link_ksettings *link_ksettings)
-{
-	int err;
-	struct ethtool_cmd cmd;
-
-	ASSERT_RTNL();
-
-	if (dev->ethtool_ops->get_link_ksettings) {
-		memset(link_ksettings, 0, sizeof(*link_ksettings));
-		return dev->ethtool_ops->get_link_ksettings(dev,
-							    link_ksettings);
-	}
-
-	/* driver doesn't support %ethtool_link_ksettings API. revert to
-	 * legacy %ethtool_cmd API, unless it's not supported either.
-	 * TODO: remove when ethtool_ops::get_settings disappears internally
-	 */
-	if (!dev->ethtool_ops->get_settings)
-		return -EOPNOTSUPP;
-
-	memset(&cmd, 0, sizeof(cmd));
-	cmd.cmd = ETHTOOL_GSET;
-	err = dev->ethtool_ops->get_settings(dev, &cmd);
-	if (err < 0)
-		return err;
-
-	/* we ignore deprecated fields transceiver/maxrxpkt/maxtxpkt
-	 */
-	convert_legacy_settings_to_link_ksettings(link_ksettings, &cmd);
-	return err;
-}
-EXPORT_SYMBOL(__ethtool_get_link_ksettings);
-
 /* convert ethtool_link_usettings in user space to a kernel internal
  * ethtool_link_ksettings. return 0 on success, errno on error.
  */
@@ -1359,11 +1267,11 @@  static int ethtool_reset(struct net_device *dev, char __user *useraddr)
 static int ethtool_get_wol(struct net_device *dev, char __user *useraddr)
 {
 	struct ethtool_wolinfo wol = { .cmd = ETHTOOL_GWOL };
+	int rc;
 
-	if (!dev->ethtool_ops->get_wol)
-		return -EOPNOTSUPP;
-
-	dev->ethtool_ops->get_wol(dev, &wol);
+	rc = __ethtool_get_wol(dev, &wol);
+	if (rc < 0)
+		return rc;
 
 	if (copy_to_user(useraddr, &wol, sizeof(wol)))
 		return -EFAULT;
@@ -1428,12 +1336,12 @@  static int ethtool_nway_reset(struct net_device *dev)
 static int ethtool_get_link(struct net_device *dev, char __user *useraddr)
 {
 	struct ethtool_value edata = { .cmd = ETHTOOL_GLINK };
+	int link = __ethtool_get_link(dev);
 
-	if (!dev->ethtool_ops->get_link)
-		return -EOPNOTSUPP;
-
-	edata.data = netif_running(dev) && dev->ethtool_ops->get_link(dev);
+	if (link < 0)
+		return link;
 
+	edata.data = link;
 	if (copy_to_user(useraddr, &edata, sizeof(edata)))
 		return -EFAULT;
 	return 0;
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 305baa02ff70..6c14185a6466 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -2,12 +2,68 @@ 
 
 #include <linux/module.h>
 #include <linux/bitmap.h>
+#include <linux/rtnetlink.h>
 #include <net/sock.h>
 #include <linux/ethtool_netlink.h>
 #include "netlink.h"
 
 u32 ethnl_bcast_seq;
 
+const char *const link_mode_names[] = {
+	[ETHTOOL_LINK_MODE_10baseT_Half_BIT]		= "10baseT/Half",
+	[ETHTOOL_LINK_MODE_10baseT_Full_BIT]		= "10baseT/Full",
+	[ETHTOOL_LINK_MODE_100baseT_Half_BIT]		= "100baseT/Half",
+	[ETHTOOL_LINK_MODE_100baseT_Full_BIT]		= "100baseT/Full",
+	[ETHTOOL_LINK_MODE_1000baseT_Half_BIT]		= "1000baseT/Half",
+	[ETHTOOL_LINK_MODE_1000baseT_Full_BIT]		= "1000baseT/Full",
+	[ETHTOOL_LINK_MODE_Autoneg_BIT]			= "Autoneg",
+	[ETHTOOL_LINK_MODE_TP_BIT]			= "TP",
+	[ETHTOOL_LINK_MODE_AUI_BIT]			= "AUI",
+	[ETHTOOL_LINK_MODE_MII_BIT]			= "MII",
+	[ETHTOOL_LINK_MODE_FIBRE_BIT]			= "FIBRE",
+	[ETHTOOL_LINK_MODE_BNC_BIT]			= "BNC",
+	[ETHTOOL_LINK_MODE_10000baseT_Full_BIT]		= "10000baseT/Full",
+	[ETHTOOL_LINK_MODE_Pause_BIT]			= "Pause",
+	[ETHTOOL_LINK_MODE_Asym_Pause_BIT]		= "Asym_Pause",
+	[ETHTOOL_LINK_MODE_2500baseX_Full_BIT]		= "2500baseX/Full",
+	[ETHTOOL_LINK_MODE_Backplane_BIT]		= "Backplane",
+	[ETHTOOL_LINK_MODE_1000baseKX_Full_BIT]		= "1000baseKX/Full",
+	[ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT]	= "10000baseKX4/Full",
+	[ETHTOOL_LINK_MODE_10000baseKR_Full_BIT]	= "10000baseKR/Full",
+	[ETHTOOL_LINK_MODE_10000baseR_FEC_BIT]		= "10000baseR/FEC",
+	[ETHTOOL_LINK_MODE_20000baseMLD2_Full_BIT]	= "20000baseMLD2/Full",
+	[ETHTOOL_LINK_MODE_20000baseKR2_Full_BIT]	= "20000baseKR2/Full",
+	[ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT]	= "40000baseKR4/Full",
+	[ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT]	= "40000baseCR4/Full",
+	[ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT]	= "40000baseSR4/Full",
+	[ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT]	= "40000baseLR4/Full",
+	[ETHTOOL_LINK_MODE_56000baseKR4_Full_BIT]	= "56000baseKR4/Full",
+	[ETHTOOL_LINK_MODE_56000baseCR4_Full_BIT]	= "56000baseCR4/Full",
+	[ETHTOOL_LINK_MODE_56000baseSR4_Full_BIT]	= "56000baseSR4/Full",
+	[ETHTOOL_LINK_MODE_56000baseLR4_Full_BIT]	= "56000baseLR4/Full",
+	[ETHTOOL_LINK_MODE_25000baseCR_Full_BIT]	= "25000baseCR/Full",
+	[ETHTOOL_LINK_MODE_25000baseKR_Full_BIT]	= "25000baseKR/Full",
+	[ETHTOOL_LINK_MODE_25000baseSR_Full_BIT]	= "25000baseSR/Full",
+	[ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT]	= "50000baseCR2/Full",
+	[ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT]	= "50000baseKR2/Full",
+	[ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT]	= "100000baseKR4/Full",
+	[ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT]	= "100000baseSR4/Full",
+	[ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT]	= "100000baseCR4/Full",
+	[ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT]	= "100000baseLR4/ER4_Full",
+	[ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT]	= "50000baseSR2/Full",
+	[ETHTOOL_LINK_MODE_1000baseX_Full_BIT]		= "1000baseX/Full",
+	[ETHTOOL_LINK_MODE_10000baseCR_Full_BIT]	= "10000baseCR/Full",
+	[ETHTOOL_LINK_MODE_10000baseSR_Full_BIT]	= "10000baseSR/Full",
+	[ETHTOOL_LINK_MODE_10000baseLR_Full_BIT]	= "10000baseLR/Full",
+	[ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT]	= "10000baseLRM/Full",
+	[ETHTOOL_LINK_MODE_10000baseER_Full_BIT]	= "10000baseER/Full",
+	[ETHTOOL_LINK_MODE_2500baseT_Full_BIT]		= "2500baseT/Full",
+	[ETHTOOL_LINK_MODE_5000baseT_Full_BIT]		= "5000baseT/Full",
+	[ETHTOOL_LINK_MODE_FEC_NONE_BIT]		= "None",
+	[ETHTOOL_LINK_MODE_FEC_RS_BIT]			= "RS",
+	[ETHTOOL_LINK_MODE_FEC_BASER_BIT]		= "BASER",
+};
+
 static const struct nla_policy dev_policy[ETHA_DEV_MAX + 1] = {
 	[ETHA_DEV_UNSPEC]	= { .type = NLA_UNSPEC },
 	[ETHA_DEV_INDEX]	= { .type = NLA_U32 },
@@ -671,11 +727,14 @@  static struct notifier_block ethnl_netdev_notifier = {
 
 int ethnl_get_strset(struct sk_buff *skb, struct genl_info *info);
 int ethnl_get_drvinfo(struct sk_buff *skb, struct genl_info *info);
+int ethnl_get_settings(struct sk_buff *skb, struct genl_info *info);
 
 int ethnl_strset_start(struct netlink_callback *cb);
 int ethnl_drvinfo_start(struct netlink_callback *cb);
+int ethnl_settings_start(struct netlink_callback *cb);
 
 int ethnl_strset_done(struct netlink_callback *cb);
+int ethnl_settings_done(struct netlink_callback *cb);
 
 static const struct genl_ops ethtool_genl_ops[] = {
 	{
@@ -691,6 +750,13 @@  static const struct genl_ops ethtool_genl_ops[] = {
 		.start	= ethnl_drvinfo_start,
 		.dumpit	= ethnl_dumpit,
 	},
+	{
+		.cmd	= ETHNL_CMD_GET_SETTINGS,
+		.doit	= ethnl_get_settings,
+		.start	= ethnl_settings_start,
+		.dumpit	= ethnl_dumpit,
+		.done	= ethnl_settings_done,
+	},
 };
 
 static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
@@ -715,6 +781,9 @@  static int __init ethtool_nl_init(void)
 {
 	int ret;
 
+	BUILD_BUG_ON(ARRAY_SIZE(link_mode_names) <
+		     __ETHTOOL_LINK_MODE_MASK_NBITS);
+
 	ret = genl_register_family(&ethtool_genl_family);
 	if (ret < 0)
 		return ret;
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 94c14ec2c3fc..789aceae9f5e 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -15,6 +15,8 @@  extern u32 ethnl_bcast_seq;
 
 extern struct genl_family ethtool_genl_family;
 
+extern const char *const link_mode_names[];
+
 struct net_device *ethnl_dev_get(struct genl_info *info, struct nlattr *nest);
 int ethnl_fill_dev(struct sk_buff *msg, struct net_device *dev, u16 attrtype);
 
diff --git a/net/ethtool/settings.c b/net/ethtool/settings.c
new file mode 100644
index 000000000000..dd76599f311f
--- /dev/null
+++ b/net/ethtool/settings.c
@@ -0,0 +1,416 @@ 
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#include "netlink.h"
+#include "common.h"
+#include <linux/rtnetlink.h>
+
+struct settings_data {
+	struct ethtool_link_ksettings	ksettings;
+	struct ethtool_link_settings	*lsettings;
+	struct ethtool_wolinfo		wolinfo;
+	int				link;
+	u32				msglevel;
+	bool				lpm_empty;
+	u32				req_mask;
+};
+
+struct settings_reqinfo {
+	struct net_device		*dev;
+	u32				req_mask;
+	bool				compact;
+	bool				is_privileged;
+	bool				have_rtnl;
+};
+
+/* We want to allow ~0 as selector for backward compatibility (to just set
+ * given set of modes, whatever kernel supports) so that we allow all bits
+ * on validation and do our own sanity check later.
+ */
+static u32 all_bits = ~(u32)0;
+
+static const struct nla_policy settings_policy[ETHA_SETTINGS_MAX + 1] = {
+	[ETHA_SETTINGS_UNSPEC]		= { .type = NLA_UNSPEC },
+	[ETHA_SETTINGS_DEV]		= { .type = NLA_NESTED },
+	[ETHA_SETTINGS_INFOMASK]	= { .type = NLA_U32 },
+	[ETHA_SETTINGS_COMPACT]		= { .type = NLA_FLAG },
+	[ETHA_SETTINGS_SPEED]		= { .type = NLA_U32 },
+	[ETHA_SETTINGS_DUPLEX]		= { .type = NLA_U8 },
+	[ETHA_SETTINGS_PORT]		= { .type = NLA_U8 },
+	[ETHA_SETTINGS_PHYADDR]		= { .type = NLA_U8 },
+	[ETHA_SETTINGS_AUTONEG]		= { .type = NLA_U8 },
+	[ETHA_SETTINGS_MDIO_SUPPORT]	= { .type = NLA_BITFIELD32 },
+	[ETHA_SETTINGS_TP_MDIX]		= { .type = NLA_U8 },
+	[ETHA_SETTINGS_TP_MDIX_CTRL]	= { .type = NLA_U8 },
+	[ETHA_SETTINGS_TRANSCEIVER]	= { .type = NLA_U8 },
+	[ETHA_SETTINGS_WOL_MODES]	= { .type = NLA_BITFIELD32,
+					    .validation_data = &all_bits },
+	[ETHA_SETTINGS_SOPASS]		= { .type = NLA_BINARY,
+					    .len = SOPASS_MAX },
+	[ETHA_SETTINGS_MSGLVL]		= { .type = NLA_BITFIELD32,
+					    .validation_data = &all_bits },
+	[ETHA_SETTINGS_LINK_MODES]	= { .type = NLA_NESTED },
+	[ETHA_SETTINGS_PEER_MODES]	= { .type = NLA_NESTED },
+	[ETHA_SETTINGS_LINK]		= { .type = NLA_FLAG },
+};
+
+/* To keep things simple, reserve space for some attributes which may not
+ * be added to the message (e.g. ETHA_SETTINGS_SOPASS); therefore the length
+ * returned may be bigger than the actual length of the message sent
+ */
+static int settings_size(struct settings_data *data,
+			 struct settings_reqinfo *req_info)
+{
+	struct ethtool_link_ksettings *ksettings = &data->ksettings;
+	u32 req_mask = req_info->req_mask;
+	bool compact = req_info->compact;
+	size_t len = 0;
+	int ret = 0;
+
+	if (req_mask & ETH_SETTINGS_IM_LINKINFO) {
+		/* speed */
+		len += nla_total_size(sizeof(u32));
+		/* duplex, autoneg, port, phyaddr, mdix, mdixctrl, transcvr */
+		len += 7 * nla_total_size(sizeof(u8));
+		/* mdio_support */
+		len += nla_total_size(sizeof(struct nla_bitfield32));
+	}
+	if (req_mask & ETH_SETTINGS_IM_LINKMODES) {
+		u32 *supported = (u32 *)ksettings->link_modes.supported;
+		u32 *advertising = (u32 *)ksettings->link_modes.advertising;
+		u32 *lp_advertising =
+			(u32 *)ksettings->link_modes.lp_advertising;
+
+		ret = ethnl_bitset32_size(compact,
+					  __ETHTOOL_LINK_MODE_MASK_NBITS,
+					  advertising, supported,
+					  link_mode_names);
+		if (ret < 0)
+			return ret;
+		len += ret;
+		ret = ethnl_bitset32_size(compact,
+					  __ETHTOOL_LINK_MODE_MASK_NBITS,
+					  lp_advertising, lp_advertising,
+					  link_mode_names);
+		if (ret < 0)
+			return ret;
+		len += ret;
+	}
+	if (req_mask & ETH_SETTINGS_IM_MSGLEVEL)
+		len += nla_total_size(sizeof(struct nla_bitfield32));
+	if (req_mask & ETH_SETTINGS_IM_WOLINFO) {
+		/* wolopts / wol_supported */
+		len += nla_total_size(sizeof(struct nla_bitfield32));
+		/* sopass */
+		len += nla_total_size(SOPASS_MAX);
+	}
+	if (req_mask & ETH_SETTINGS_IM_LINK)
+		len += nla_total_size(sizeof(u32));
+
+	return len;
+}
+
+static int ethnl_get_link_ksettings(struct genl_info *info,
+				    struct net_device *dev,
+				    struct ethtool_link_ksettings *ksettings)
+{
+	int ret;
+
+	ret = __ethtool_get_link_ksettings(dev, ksettings);
+
+	if (ret < 0)
+		ETHNL_SET_ERRMSG(info, "failed to retrieve link settings");
+	return ret;
+}
+
+static int ethnl_get_legacy_settings(struct genl_info *info,
+				     struct net_device *dev,
+				     struct ethtool_cmd *cmd)
+{
+	int ret;
+
+	if (!dev->ethtool_ops->get_settings) {
+		/* we already tried ->get_link_ksettings */
+		ETHNL_SET_ERRMSG(info, "link settings retrieval unsupported");
+		return -EOPNOTSUPP;
+	}
+	ret = dev->ethtool_ops->get_settings(dev, cmd);
+	if (ret < 0)
+		ETHNL_SET_ERRMSG(info, "failed to retrieve link settings");
+
+	return ret;
+}
+
+static int ethnl_get_wol(struct genl_info *info, struct net_device *dev,
+			 struct ethtool_wolinfo *wolinfo)
+{
+	int ret = __ethtool_get_wol(dev, wolinfo);
+
+	if (ret < 0)
+		ETHNL_SET_ERRMSG(info, "failed to retrieve wol info");
+	return ret;
+}
+
+static int parse_settings_req(struct settings_reqinfo *req_info,
+			      struct genl_info *info, struct sk_buff *skb,
+			      const struct nlmsghdr *nlhdr)
+{
+	struct nlattr *tb[ETHA_SETTINGS_MAX + 1];
+	int ret;
+
+	memset(req_info, '\0', sizeof(*req_info));
+	req_info->is_privileged = ethnl_is_privileged(skb);
+
+	ret = genlmsg_parse(nlhdr, &ethtool_genl_family, tb,
+			    ETHA_SETTINGS_MAX, settings_policy,
+			    info ? info->extack : NULL);
+	if (ret < 0)
+		return ret;
+
+	if (tb[ETHA_SETTINGS_DEV]) {
+		req_info->dev = ethnl_dev_get(info, tb[ETHA_SETTINGS_DEV]);
+		if (IS_ERR(req_info->dev)) {
+			ret = PTR_ERR(req_info->dev);
+			req_info->dev = NULL;
+			return ret;
+		}
+	}
+	if (tb[ETHA_SETTINGS_INFOMASK])
+		req_info->req_mask = nla_get_u32(tb[ETHA_SETTINGS_INFOMASK]);
+	if (tb[ETHA_SETTINGS_COMPACT])
+		req_info->compact = true;
+	if (req_info->req_mask == 0)
+		req_info->req_mask = ETH_SETTINGS_IM_DEFAULT;
+
+	return 0;
+}
+
+static int prepare_settings(struct settings_data *data,
+			    struct settings_reqinfo *req_info,
+			    struct genl_info *info, struct net_device *dev)
+{
+	u32 req_mask = req_info->req_mask;
+	int ret;
+
+	memset(data, '\0', sizeof(*data));
+	data->lsettings = &data->ksettings.base;
+	data->lpm_empty = true;
+	data->link = -EOPNOTSUPP;
+
+	if (!req_info->have_rtnl)
+		rtnl_lock();
+	if (req_mask & (ETH_SETTINGS_IM_LINKINFO | ETH_SETTINGS_IM_LINKMODES)) {
+		ret = ethnl_get_link_ksettings(info, dev, &data->ksettings);
+		if (ret < 0) {
+			warn_partial_info(info);
+			req_mask &= ~(ETH_SETTINGS_IM_LINKINFO |
+				      ETH_SETTINGS_IM_LINKMODES);
+		}
+	}
+	if (req_mask & ETH_SETTINGS_IM_LINKMODES) {
+		data->lpm_empty = bitmap_empty(data->ksettings.link_modes.lp_advertising,
+					       __ETHTOOL_LINK_MODE_MASK_NBITS);
+		ethnl_bitmap_to_u32(data->ksettings.link_modes.supported,
+				    __ETHTOOL_LINK_MODE_MASK_NWORDS);
+		ethnl_bitmap_to_u32(data->ksettings.link_modes.advertising,
+				    __ETHTOOL_LINK_MODE_MASK_NWORDS);
+		ethnl_bitmap_to_u32(data->ksettings.link_modes.lp_advertising,
+				    __ETHTOOL_LINK_MODE_MASK_NWORDS);
+	}
+	if (req_mask & ETH_SETTINGS_IM_MSGLEVEL) {
+		if (dev->ethtool_ops->get_msglevel) {
+			data->msglevel = dev->ethtool_ops->get_msglevel(dev);
+		} else {
+			warn_partial_info(info);
+			req_mask &= ~ETH_SETTINGS_IM_MSGLEVEL;
+		}
+	}
+	if (req_mask & ETH_SETTINGS_IM_WOLINFO) {
+		ret = ethnl_get_wol(info, dev, &data->wolinfo);
+		if (ret < 0) {
+			warn_partial_info(info);
+			req_mask &= ~ETH_SETTINGS_IM_WOLINFO;
+		}
+	}
+	if (req_mask & ETH_SETTINGS_IM_LINK)
+		data->link = __ethtool_get_link(dev);
+	if (!req_info->have_rtnl)
+		rtnl_unlock();
+
+	data->req_mask = req_mask;
+	return 0;
+}
+
+static int fill_settings(struct sk_buff *rskb, struct settings_data *data,
+			 struct settings_reqinfo *req_info)
+{
+	struct ethtool_link_settings *lsettings = data->lsettings;
+	u32 req_mask = data->req_mask;
+	bool compact = req_info->compact;
+	int ret;
+
+	ret = -EMSGSIZE;
+	if (req_mask & ETH_SETTINGS_IM_LINKINFO) {
+		if (nla_put_u32(rskb, ETHA_SETTINGS_SPEED, lsettings->speed) ||
+		    nla_put_u8(rskb, ETHA_SETTINGS_DUPLEX, lsettings->duplex) ||
+		    nla_put_u8(rskb, ETHA_SETTINGS_PORT, lsettings->port) ||
+		    nla_put_u8(rskb, ETHA_SETTINGS_PHYADDR,
+			       lsettings->phy_address) ||
+		    nla_put_u8(rskb, ETHA_SETTINGS_AUTONEG,
+			       lsettings->autoneg) ||
+		    nla_put_bitfield32(rskb, ETHA_SETTINGS_MDIO_SUPPORT,
+				       lsettings->mdio_support,
+				       ETH_MDIO_SUPPORTS_ALL) ||
+		    nla_put_u8(rskb, ETHA_SETTINGS_TP_MDIX,
+			       lsettings->eth_tp_mdix) ||
+		    nla_put_u8(rskb, ETHA_SETTINGS_TP_MDIX_CTRL,
+			       lsettings->eth_tp_mdix_ctrl) ||
+		    nla_put_u8(rskb, ETHA_SETTINGS_TRANSCEIVER,
+			       lsettings->transceiver))
+			return ret;
+	}
+	if (req_mask & ETH_SETTINGS_IM_LINKMODES) {
+		u32 *supported = (u32 *)data->ksettings.link_modes.supported;
+		u32 *advertising =
+			(u32 *)data->ksettings.link_modes.advertising;
+		u32 *lp_advertising =
+			(u32 *)data->ksettings.link_modes.lp_advertising;
+
+		ret = ethnl_put_bitset32(rskb, ETHA_SETTINGS_LINK_MODES,
+					 compact,
+					 __ETHTOOL_LINK_MODE_MASK_NBITS,
+					 advertising, supported,
+					 link_mode_names);
+		if (ret < 0)
+			return ret;
+		if (!data->lpm_empty) {
+			ret = ethnl_put_bitset32(rskb, ETHA_SETTINGS_PEER_MODES,
+						 compact,
+						 __ETHTOOL_LINK_MODE_MASK_NBITS,
+						 lp_advertising, lp_advertising,
+						 link_mode_names);
+			if (ret < 0)
+				return ret;
+		}
+		ret = -EMSGSIZE;
+	}
+	if (req_mask & ETH_SETTINGS_IM_MSGLEVEL) {
+		if (nla_put_bitfield32(rskb, ETHA_SETTINGS_MSGLVL,
+				       data->msglevel, NETIF_MSG_ALL))
+			return ret;
+	}
+	if (req_mask & ETH_SETTINGS_IM_WOLINFO) {
+		/* ioctl() restricts read access to wolinfo but the actual
+		 * reason is to hide sopass from unprivileged users; netlink
+		 * can show wol modes without sopass
+		 */
+		if (nla_put_bitfield32(rskb, ETHA_SETTINGS_WOL_MODES,
+				       data->wolinfo.wolopts,
+				       data->wolinfo.supported))
+			return ret;
+		if (req_info->is_privileged &&
+		    nla_put(rskb, ETHA_SETTINGS_SOPASS,
+			    sizeof(data->wolinfo.sopass), data->wolinfo.sopass))
+			return ret;
+	}
+	if (req_mask & ETH_SETTINGS_IM_LINK && data->link >= 0) {
+		if (nla_put_u32(rskb, ETHA_SETTINGS_LINK, data->link))
+			return ret;
+	}
+
+	return 0;
+}
+
+int ethnl_get_settings(struct sk_buff *skb, struct genl_info *info)
+{
+	struct settings_data data;
+	struct settings_reqinfo req_info;
+	struct sk_buff *rskb;
+	int reply_len;
+	void *ehdr;
+	int ret;
+
+	ret = parse_settings_req(&req_info, info, skb, info->nlhdr);
+	if (ret < 0)
+		goto err_dev;
+	ret = prepare_settings(&data, &req_info, info, req_info.dev);
+	if (ret < 0)
+		goto err_dev;
+	reply_len = settings_size(&data, &req_info);
+	if (ret < 0)
+		goto err_dev;
+	ret = -ENOMEM;
+	rskb = ethnl_reply_init(reply_len, req_info.dev, ETHNL_CMD_SET_SETTINGS,
+				ETHA_SETTINGS_DEV, info, &ehdr);
+	if (!rskb)
+		goto err_dev;
+	ret = fill_settings(rskb, &data, &req_info);
+	if (ret < 0)
+		goto err;
+
+	genlmsg_end(rskb, ehdr);
+	dev_put(req_info.dev);
+	return genlmsg_reply(rskb, info);
+
+err:
+	WARN_ONCE(ret == -EMSGSIZE,
+		  "calculated message payload length (%d) not sufficient\n",
+		  reply_len);
+	nlmsg_free(rskb);
+err_dev:
+	if (req_info.dev)
+		dev_put(req_info.dev);
+	return ret;
+}
+
+static int settings_dump(struct sk_buff *skb, struct netlink_callback *cb,
+			struct net_device *dev)
+{
+	struct settings_data data;
+	struct settings_reqinfo *req_info;
+	int ret;
+
+	req_info = (struct settings_reqinfo *)cb->args[4];
+	ret = prepare_settings(&data, req_info, NULL, dev);
+	if (ret < 0)
+		return ret;
+	ret = ethnl_fill_dev(skb, dev, ETHA_SETTINGS_DEV);
+	if (ret < 0)
+		return ret;
+	ret = fill_settings(skb, &data, req_info);
+	return ret;
+}
+
+int ethnl_settings_start(struct netlink_callback *cb)
+{
+	struct settings_reqinfo *req_info;
+	int ret;
+
+	req_info = kmalloc(sizeof(*req_info), GFP_KERNEL);
+	if (!req_info)
+		return -ENOMEM;
+	ret = parse_settings_req(req_info, NULL, cb->skb, cb->nlh);
+	if (ret < 0) {
+		if (req_info->dev)
+			dev_put(req_info->dev);
+		req_info->dev = NULL;
+		return ret;
+	}
+
+	cb->args[0] = (long)settings_dump;
+	cb->args[1] = ETHNL_CMD_SET_SETTINGS;
+	cb->args[4] = (long)req_info;
+
+	return 0;
+}
+
+int ethnl_settings_done(struct netlink_callback *cb)
+{
+	struct settings_reqinfo *req_info;
+
+	req_info = (struct settings_reqinfo *)cb->args[4];
+	if (req_info->dev)
+		dev_put(req_info->dev);
+	kfree(req_info);
+
+	return 0;
+}