]> Pileus Git - ~andy/linux/blobdiff - net/core/ethtool.c
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid
[~andy/linux] / net / core / ethtool.c
index 74ead9eca126cd99121f3bb581e5bb7359c0406e..84e7304532e69f7ba1cc32fc47a4bdf8b7f5fc87 100644 (file)
@@ -21,6 +21,8 @@
 #include <linux/uaccess.h>
 #include <linux/vmalloc.h>
 #include <linux/slab.h>
+#include <linux/rtnetlink.h>
+#include <linux/sched.h>
 
 /*
  * Some useful ethtool_ops methods that're device independent.
@@ -317,7 +319,7 @@ static int ethtool_set_features(struct net_device *dev, void __user *useraddr)
 
        dev->wanted_features &= ~features[0].valid;
        dev->wanted_features |= features[0].valid & features[0].requested;
-       netdev_update_features(dev);
+       __netdev_update_features(dev);
 
        if ((dev->wanted_features ^ dev->features) & features[0].valid)
                ret |= ETHTOOL_F_WISH;
@@ -330,7 +332,7 @@ static const char netdev_features_strings[ETHTOOL_DEV_FEATURE_WORDS * 32][ETH_GS
        /* 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_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",
@@ -359,8 +361,8 @@ static const char netdev_features_strings[ETHTOOL_DEV_FEATURE_WORDS * 32][ETH_GS
        /* NETIF_F_NTUPLE */          "rx-ntuple-filter",
        /* NETIF_F_RXHASH */          "rx-hashing",
        /* NETIF_F_RXCSUM */          "rx-checksum",
-       "",
-       "",
+       /* NETIF_F_NOCACHE_COPY */    "tx-nocache-copy",
+       /* NETIF_F_LOOPBACK */        "loopback",
 };
 
 static int __ethtool_get_sset_count(struct net_device *dev, int sset)
@@ -499,7 +501,7 @@ static int ethtool_set_one_feature(struct net_device *dev,
                else
                        dev->wanted_features &= ~mask;
 
-               netdev_update_features(dev);
+               __netdev_update_features(dev);
                return 0;
        }
 
@@ -544,14 +546,14 @@ int __ethtool_set_flags(struct net_device *dev, u32 data)
        }
 
        /* allow changing only bits set in hw_features */
-       changed = (data ^ dev->wanted_features) & flags_dup_features;
+       changed = (data ^ dev->features) & flags_dup_features;
        if (changed & ~dev->hw_features)
                return (changed & dev->hw_features) ? -EINVAL : -EOPNOTSUPP;
 
        dev->wanted_features =
-               (dev->wanted_features & ~changed) | data;
+               (dev->wanted_features & ~changed) | (data & dev->hw_features);
 
-       netdev_update_features(dev);
+       __netdev_update_features(dev);
 
        return 0;
 }
@@ -908,6 +910,9 @@ static noinline_for_stack int ethtool_set_rx_ntuple(struct net_device *dev,
        struct ethtool_rx_ntuple_flow_spec_container *fsc = NULL;
        int ret;
 
+       if (!ops->set_rx_ntuple)
+               return -EOPNOTSUPP;
+
        if (!(dev->features & NETIF_F_NTUPLE))
                return -EINVAL;
 
@@ -1441,6 +1446,35 @@ static int ethtool_set_ringparam(struct net_device *dev, void __user *useraddr)
        return dev->ethtool_ops->set_ringparam(dev, &ringparam);
 }
 
+static noinline_for_stack int ethtool_get_channels(struct net_device *dev,
+                                                  void __user *useraddr)
+{
+       struct ethtool_channels channels = { .cmd = ETHTOOL_GCHANNELS };
+
+       if (!dev->ethtool_ops->get_channels)
+               return -EOPNOTSUPP;
+
+       dev->ethtool_ops->get_channels(dev, &channels);
+
+       if (copy_to_user(useraddr, &channels, sizeof(channels)))
+               return -EFAULT;
+       return 0;
+}
+
+static noinline_for_stack int ethtool_set_channels(struct net_device *dev,
+                                                  void __user *useraddr)
+{
+       struct ethtool_channels channels;
+
+       if (!dev->ethtool_ops->set_channels)
+               return -EOPNOTSUPP;
+
+       if (copy_from_user(&channels, useraddr, sizeof(channels)))
+               return -EFAULT;
+
+       return dev->ethtool_ops->set_channels(dev, &channels);
+}
+
 static int ethtool_get_pauseparam(struct net_device *dev, void __user *useraddr)
 {
        struct ethtool_pauseparam pauseparam = { ETHTOOL_GPAUSEPARAM };
@@ -1618,14 +1652,60 @@ out:
 static int ethtool_phys_id(struct net_device *dev, void __user *useraddr)
 {
        struct ethtool_value id;
+       static bool busy;
+       int rc;
 
-       if (!dev->ethtool_ops->phys_id)
+       if (!dev->ethtool_ops->set_phys_id)
                return -EOPNOTSUPP;
 
+       if (busy)
+               return -EBUSY;
+
        if (copy_from_user(&id, useraddr, sizeof(id)))
                return -EFAULT;
 
-       return dev->ethtool_ops->phys_id(dev, id.data);
+       rc = dev->ethtool_ops->set_phys_id(dev, ETHTOOL_ID_ACTIVE);
+       if (rc < 0)
+               return rc;
+
+       /* Drop the RTNL lock while waiting, but prevent reentry or
+        * removal of the device.
+        */
+       busy = true;
+       dev_hold(dev);
+       rtnl_unlock();
+
+       if (rc == 0) {
+               /* Driver will handle this itself */
+               schedule_timeout_interruptible(
+                       id.data ? (id.data * HZ) : MAX_SCHEDULE_TIMEOUT);
+       } else {
+               /* Driver expects to be called at twice the frequency in rc */
+               int n = rc * 2, i, interval = HZ / n;
+
+               /* Count down seconds */
+               do {
+                       /* Count down iterations per second */
+                       i = n;
+                       do {
+                               rtnl_lock();
+                               rc = dev->ethtool_ops->set_phys_id(dev,
+                                   (i & 1) ? ETHTOOL_ID_OFF : ETHTOOL_ID_ON);
+                               rtnl_unlock();
+                               if (rc)
+                                       break;
+                               schedule_timeout_interruptible(interval);
+                       } while (!signal_pending(current) && --i != 0);
+               } while (!signal_pending(current) &&
+                        (id.data == 0 || --id.data != 0));
+       }
+
+       rtnl_lock();
+       dev_put(dev);
+       busy = false;
+
+       (void)dev->ethtool_ops->set_phys_id(dev, ETHTOOL_ID_INACTIVE);
+       return rc;
 }
 
 static int ethtool_get_stats(struct net_device *dev, void __user *useraddr)
@@ -1743,6 +1823,87 @@ static noinline_for_stack int ethtool_flash_device(struct net_device *dev,
        return dev->ethtool_ops->flash_device(dev, &efl);
 }
 
+static int ethtool_set_dump(struct net_device *dev,
+                       void __user *useraddr)
+{
+       struct ethtool_dump dump;
+
+       if (!dev->ethtool_ops->set_dump)
+               return -EOPNOTSUPP;
+
+       if (copy_from_user(&dump, useraddr, sizeof(dump)))
+               return -EFAULT;
+
+       return dev->ethtool_ops->set_dump(dev, &dump);
+}
+
+static int ethtool_get_dump_flag(struct net_device *dev,
+                               void __user *useraddr)
+{
+       int ret;
+       struct ethtool_dump dump;
+       const struct ethtool_ops *ops = dev->ethtool_ops;
+
+       if (!dev->ethtool_ops->get_dump_flag)
+               return -EOPNOTSUPP;
+
+       if (copy_from_user(&dump, useraddr, sizeof(dump)))
+               return -EFAULT;
+
+       ret = ops->get_dump_flag(dev, &dump);
+       if (ret)
+               return ret;
+
+       if (copy_to_user(useraddr, &dump, sizeof(dump)))
+               return -EFAULT;
+       return 0;
+}
+
+static int ethtool_get_dump_data(struct net_device *dev,
+                               void __user *useraddr)
+{
+       int ret;
+       __u32 len;
+       struct ethtool_dump dump, tmp;
+       const struct ethtool_ops *ops = dev->ethtool_ops;
+       void *data = NULL;
+
+       if (!dev->ethtool_ops->get_dump_data ||
+               !dev->ethtool_ops->get_dump_flag)
+               return -EOPNOTSUPP;
+
+       if (copy_from_user(&dump, useraddr, sizeof(dump)))
+               return -EFAULT;
+
+       memset(&tmp, 0, sizeof(tmp));
+       tmp.cmd = ETHTOOL_GET_DUMP_FLAG;
+       ret = ops->get_dump_flag(dev, &tmp);
+       if (ret)
+               return ret;
+
+       len = (tmp.len > dump.len) ? dump.len : tmp.len;
+       if (!len)
+               return -EFAULT;
+
+       data = vzalloc(tmp.len);
+       if (!data)
+               return -ENOMEM;
+       ret = ops->get_dump_data(dev, &dump, data);
+       if (ret)
+               goto out;
+
+       if (copy_to_user(useraddr, &dump, sizeof(dump))) {
+               ret = -EFAULT;
+               goto out;
+       }
+       useraddr += offsetof(struct ethtool_dump, data);
+       if (copy_to_user(useraddr, data, len))
+               ret = -EFAULT;
+out:
+       vfree(data);
+       return ret;
+}
+
 /* The main entry point in this file.  Called from net/core/dev.c */
 
 int dev_ethtool(struct net *net, struct ifreq *ifr)
@@ -1953,6 +2114,21 @@ int dev_ethtool(struct net *net, struct ifreq *ifr)
        case ETHTOOL_SGRO:
                rc = ethtool_set_one_feature(dev, useraddr, ethcmd);
                break;
+       case ETHTOOL_GCHANNELS:
+               rc = ethtool_get_channels(dev, useraddr);
+               break;
+       case ETHTOOL_SCHANNELS:
+               rc = ethtool_set_channels(dev, useraddr);
+               break;
+       case ETHTOOL_SET_DUMP:
+               rc = ethtool_set_dump(dev, useraddr);
+               break;
+       case ETHTOOL_GET_DUMP_FLAG:
+               rc = ethtool_get_dump_flag(dev, useraddr);
+               break;
+       case ETHTOOL_GET_DUMP_DATA:
+               rc = ethtool_get_dump_data(dev, useraddr);
+               break;
        default:
                rc = -EOPNOTSUPP;
        }