net: Fix ETHTOOL_GFEATURES compatibility
[pandora-kernel.git] / net / core / ethtool.c
index 5984ee0..69a3edc 100644 (file)
@@ -34,12 +34,6 @@ u32 ethtool_op_get_link(struct net_device *dev)
 }
 EXPORT_SYMBOL(ethtool_op_get_link);
 
-u32 ethtool_op_get_rx_csum(struct net_device *dev)
-{
-       return (dev->features & NETIF_F_ALL_CSUM) != 0;
-}
-EXPORT_SYMBOL(ethtool_op_get_rx_csum);
-
 u32 ethtool_op_get_tx_csum(struct net_device *dev)
 {
        return (dev->features & NETIF_F_ALL_CSUM) != 0;
@@ -55,6 +49,7 @@ int ethtool_op_set_tx_csum(struct net_device *dev, u32 data)
 
        return 0;
 }
+EXPORT_SYMBOL(ethtool_op_set_tx_csum);
 
 int ethtool_op_set_tx_hw_csum(struct net_device *dev, u32 data)
 {
@@ -171,6 +166,320 @@ EXPORT_SYMBOL(ethtool_ntuple_flush);
 
 /* Handlers for each ethtool command */
 
+#define ETHTOOL_DEV_FEATURE_WORDS      1
+
+static void ethtool_get_features_compat(struct net_device *dev,
+       struct ethtool_get_features_block *features)
+{
+       if (!dev->ethtool_ops)
+               return;
+
+       /* getting RX checksum */
+       if (dev->ethtool_ops->get_rx_csum)
+               if (dev->ethtool_ops->get_rx_csum(dev))
+                       features[0].active |= NETIF_F_RXCSUM;
+}
+
+static int ethtool_get_features(struct net_device *dev, void __user *useraddr)
+{
+       struct ethtool_gfeatures cmd = {
+               .cmd = ETHTOOL_GFEATURES,
+               .size = ETHTOOL_DEV_FEATURE_WORDS,
+       };
+       struct ethtool_get_features_block features[ETHTOOL_DEV_FEATURE_WORDS] = {
+               {
+                       .available = dev->hw_features,
+                       .requested = dev->wanted_features,
+                       .active = dev->features,
+                       .never_changed = NETIF_F_NEVER_CHANGE,
+               },
+       };
+       u32 __user *sizeaddr;
+       u32 copy_size;
+
+       ethtool_get_features_compat(dev, features);
+
+       sizeaddr = useraddr + offsetof(struct ethtool_gfeatures, size);
+       if (get_user(copy_size, sizeaddr))
+               return -EFAULT;
+
+       if (copy_size > ETHTOOL_DEV_FEATURE_WORDS)
+               copy_size = ETHTOOL_DEV_FEATURE_WORDS;
+
+       if (copy_to_user(useraddr, &cmd, sizeof(cmd)))
+               return -EFAULT;
+       useraddr += sizeof(cmd);
+       if (copy_to_user(useraddr, features, copy_size * sizeof(*features)))
+               return -EFAULT;
+
+       return 0;
+}
+
+static int ethtool_set_features(struct net_device *dev, void __user *useraddr)
+{
+       struct ethtool_sfeatures cmd;
+       struct ethtool_set_features_block features[ETHTOOL_DEV_FEATURE_WORDS];
+       int ret = 0;
+
+       if (copy_from_user(&cmd, useraddr, sizeof(cmd)))
+               return -EFAULT;
+       useraddr += sizeof(cmd);
+
+       if (cmd.size != ETHTOOL_DEV_FEATURE_WORDS)
+               return -EINVAL;
+
+       if (copy_from_user(features, useraddr, sizeof(features)))
+               return -EFAULT;
+
+       if (features[0].valid & ~NETIF_F_ETHTOOL_BITS)
+               return -EINVAL;
+
+       if (features[0].valid & ~dev->hw_features) {
+               features[0].valid &= dev->hw_features;
+               ret |= ETHTOOL_F_UNSUPPORTED;
+       }
+
+       dev->wanted_features &= ~features[0].valid;
+       dev->wanted_features |= features[0].valid & features[0].requested;
+       netdev_update_features(dev);
+
+       if ((dev->wanted_features ^ dev->features) & features[0].valid)
+               ret |= ETHTOOL_F_WISH;
+
+       return ret;
+}
+
+static const char netdev_features_strings[ETHTOOL_DEV_FEATURE_WORDS * 32][ETH_GSTRING_LEN] = {
+       /* NETIF_F_SG */              "tx-scatter-gather",
+       /* NETIF_F_IP_CSUM */         "tx-checksum-ipv4",
+       /* NETIF_F_NO_CSUM */         "tx-checksum-unneeded",
+       /* NETIF_F_HW_CSUM */         "tx-checksum-ip-generic",
+       /* NETIF_F_IPV6_CSUM */       "tx_checksum-ipv6",
+       /* NETIF_F_HIGHDMA */         "highdma",
+       /* NETIF_F_FRAGLIST */        "tx-scatter-gather-fraglist",
+       /* NETIF_F_HW_VLAN_TX */      "tx-vlan-hw-insert",
+
+       /* NETIF_F_HW_VLAN_RX */      "rx-vlan-hw-parse",
+       /* NETIF_F_HW_VLAN_FILTER */  "rx-vlan-filter",
+       /* NETIF_F_VLAN_CHALLENGED */ "vlan-challenged",
+       /* NETIF_F_GSO */             "tx-generic-segmentation",
+       /* NETIF_F_LLTX */            "tx-lockless",
+       /* NETIF_F_NETNS_LOCAL */     "netns-local",
+       /* NETIF_F_GRO */             "rx-gro",
+       /* NETIF_F_LRO */             "rx-lro",
+
+       /* NETIF_F_TSO */             "tx-tcp-segmentation",
+       /* NETIF_F_UFO */             "tx-udp-fragmentation",
+       /* NETIF_F_GSO_ROBUST */      "tx-gso-robust",
+       /* NETIF_F_TSO_ECN */         "tx-tcp-ecn-segmentation",
+       /* NETIF_F_TSO6 */            "tx-tcp6-segmentation",
+       /* NETIF_F_FSO */             "tx-fcoe-segmentation",
+       "",
+       "",
+
+       /* NETIF_F_FCOE_CRC */        "tx-checksum-fcoe-crc",
+       /* NETIF_F_SCTP_CSUM */       "tx-checksum-sctp",
+       /* NETIF_F_FCOE_MTU */        "fcoe-mtu",
+       /* NETIF_F_NTUPLE */          "rx-ntuple-filter",
+       /* NETIF_F_RXHASH */          "rx-hashing",
+       /* NETIF_F_RXCSUM */          "rx-checksum",
+       "",
+       "",
+};
+
+static int __ethtool_get_sset_count(struct net_device *dev, int sset)
+{
+       const struct ethtool_ops *ops = dev->ethtool_ops;
+
+       if (sset == ETH_SS_FEATURES)
+               return ARRAY_SIZE(netdev_features_strings);
+
+       if (ops && ops->get_sset_count && ops->get_strings)
+               return ops->get_sset_count(dev, sset);
+       else
+               return -EOPNOTSUPP;
+}
+
+static void __ethtool_get_strings(struct net_device *dev,
+       u32 stringset, u8 *data)
+{
+       const struct ethtool_ops *ops = dev->ethtool_ops;
+
+       if (stringset == ETH_SS_FEATURES)
+               memcpy(data, netdev_features_strings,
+                       sizeof(netdev_features_strings));
+       else
+               /* ops->get_strings is valid because checked earlier */
+               ops->get_strings(dev, stringset, data);
+}
+
+static u32 ethtool_get_feature_mask(u32 eth_cmd)
+{
+       /* feature masks of legacy discrete ethtool ops */
+
+       switch (eth_cmd) {
+       case ETHTOOL_GTXCSUM:
+       case ETHTOOL_STXCSUM:
+               return NETIF_F_ALL_CSUM | NETIF_F_SCTP_CSUM;
+       case ETHTOOL_GRXCSUM:
+       case ETHTOOL_SRXCSUM:
+               return NETIF_F_RXCSUM;
+       case ETHTOOL_GSG:
+       case ETHTOOL_SSG:
+               return NETIF_F_SG;
+       case ETHTOOL_GTSO:
+       case ETHTOOL_STSO:
+               return NETIF_F_ALL_TSO;
+       case ETHTOOL_GUFO:
+       case ETHTOOL_SUFO:
+               return NETIF_F_UFO;
+       case ETHTOOL_GGSO:
+       case ETHTOOL_SGSO:
+               return NETIF_F_GSO;
+       case ETHTOOL_GGRO:
+       case ETHTOOL_SGRO:
+               return NETIF_F_GRO;
+       default:
+               BUG();
+       }
+}
+
+static void *__ethtool_get_one_feature_actor(struct net_device *dev, u32 ethcmd)
+{
+       const struct ethtool_ops *ops = dev->ethtool_ops;
+
+       if (!ops)
+               return NULL;
+
+       switch (ethcmd) {
+       case ETHTOOL_GTXCSUM:
+               return ops->get_tx_csum;
+       case ETHTOOL_GRXCSUM:
+               return ops->get_rx_csum;
+       case ETHTOOL_SSG:
+               return ops->get_sg;
+       case ETHTOOL_STSO:
+               return ops->get_tso;
+       case ETHTOOL_SUFO:
+               return ops->get_ufo;
+       default:
+               return NULL;
+       }
+}
+
+static u32 __ethtool_get_rx_csum_oldbug(struct net_device *dev)
+{
+       return !!(dev->features & NETIF_F_ALL_CSUM);
+}
+
+static int ethtool_get_one_feature(struct net_device *dev,
+       char __user *useraddr, u32 ethcmd)
+{
+       u32 mask = ethtool_get_feature_mask(ethcmd);
+       struct ethtool_value edata = {
+               .cmd = ethcmd,
+               .data = !!(dev->features & mask),
+       };
+
+       /* compatibility with discrete get_ ops */
+       if (!(dev->hw_features & mask)) {
+               u32 (*actor)(struct net_device *);
+
+               actor = __ethtool_get_one_feature_actor(dev, ethcmd);
+
+               /* bug compatibility with old get_rx_csum */
+               if (ethcmd == ETHTOOL_GRXCSUM && !actor)
+                       actor = __ethtool_get_rx_csum_oldbug;
+
+               if (actor)
+                       edata.data = actor(dev);
+       }
+
+       if (copy_to_user(useraddr, &edata, sizeof(edata)))
+               return -EFAULT;
+       return 0;
+}
+
+static int __ethtool_set_tx_csum(struct net_device *dev, u32 data);
+static int __ethtool_set_rx_csum(struct net_device *dev, u32 data);
+static int __ethtool_set_sg(struct net_device *dev, u32 data);
+static int __ethtool_set_tso(struct net_device *dev, u32 data);
+static int __ethtool_set_ufo(struct net_device *dev, u32 data);
+
+static int ethtool_set_one_feature(struct net_device *dev,
+       void __user *useraddr, u32 ethcmd)
+{
+       struct ethtool_value edata;
+       u32 mask;
+
+       if (copy_from_user(&edata, useraddr, sizeof(edata)))
+               return -EFAULT;
+
+       mask = ethtool_get_feature_mask(ethcmd);
+       mask &= dev->hw_features;
+       if (mask) {
+               if (edata.data)
+                       dev->wanted_features |= mask;
+               else
+                       dev->wanted_features &= ~mask;
+
+               netdev_update_features(dev);
+               return 0;
+       }
+
+       /* Driver is not converted to ndo_fix_features or does not
+        * support changing this offload. In the latter case it won't
+        * have corresponding ethtool_ops field set.
+        *
+        * Following part is to be removed after all drivers advertise
+        * their changeable features in netdev->hw_features and stop
+        * using discrete offload setting ops.
+        */
+
+       switch (ethcmd) {
+       case ETHTOOL_STXCSUM:
+               return __ethtool_set_tx_csum(dev, edata.data);
+       case ETHTOOL_SRXCSUM:
+               return __ethtool_set_rx_csum(dev, edata.data);
+       case ETHTOOL_SSG:
+               return __ethtool_set_sg(dev, edata.data);
+       case ETHTOOL_STSO:
+               return __ethtool_set_tso(dev, edata.data);
+       case ETHTOOL_SUFO:
+               return __ethtool_set_ufo(dev, edata.data);
+       default:
+               return -EOPNOTSUPP;
+       }
+}
+
+static int __ethtool_set_flags(struct net_device *dev, u32 data)
+{
+       u32 changed;
+
+       if (data & ~flags_dup_features)
+               return -EINVAL;
+
+       /* legacy set_flags() op */
+       if (dev->ethtool_ops->set_flags) {
+               if (unlikely(dev->hw_features & flags_dup_features))
+                       netdev_warn(dev,
+                               "driver BUG: mixed hw_features and set_flags()\n");
+               return dev->ethtool_ops->set_flags(dev, data);
+       }
+
+       /* allow changing only bits set in hw_features */
+       changed = (data ^ dev->wanted_features) & flags_dup_features;
+       if (changed & ~dev->hw_features)
+               return (changed & dev->hw_features) ? -EINVAL : -EOPNOTSUPP;
+
+       dev->wanted_features =
+               (dev->wanted_features & ~changed) | data;
+
+       netdev_update_features(dev);
+
+       return 0;
+}
+
 static int ethtool_get_settings(struct net_device *dev, void __user *useraddr)
 {
        struct ethtool_cmd cmd = { .cmd = ETHTOOL_GSET };
@@ -251,14 +560,10 @@ static noinline_for_stack int ethtool_get_sset_info(struct net_device *dev,
                                                    void __user *useraddr)
 {
        struct ethtool_sset_info info;
-       const struct ethtool_ops *ops = dev->ethtool_ops;
        u64 sset_mask;
        int i, idx = 0, n_bits = 0, ret, rc;
        u32 *info_buf = NULL;
 
-       if (!ops->get_sset_count)
-               return -EOPNOTSUPP;
-
        if (copy_from_user(&info, useraddr, sizeof(info)))
                return -EFAULT;
 
@@ -285,7 +590,7 @@ static noinline_for_stack int ethtool_get_sset_info(struct net_device *dev,
                if (!(sset_mask & (1ULL << i)))
                        continue;
 
-               rc = ops->get_sset_count(dev, i);
+               rc = __ethtool_get_sset_count(dev, i);
                if (rc >= 0) {
                        info.sset_mask |= (1ULL << i);
                        info_buf[idx++] = rc;
@@ -1091,6 +1396,9 @@ static int __ethtool_set_sg(struct net_device *dev, u32 data)
 {
        int err;
 
+       if (data && !(dev->features & NETIF_F_ALL_CSUM))
+               return -EINVAL;
+
        if (!data && dev->ethtool_ops->set_tso) {
                err = dev->ethtool_ops->set_tso(dev, 0);
                if (err)
@@ -1105,145 +1413,55 @@ static int __ethtool_set_sg(struct net_device *dev, u32 data)
        return dev->ethtool_ops->set_sg(dev, data);
 }
 
-static int ethtool_set_tx_csum(struct net_device *dev, char __user *useraddr)
+static int __ethtool_set_tx_csum(struct net_device *dev, u32 data)
 {
-       struct ethtool_value edata;
        int err;
 
        if (!dev->ethtool_ops->set_tx_csum)
                return -EOPNOTSUPP;
 
-       if (copy_from_user(&edata, useraddr, sizeof(edata)))
-               return -EFAULT;
-
-       if (!edata.data && dev->ethtool_ops->set_sg) {
+       if (!data && dev->ethtool_ops->set_sg) {
                err = __ethtool_set_sg(dev, 0);
                if (err)
                        return err;
        }
 
-       return dev->ethtool_ops->set_tx_csum(dev, edata.data);
+       return dev->ethtool_ops->set_tx_csum(dev, data);
 }
-EXPORT_SYMBOL(ethtool_op_set_tx_csum);
 
-static int ethtool_set_rx_csum(struct net_device *dev, char __user *useraddr)
+static int __ethtool_set_rx_csum(struct net_device *dev, u32 data)
 {
-       struct ethtool_value edata;
-
        if (!dev->ethtool_ops->set_rx_csum)
                return -EOPNOTSUPP;
 
-       if (copy_from_user(&edata, useraddr, sizeof(edata)))
-               return -EFAULT;
-
-       if (!edata.data && dev->ethtool_ops->set_sg)
+       if (!data)
                dev->features &= ~NETIF_F_GRO;
 
-       return dev->ethtool_ops->set_rx_csum(dev, edata.data);
-}
-
-static int ethtool_set_sg(struct net_device *dev, char __user *useraddr)
-{
-       struct ethtool_value edata;
-
-       if (!dev->ethtool_ops->set_sg)
-               return -EOPNOTSUPP;
-
-       if (copy_from_user(&edata, useraddr, sizeof(edata)))
-               return -EFAULT;
-
-       if (edata.data &&
-           !(dev->features & NETIF_F_ALL_CSUM))
-               return -EINVAL;
-
-       return __ethtool_set_sg(dev, edata.data);
+       return dev->ethtool_ops->set_rx_csum(dev, data);
 }
 
-static int ethtool_set_tso(struct net_device *dev, char __user *useraddr)
+static int __ethtool_set_tso(struct net_device *dev, u32 data)
 {
-       struct ethtool_value edata;
-
        if (!dev->ethtool_ops->set_tso)
                return -EOPNOTSUPP;
 
-       if (copy_from_user(&edata, useraddr, sizeof(edata)))
-               return -EFAULT;
-
-       if (edata.data && !(dev->features & NETIF_F_SG))
+       if (data && !(dev->features & NETIF_F_SG))
                return -EINVAL;
 
-       return dev->ethtool_ops->set_tso(dev, edata.data);
+       return dev->ethtool_ops->set_tso(dev, data);
 }
 
-static int ethtool_set_ufo(struct net_device *dev, char __user *useraddr)
+static int __ethtool_set_ufo(struct net_device *dev, u32 data)
 {
-       struct ethtool_value edata;
-
        if (!dev->ethtool_ops->set_ufo)
                return -EOPNOTSUPP;
-       if (copy_from_user(&edata, useraddr, sizeof(edata)))
-               return -EFAULT;
-       if (edata.data && !(dev->features & NETIF_F_SG))
+       if (data && !(dev->features & NETIF_F_SG))
                return -EINVAL;
-       if (edata.data && !((dev->features & NETIF_F_GEN_CSUM) ||
+       if (data && !((dev->features & NETIF_F_GEN_CSUM) ||
                (dev->features & (NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM))
                        == (NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM)))
                return -EINVAL;
-       return dev->ethtool_ops->set_ufo(dev, edata.data);
-}
-
-static int ethtool_get_gso(struct net_device *dev, char __user *useraddr)
-{
-       struct ethtool_value edata = { ETHTOOL_GGSO };
-
-       edata.data = dev->features & NETIF_F_GSO;
-       if (copy_to_user(useraddr, &edata, sizeof(edata)))
-               return -EFAULT;
-       return 0;
-}
-
-static int ethtool_set_gso(struct net_device *dev, char __user *useraddr)
-{
-       struct ethtool_value edata;
-
-       if (copy_from_user(&edata, useraddr, sizeof(edata)))
-               return -EFAULT;
-       if (edata.data)
-               dev->features |= NETIF_F_GSO;
-       else
-               dev->features &= ~NETIF_F_GSO;
-       return 0;
-}
-
-static int ethtool_get_gro(struct net_device *dev, char __user *useraddr)
-{
-       struct ethtool_value edata = { ETHTOOL_GGRO };
-
-       edata.data = dev->features & NETIF_F_GRO;
-       if (copy_to_user(useraddr, &edata, sizeof(edata)))
-               return -EFAULT;
-       return 0;
-}
-
-static int ethtool_set_gro(struct net_device *dev, char __user *useraddr)
-{
-       struct ethtool_value edata;
-
-       if (copy_from_user(&edata, useraddr, sizeof(edata)))
-               return -EFAULT;
-
-       if (edata.data) {
-               u32 rxcsum = dev->ethtool_ops->get_rx_csum ?
-                               dev->ethtool_ops->get_rx_csum(dev) :
-                               ethtool_op_get_rx_csum(dev);
-
-               if (!rxcsum)
-                       return -EINVAL;
-               dev->features |= NETIF_F_GRO;
-       } else
-               dev->features &= ~NETIF_F_GRO;
-
-       return 0;
+       return dev->ethtool_ops->set_ufo(dev, data);
 }
 
 static int ethtool_self_test(struct net_device *dev, char __user *useraddr)
@@ -1287,17 +1505,13 @@ static int ethtool_self_test(struct net_device *dev, char __user *useraddr)
 static int ethtool_get_strings(struct net_device *dev, void __user *useraddr)
 {
        struct ethtool_gstrings gstrings;
-       const struct ethtool_ops *ops = dev->ethtool_ops;
        u8 *data;
        int ret;
 
-       if (!ops->get_strings || !ops->get_sset_count)
-               return -EOPNOTSUPP;
-
        if (copy_from_user(&gstrings, useraddr, sizeof(gstrings)))
                return -EFAULT;
 
-       ret = ops->get_sset_count(dev, gstrings.string_set);
+       ret = __ethtool_get_sset_count(dev, gstrings.string_set);
        if (ret < 0)
                return ret;
 
@@ -1307,7 +1521,7 @@ static int ethtool_get_strings(struct net_device *dev, void __user *useraddr)
        if (!data)
                return -ENOMEM;
 
-       ops->get_strings(dev, gstrings.string_set, data);
+       __ethtool_get_strings(dev, gstrings.string_set, data);
 
        ret = -EFAULT;
        if (copy_to_user(useraddr, &gstrings, sizeof(gstrings)))
@@ -1317,7 +1531,7 @@ static int ethtool_get_strings(struct net_device *dev, void __user *useraddr)
                goto out;
        ret = 0;
 
- out:
+out:
        kfree(data);
        return ret;
 }
@@ -1500,6 +1714,7 @@ int dev_ethtool(struct net *net, struct ifreq *ifr)
        case ETHTOOL_GRXCLSRLCNT:
        case ETHTOOL_GRXCLSRULE:
        case ETHTOOL_GRXCLSRLALL:
+       case ETHTOOL_GFEATURES:
                break;
        default:
                if (!capable(CAP_NET_ADMIN))
@@ -1570,42 +1785,6 @@ int dev_ethtool(struct net *net, struct ifreq *ifr)
        case ETHTOOL_SPAUSEPARAM:
                rc = ethtool_set_pauseparam(dev, useraddr);
                break;
-       case ETHTOOL_GRXCSUM:
-               rc = ethtool_get_value(dev, useraddr, ethcmd,
-                                      (dev->ethtool_ops->get_rx_csum ?
-                                       dev->ethtool_ops->get_rx_csum :
-                                       ethtool_op_get_rx_csum));
-               break;
-       case ETHTOOL_SRXCSUM:
-               rc = ethtool_set_rx_csum(dev, useraddr);
-               break;
-       case ETHTOOL_GTXCSUM:
-               rc = ethtool_get_value(dev, useraddr, ethcmd,
-                                      (dev->ethtool_ops->get_tx_csum ?
-                                       dev->ethtool_ops->get_tx_csum :
-                                       ethtool_op_get_tx_csum));
-               break;
-       case ETHTOOL_STXCSUM:
-               rc = ethtool_set_tx_csum(dev, useraddr);
-               break;
-       case ETHTOOL_GSG:
-               rc = ethtool_get_value(dev, useraddr, ethcmd,
-                                      (dev->ethtool_ops->get_sg ?
-                                       dev->ethtool_ops->get_sg :
-                                       ethtool_op_get_sg));
-               break;
-       case ETHTOOL_SSG:
-               rc = ethtool_set_sg(dev, useraddr);
-               break;
-       case ETHTOOL_GTSO:
-               rc = ethtool_get_value(dev, useraddr, ethcmd,
-                                      (dev->ethtool_ops->get_tso ?
-                                       dev->ethtool_ops->get_tso :
-                                       ethtool_op_get_tso));
-               break;
-       case ETHTOOL_STSO:
-               rc = ethtool_set_tso(dev, useraddr);
-               break;
        case ETHTOOL_TEST:
                rc = ethtool_self_test(dev, useraddr);
                break;
@@ -1621,21 +1800,6 @@ int dev_ethtool(struct net *net, struct ifreq *ifr)
        case ETHTOOL_GPERMADDR:
                rc = ethtool_get_perm_addr(dev, useraddr);
                break;
-       case ETHTOOL_GUFO:
-               rc = ethtool_get_value(dev, useraddr, ethcmd,
-                                      (dev->ethtool_ops->get_ufo ?
-                                       dev->ethtool_ops->get_ufo :
-                                       ethtool_op_get_ufo));
-               break;
-       case ETHTOOL_SUFO:
-               rc = ethtool_set_ufo(dev, useraddr);
-               break;
-       case ETHTOOL_GGSO:
-               rc = ethtool_get_gso(dev, useraddr);
-               break;
-       case ETHTOOL_SGSO:
-               rc = ethtool_set_gso(dev, useraddr);
-               break;
        case ETHTOOL_GFLAGS:
                rc = ethtool_get_value(dev, useraddr, ethcmd,
                                       (dev->ethtool_ops->get_flags ?
@@ -1643,8 +1807,7 @@ int dev_ethtool(struct net *net, struct ifreq *ifr)
                                        ethtool_op_get_flags));
                break;
        case ETHTOOL_SFLAGS:
-               rc = ethtool_set_value(dev, useraddr,
-                                      dev->ethtool_ops->set_flags);
+               rc = ethtool_set_value(dev, useraddr, __ethtool_set_flags);
                break;
        case ETHTOOL_GPFLAGS:
                rc = ethtool_get_value(dev, useraddr, ethcmd,
@@ -1666,12 +1829,6 @@ int dev_ethtool(struct net *net, struct ifreq *ifr)
        case ETHTOOL_SRXCLSRLINS:
                rc = ethtool_set_rxnfc(dev, ethcmd, useraddr);
                break;
-       case ETHTOOL_GGRO:
-               rc = ethtool_get_gro(dev, useraddr);
-               break;
-       case ETHTOOL_SGRO:
-               rc = ethtool_set_gro(dev, useraddr);
-               break;
        case ETHTOOL_FLASHDEV:
                rc = ethtool_flash_device(dev, useraddr);
                break;
@@ -1693,6 +1850,30 @@ int dev_ethtool(struct net *net, struct ifreq *ifr)
        case ETHTOOL_SRXFHINDIR:
                rc = ethtool_set_rxfh_indir(dev, useraddr);
                break;
+       case ETHTOOL_GFEATURES:
+               rc = ethtool_get_features(dev, useraddr);
+               break;
+       case ETHTOOL_SFEATURES:
+               rc = ethtool_set_features(dev, useraddr);
+               break;
+       case ETHTOOL_GTXCSUM:
+       case ETHTOOL_GRXCSUM:
+       case ETHTOOL_GSG:
+       case ETHTOOL_GTSO:
+       case ETHTOOL_GUFO:
+       case ETHTOOL_GGSO:
+       case ETHTOOL_GGRO:
+               rc = ethtool_get_one_feature(dev, useraddr, ethcmd);
+               break;
+       case ETHTOOL_STXCSUM:
+       case ETHTOOL_SRXCSUM:
+       case ETHTOOL_SSG:
+       case ETHTOOL_STSO:
+       case ETHTOOL_SUFO:
+       case ETHTOOL_SGSO:
+       case ETHTOOL_SGRO:
+               rc = ethtool_set_one_feature(dev, useraddr, ethcmd);
+               break;
        default:
                rc = -EOPNOTSUPP;
        }