Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753313AbdLKNyc (ORCPT ); Mon, 11 Dec 2017 08:54:32 -0500 Received: from mx2.suse.de ([195.135.220.15]:60285 "EHLO mx2.suse.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753052AbdLKNyX (ORCPT ); Mon, 11 Dec 2017 08:54:23 -0500 Message-Id: <12192c66beef43b40f50714b99251bb405bd0cbe.1513000306.git.mkubecek@suse.cz> In-Reply-To: References: From: Michal Kubecek Subject: [RFC PATCH 7/9] ethtool: implement SET_SETTINGS message To: netdev@vger.kernel.org Cc: linux-kernel@vger.kernel.org Date: Mon, 11 Dec 2017 14:54:21 +0100 (CET) Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 20622 Lines: 685 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 --- Documentation/networking/ethtool-netlink.txt | 42 +- net/core/ethtool_netlink.c | 547 +++++++++++++++++++++++++++ 2 files changed, 584 insertions(+), 5 deletions(-) diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt index 7aabc87c9f09..187fab33f721 100644 --- a/Documentation/networking/ethtool-netlink.txt +++ b/Documentation/networking/ethtool-netlink.txt @@ -110,7 +110,7 @@ List of message types ETHTOOL_CMD_GET_DRVINFO ETHTOOL_CMD_SET_DRVINFO response only ETHTOOL_CMD_GET_SETTINGS - ETHTOOL_CMD_SET_SETTINGS response only (for now) + ETHTOOL_CMD_SET_SETTINGS All constants use ETHTOOL_CMD_ prefix followed by "GET", "SET" or "ACT" to indicate the type. @@ -199,6 +199,38 @@ is only provided by kernel in response to privileged (netns CAP_NET_ADMIN) requests. +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_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 ------------------- @@ -209,13 +241,13 @@ have their netlink replacement yet. ioctl command netlink command --------------------------------------------------------------------- ETHTOOL_GSET ETHTOOL_CMD_GET_SETTINGS -ETHTOOL_SSET n/a +ETHTOOL_SSET ETHTOOL_CMD_SET_SETTINGS ETHTOOL_GDRVINFO ETHTOOL_CMD_GET_DRVINFO ETHTOOL_GREGS n/a ETHTOOL_GWOL ETHTOOL_CMD_GET_SETTINGS -ETHTOOL_SWOL n/a +ETHTOOL_SWOL ETHTOOL_CMD_SET_SETTINGS ETHTOOL_GMSGLVL ETHTOOL_CMD_GET_SETTINGS -ETHTOOL_SMSGLVL n/a +ETHTOOL_SMSGLVL ETHTOOL_CMD_SET_SETTINGS ETHTOOL_NWAY_RST n/a ETHTOOL_GLINK ETHTOOL_CMD_GET_SETTINGS ETHTOOL_GEEPROM n/a @@ -283,7 +315,7 @@ ETHTOOL_STUNABLE n/a ETHTOOL_GPHYSTATS n/a ETHTOOL_PERQUEUE n/a ETHTOOL_GLINKSETTINGS ETHTOOL_CMD_GET_SETTINGS -ETHTOOL_SLINKSETTINGS n/a +ETHTOOL_SLINKSETTINGS ETHTOOL_CMD_SET_SETTINGS ETHTOOL_PHY_GTUNABLE n/a ETHTOOL_PHY_STUNABLE n/a ETHTOOL_GFECPARAM n/a diff --git a/net/core/ethtool_netlink.c b/net/core/ethtool_netlink.c index 0c2bed7850bc..4b14a02be12a 100644 --- a/net/core/ethtool_netlink.c +++ b/net/core/ethtool_netlink.c @@ -67,6 +67,222 @@ static const char *const link_mode_names[] = { [ETHTOOL_LINK_MODE_FEC_BASER_BIT] = "BASER", }; +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, + }, +}; + /* misc helper functions */ static int ethnl_str_size(const char *s) @@ -662,6 +878,34 @@ static int ethnl_get_drvinfo(struct sk_buff *skb, struct genl_info *info) /* GET_SETTINGS */ +/* 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_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 @@ -727,6 +971,24 @@ static int ethnl_get_link_ksettings(struct genl_info *info, 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 */ + GENL_SET_ERR_MSG(info, "link settings retrieval unsupported"); + return -EOPNOTSUPP; + } + ret = dev->ethtool_ops->get_settings(dev, cmd); + if (ret < 0) + GENL_SET_ERR_MSG(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) { @@ -737,6 +999,79 @@ static int ethnl_get_wol(struct genl_info *info, struct net_device *dev, return ret; } +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) + GENL_SET_ERR_MSG(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 */ + GENL_SET_ERR_MSG(info, "link settings update unsupported"); + return -EOPNOTSUPP; + } + ret = dev->ethtool_ops->set_settings(dev, cmd); + if (ret < 0) + GENL_SET_ERR_MSG(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) + GENL_SET_ERR_MSG(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); +} + static int ethnl_get_settings(struct sk_buff *skb, struct genl_info *info) { struct ethtool_link_ksettings ksettings = {}; @@ -897,6 +1232,212 @@ static int ethnl_get_settings(struct sk_buff *skb, struct genl_info *info) return ret; } +/* 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; + + rtnl_lock(); + ret = ethnl_get_link_ksettings(info, dev, &ksettings); + if (ret < 0) + goto out_unlock; + 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, + __ETHTOOL_LINK_MODE_MASK_NBITS, + tb[ETHA_SETTINGS_LINK_MODES], + &ret, link_mode_names, info)) + mod = true; + if (ret < 0) + goto out_unlock; + + 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) + goto out_unlock; + } + + ret = 0; +out_unlock: + rtnl_unlock(); + return ret; +} + +/* 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 = {}; + DECLARE_BITMAP(advertising, sizeof(u32)); + DECLARE_BITMAP(supported, sizeof(u32)); + bool mod = false; + u32 speed; + int ret; + + rtnl_lock(); + ret = ethnl_get_legacy_settings(info, dev, &cmd); + if (ret < 0) + goto out_unlock; + bitmap_from_u32array(supported, sizeof(u32), &cmd.supported, 1); + bitmap_from_u32array(advertising, sizeof(u32), &cmd.advertising, 1); + + 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, sizeof(cmd.advertising), + tb[ETHA_SETTINGS_LINK_MODES], + &ret, link_mode_names, info)) { + bitmap_to_u32array(&cmd.advertising, 1, advertising, + sizeof(cmd.advertising)); + mod = true; + } + if (ret < 0) + goto out_unlock; + + if (!tb[ETHA_SETTINGS_LINK_MODES] && cmd.autoneg && + (tb[ETHA_SETTINGS_SPEED] || tb[ETHA_SETTINGS_DUPLEX])) { + if (auto_link_modes(supported, advertising, + sizeof(cmd.advertising), + tb[ETHA_SETTINGS_SPEED], + tb[ETHA_SETTINGS_DUPLEX])) { + bitmap_to_u32array(&cmd.advertising, 1, advertising, + sizeof(cmd.advertising)); + mod = true; + } + } + + if (mod) { + ret = ethnl_set_legacy_settings(info, dev, &cmd); + if (ret < 0) + goto out_unlock; + } + + ret = 0; +out_unlock: + rtnl_unlock(); + return ret; +} + +static int ethnl_set_settings(struct sk_buff *skb, struct genl_info *info) +{ + struct nlattr *tb[ETHA_SETTINGS_MAX + 1]; + struct ethnlmsghdr *ehdr; + struct ethtool_wolinfo wolinfo = {}; + struct net_device *dev; + bool mod; + int ret; + + ehdr = info->userhdr; + dev = ethnl_dev_get(info); + if (IS_ERR(dev)) + return PTR_ERR(dev); + + ret = genlmsg_parse(info->nlhdr, ðtool_genl_family, tb, + ETHA_SETTINGS_MAX, settings_policy, info->extack); + if (ret < 0) + goto err_putdev; + + /* 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]) { + GENL_SET_ERR_MSG(info, "attempt to set a read only attribute"); + goto err_putdev; + } + + 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 err_putdev; + } + if (tb[ETHA_SETTINGS_WOL_MODES] || tb[ETHA_SETTINGS_SOPASS]) { + ret = ethnl_get_wol(info, dev, &wolinfo); + if (ret < 0) + goto err_putdev; + + 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 err_putdev; + } + } + if (tb[ETHA_SETTINGS_MSGLVL]) { + u32 msglvl; + + ret = -EOPNOTSUPP; + if (!dev->ethtool_ops->get_msglevel || + !dev->ethtool_ops->set_msglevel) { + GENL_SET_ERR_MSG(info, + "device does not provide msglvl access"); + goto err_putdev; + } + msglvl = dev->ethtool_ops->get_msglevel(dev); + if (ethnl_update_bitfield32(&msglvl, tb[ETHA_SETTINGS_MSGLVL])) + dev->ethtool_ops->set_msglevel(dev, msglvl); + } + + ret = 0; +err_putdev: + dev_put(dev); + return ret; +} + /* genetlink paperwork */ static const struct genl_ops ethtool_genl_ops[] = { @@ -908,6 +1449,12 @@ static const struct genl_ops ethtool_genl_ops[] = { .cmd = ETHTOOL_CMD_GET_SETTINGS, .doit = ethnl_get_settings, }, + { + .cmd = ETHTOOL_CMD_SET_SETTINGS, + .flags = GENL_UNS_ADMIN_PERM, + .policy = settings_policy, + .doit = ethnl_set_settings, + }, }; static struct genl_family ethtool_genl_family = { -- 2.15.1