lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <766faf06bab06022ab53820e2d6bc7b213fcd989.1532953989.git.mkubecek@suse.cz>
Date:   Mon, 30 Jul 2018 14:53:47 +0200 (CEST)
From:   Michal Kubecek <mkubecek@...e.cz>
To:     netdev@...r.kernel.org
Cc:     linux-kernel@...r.kernel.org, Jiri Pirko <jiri@...nulli.us>,
        David Miller <davem@...emloft.net>,
        Florian Fainelli <f.fainelli@...il.com>,
        Roopa Prabhu <roopa@...ulusnetworks.com>,
        Jakub Kicinski <kubakici@...pl>,
        "John W. Linville" <linville@...driver.com>
Subject: [RFC PATCH net-next v2 13/17] ethtool: implement SET_SETTINGS message

Sets the information provided by ETHTOOL_SLINKSETTINGS, ETHTOOL_SWOL and
ETHTOOL_SMSGLVL. Unlike with ioctl(), userspace can send only some
attributes so that we only need to call ethtool_ops callbacks which we
really need (and the "set" callback is only called when we actually changed
some setting).

Signed-off-by: Michal Kubecek <mkubecek@...e.cz>
---
 Documentation/networking/ethtool-netlink.txt |  43 +-
 net/ethtool/netlink.c                        |   6 +
 net/ethtool/settings.c                       | 495 +++++++++++++++++++
 3 files changed, 539 insertions(+), 5 deletions(-)

diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt
index 3993652f81a9..c7fe4f518972 100644
--- a/Documentation/networking/ethtool-netlink.txt
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -124,7 +124,7 @@ List of message types
     ETHNL_CMD_GET_DRVINFO
     ETHNL_CMD_SET_DRVINFO		response only
     ETHNL_CMD_GET_SETTINGS
-    ETHNL_CMD_SET_SETTINGS		response only (for now)
+    ETHNL_CMD_SET_SETTINGS
 
 All constants use ETHNL_CMD_ prefix, usually followed by "GET", "SET" or "ACT"
 to indicate the type.
@@ -267,6 +267,39 @@ netlink or ioctl ethtool interface; feature notifications are also sent
 whenever netdev_update_features() or netdev_change_features() is called.
 
 
+SET_SETTINGS
+------------
+
+SET_SETTINGS request allows setting some of the data reported by GET_SETTINGS.
+Request flags, info_mask and index are ignored. These attributes are allowed
+to be passed with SET_SETTINGS request:
+
+    ETHA_SETTINGS_DEV		(nested)	device identification
+    ETHA_SETTINGS_SPEED         (u32)           link speed (Mb/s)
+    ETHA_SETTINGS_DUPLEX        (u8)            duplex mode
+    ETHA_SETTINGS_PORT          (u8)            physical port
+    ETHA_SETTINGS_PHYADDR       (u8)            MDIO address of phy
+    ETHA_SETTINGS_AUTONEG       (u8)            autoneotiation status
+    ETHA_SETTINGS_TP_MDIX_CTRL  (u8)            MDI(-X) control
+    ETHA_SETTINGS_WOL_MODES     (bitfield32)    wake-on-lan modes
+    ETHA_SETTINGS_SOPASS        (binary)        SecureOn(tm) password
+    ETHA_SETTINGS_MSGLVL        (bitfield32)    debug level
+    ETHA_SETTINGS_LINK_MODES    (bitset)        device link modes
+
+For both bitfield32 types, value and selector work the usual way, i.e. bits
+set in selector are set to corresponding bits from value and the rest is
+preserved. In a similar fashion, ETHA_SETTINGS_LINK_MODES allows setting
+advertised link modes.
+
+If autonegotiation is on (either set now or kept from before), advertised
+modes are not changed (no ETHA_SETTINGS_LINK_MODES attribute) and at least one
+of speed and duplex is specified, kernel adjusts advertised modes to all
+supported modes matching speed, duplex or both (whatever is specified). This
+autoselection is done on ethtool side with ioctl interface, netlink interface
+is supposed to allow requesting changes without knowing what exactly kernel
+supports.
+
+
 Request translation
 -------------------
 
@@ -277,13 +310,13 @@ have their netlink replacement yet.
 ioctl command			netlink command
 ---------------------------------------------------------------------
 ETHTOOL_GSET			ETHNL_CMD_GET_SETTINGS
-ETHTOOL_SSET			n/a
+ETHTOOL_SSET			ETHNL_CMD_SET_SETTINGS
 ETHTOOL_GDRVINFO		ETHNL_CMD_GET_DRVINFO
 ETHTOOL_GREGS			n/a
 ETHTOOL_GWOL			ETHNL_CMD_GET_SETTINGS
-ETHTOOL_SWOL			n/a
+ETHTOOL_SWOL			ETHNL_CMD_SET_SETTINGS
 ETHTOOL_GMSGLVL			ETHNL_CMD_GET_SETTINGS
-ETHTOOL_SMSGLVL			n/a
+ETHTOOL_SMSGLVL			ETHNL_CMD_SET_SETTINGS
 ETHTOOL_NWAY_RST		n/a
 ETHTOOL_GLINK			ETHNL_CMD_GET_SETTINGS
 ETHTOOL_GEEPROM			n/a
@@ -351,7 +384,7 @@ ETHTOOL_STUNABLE		n/a
 ETHTOOL_GPHYSTATS		n/a
 ETHTOOL_PERQUEUE		n/a
 ETHTOOL_GLINKSETTINGS		ETHNL_CMD_GET_SETTINGS
-ETHTOOL_SLINKSETTINGS		n/a
+ETHTOOL_SLINKSETTINGS		ETHNL_CMD_SET_SETTINGS
 ETHTOOL_PHY_GTUNABLE		n/a
 ETHTOOL_PHY_STUNABLE		n/a
 ETHTOOL_GFECPARAM		n/a
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 0e2158092a44..6e183caca01f 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -730,6 +730,7 @@ static struct notifier_block ethnl_netdev_notifier = {
 int ethnl_get_strset(struct sk_buff *skb, struct genl_info *info);
 int ethnl_get_drvinfo(struct sk_buff *skb, struct genl_info *info);
 int ethnl_get_settings(struct sk_buff *skb, struct genl_info *info);
+int ethnl_set_settings(struct sk_buff *skb, struct genl_info *info);
 
 int ethnl_strset_start(struct netlink_callback *cb);
 int ethnl_drvinfo_start(struct netlink_callback *cb);
@@ -759,6 +760,11 @@ static const struct genl_ops ethtool_genl_ops[] = {
 		.dumpit	= ethnl_dumpit,
 		.done	= ethnl_settings_done,
 	},
+	{
+		.cmd	= ETHNL_CMD_SET_SETTINGS,
+		.flags	= GENL_UNS_ADMIN_PERM,
+		.doit	= ethnl_set_settings,
+	},
 };
 
 static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
diff --git a/net/ethtool/settings.c b/net/ethtool/settings.c
index 739bd006c924..678199e621a2 100644
--- a/net/ethtool/settings.c
+++ b/net/ethtool/settings.c
@@ -88,6 +88,222 @@ struct settings_reqinfo {
 	bool				have_rtnl;
 };
 
+struct link_mode_info {
+	int				speed;
+	u8				duplex;
+};
+
+static const struct link_mode_info link_mode_params[] = {
+	[ETHTOOL_LINK_MODE_10baseT_Half_BIT]		= {
+		.speed	= 10,
+		.duplex	= DUPLEX_HALF,
+	},
+	[ETHTOOL_LINK_MODE_10baseT_Full_BIT]		= {
+		.speed	= 10,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_100baseT_Half_BIT]		= {
+		.speed	= 100,
+		.duplex	= DUPLEX_HALF,
+	},
+	[ETHTOOL_LINK_MODE_100baseT_Full_BIT]		= {
+		.speed	= 100,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_1000baseT_Half_BIT]		= {
+		.speed	= 1000,
+		.duplex	= DUPLEX_HALF,
+	},
+	[ETHTOOL_LINK_MODE_1000baseT_Full_BIT]		= {
+		.speed	= 1000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_Autoneg_BIT]			= {
+		.speed	= SPEED_UNKNOWN,
+		.duplex	= DUPLEX_UNKNOWN,
+	},
+	[ETHTOOL_LINK_MODE_TP_BIT]			= {
+		.speed	= SPEED_UNKNOWN,
+		.duplex	= DUPLEX_UNKNOWN,
+	},
+	[ETHTOOL_LINK_MODE_AUI_BIT]			= {
+		.speed	= SPEED_UNKNOWN,
+		.duplex	= DUPLEX_UNKNOWN,
+	},
+	[ETHTOOL_LINK_MODE_MII_BIT]			= {
+		.speed	= SPEED_UNKNOWN,
+		.duplex	= DUPLEX_UNKNOWN,
+	},
+	[ETHTOOL_LINK_MODE_FIBRE_BIT]			= {
+		.speed	= SPEED_UNKNOWN,
+		.duplex	= DUPLEX_UNKNOWN,
+	},
+	[ETHTOOL_LINK_MODE_BNC_BIT]			= {
+		.speed	= SPEED_UNKNOWN,
+		.duplex	= DUPLEX_UNKNOWN,
+	},
+	[ETHTOOL_LINK_MODE_10000baseT_Full_BIT]		= {
+		.speed	= 10000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_Pause_BIT]			= {
+		.speed	= SPEED_UNKNOWN,
+		.duplex	= DUPLEX_UNKNOWN,
+	},
+	[ETHTOOL_LINK_MODE_Asym_Pause_BIT]		= {
+		.speed	= SPEED_UNKNOWN,
+		.duplex	= DUPLEX_UNKNOWN,
+	},
+	[ETHTOOL_LINK_MODE_2500baseX_Full_BIT]		= {
+		.speed	= 2500,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_Backplane_BIT]		= {
+		.speed	= SPEED_UNKNOWN,
+		.duplex	= DUPLEX_UNKNOWN,
+	},
+	[ETHTOOL_LINK_MODE_1000baseKX_Full_BIT]		= {
+		.speed	= 1000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT]	= {
+		.speed	= 10000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_10000baseKR_Full_BIT]	= {
+		.speed	= 10000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_10000baseR_FEC_BIT]		= {
+		.speed	= 10000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_20000baseMLD2_Full_BIT]	= {
+		.speed	= 20000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_20000baseKR2_Full_BIT]	= {
+		.speed	= 20000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT]	= {
+		.speed	= 40000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT]	= {
+		.speed	= 40000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT]	= {
+		.speed	= 40000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT]	= {
+		.speed	= 40000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_56000baseKR4_Full_BIT]	= {
+		.speed	= 56000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_56000baseCR4_Full_BIT]	= {
+		.speed	= 56000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_56000baseSR4_Full_BIT]	= {
+		.speed	= 56000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_56000baseLR4_Full_BIT]	= {
+		.speed	= 56000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_25000baseCR_Full_BIT]	= {
+		.speed	= 25000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_25000baseKR_Full_BIT]	= {
+		.speed	= 25000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_25000baseSR_Full_BIT]	= {
+		.speed	= 25000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT]	= {
+		.speed	= 50000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT]	= {
+		.speed	= 50000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT]	= {
+		.speed	= 100000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT]	= {
+		.speed	= 100000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT]	= {
+		.speed	= 100000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT]	= {
+		.speed	= 100000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT]	= {
+		.speed	= 50000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_1000baseX_Full_BIT]		= {
+		.speed	= 1000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_10000baseCR_Full_BIT]	= {
+		.speed	= 10000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_10000baseSR_Full_BIT]	= {
+		.speed	= 10000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_10000baseLR_Full_BIT]	= {
+		.speed	= 10000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT]	= {
+		.speed	= 10000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_10000baseER_Full_BIT]	= {
+		.speed	= 10000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_2500baseT_Full_BIT]		= {
+		.speed	= 2500,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_5000baseT_Full_BIT]		= {
+		.speed	= 5000,
+		.duplex	= DUPLEX_FULL,
+	},
+	[ETHTOOL_LINK_MODE_FEC_NONE_BIT]		= {
+		.speed	= SPEED_UNKNOWN,
+		.duplex	= DUPLEX_UNKNOWN,
+	},
+	[ETHTOOL_LINK_MODE_FEC_RS_BIT]			= {
+		.speed	= SPEED_UNKNOWN,
+		.duplex	= DUPLEX_UNKNOWN,
+	},
+	[ETHTOOL_LINK_MODE_FEC_BASER_BIT]		= {
+		.speed	= SPEED_UNKNOWN,
+		.duplex	= DUPLEX_UNKNOWN,
+	},
+};
+
 /* We want to allow ~0 as selector for backward compatibility (to just set
  * given set of modes, whatever kernel supports) so that we allow all bits
  * on validation and do our own sanity check later.
@@ -606,3 +822,282 @@ void ethnl_settings_notify(struct netdev_notifier_ethtool_info *info)
 err_skb:
 	nlmsg_free(skb);
 }
+
+/* SET_SETTINGS */
+
+static int ethnl_set_link_ksettings(struct genl_info *info,
+				    struct net_device *dev,
+				    struct ethtool_link_ksettings *ksettings)
+{
+	int ret = dev->ethtool_ops->set_link_ksettings(dev, ksettings);
+
+	if (ret < 0)
+		ETHNL_SET_ERRMSG(info, "link settings update failed");
+	return ret;
+}
+
+static int ethnl_set_legacy_settings(struct genl_info *info,
+				     struct net_device *dev,
+				     struct ethtool_cmd *cmd)
+{
+	int ret;
+
+	if (!dev->ethtool_ops->set_settings) {
+		/* we already tried ->set_link_ksettings */
+		ETHNL_SET_ERRMSG(info, "link settings update unsupported");
+		return -EOPNOTSUPP;
+	}
+	ret = dev->ethtool_ops->set_settings(dev, cmd);
+	if (ret < 0)
+		ETHNL_SET_ERRMSG(info, "link settings update failed");
+
+	return ret;
+}
+
+static int ethnl_set_wol(struct genl_info *info, struct net_device *dev,
+			 struct ethtool_wolinfo *wolinfo)
+{
+	int ret = dev->ethtool_ops->set_wol(dev, wolinfo);
+
+	if (ret < 0)
+		ETHNL_SET_ERRMSG(info, "wol info update failed");
+	return ret;
+}
+
+/* Set advertised link modes to all supported modes matching requested speed
+ * and duplex values. Called when autonegotiation is on, speed or duplex is
+ * requested but no link mode change. This is done in userspace with ioctl()
+ * interface, move it into kernel for netlink.
+ * Returns true if advertised modes bitmap was modified.
+ */
+static bool auto_link_modes(unsigned long *supported,
+			    unsigned long *advertising, unsigned int nbits,
+			    struct nlattr *speed_attr,
+			    struct nlattr *duplex_attr)
+{
+	u8 duplex = duplex_attr ? nla_get_u8(duplex_attr) : DUPLEX_UNKNOWN;
+	u32 speed = speed_attr ? nla_get_u32(speed_attr) : SPEED_UNKNOWN;
+	DECLARE_BITMAP(old_adv, nbits);
+	unsigned int i;
+
+	bitmap_copy(old_adv, advertising, nbits);
+
+	for (i = 0; i < nbits; i++) {
+		const struct link_mode_info *info = &link_mode_params[i];
+
+		if (info->speed == SPEED_UNKNOWN)
+			continue;
+		if (test_bit(i, supported) &&
+		    (!speed_attr || info->speed == speed) &&
+		    (!duplex_attr || info->duplex == duplex))
+			set_bit(i, advertising);
+		else
+			clear_bit(i, advertising);
+	}
+
+	return !bitmap_equal(old_adv, advertising, nbits);
+}
+
+/* Update device settings using ->set_link_ksettings() callback */
+static int ethnl_update_ksettings(struct genl_info *info, struct nlattr **tb,
+				  struct net_device *dev)
+{
+	struct ethtool_link_ksettings ksettings = {};
+	struct ethtool_link_settings *lsettings;
+	bool mod = false;
+	int ret;
+
+	ret = ethnl_get_link_ksettings(info, dev, &ksettings);
+	if (ret < 0)
+		return ret;
+	lsettings = &ksettings.base;
+
+	mod = false;
+	if (ethnl_update_u32(&lsettings->speed, tb[ETHA_SETTINGS_SPEED]))
+		mod = true;
+	if (ethnl_update_u8(&lsettings->duplex, tb[ETHA_SETTINGS_DUPLEX]))
+		mod = true;
+	if (ethnl_update_u8(&lsettings->port, tb[ETHA_SETTINGS_PORT]))
+		mod = true;
+	if (ethnl_update_u8(&lsettings->phy_address, tb[ETHA_SETTINGS_PHYADDR]))
+		mod = true;
+	if (ethnl_update_u8(&lsettings->autoneg, tb[ETHA_SETTINGS_AUTONEG]))
+		mod = true;
+	if (ethnl_update_u8(&lsettings->eth_tp_mdix_ctrl,
+			    tb[ETHA_SETTINGS_TP_MDIX_CTRL]))
+		mod = true;
+	if (ethnl_update_bitset(ksettings.link_modes.advertising, NULL,
+				__ETHTOOL_LINK_MODE_MASK_NBITS,
+				tb[ETHA_SETTINGS_LINK_MODES],
+				&ret, link_mode_names, info))
+		mod = true;
+	if (ret < 0)
+		return ret;
+
+	if (!tb[ETHA_SETTINGS_LINK_MODES] && lsettings->autoneg &&
+	    (tb[ETHA_SETTINGS_SPEED] || tb[ETHA_SETTINGS_DUPLEX])) {
+		if (auto_link_modes(ksettings.link_modes.supported,
+				    ksettings.link_modes.advertising,
+				    __ETHTOOL_LINK_MODE_MASK_NBITS,
+				    tb[ETHA_SETTINGS_SPEED],
+				    tb[ETHA_SETTINGS_DUPLEX]))
+			mod = true;
+	}
+
+	if (mod) {
+		ret = ethnl_set_link_ksettings(info, dev, &ksettings);
+		if (ret < 0)
+			return ret;
+	}
+
+	return mod ? 1 : 0;
+}
+
+/* Update legacy settings using ->set_settings() callback. */
+static int ethnl_update_lsettings(struct genl_info *info, struct nlattr **tb,
+				  struct net_device *dev)
+{
+	struct ethtool_cmd cmd = {};
+	const unsigned int nbits = sizeof(cmd.advertising) * BITS_PER_BYTE;
+	DECLARE_BITMAP(advertising, nbits);
+	DECLARE_BITMAP(supported, nbits);
+	bool mod = false;
+	u32 speed;
+	int ret;
+
+	ret = ethnl_get_legacy_settings(info, dev, &cmd);
+	if (ret < 0)
+		return ret;
+	bitmap_from_arr32(supported, &cmd.supported, nbits);
+	bitmap_from_arr32(advertising, &cmd.advertising, nbits);
+
+	mod = false;
+	speed = ethtool_cmd_speed(&cmd);
+	if (ethnl_update_u32(&speed, tb[ETHA_SETTINGS_SPEED])) {
+		ethtool_cmd_speed_set(&cmd, speed);
+		mod = true;
+	}
+	if (ethnl_update_u8(&cmd.duplex, tb[ETHA_SETTINGS_DUPLEX]))
+		mod = true;
+	if (ethnl_update_u8(&cmd.port, tb[ETHA_SETTINGS_PORT]))
+		mod = true;
+	if (ethnl_update_u8(&cmd.phy_address, tb[ETHA_SETTINGS_PHYADDR]))
+		mod = true;
+	if (ethnl_update_u8(&cmd.autoneg, tb[ETHA_SETTINGS_AUTONEG]))
+		mod = true;
+	if (ethnl_update_u8(&cmd.eth_tp_mdix_ctrl,
+			    tb[ETHA_SETTINGS_TP_MDIX_CTRL]))
+		mod = true;
+	if (ethnl_update_bitset(advertising, NULL, nbits,
+				tb[ETHA_SETTINGS_LINK_MODES],
+				&ret, link_mode_names, info)) {
+		bitmap_to_arr32(&cmd.advertising, advertising, nbits);
+		mod = true;
+	}
+	if (ret < 0)
+		return ret;
+
+	if (!tb[ETHA_SETTINGS_LINK_MODES] && cmd.autoneg &&
+	    (tb[ETHA_SETTINGS_SPEED] || tb[ETHA_SETTINGS_DUPLEX])) {
+		if (auto_link_modes(supported, advertising, nbits,
+				    tb[ETHA_SETTINGS_SPEED],
+				    tb[ETHA_SETTINGS_DUPLEX])) {
+			bitmap_to_arr32(&cmd.advertising, advertising, nbits);
+			mod = true;
+		}
+	}
+
+	if (mod) {
+		ret = ethnl_set_legacy_settings(info, dev, &cmd);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+int ethnl_set_settings(struct sk_buff *skb, struct genl_info *info)
+{
+	struct nlattr *tb[ETHA_SETTINGS_MAX + 1];
+	struct ethtool_wolinfo wolinfo = {};
+	struct net_device *dev;
+	u32 req_mask = 0;
+	bool mod;
+	int ret;
+
+	ret = genlmsg_parse(info->nlhdr, &ethtool_genl_family, tb,
+			    ETHA_SETTINGS_MAX, settings_policy, info->extack);
+	if (ret < 0)
+		return ret;
+	dev = ethnl_dev_get(info, tb[ETHA_SETTINGS_DEV]);
+	if (IS_ERR(dev))
+		return PTR_ERR(dev);
+	rtnl_lock();
+
+	/* read only attributes */
+	ret = -EINVAL;
+	if (tb[ETHA_SETTINGS_MDIO_SUPPORT] || tb[ETHA_SETTINGS_TP_MDIX] ||
+	    tb[ETHA_SETTINGS_TRANSCEIVER] || tb[ETHA_SETTINGS_PEER_MODES] ||
+	    tb[ETHA_SETTINGS_LINK]) {
+		ETHNL_SET_ERRMSG(info, "attempt to set a read only attribute");
+		goto out_unlock;
+	}
+
+	if (tb[ETHA_SETTINGS_SPEED] || tb[ETHA_SETTINGS_DUPLEX] ||
+	    tb[ETHA_SETTINGS_PORT] || tb[ETHA_SETTINGS_PHYADDR] ||
+	    tb[ETHA_SETTINGS_AUTONEG] || tb[ETHA_SETTINGS_TP_MDIX_CTRL] ||
+	    tb[ETHA_SETTINGS_LINK_MODES]) {
+		if (dev->ethtool_ops->get_link_ksettings)
+			ret = ethnl_update_ksettings(info, tb, dev);
+		else
+			ret = ethnl_update_lsettings(info, tb, dev);
+		if (ret < 0)
+			goto out_unlock;
+		req_mask |= ETH_SETTINGS_IM_LINKINFO |
+			    ETH_SETTINGS_IM_LINKMODES;
+	}
+	if (tb[ETHA_SETTINGS_WOL_MODES] || tb[ETHA_SETTINGS_SOPASS]) {
+		ret = ethnl_get_wol(info, dev, &wolinfo);
+		if (ret < 0)
+			goto out_unlock;
+
+		mod = false;
+		if (ethnl_update_bitfield32(&wolinfo.wolopts,
+					    tb[ETHA_SETTINGS_WOL_MODES]))
+			mod = true;
+		if (ethnl_update_binary(wolinfo.sopass, SOPASS_MAX,
+					tb[ETHA_SETTINGS_SOPASS]))
+			mod = true;
+		if (mod) {
+			ret = ethnl_set_wol(info, dev, &wolinfo);
+			if (ret < 0)
+				goto out_unlock;
+			req_mask |= ETH_SETTINGS_IM_WOLINFO;
+		}
+	}
+	if (tb[ETHA_SETTINGS_MSGLVL]) {
+		u32 msglvl;
+
+		ret = -EOPNOTSUPP;
+		if (!dev->ethtool_ops->get_msglevel ||
+		    !dev->ethtool_ops->set_msglevel) {
+			ETHNL_SET_ERRMSG(info,
+					 "device does not provide msglvl access");
+			goto out_unlock;
+		}
+		msglvl = dev->ethtool_ops->get_msglevel(dev);
+		if (ethnl_update_bitfield32(&msglvl,
+					    tb[ETHA_SETTINGS_MSGLVL])) {
+			dev->ethtool_ops->set_msglevel(dev, msglvl);
+			req_mask |= ETH_SETTINGS_IM_MSGLEVEL;
+		}
+	}
+	ret = 0;
+
+out_unlock:
+	if (req_mask)
+		ethnl_notify(dev, NULL, ETHNL_CMD_SET_SETTINGS, req_mask);
+	rtnl_unlock();
+	dev_put(dev);
+	return ret;
+}
-- 
2.18.0

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ