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 <mkube...@suse.cz>
---
 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, &ethtool_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

Reply via email to