net: Implement SFEATURES compatibility for not updated drivers
[pandora-kernel.git] / net / core / ethtool.c
index 9577396..c1a71bb 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;
@@ -174,6 +168,76 @@ EXPORT_SYMBOL(ethtool_ntuple_flush);
 
 #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;
+
+       /* mark legacy-changeable features */
+       if (dev->ethtool_ops->set_sg)
+               features[0].available |= NETIF_F_SG;
+       if (dev->ethtool_ops->set_tx_csum)
+               features[0].available |= NETIF_F_ALL_CSUM;
+       if (dev->ethtool_ops->set_tso)
+               features[0].available |= NETIF_F_ALL_TSO;
+       if (dev->ethtool_ops->set_rx_csum)
+               features[0].available |= NETIF_F_RXCSUM;
+       if (dev->ethtool_ops->set_flags)
+               features[0].available |= flags_dup_features;
+}
+
+static int ethtool_set_feature_compat(struct net_device *dev,
+       int (*legacy_set)(struct net_device *, u32),
+       struct ethtool_set_features_block *features, u32 mask)
+{
+       u32 do_set;
+
+       if (!legacy_set)
+               return 0;
+
+       if (!(features[0].valid & mask))
+               return 0;
+
+       features[0].valid &= ~mask;
+
+       do_set = !!(features[0].requested & mask);
+
+       if (legacy_set(dev, do_set) < 0)
+               netdev_info(dev,
+                       "Legacy feature change (%s) failed for 0x%08x\n",
+                       do_set ? "set" : "clear", mask);
+
+       return 1;
+}
+
+static int ethtool_set_features_compat(struct net_device *dev,
+       struct ethtool_set_features_block *features)
+{
+       int compat;
+
+       if (!dev->ethtool_ops)
+               return 0;
+
+       compat  = ethtool_set_feature_compat(dev, dev->ethtool_ops->set_sg,
+               features, NETIF_F_SG);
+       compat |= ethtool_set_feature_compat(dev, dev->ethtool_ops->set_tx_csum,
+               features, NETIF_F_ALL_CSUM);
+       compat |= ethtool_set_feature_compat(dev, dev->ethtool_ops->set_tso,
+               features, NETIF_F_ALL_TSO);
+       compat |= ethtool_set_feature_compat(dev, dev->ethtool_ops->set_rx_csum,
+               features, NETIF_F_RXCSUM);
+       compat |= ethtool_set_feature_compat(dev, dev->ethtool_ops->set_flags,
+               features, flags_dup_features);
+
+       return compat;
+}
+
 static int ethtool_get_features(struct net_device *dev, void __user *useraddr)
 {
        struct ethtool_gfeatures cmd = {
@@ -191,6 +255,8 @@ static int ethtool_get_features(struct net_device *dev, void __user *useraddr)
        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;
@@ -226,6 +292,9 @@ static int ethtool_set_features(struct net_device *dev, void __user *useraddr)
        if (features[0].valid & ~NETIF_F_ETHTOOL_BITS)
                return -EINVAL;
 
+       if (ethtool_set_features_compat(dev, features))
+               ret |= ETHTOOL_F_COMPAT;
+
        if (features[0].valid & ~dev->hw_features) {
                features[0].valid &= dev->hw_features;
                ret |= ETHTOOL_F_UNSUPPORTED;
@@ -274,7 +343,7 @@ static const char netdev_features_strings[ETHTOOL_DEV_FEATURE_WORDS * 32][ETH_GS
        /* NETIF_F_FCOE_MTU */        "fcoe-mtu",
        /* NETIF_F_NTUPLE */          "rx-ntuple-filter",
        /* NETIF_F_RXHASH */          "rx-hashing",
-       "",
+       /* NETIF_F_RXCSUM */          "rx-checksum",
        "",
        "",
 };
@@ -313,6 +382,9 @@ static u32 ethtool_get_feature_mask(u32 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;
@@ -343,6 +415,8 @@ static void *__ethtool_get_one_feature_actor(struct net_device *dev, u32 ethcmd)
        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:
@@ -354,18 +428,33 @@ static void *__ethtool_get_one_feature_actor(struct net_device *dev, u32 ethcmd)
        }
 }
 
+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 & ethtool_get_feature_mask(ethcmd)),
+               .data = !!(dev->features & mask),
        };
-       u32 (*actor)(struct net_device *);
 
-       actor = __ethtool_get_one_feature_actor(dev, ethcmd);
-       if (actor)
-               edata.data = actor(dev);
+       /* 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;
@@ -373,6 +462,7 @@ static int ethtool_get_one_feature(struct net_device *dev,
 }
 
 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);
@@ -386,28 +476,71 @@ static int ethtool_set_one_feature(struct net_device *dev,
        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);
-       case ETHTOOL_SGSO:
-       case ETHTOOL_SGRO:
-               mask = ethtool_get_feature_mask(ethcmd);
-               if (edata.data)
-                       dev->features |= mask;
-               else
-                       dev->features &= ~mask;
-               return 0;
        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 };
@@ -1357,20 +1490,15 @@ static int __ethtool_set_tx_csum(struct net_device *dev, u32 data)
        return dev->ethtool_ops->set_tx_csum(dev, data);
 }
 
-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);
+       return dev->ethtool_ops->set_rx_csum(dev, data);
 }
 
 static int __ethtool_set_tso(struct net_device *dev, u32 data)
@@ -1718,15 +1846,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_TEST:
                rc = ethtool_self_test(dev, useraddr);
                break;
@@ -1749,8 +1868,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,
@@ -1800,6 +1918,7 @@ int dev_ethtool(struct net *net, struct ifreq *ifr)
                rc = ethtool_set_features(dev, useraddr);
                break;
        case ETHTOOL_GTXCSUM:
+       case ETHTOOL_GRXCSUM:
        case ETHTOOL_GSG:
        case ETHTOOL_GTSO:
        case ETHTOOL_GUFO:
@@ -1808,6 +1927,7 @@ int dev_ethtool(struct net *net, struct ifreq *ifr)
                rc = ethtool_get_one_feature(dev, useraddr, ethcmd);
                break;
        case ETHTOOL_STXCSUM:
+       case ETHTOOL_SRXCSUM:
        case ETHTOOL_SSG:
        case ETHTOOL_STSO:
        case ETHTOOL_SUFO: