fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / net / core / ethtool.c
index 9925b81..87dc556 100644 (file)
@@ -11,6 +11,7 @@
 
 #include <linux/module.h>
 #include <linux/types.h>
+#include <linux/capability.h>
 #include <linux/errno.h>
 #include <linux/ethtool.h>
 #include <linux/netdevice.h>
@@ -29,7 +30,7 @@ u32 ethtool_op_get_link(struct net_device *dev)
 
 u32 ethtool_op_get_tx_csum(struct net_device *dev)
 {
-       return (dev->features & NETIF_F_IP_CSUM) != 0;
+       return (dev->features & NETIF_F_ALL_CSUM) != 0;
 }
 
 int ethtool_op_set_tx_csum(struct net_device *dev, u32 data)
@@ -42,6 +43,15 @@ int ethtool_op_set_tx_csum(struct net_device *dev, u32 data)
        return 0;
 }
 
+int ethtool_op_set_tx_hw_csum(struct net_device *dev, u32 data)
+{
+       if (data)
+               dev->features |= NETIF_F_HW_CSUM;
+       else
+               dev->features &= ~NETIF_F_HW_CSUM;
+
+       return 0;
+}
 u32 ethtool_op_get_sg(struct net_device *dev)
 {
        return (dev->features & NETIF_F_SG) != 0;
@@ -72,6 +82,32 @@ int ethtool_op_set_tso(struct net_device *dev, u32 data)
        return 0;
 }
 
+int ethtool_op_get_perm_addr(struct net_device *dev, struct ethtool_perm_addr *addr, u8 *data)
+{
+       unsigned char len = dev->addr_len;
+       if ( addr->size < len )
+               return -ETOOSMALL;
+       
+       addr->size = len;
+       memcpy(data, dev->perm_addr, len);
+       return 0;
+}
+
+u32 ethtool_op_get_ufo(struct net_device *dev)
+{
+       return (dev->features & NETIF_F_UFO) != 0;
+}
+
+int ethtool_op_set_ufo(struct net_device *dev, u32 data)
+{
+       if (data)
+               dev->features |= NETIF_F_UFO;
+       else
+               dev->features &= ~NETIF_F_UFO;
+       return 0;
+}
+
 /* Handlers for each ethtool command */
 
 static int ethtool_get_settings(struct net_device *dev, void __user *useraddr)
@@ -107,7 +143,7 @@ static int ethtool_set_settings(struct net_device *dev, void __user *useraddr)
 static int ethtool_get_drvinfo(struct net_device *dev, void __user *useraddr)
 {
        struct ethtool_drvinfo info;
-       struct ethtool_ops *ops = dev->ethtool_ops;
+       const struct ethtool_ops *ops = dev->ethtool_ops;
 
        if (!ops->get_drvinfo)
                return -EOPNOTSUPP;
@@ -133,7 +169,7 @@ static int ethtool_get_drvinfo(struct net_device *dev, void __user *useraddr)
 static int ethtool_get_regs(struct net_device *dev, char __user *useraddr)
 {
        struct ethtool_regs regs;
-       struct ethtool_ops *ops = dev->ethtool_ops;
+       const struct ethtool_ops *ops = dev->ethtool_ops;
        void *regbuf;
        int reglen, ret;
 
@@ -157,7 +193,7 @@ static int ethtool_get_regs(struct net_device *dev, char __user *useraddr)
        if (copy_to_user(useraddr, &regs, sizeof(regs)))
                goto out;
        useraddr += offsetof(struct ethtool_regs, data);
-       if (copy_to_user(useraddr, regbuf, reglen))
+       if (copy_to_user(useraddr, regbuf, regs.len))
                goto out;
        ret = 0;
 
@@ -246,7 +282,7 @@ static int ethtool_get_link(struct net_device *dev, void __user *useraddr)
 static int ethtool_get_eeprom(struct net_device *dev, void __user *useraddr)
 {
        struct ethtool_eeprom eeprom;
-       struct ethtool_ops *ops = dev->ethtool_ops;
+       const struct ethtool_ops *ops = dev->ethtool_ops;
        u8 *data;
        int ret;
 
@@ -291,7 +327,7 @@ static int ethtool_get_eeprom(struct net_device *dev, void __user *useraddr)
 static int ethtool_set_eeprom(struct net_device *dev, void __user *useraddr)
 {
        struct ethtool_eeprom eeprom;
-       struct ethtool_ops *ops = dev->ethtool_ops;
+       const struct ethtool_ops *ops = dev->ethtool_ops;
        u8 *data;
        int ret;
 
@@ -347,7 +383,7 @@ static int ethtool_set_coalesce(struct net_device *dev, void __user *useraddr)
 {
        struct ethtool_coalesce coalesce;
 
-       if (!dev->ethtool_ops->get_coalesce)
+       if (!dev->ethtool_ops->set_coalesce)
                return -EOPNOTSUPP;
 
        if (copy_from_user(&coalesce, useraddr, sizeof(coalesce)))
@@ -401,7 +437,7 @@ static int ethtool_set_pauseparam(struct net_device *dev, void __user *useraddr)
 {
        struct ethtool_pauseparam pauseparam;
 
-       if (!dev->ethtool_ops->get_pauseparam)
+       if (!dev->ethtool_ops->set_pauseparam)
                return -EOPNOTSUPP;
 
        if (copy_from_user(&pauseparam, useraddr, sizeof(pauseparam)))
@@ -452,9 +488,28 @@ static int ethtool_get_tx_csum(struct net_device *dev, char __user *useraddr)
        return 0;
 }
 
+static int __ethtool_set_sg(struct net_device *dev, u32 data)
+{
+       int err;
+
+       if (!data && dev->ethtool_ops->set_tso) {
+               err = dev->ethtool_ops->set_tso(dev, 0);
+               if (err)
+                       return err;
+       }
+
+       if (!data && dev->ethtool_ops->set_ufo) {
+               err = dev->ethtool_ops->set_ufo(dev, 0);
+               if (err)
+                       return err;
+       }
+       return dev->ethtool_ops->set_sg(dev, data);
+}
+
 static int ethtool_set_tx_csum(struct net_device *dev, char __user *useraddr)
 {
        struct ethtool_value edata;
+       int err;
 
        if (!dev->ethtool_ops->set_tx_csum)
                return -EOPNOTSUPP;
@@ -462,6 +517,12 @@ static int ethtool_set_tx_csum(struct net_device *dev, char __user *useraddr)
        if (copy_from_user(&edata, useraddr, sizeof(edata)))
                return -EFAULT;
 
+       if (!edata.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);
 }
 
@@ -489,7 +550,11 @@ static int ethtool_set_sg(struct net_device *dev, char __user *useraddr)
        if (copy_from_user(&edata, useraddr, sizeof(edata)))
                return -EFAULT;
 
-       return dev->ethtool_ops->set_sg(dev, edata.data);
+       if (edata.data && 
+           !(dev->features & NETIF_F_ALL_CSUM))
+               return -EINVAL;
+
+       return __ethtool_set_sg(dev, edata.data);
 }
 
 static int ethtool_get_tso(struct net_device *dev, char __user *useraddr)
@@ -516,13 +581,66 @@ static int ethtool_set_tso(struct net_device *dev, char __user *useraddr)
        if (copy_from_user(&edata, useraddr, sizeof(edata)))
                return -EFAULT;
 
+       if (edata.data && !(dev->features & NETIF_F_SG))
+               return -EINVAL;
+
        return dev->ethtool_ops->set_tso(dev, edata.data);
 }
 
+static int ethtool_get_ufo(struct net_device *dev, char __user *useraddr)
+{
+       struct ethtool_value edata = { ETHTOOL_GUFO };
+
+       if (!dev->ethtool_ops->get_ufo)
+               return -EOPNOTSUPP;
+       edata.data = dev->ethtool_ops->get_ufo(dev);
+       if (copy_to_user(useraddr, &edata, sizeof(edata)))
+                return -EFAULT;
+       return 0;
+}
+
+static int ethtool_set_ufo(struct net_device *dev, char __user *useraddr)
+{
+       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))
+               return -EINVAL;
+       if (edata.data && !(dev->features & NETIF_F_HW_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_self_test(struct net_device *dev, char __user *useraddr)
 {
        struct ethtool_test test;
-       struct ethtool_ops *ops = dev->ethtool_ops;
+       const struct ethtool_ops *ops = dev->ethtool_ops;
        u64 *data;
        int ret;
 
@@ -555,7 +673,7 @@ 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;
-       struct ethtool_ops *ops = dev->ethtool_ops;
+       const struct ethtool_ops *ops = dev->ethtool_ops;
        u8 *data;
        int ret;
 
@@ -615,7 +733,7 @@ static int ethtool_phys_id(struct net_device *dev, void __user *useraddr)
 static int ethtool_get_stats(struct net_device *dev, void __user *useraddr)
 {
        struct ethtool_stats stats;
-       struct ethtool_ops *ops = dev->ethtool_ops;
+       const struct ethtool_ops *ops = dev->ethtool_ops;
        u64 *data;
        int ret;
 
@@ -645,20 +763,48 @@ static int ethtool_get_stats(struct net_device *dev, void __user *useraddr)
        return ret;
 }
 
+static int ethtool_get_perm_addr(struct net_device *dev, void __user *useraddr)
+{
+       struct ethtool_perm_addr epaddr;
+       u8 *data;
+       int ret;
+
+       if (!dev->ethtool_ops->get_perm_addr)
+               return -EOPNOTSUPP;
+
+       if (copy_from_user(&epaddr,useraddr,sizeof(epaddr)))
+               return -EFAULT;
+
+       data = kmalloc(epaddr.size, GFP_USER);
+       if (!data)
+               return -ENOMEM;
+
+       ret = dev->ethtool_ops->get_perm_addr(dev,&epaddr,data);
+       if (ret)
+               return ret;
+
+       ret = -EFAULT;
+       if (copy_to_user(useraddr, &epaddr, sizeof(epaddr)))
+               goto out;
+       useraddr += sizeof(epaddr);
+       if (copy_to_user(useraddr, data, epaddr.size))
+               goto out;
+       ret = 0;
+
+ out:
+       kfree(data);
+       return ret;
+}
+
 /* The main entry point in this file.  Called from net/core/dev.c */
 
 int dev_ethtool(struct ifreq *ifr)
 {
        struct net_device *dev = __dev_get_by_name(ifr->ifr_name);
-       void __user *useraddr = (void __user *) ifr->ifr_data;
+       void __user *useraddr = ifr->ifr_data;
        u32 ethcmd;
-
-       /*
-        * XXX: This can be pushed down into the ethtool_* handlers that
-        * need it.  Keep existing behaviour for the moment.
-        */
-       if (!capable(CAP_NET_ADMIN))
-               return -EPERM;
+       int rc;
+       unsigned long old_features;
 
        if (!dev || !netif_device_present(dev))
                return -ENODEV;
@@ -669,72 +815,156 @@ int dev_ethtool(struct ifreq *ifr)
        if (copy_from_user(&ethcmd, useraddr, sizeof (ethcmd)))
                return -EFAULT;
 
+       /* Allow some commands to be done by anyone */
+       switch(ethcmd) {
+       case ETHTOOL_GDRVINFO:
+       case ETHTOOL_GMSGLVL:
+       case ETHTOOL_GCOALESCE:
+       case ETHTOOL_GRINGPARAM:
+       case ETHTOOL_GPAUSEPARAM:
+       case ETHTOOL_GRXCSUM:
+       case ETHTOOL_GTXCSUM:
+       case ETHTOOL_GSG:
+       case ETHTOOL_GSTRINGS:
+       case ETHTOOL_GTSO:
+       case ETHTOOL_GPERMADDR:
+       case ETHTOOL_GUFO:
+       case ETHTOOL_GGSO:
+               break;
+       default:
+               if (!capable(CAP_NET_ADMIN))
+                       return -EPERM;
+       }
+
+       if(dev->ethtool_ops->begin)
+               if ((rc = dev->ethtool_ops->begin(dev)) < 0)
+                       return rc;
+
+       old_features = dev->features;
+
        switch (ethcmd) {
        case ETHTOOL_GSET:
-               return ethtool_get_settings(dev, useraddr);
+               rc = ethtool_get_settings(dev, useraddr);
+               break;
        case ETHTOOL_SSET:
-               return ethtool_set_settings(dev, useraddr);
+               rc = ethtool_set_settings(dev, useraddr);
+               break;
        case ETHTOOL_GDRVINFO:
-               return ethtool_get_drvinfo(dev, useraddr);
+               rc = ethtool_get_drvinfo(dev, useraddr);
+               break;
        case ETHTOOL_GREGS:
-               return ethtool_get_regs(dev, useraddr);
+               rc = ethtool_get_regs(dev, useraddr);
+               break;
        case ETHTOOL_GWOL:
-               return ethtool_get_wol(dev, useraddr);
+               rc = ethtool_get_wol(dev, useraddr);
+               break;
        case ETHTOOL_SWOL:
-               return ethtool_set_wol(dev, useraddr);
+               rc = ethtool_set_wol(dev, useraddr);
+               break;
        case ETHTOOL_GMSGLVL:
-               return ethtool_get_msglevel(dev, useraddr);
+               rc = ethtool_get_msglevel(dev, useraddr);
+               break;
        case ETHTOOL_SMSGLVL:
-               return ethtool_set_msglevel(dev, useraddr);
+               rc = ethtool_set_msglevel(dev, useraddr);
+               break;
        case ETHTOOL_NWAY_RST:
-               return ethtool_nway_reset(dev);
+               rc = ethtool_nway_reset(dev);
+               break;
        case ETHTOOL_GLINK:
-               return ethtool_get_link(dev, useraddr);
+               rc = ethtool_get_link(dev, useraddr);
+               break;
        case ETHTOOL_GEEPROM:
-               return ethtool_get_eeprom(dev, useraddr);
+               rc = ethtool_get_eeprom(dev, useraddr);
+               break;
        case ETHTOOL_SEEPROM:
-               return ethtool_set_eeprom(dev, useraddr);
+               rc = ethtool_set_eeprom(dev, useraddr);
+               break;
        case ETHTOOL_GCOALESCE:
-               return ethtool_get_coalesce(dev, useraddr);
+               rc = ethtool_get_coalesce(dev, useraddr);
+               break;
        case ETHTOOL_SCOALESCE:
-               return ethtool_set_coalesce(dev, useraddr);
+               rc = ethtool_set_coalesce(dev, useraddr);
+               break;
        case ETHTOOL_GRINGPARAM:
-               return ethtool_get_ringparam(dev, useraddr);
+               rc = ethtool_get_ringparam(dev, useraddr);
+               break;
        case ETHTOOL_SRINGPARAM:
-               return ethtool_set_ringparam(dev, useraddr);
+               rc = ethtool_set_ringparam(dev, useraddr);
+               break;
        case ETHTOOL_GPAUSEPARAM:
-               return ethtool_get_pauseparam(dev, useraddr);
+               rc = ethtool_get_pauseparam(dev, useraddr);
+               break;
        case ETHTOOL_SPAUSEPARAM:
-               return ethtool_set_pauseparam(dev, useraddr);
+               rc = ethtool_set_pauseparam(dev, useraddr);
+               break;
        case ETHTOOL_GRXCSUM:
-               return ethtool_get_rx_csum(dev, useraddr);
+               rc = ethtool_get_rx_csum(dev, useraddr);
+               break;
        case ETHTOOL_SRXCSUM:
-               return ethtool_set_rx_csum(dev, useraddr);
+               rc = ethtool_set_rx_csum(dev, useraddr);
+               break;
        case ETHTOOL_GTXCSUM:
-               return ethtool_get_tx_csum(dev, useraddr);
+               rc = ethtool_get_tx_csum(dev, useraddr);
+               break;
        case ETHTOOL_STXCSUM:
-               return ethtool_set_tx_csum(dev, useraddr);
+               rc = ethtool_set_tx_csum(dev, useraddr);
+               break;
        case ETHTOOL_GSG:
-               return ethtool_get_sg(dev, useraddr);
+               rc = ethtool_get_sg(dev, useraddr);
+               break;
        case ETHTOOL_SSG:
-               return ethtool_set_sg(dev, useraddr);
+               rc = ethtool_set_sg(dev, useraddr);
+               break;
        case ETHTOOL_GTSO:
-               return ethtool_get_tso(dev, useraddr);
+               rc = ethtool_get_tso(dev, useraddr);
+               break;
        case ETHTOOL_STSO:
-               return ethtool_set_tso(dev, useraddr);
+               rc = ethtool_set_tso(dev, useraddr);
+               break;
        case ETHTOOL_TEST:
-               return ethtool_self_test(dev, useraddr);
+               rc = ethtool_self_test(dev, useraddr);
+               break;
        case ETHTOOL_GSTRINGS:
-               return ethtool_get_strings(dev, useraddr);
+               rc = ethtool_get_strings(dev, useraddr);
+               break;
        case ETHTOOL_PHYS_ID:
-               return ethtool_phys_id(dev, useraddr);
+               rc = ethtool_phys_id(dev, useraddr);
+               break;
        case ETHTOOL_GSTATS:
-               return ethtool_get_stats(dev, useraddr);
+               rc = ethtool_get_stats(dev, useraddr);
+               break;
+       case ETHTOOL_GPERMADDR:
+               rc = ethtool_get_perm_addr(dev, useraddr);
+               break;
+       case ETHTOOL_GUFO:
+               rc = ethtool_get_ufo(dev, useraddr);
+               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;
        default:
-               return -EOPNOTSUPP;
+               rc =  -EOPNOTSUPP;
        }
+       
+       if(dev->ethtool_ops->complete)
+               dev->ethtool_ops->complete(dev);
+
+       if (old_features != dev->features)
+               netdev_features_change(dev);
+
+       return rc;
 
  ioctl:
+       /* Keep existing behaviour for the moment.       */
+       if (!capable(CAP_NET_ADMIN))
+               return -EPERM;
+
        if (dev->do_ioctl)
                return dev->do_ioctl(dev, ifr, SIOCETHTOOL);
        return -EOPNOTSUPP;
@@ -742,9 +972,13 @@ int dev_ethtool(struct ifreq *ifr)
 
 EXPORT_SYMBOL(dev_ethtool);
 EXPORT_SYMBOL(ethtool_op_get_link);
+EXPORT_SYMBOL_GPL(ethtool_op_get_perm_addr);
 EXPORT_SYMBOL(ethtool_op_get_sg);
 EXPORT_SYMBOL(ethtool_op_get_tso);
 EXPORT_SYMBOL(ethtool_op_get_tx_csum);
 EXPORT_SYMBOL(ethtool_op_set_sg);
 EXPORT_SYMBOL(ethtool_op_set_tso);
 EXPORT_SYMBOL(ethtool_op_set_tx_csum);
+EXPORT_SYMBOL(ethtool_op_set_tx_hw_csum);
+EXPORT_SYMBOL(ethtool_op_set_ufo);
+EXPORT_SYMBOL(ethtool_op_get_ufo);