![Ananda Raju](/assets/img/avatar_default.png)
Attached is kernel patch for UDP Fragmentation Offload (UFO) feature. 1. This patch incorporate the review comments by Jeff Garzik. 2. Renamed USO as UFO (UDP Fragmentation Offload) 3. udp sendfile support with UFO This patches uses scatter-gather feature of skb to generate large UDP datagram. Below is a "how-to" on changes required in network device driver to use the UFO interface. UDP Fragmentation Offload (UFO) Interface: ------------------------------------------- UFO is a feature wherein the Linux kernel network stack will offload the IP fragmentation functionality of large UDP datagram to hardware. This will reduce the overhead of stack in fragmenting the large UDP datagram to MTU sized packets 1) Drivers indicate their capability of UFO using dev->features |= NETIF_F_UFO | NETIF_F_HW_CSUM | NETIF_F_SG NETIF_F_HW_CSUM is required for UFO over ipv6. 2) UFO packet will be submitted for transmission using driver xmit routine. UFO packet will have a non-zero value for "skb_shinfo(skb)->ufo_size" skb_shinfo(skb)->ufo_size will indicate the length of data part in each IP fragment going out of the adapter after IP fragmentation by hardware. skb->data will contain MAC/IP/UDP header and skb_shinfo(skb)->frags[] contains the data payload. The skb->ip_summed will be set to CHECKSUM_HW indicating that hardware has to do checksum calculation. Hardware should compute the UDP checksum of complete datagram and also ip header checksum of each fragmented IP packet. For IPV6 the UFO provides the fragment identification-id in skb_shinfo(skb)->ip6_frag_id. The adapter should use this ID for generating IPv6 fragments. Signed-off-by: Ananda Raju <ananda.raju@neterion.com> Signed-off-by: Rusty Russell <rusty@rustcorp.com.au> (forwarded) Signed-off-by: Arnaldo Carvalho de Melo <acme@mandriva.com>
938 lines
21 KiB
C
938 lines
21 KiB
C
/*
|
|
* net/core/ethtool.c - Ethtool ioctl handler
|
|
* Copyright (c) 2003 Matthew Wilcox <matthew@wil.cx>
|
|
*
|
|
* This file is where we call all the ethtool_ops commands to get
|
|
* the information ethtool needs. We fall back to calling do_ioctl()
|
|
* for drivers which haven't been converted to ethtool_ops yet.
|
|
*
|
|
* It's GPL, stupid.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/types.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/ethtool.h>
|
|
#include <linux/netdevice.h>
|
|
#include <asm/uaccess.h>
|
|
|
|
/*
|
|
* Some useful ethtool_ops methods that're device independent.
|
|
* If we find that all drivers want to do the same thing here,
|
|
* we can turn these into dev_() function calls.
|
|
*/
|
|
|
|
u32 ethtool_op_get_link(struct net_device *dev)
|
|
{
|
|
return netif_carrier_ok(dev) ? 1 : 0;
|
|
}
|
|
|
|
u32 ethtool_op_get_tx_csum(struct net_device *dev)
|
|
{
|
|
return (dev->features & (NETIF_F_IP_CSUM | NETIF_F_HW_CSUM)) != 0;
|
|
}
|
|
|
|
int ethtool_op_set_tx_csum(struct net_device *dev, u32 data)
|
|
{
|
|
if (data)
|
|
dev->features |= NETIF_F_IP_CSUM;
|
|
else
|
|
dev->features &= ~NETIF_F_IP_CSUM;
|
|
|
|
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;
|
|
}
|
|
|
|
int ethtool_op_set_sg(struct net_device *dev, u32 data)
|
|
{
|
|
if (data)
|
|
dev->features |= NETIF_F_SG;
|
|
else
|
|
dev->features &= ~NETIF_F_SG;
|
|
|
|
return 0;
|
|
}
|
|
|
|
u32 ethtool_op_get_tso(struct net_device *dev)
|
|
{
|
|
return (dev->features & NETIF_F_TSO) != 0;
|
|
}
|
|
|
|
int ethtool_op_set_tso(struct net_device *dev, u32 data)
|
|
{
|
|
if (data)
|
|
dev->features |= NETIF_F_TSO;
|
|
else
|
|
dev->features &= ~NETIF_F_TSO;
|
|
|
|
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)
|
|
{
|
|
struct ethtool_cmd cmd = { ETHTOOL_GSET };
|
|
int err;
|
|
|
|
if (!dev->ethtool_ops->get_settings)
|
|
return -EOPNOTSUPP;
|
|
|
|
err = dev->ethtool_ops->get_settings(dev, &cmd);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
if (copy_to_user(useraddr, &cmd, sizeof(cmd)))
|
|
return -EFAULT;
|
|
return 0;
|
|
}
|
|
|
|
static int ethtool_set_settings(struct net_device *dev, void __user *useraddr)
|
|
{
|
|
struct ethtool_cmd cmd;
|
|
|
|
if (!dev->ethtool_ops->set_settings)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (copy_from_user(&cmd, useraddr, sizeof(cmd)))
|
|
return -EFAULT;
|
|
|
|
return dev->ethtool_ops->set_settings(dev, &cmd);
|
|
}
|
|
|
|
static int ethtool_get_drvinfo(struct net_device *dev, void __user *useraddr)
|
|
{
|
|
struct ethtool_drvinfo info;
|
|
struct ethtool_ops *ops = dev->ethtool_ops;
|
|
|
|
if (!ops->get_drvinfo)
|
|
return -EOPNOTSUPP;
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
info.cmd = ETHTOOL_GDRVINFO;
|
|
ops->get_drvinfo(dev, &info);
|
|
|
|
if (ops->self_test_count)
|
|
info.testinfo_len = ops->self_test_count(dev);
|
|
if (ops->get_stats_count)
|
|
info.n_stats = ops->get_stats_count(dev);
|
|
if (ops->get_regs_len)
|
|
info.regdump_len = ops->get_regs_len(dev);
|
|
if (ops->get_eeprom_len)
|
|
info.eedump_len = ops->get_eeprom_len(dev);
|
|
|
|
if (copy_to_user(useraddr, &info, sizeof(info)))
|
|
return -EFAULT;
|
|
return 0;
|
|
}
|
|
|
|
static int ethtool_get_regs(struct net_device *dev, char __user *useraddr)
|
|
{
|
|
struct ethtool_regs regs;
|
|
struct ethtool_ops *ops = dev->ethtool_ops;
|
|
void *regbuf;
|
|
int reglen, ret;
|
|
|
|
if (!ops->get_regs || !ops->get_regs_len)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (copy_from_user(®s, useraddr, sizeof(regs)))
|
|
return -EFAULT;
|
|
|
|
reglen = ops->get_regs_len(dev);
|
|
if (regs.len > reglen)
|
|
regs.len = reglen;
|
|
|
|
regbuf = kmalloc(reglen, GFP_USER);
|
|
if (!regbuf)
|
|
return -ENOMEM;
|
|
|
|
ops->get_regs(dev, ®s, regbuf);
|
|
|
|
ret = -EFAULT;
|
|
if (copy_to_user(useraddr, ®s, sizeof(regs)))
|
|
goto out;
|
|
useraddr += offsetof(struct ethtool_regs, data);
|
|
if (copy_to_user(useraddr, regbuf, regs.len))
|
|
goto out;
|
|
ret = 0;
|
|
|
|
out:
|
|
kfree(regbuf);
|
|
return ret;
|
|
}
|
|
|
|
static int ethtool_get_wol(struct net_device *dev, char __user *useraddr)
|
|
{
|
|
struct ethtool_wolinfo wol = { ETHTOOL_GWOL };
|
|
|
|
if (!dev->ethtool_ops->get_wol)
|
|
return -EOPNOTSUPP;
|
|
|
|
dev->ethtool_ops->get_wol(dev, &wol);
|
|
|
|
if (copy_to_user(useraddr, &wol, sizeof(wol)))
|
|
return -EFAULT;
|
|
return 0;
|
|
}
|
|
|
|
static int ethtool_set_wol(struct net_device *dev, char __user *useraddr)
|
|
{
|
|
struct ethtool_wolinfo wol;
|
|
|
|
if (!dev->ethtool_ops->set_wol)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (copy_from_user(&wol, useraddr, sizeof(wol)))
|
|
return -EFAULT;
|
|
|
|
return dev->ethtool_ops->set_wol(dev, &wol);
|
|
}
|
|
|
|
static int ethtool_get_msglevel(struct net_device *dev, char __user *useraddr)
|
|
{
|
|
struct ethtool_value edata = { ETHTOOL_GMSGLVL };
|
|
|
|
if (!dev->ethtool_ops->get_msglevel)
|
|
return -EOPNOTSUPP;
|
|
|
|
edata.data = dev->ethtool_ops->get_msglevel(dev);
|
|
|
|
if (copy_to_user(useraddr, &edata, sizeof(edata)))
|
|
return -EFAULT;
|
|
return 0;
|
|
}
|
|
|
|
static int ethtool_set_msglevel(struct net_device *dev, char __user *useraddr)
|
|
{
|
|
struct ethtool_value edata;
|
|
|
|
if (!dev->ethtool_ops->set_msglevel)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (copy_from_user(&edata, useraddr, sizeof(edata)))
|
|
return -EFAULT;
|
|
|
|
dev->ethtool_ops->set_msglevel(dev, edata.data);
|
|
return 0;
|
|
}
|
|
|
|
static int ethtool_nway_reset(struct net_device *dev)
|
|
{
|
|
if (!dev->ethtool_ops->nway_reset)
|
|
return -EOPNOTSUPP;
|
|
|
|
return dev->ethtool_ops->nway_reset(dev);
|
|
}
|
|
|
|
static int ethtool_get_link(struct net_device *dev, void __user *useraddr)
|
|
{
|
|
struct ethtool_value edata = { ETHTOOL_GLINK };
|
|
|
|
if (!dev->ethtool_ops->get_link)
|
|
return -EOPNOTSUPP;
|
|
|
|
edata.data = dev->ethtool_ops->get_link(dev);
|
|
|
|
if (copy_to_user(useraddr, &edata, sizeof(edata)))
|
|
return -EFAULT;
|
|
return 0;
|
|
}
|
|
|
|
static int ethtool_get_eeprom(struct net_device *dev, void __user *useraddr)
|
|
{
|
|
struct ethtool_eeprom eeprom;
|
|
struct ethtool_ops *ops = dev->ethtool_ops;
|
|
u8 *data;
|
|
int ret;
|
|
|
|
if (!ops->get_eeprom || !ops->get_eeprom_len)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (copy_from_user(&eeprom, useraddr, sizeof(eeprom)))
|
|
return -EFAULT;
|
|
|
|
/* Check for wrap and zero */
|
|
if (eeprom.offset + eeprom.len <= eeprom.offset)
|
|
return -EINVAL;
|
|
|
|
/* Check for exceeding total eeprom len */
|
|
if (eeprom.offset + eeprom.len > ops->get_eeprom_len(dev))
|
|
return -EINVAL;
|
|
|
|
data = kmalloc(eeprom.len, GFP_USER);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
ret = -EFAULT;
|
|
if (copy_from_user(data, useraddr + sizeof(eeprom), eeprom.len))
|
|
goto out;
|
|
|
|
ret = ops->get_eeprom(dev, &eeprom, data);
|
|
if (ret)
|
|
goto out;
|
|
|
|
ret = -EFAULT;
|
|
if (copy_to_user(useraddr, &eeprom, sizeof(eeprom)))
|
|
goto out;
|
|
if (copy_to_user(useraddr + sizeof(eeprom), data, eeprom.len))
|
|
goto out;
|
|
ret = 0;
|
|
|
|
out:
|
|
kfree(data);
|
|
return ret;
|
|
}
|
|
|
|
static int ethtool_set_eeprom(struct net_device *dev, void __user *useraddr)
|
|
{
|
|
struct ethtool_eeprom eeprom;
|
|
struct ethtool_ops *ops = dev->ethtool_ops;
|
|
u8 *data;
|
|
int ret;
|
|
|
|
if (!ops->set_eeprom || !ops->get_eeprom_len)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (copy_from_user(&eeprom, useraddr, sizeof(eeprom)))
|
|
return -EFAULT;
|
|
|
|
/* Check for wrap and zero */
|
|
if (eeprom.offset + eeprom.len <= eeprom.offset)
|
|
return -EINVAL;
|
|
|
|
/* Check for exceeding total eeprom len */
|
|
if (eeprom.offset + eeprom.len > ops->get_eeprom_len(dev))
|
|
return -EINVAL;
|
|
|
|
data = kmalloc(eeprom.len, GFP_USER);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
ret = -EFAULT;
|
|
if (copy_from_user(data, useraddr + sizeof(eeprom), eeprom.len))
|
|
goto out;
|
|
|
|
ret = ops->set_eeprom(dev, &eeprom, data);
|
|
if (ret)
|
|
goto out;
|
|
|
|
if (copy_to_user(useraddr + sizeof(eeprom), data, eeprom.len))
|
|
ret = -EFAULT;
|
|
|
|
out:
|
|
kfree(data);
|
|
return ret;
|
|
}
|
|
|
|
static int ethtool_get_coalesce(struct net_device *dev, void __user *useraddr)
|
|
{
|
|
struct ethtool_coalesce coalesce = { ETHTOOL_GCOALESCE };
|
|
|
|
if (!dev->ethtool_ops->get_coalesce)
|
|
return -EOPNOTSUPP;
|
|
|
|
dev->ethtool_ops->get_coalesce(dev, &coalesce);
|
|
|
|
if (copy_to_user(useraddr, &coalesce, sizeof(coalesce)))
|
|
return -EFAULT;
|
|
return 0;
|
|
}
|
|
|
|
static int ethtool_set_coalesce(struct net_device *dev, void __user *useraddr)
|
|
{
|
|
struct ethtool_coalesce coalesce;
|
|
|
|
if (!dev->ethtool_ops->set_coalesce)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (copy_from_user(&coalesce, useraddr, sizeof(coalesce)))
|
|
return -EFAULT;
|
|
|
|
return dev->ethtool_ops->set_coalesce(dev, &coalesce);
|
|
}
|
|
|
|
static int ethtool_get_ringparam(struct net_device *dev, void __user *useraddr)
|
|
{
|
|
struct ethtool_ringparam ringparam = { ETHTOOL_GRINGPARAM };
|
|
|
|
if (!dev->ethtool_ops->get_ringparam)
|
|
return -EOPNOTSUPP;
|
|
|
|
dev->ethtool_ops->get_ringparam(dev, &ringparam);
|
|
|
|
if (copy_to_user(useraddr, &ringparam, sizeof(ringparam)))
|
|
return -EFAULT;
|
|
return 0;
|
|
}
|
|
|
|
static int ethtool_set_ringparam(struct net_device *dev, void __user *useraddr)
|
|
{
|
|
struct ethtool_ringparam ringparam;
|
|
|
|
if (!dev->ethtool_ops->set_ringparam)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (copy_from_user(&ringparam, useraddr, sizeof(ringparam)))
|
|
return -EFAULT;
|
|
|
|
return dev->ethtool_ops->set_ringparam(dev, &ringparam);
|
|
}
|
|
|
|
static int ethtool_get_pauseparam(struct net_device *dev, void __user *useraddr)
|
|
{
|
|
struct ethtool_pauseparam pauseparam = { ETHTOOL_GPAUSEPARAM };
|
|
|
|
if (!dev->ethtool_ops->get_pauseparam)
|
|
return -EOPNOTSUPP;
|
|
|
|
dev->ethtool_ops->get_pauseparam(dev, &pauseparam);
|
|
|
|
if (copy_to_user(useraddr, &pauseparam, sizeof(pauseparam)))
|
|
return -EFAULT;
|
|
return 0;
|
|
}
|
|
|
|
static int ethtool_set_pauseparam(struct net_device *dev, void __user *useraddr)
|
|
{
|
|
struct ethtool_pauseparam pauseparam;
|
|
|
|
if (!dev->ethtool_ops->get_pauseparam)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (copy_from_user(&pauseparam, useraddr, sizeof(pauseparam)))
|
|
return -EFAULT;
|
|
|
|
return dev->ethtool_ops->set_pauseparam(dev, &pauseparam);
|
|
}
|
|
|
|
static int ethtool_get_rx_csum(struct net_device *dev, char __user *useraddr)
|
|
{
|
|
struct ethtool_value edata = { ETHTOOL_GRXCSUM };
|
|
|
|
if (!dev->ethtool_ops->get_rx_csum)
|
|
return -EOPNOTSUPP;
|
|
|
|
edata.data = dev->ethtool_ops->get_rx_csum(dev);
|
|
|
|
if (copy_to_user(useraddr, &edata, sizeof(edata)))
|
|
return -EFAULT;
|
|
return 0;
|
|
}
|
|
|
|
static int ethtool_set_rx_csum(struct net_device *dev, char __user *useraddr)
|
|
{
|
|
struct ethtool_value edata;
|
|
|
|
if (!dev->ethtool_ops->set_rx_csum)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (copy_from_user(&edata, useraddr, sizeof(edata)))
|
|
return -EFAULT;
|
|
|
|
dev->ethtool_ops->set_rx_csum(dev, edata.data);
|
|
return 0;
|
|
}
|
|
|
|
static int ethtool_get_tx_csum(struct net_device *dev, char __user *useraddr)
|
|
{
|
|
struct ethtool_value edata = { ETHTOOL_GTXCSUM };
|
|
|
|
if (!dev->ethtool_ops->get_tx_csum)
|
|
return -EOPNOTSUPP;
|
|
|
|
edata.data = dev->ethtool_ops->get_tx_csum(dev);
|
|
|
|
if (copy_to_user(useraddr, &edata, sizeof(edata)))
|
|
return -EFAULT;
|
|
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;
|
|
|
|
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);
|
|
}
|
|
|
|
static int ethtool_get_sg(struct net_device *dev, char __user *useraddr)
|
|
{
|
|
struct ethtool_value edata = { ETHTOOL_GSG };
|
|
|
|
if (!dev->ethtool_ops->get_sg)
|
|
return -EOPNOTSUPP;
|
|
|
|
edata.data = dev->ethtool_ops->get_sg(dev);
|
|
|
|
if (copy_to_user(useraddr, &edata, sizeof(edata)))
|
|
return -EFAULT;
|
|
return 0;
|
|
}
|
|
|
|
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_IP_CSUM |
|
|
NETIF_F_NO_CSUM |
|
|
NETIF_F_HW_CSUM)))
|
|
return -EINVAL;
|
|
|
|
return __ethtool_set_sg(dev, edata.data);
|
|
}
|
|
|
|
static int ethtool_get_tso(struct net_device *dev, char __user *useraddr)
|
|
{
|
|
struct ethtool_value edata = { ETHTOOL_GTSO };
|
|
|
|
if (!dev->ethtool_ops->get_tso)
|
|
return -EOPNOTSUPP;
|
|
|
|
edata.data = dev->ethtool_ops->get_tso(dev);
|
|
|
|
if (copy_to_user(useraddr, &edata, sizeof(edata)))
|
|
return -EFAULT;
|
|
return 0;
|
|
}
|
|
|
|
static int ethtool_set_tso(struct net_device *dev, char __user *useraddr)
|
|
{
|
|
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))
|
|
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_GTSO };
|
|
|
|
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_self_test(struct net_device *dev, char __user *useraddr)
|
|
{
|
|
struct ethtool_test test;
|
|
struct ethtool_ops *ops = dev->ethtool_ops;
|
|
u64 *data;
|
|
int ret;
|
|
|
|
if (!ops->self_test || !ops->self_test_count)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (copy_from_user(&test, useraddr, sizeof(test)))
|
|
return -EFAULT;
|
|
|
|
test.len = ops->self_test_count(dev);
|
|
data = kmalloc(test.len * sizeof(u64), GFP_USER);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
ops->self_test(dev, &test, data);
|
|
|
|
ret = -EFAULT;
|
|
if (copy_to_user(useraddr, &test, sizeof(test)))
|
|
goto out;
|
|
useraddr += sizeof(test);
|
|
if (copy_to_user(useraddr, data, test.len * sizeof(u64)))
|
|
goto out;
|
|
ret = 0;
|
|
|
|
out:
|
|
kfree(data);
|
|
return ret;
|
|
}
|
|
|
|
static int ethtool_get_strings(struct net_device *dev, void __user *useraddr)
|
|
{
|
|
struct ethtool_gstrings gstrings;
|
|
struct ethtool_ops *ops = dev->ethtool_ops;
|
|
u8 *data;
|
|
int ret;
|
|
|
|
if (!ops->get_strings)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (copy_from_user(&gstrings, useraddr, sizeof(gstrings)))
|
|
return -EFAULT;
|
|
|
|
switch (gstrings.string_set) {
|
|
case ETH_SS_TEST:
|
|
if (!ops->self_test_count)
|
|
return -EOPNOTSUPP;
|
|
gstrings.len = ops->self_test_count(dev);
|
|
break;
|
|
case ETH_SS_STATS:
|
|
if (!ops->get_stats_count)
|
|
return -EOPNOTSUPP;
|
|
gstrings.len = ops->get_stats_count(dev);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
data = kmalloc(gstrings.len * ETH_GSTRING_LEN, GFP_USER);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
ops->get_strings(dev, gstrings.string_set, data);
|
|
|
|
ret = -EFAULT;
|
|
if (copy_to_user(useraddr, &gstrings, sizeof(gstrings)))
|
|
goto out;
|
|
useraddr += sizeof(gstrings);
|
|
if (copy_to_user(useraddr, data, gstrings.len * ETH_GSTRING_LEN))
|
|
goto out;
|
|
ret = 0;
|
|
|
|
out:
|
|
kfree(data);
|
|
return ret;
|
|
}
|
|
|
|
static int ethtool_phys_id(struct net_device *dev, void __user *useraddr)
|
|
{
|
|
struct ethtool_value id;
|
|
|
|
if (!dev->ethtool_ops->phys_id)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (copy_from_user(&id, useraddr, sizeof(id)))
|
|
return -EFAULT;
|
|
|
|
return dev->ethtool_ops->phys_id(dev, id.data);
|
|
}
|
|
|
|
static int ethtool_get_stats(struct net_device *dev, void __user *useraddr)
|
|
{
|
|
struct ethtool_stats stats;
|
|
struct ethtool_ops *ops = dev->ethtool_ops;
|
|
u64 *data;
|
|
int ret;
|
|
|
|
if (!ops->get_ethtool_stats || !ops->get_stats_count)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (copy_from_user(&stats, useraddr, sizeof(stats)))
|
|
return -EFAULT;
|
|
|
|
stats.n_stats = ops->get_stats_count(dev);
|
|
data = kmalloc(stats.n_stats * sizeof(u64), GFP_USER);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
ops->get_ethtool_stats(dev, &stats, data);
|
|
|
|
ret = -EFAULT;
|
|
if (copy_to_user(useraddr, &stats, sizeof(stats)))
|
|
goto out;
|
|
useraddr += sizeof(stats);
|
|
if (copy_to_user(useraddr, data, stats.n_stats * sizeof(u64)))
|
|
goto out;
|
|
ret = 0;
|
|
|
|
out:
|
|
kfree(data);
|
|
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 = ifr->ifr_data;
|
|
u32 ethcmd;
|
|
int rc;
|
|
unsigned long old_features;
|
|
|
|
/*
|
|
* 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;
|
|
|
|
if (!dev || !netif_device_present(dev))
|
|
return -ENODEV;
|
|
|
|
if (!dev->ethtool_ops)
|
|
goto ioctl;
|
|
|
|
if (copy_from_user(ðcmd, useraddr, sizeof (ethcmd)))
|
|
return -EFAULT;
|
|
|
|
if(dev->ethtool_ops->begin)
|
|
if ((rc = dev->ethtool_ops->begin(dev)) < 0)
|
|
return rc;
|
|
|
|
old_features = dev->features;
|
|
|
|
switch (ethcmd) {
|
|
case ETHTOOL_GSET:
|
|
rc = ethtool_get_settings(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_SSET:
|
|
rc = ethtool_set_settings(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_GDRVINFO:
|
|
rc = ethtool_get_drvinfo(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_GREGS:
|
|
rc = ethtool_get_regs(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_GWOL:
|
|
rc = ethtool_get_wol(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_SWOL:
|
|
rc = ethtool_set_wol(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_GMSGLVL:
|
|
rc = ethtool_get_msglevel(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_SMSGLVL:
|
|
rc = ethtool_set_msglevel(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_NWAY_RST:
|
|
rc = ethtool_nway_reset(dev);
|
|
break;
|
|
case ETHTOOL_GLINK:
|
|
rc = ethtool_get_link(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_GEEPROM:
|
|
rc = ethtool_get_eeprom(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_SEEPROM:
|
|
rc = ethtool_set_eeprom(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_GCOALESCE:
|
|
rc = ethtool_get_coalesce(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_SCOALESCE:
|
|
rc = ethtool_set_coalesce(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_GRINGPARAM:
|
|
rc = ethtool_get_ringparam(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_SRINGPARAM:
|
|
rc = ethtool_set_ringparam(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_GPAUSEPARAM:
|
|
rc = ethtool_get_pauseparam(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_SPAUSEPARAM:
|
|
rc = ethtool_set_pauseparam(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_GRXCSUM:
|
|
rc = ethtool_get_rx_csum(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_SRXCSUM:
|
|
rc = ethtool_set_rx_csum(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_GTXCSUM:
|
|
rc = ethtool_get_tx_csum(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_STXCSUM:
|
|
rc = ethtool_set_tx_csum(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_GSG:
|
|
rc = ethtool_get_sg(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_SSG:
|
|
rc = ethtool_set_sg(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_GTSO:
|
|
rc = ethtool_get_tso(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_STSO:
|
|
rc = ethtool_set_tso(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_TEST:
|
|
rc = ethtool_self_test(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_GSTRINGS:
|
|
rc = ethtool_get_strings(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_PHYS_ID:
|
|
rc = ethtool_phys_id(dev, useraddr);
|
|
break;
|
|
case ETHTOOL_GSTATS:
|
|
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;
|
|
default:
|
|
rc = -EOPNOTSUPP;
|
|
}
|
|
|
|
if(dev->ethtool_ops->complete)
|
|
dev->ethtool_ops->complete(dev);
|
|
|
|
if (old_features != dev->features)
|
|
netdev_features_change(dev);
|
|
|
|
return rc;
|
|
|
|
ioctl:
|
|
if (dev->do_ioctl)
|
|
return dev->do_ioctl(dev, ifr, SIOCETHTOOL);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
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);
|