2005-04-17 06:20:36 +08:00
|
|
|
/*
|
|
|
|
* 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
|
2007-08-01 05:00:02 +08:00
|
|
|
* the information ethtool needs.
|
2005-04-17 06:20:36 +08:00
|
|
|
*
|
2007-08-01 05:00:02 +08:00
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/types.h>
|
2006-01-12 04:17:47 +08:00
|
|
|
#include <linux/capability.h>
|
2005-04-17 06:20:36 +08:00
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/ethtool.h>
|
|
|
|
#include <linux/netdevice.h>
|
ethtool: Add direct access to ops->get_sset_count
On 03/04/2010 09:26 AM, Ben Hutchings wrote:
> On Thu, 2010-03-04 at 00:51 -0800, Jeff Kirsher wrote:
>> From: Jeff Garzik<jgarzik@redhat.com>
>>
>> This patch is an alternative approach for accessing string
>> counts, vs. the drvinfo indirect approach. This way the drvinfo
>> space doesn't run out, and we don't break ABI later.
> [...]
>> --- a/net/core/ethtool.c
>> +++ b/net/core/ethtool.c
>> @@ -214,6 +214,10 @@ static noinline int ethtool_get_drvinfo(struct net_device *dev, void __user *use
>> info.cmd = ETHTOOL_GDRVINFO;
>> ops->get_drvinfo(dev,&info);
>>
>> + /*
>> + * this method of obtaining string set info is deprecated;
>> + * consider using ETHTOOL_GSSET_INFO instead
>> + */
>
> This comment belongs on the interface (ethtool.h) not the
> implementation.
Debatable -- the current comment is located at the callsite of
ops->get_sset_count(), which is where an implementor might think to add
a new call. Not all the numeric fields in ethtool_drvinfo are obtained
from ->get_sset_count().
Hence the "some" in the attached patch to include/linux/ethtool.h,
addressing your comment.
> [...]
>> +static noinline int ethtool_get_sset_info(struct net_device *dev,
>> + void __user *useraddr)
>> +{
> [...]
>> + /* calculate size of return buffer */
>> + for (i = 0; i< 64; i++)
>> + if (sset_mask& (1ULL<< i))
>> + n_bits++;
> [...]
>
> We have a function for this:
>
> n_bits = hweight64(sset_mask);
Agreed.
I've attached a follow-up patch, which should enable my/Jeff's kernel
patch to be applied, followed by this one.
Signed-off-by: Jeff Garzik <jgarzik@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2010-03-04 16:21:53 +08:00
|
|
|
#include <linux/bitops.h>
|
2010-04-08 12:54:42 +08:00
|
|
|
#include <linux/uaccess.h>
|
2010-09-22 07:12:11 +08:00
|
|
|
#include <linux/vmalloc.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 16:04:11 +08:00
|
|
|
#include <linux/slab.h>
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-02-09 22:24:36 +08:00
|
|
|
/*
|
2005-04-17 06:20:36 +08:00
|
|
|
* 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;
|
|
|
|
}
|
2010-04-08 12:54:42 +08:00
|
|
|
EXPORT_SYMBOL(ethtool_op_get_link);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
u32 ethtool_op_get_tx_csum(struct net_device *dev)
|
|
|
|
{
|
2006-06-18 13:06:05 +08:00
|
|
|
return (dev->features & NETIF_F_ALL_CSUM) != 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2009-07-27 07:18:11 +08:00
|
|
|
EXPORT_SYMBOL(ethtool_op_get_tx_csum);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2011-02-16 00:59:16 +08:00
|
|
|
EXPORT_SYMBOL(ethtool_op_set_tx_csum);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-05-30 11:27:24 +08:00
|
|
|
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;
|
|
|
|
}
|
2010-04-08 12:54:42 +08:00
|
|
|
EXPORT_SYMBOL(ethtool_op_set_tx_hw_csum);
|
2007-07-15 10:07:52 +08:00
|
|
|
|
|
|
|
int ethtool_op_set_tx_ipv6_csum(struct net_device *dev, u32 data)
|
|
|
|
{
|
|
|
|
if (data)
|
|
|
|
dev->features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM;
|
|
|
|
else
|
|
|
|
dev->features &= ~(NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2010-04-08 12:54:42 +08:00
|
|
|
EXPORT_SYMBOL(ethtool_op_set_tx_ipv6_csum);
|
2007-07-15 10:07:52 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
u32 ethtool_op_get_sg(struct net_device *dev)
|
|
|
|
{
|
|
|
|
return (dev->features & NETIF_F_SG) != 0;
|
|
|
|
}
|
2010-04-08 12:54:42 +08:00
|
|
|
EXPORT_SYMBOL(ethtool_op_get_sg);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2010-04-08 12:54:42 +08:00
|
|
|
EXPORT_SYMBOL(ethtool_op_set_sg);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
u32 ethtool_op_get_tso(struct net_device *dev)
|
|
|
|
{
|
|
|
|
return (dev->features & NETIF_F_TSO) != 0;
|
|
|
|
}
|
2010-04-08 12:54:42 +08:00
|
|
|
EXPORT_SYMBOL(ethtool_op_get_tso);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2010-04-08 12:54:42 +08:00
|
|
|
EXPORT_SYMBOL(ethtool_op_set_tso);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-10-19 06:46:41 +08:00
|
|
|
u32 ethtool_op_get_ufo(struct net_device *dev)
|
|
|
|
{
|
|
|
|
return (dev->features & NETIF_F_UFO) != 0;
|
|
|
|
}
|
2010-04-08 12:54:42 +08:00
|
|
|
EXPORT_SYMBOL(ethtool_op_get_ufo);
|
2005-10-19 06:46:41 +08:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2010-04-08 12:54:42 +08:00
|
|
|
EXPORT_SYMBOL(ethtool_op_set_ufo);
|
2005-10-19 06:46:41 +08:00
|
|
|
|
2007-08-16 07:00:51 +08:00
|
|
|
/* the following list of flags are the same as their associated
|
|
|
|
* NETIF_F_xxx values in include/linux/netdevice.h
|
|
|
|
*/
|
|
|
|
static const u32 flags_dup_features =
|
2010-10-20 21:56:07 +08:00
|
|
|
(ETH_FLAG_LRO | ETH_FLAG_RXVLAN | ETH_FLAG_TXVLAN | ETH_FLAG_NTUPLE |
|
|
|
|
ETH_FLAG_RXHASH);
|
2007-08-16 07:00:51 +08:00
|
|
|
|
|
|
|
u32 ethtool_op_get_flags(struct net_device *dev)
|
|
|
|
{
|
|
|
|
/* in the future, this function will probably contain additional
|
|
|
|
* handling for flags which are not so easily handled
|
|
|
|
* by a simple masking operation
|
|
|
|
*/
|
|
|
|
|
|
|
|
return dev->features & flags_dup_features;
|
|
|
|
}
|
2010-04-08 12:54:42 +08:00
|
|
|
EXPORT_SYMBOL(ethtool_op_get_flags);
|
2007-08-16 07:00:51 +08:00
|
|
|
|
2010-06-30 10:44:32 +08:00
|
|
|
int ethtool_op_set_flags(struct net_device *dev, u32 data, u32 supported)
|
2007-08-16 07:00:51 +08:00
|
|
|
{
|
2010-06-30 10:44:32 +08:00
|
|
|
if (data & ~supported)
|
|
|
|
return -EINVAL;
|
2010-02-12 21:48:25 +08:00
|
|
|
|
2010-06-30 10:44:32 +08:00
|
|
|
dev->features = ((dev->features & ~flags_dup_features) |
|
|
|
|
(data & flags_dup_features));
|
2007-08-16 07:00:51 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2010-04-08 12:54:42 +08:00
|
|
|
EXPORT_SYMBOL(ethtool_op_set_flags);
|
2007-08-16 07:00:51 +08:00
|
|
|
|
2010-02-11 12:03:05 +08:00
|
|
|
void ethtool_ntuple_flush(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct ethtool_rx_ntuple_flow_spec_container *fsc, *f;
|
|
|
|
|
|
|
|
list_for_each_entry_safe(fsc, f, &dev->ethtool_ntuple_list.list, list) {
|
|
|
|
list_del(&fsc->list);
|
|
|
|
kfree(fsc);
|
|
|
|
}
|
|
|
|
dev->ethtool_ntuple_list.count = 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ethtool_ntuple_flush);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/* Handlers for each ethtool command */
|
|
|
|
|
2011-02-16 00:59:17 +08:00
|
|
|
#define ETHTOOL_DEV_FEATURE_WORDS 1
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
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",
|
2011-02-16 00:59:18 +08:00
|
|
|
/* NETIF_F_RXCSUM */ "rx-checksum",
|
2011-02-16 00:59:17 +08:00
|
|
|
"",
|
|
|
|
"",
|
|
|
|
};
|
|
|
|
|
2011-02-16 00:59:16 +08:00
|
|
|
static int __ethtool_get_sset_count(struct net_device *dev, int sset)
|
|
|
|
{
|
|
|
|
const struct ethtool_ops *ops = dev->ethtool_ops;
|
|
|
|
|
2011-02-16 00:59:17 +08:00
|
|
|
if (sset == ETH_SS_FEATURES)
|
|
|
|
return ARRAY_SIZE(netdev_features_strings);
|
|
|
|
|
2011-02-16 00:59:16 +08:00
|
|
|
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;
|
|
|
|
|
2011-02-16 00:59:17 +08:00
|
|
|
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);
|
2011-02-16 00:59:16 +08:00
|
|
|
}
|
|
|
|
|
2011-02-16 00:59:17 +08:00
|
|
|
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;
|
2011-02-16 00:59:18 +08:00
|
|
|
case ETHTOOL_GRXCSUM:
|
|
|
|
case ETHTOOL_SRXCSUM:
|
|
|
|
return NETIF_F_RXCSUM;
|
2011-02-16 00:59:17 +08:00
|
|
|
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;
|
2011-02-16 00:59:18 +08:00
|
|
|
case ETHTOOL_GRXCSUM:
|
|
|
|
return ops->get_rx_csum;
|
2011-02-16 00:59:17 +08:00
|
|
|
case ETHTOOL_SSG:
|
|
|
|
return ops->get_sg;
|
|
|
|
case ETHTOOL_STSO:
|
|
|
|
return ops->get_tso;
|
|
|
|
case ETHTOOL_SUFO:
|
|
|
|
return ops->get_ufo;
|
|
|
|
default:
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-02-16 00:59:18 +08:00
|
|
|
static u32 __ethtool_get_rx_csum_oldbug(struct net_device *dev)
|
|
|
|
{
|
|
|
|
return !!(dev->features & NETIF_F_ALL_CSUM);
|
|
|
|
}
|
|
|
|
|
2011-02-16 00:59:17 +08:00
|
|
|
static int ethtool_get_one_feature(struct net_device *dev,
|
|
|
|
char __user *useraddr, u32 ethcmd)
|
|
|
|
{
|
2011-02-16 00:59:17 +08:00
|
|
|
u32 mask = ethtool_get_feature_mask(ethcmd);
|
2011-02-16 00:59:17 +08:00
|
|
|
struct ethtool_value edata = {
|
|
|
|
.cmd = ethcmd,
|
2011-02-16 00:59:17 +08:00
|
|
|
.data = !!(dev->features & mask),
|
2011-02-16 00:59:17 +08:00
|
|
|
};
|
|
|
|
|
2011-02-16 00:59:17 +08:00
|
|
|
/* compatibility with discrete get_ ops */
|
|
|
|
if (!(dev->hw_features & mask)) {
|
|
|
|
u32 (*actor)(struct net_device *);
|
|
|
|
|
|
|
|
actor = __ethtool_get_one_feature_actor(dev, ethcmd);
|
|
|
|
|
2011-02-16 00:59:18 +08:00
|
|
|
/* bug compatibility with old get_rx_csum */
|
|
|
|
if (ethcmd == ETHTOOL_GRXCSUM && !actor)
|
|
|
|
actor = __ethtool_get_rx_csum_oldbug;
|
|
|
|
|
2011-02-16 00:59:17 +08:00
|
|
|
if (actor)
|
|
|
|
edata.data = actor(dev);
|
|
|
|
}
|
2011-02-16 00:59:17 +08:00
|
|
|
|
|
|
|
if (copy_to_user(useraddr, &edata, sizeof(edata)))
|
|
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __ethtool_set_tx_csum(struct net_device *dev, u32 data);
|
2011-02-16 00:59:18 +08:00
|
|
|
static int __ethtool_set_rx_csum(struct net_device *dev, u32 data);
|
2011-02-16 00:59:17 +08:00
|
|
|
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;
|
|
|
|
|
2011-02-16 00:59:17 +08:00
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2011-02-16 00:59:17 +08:00
|
|
|
switch (ethcmd) {
|
|
|
|
case ETHTOOL_STXCSUM:
|
|
|
|
return __ethtool_set_tx_csum(dev, edata.data);
|
2011-02-16 00:59:18 +08:00
|
|
|
case ETHTOOL_SRXCSUM:
|
|
|
|
return __ethtool_set_rx_csum(dev, edata.data);
|
2011-02-16 00:59:17 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-02-16 00:59:18 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
static int ethtool_get_settings(struct net_device *dev, void __user *useraddr)
|
|
|
|
{
|
2010-02-12 04:14:23 +08:00
|
|
|
struct ethtool_cmd cmd = { .cmd = ETHTOOL_GSET };
|
2005-04-17 06:20:36 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2010-04-08 12:54:42 +08:00
|
|
|
static noinline_for_stack int ethtool_get_drvinfo(struct net_device *dev,
|
|
|
|
void __user *useraddr)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct ethtool_drvinfo info;
|
2006-09-09 02:16:13 +08:00
|
|
|
const struct ethtool_ops *ops = dev->ethtool_ops;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
|
|
info.cmd = ETHTOOL_GDRVINFO;
|
2010-08-17 17:31:15 +08:00
|
|
|
if (ops && ops->get_drvinfo) {
|
|
|
|
ops->get_drvinfo(dev, &info);
|
|
|
|
} else if (dev->dev.parent && dev->dev.parent->driver) {
|
|
|
|
strlcpy(info.bus_info, dev_name(dev->dev.parent),
|
|
|
|
sizeof(info.bus_info));
|
|
|
|
strlcpy(info.driver, dev->dev.parent->driver->name,
|
|
|
|
sizeof(info.driver));
|
|
|
|
} else {
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2010-03-04 06:51:50 +08:00
|
|
|
/*
|
|
|
|
* this method of obtaining string set info is deprecated;
|
ethtool: Add direct access to ops->get_sset_count
On 03/04/2010 09:26 AM, Ben Hutchings wrote:
> On Thu, 2010-03-04 at 00:51 -0800, Jeff Kirsher wrote:
>> From: Jeff Garzik<jgarzik@redhat.com>
>>
>> This patch is an alternative approach for accessing string
>> counts, vs. the drvinfo indirect approach. This way the drvinfo
>> space doesn't run out, and we don't break ABI later.
> [...]
>> --- a/net/core/ethtool.c
>> +++ b/net/core/ethtool.c
>> @@ -214,6 +214,10 @@ static noinline int ethtool_get_drvinfo(struct net_device *dev, void __user *use
>> info.cmd = ETHTOOL_GDRVINFO;
>> ops->get_drvinfo(dev,&info);
>>
>> + /*
>> + * this method of obtaining string set info is deprecated;
>> + * consider using ETHTOOL_GSSET_INFO instead
>> + */
>
> This comment belongs on the interface (ethtool.h) not the
> implementation.
Debatable -- the current comment is located at the callsite of
ops->get_sset_count(), which is where an implementor might think to add
a new call. Not all the numeric fields in ethtool_drvinfo are obtained
from ->get_sset_count().
Hence the "some" in the attached patch to include/linux/ethtool.h,
addressing your comment.
> [...]
>> +static noinline int ethtool_get_sset_info(struct net_device *dev,
>> + void __user *useraddr)
>> +{
> [...]
>> + /* calculate size of return buffer */
>> + for (i = 0; i< 64; i++)
>> + if (sset_mask& (1ULL<< i))
>> + n_bits++;
> [...]
>
> We have a function for this:
>
> n_bits = hweight64(sset_mask);
Agreed.
I've attached a follow-up patch, which should enable my/Jeff's kernel
patch to be applied, followed by this one.
Signed-off-by: Jeff Garzik <jgarzik@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2010-03-04 16:21:53 +08:00
|
|
|
* Use ETHTOOL_GSSET_INFO instead.
|
2010-03-04 06:51:50 +08:00
|
|
|
*/
|
2010-08-17 17:31:15 +08:00
|
|
|
if (ops && ops->get_sset_count) {
|
2007-08-16 07:01:08 +08:00
|
|
|
int rc;
|
|
|
|
|
|
|
|
rc = ops->get_sset_count(dev, ETH_SS_TEST);
|
|
|
|
if (rc >= 0)
|
|
|
|
info.testinfo_len = rc;
|
|
|
|
rc = ops->get_sset_count(dev, ETH_SS_STATS);
|
|
|
|
if (rc >= 0)
|
|
|
|
info.n_stats = rc;
|
2007-08-16 07:01:32 +08:00
|
|
|
rc = ops->get_sset_count(dev, ETH_SS_PRIV_FLAGS);
|
|
|
|
if (rc >= 0)
|
|
|
|
info.n_priv_flags = rc;
|
2007-08-16 07:01:08 +08:00
|
|
|
}
|
2010-08-17 17:31:15 +08:00
|
|
|
if (ops && ops->get_regs_len)
|
2005-04-17 06:20:36 +08:00
|
|
|
info.regdump_len = ops->get_regs_len(dev);
|
2010-08-17 17:31:15 +08:00
|
|
|
if (ops && ops->get_eeprom_len)
|
2005-04-17 06:20:36 +08:00
|
|
|
info.eedump_len = ops->get_eeprom_len(dev);
|
|
|
|
|
|
|
|
if (copy_to_user(useraddr, &info, sizeof(info)))
|
|
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-03-09 04:17:04 +08:00
|
|
|
static noinline_for_stack int ethtool_get_sset_info(struct net_device *dev,
|
2010-04-08 12:54:42 +08:00
|
|
|
void __user *useraddr)
|
2010-03-04 06:51:50 +08:00
|
|
|
{
|
|
|
|
struct ethtool_sset_info info;
|
|
|
|
u64 sset_mask;
|
|
|
|
int i, idx = 0, n_bits = 0, ret, rc;
|
|
|
|
u32 *info_buf = NULL;
|
|
|
|
|
|
|
|
if (copy_from_user(&info, useraddr, sizeof(info)))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
/* store copy of mask, because we zero struct later on */
|
|
|
|
sset_mask = info.sset_mask;
|
|
|
|
if (!sset_mask)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* calculate size of return buffer */
|
ethtool: Add direct access to ops->get_sset_count
On 03/04/2010 09:26 AM, Ben Hutchings wrote:
> On Thu, 2010-03-04 at 00:51 -0800, Jeff Kirsher wrote:
>> From: Jeff Garzik<jgarzik@redhat.com>
>>
>> This patch is an alternative approach for accessing string
>> counts, vs. the drvinfo indirect approach. This way the drvinfo
>> space doesn't run out, and we don't break ABI later.
> [...]
>> --- a/net/core/ethtool.c
>> +++ b/net/core/ethtool.c
>> @@ -214,6 +214,10 @@ static noinline int ethtool_get_drvinfo(struct net_device *dev, void __user *use
>> info.cmd = ETHTOOL_GDRVINFO;
>> ops->get_drvinfo(dev,&info);
>>
>> + /*
>> + * this method of obtaining string set info is deprecated;
>> + * consider using ETHTOOL_GSSET_INFO instead
>> + */
>
> This comment belongs on the interface (ethtool.h) not the
> implementation.
Debatable -- the current comment is located at the callsite of
ops->get_sset_count(), which is where an implementor might think to add
a new call. Not all the numeric fields in ethtool_drvinfo are obtained
from ->get_sset_count().
Hence the "some" in the attached patch to include/linux/ethtool.h,
addressing your comment.
> [...]
>> +static noinline int ethtool_get_sset_info(struct net_device *dev,
>> + void __user *useraddr)
>> +{
> [...]
>> + /* calculate size of return buffer */
>> + for (i = 0; i< 64; i++)
>> + if (sset_mask& (1ULL<< i))
>> + n_bits++;
> [...]
>
> We have a function for this:
>
> n_bits = hweight64(sset_mask);
Agreed.
I've attached a follow-up patch, which should enable my/Jeff's kernel
patch to be applied, followed by this one.
Signed-off-by: Jeff Garzik <jgarzik@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2010-03-04 16:21:53 +08:00
|
|
|
n_bits = hweight64(sset_mask);
|
2010-03-04 06:51:50 +08:00
|
|
|
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
|
|
info.cmd = ETHTOOL_GSSET_INFO;
|
|
|
|
|
|
|
|
info_buf = kzalloc(n_bits * sizeof(u32), GFP_USER);
|
|
|
|
if (!info_buf)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* fill return buffer based on input bitmask and successful
|
|
|
|
* get_sset_count return
|
|
|
|
*/
|
|
|
|
for (i = 0; i < 64; i++) {
|
|
|
|
if (!(sset_mask & (1ULL << i)))
|
|
|
|
continue;
|
|
|
|
|
2011-02-16 00:59:16 +08:00
|
|
|
rc = __ethtool_get_sset_count(dev, i);
|
2010-03-04 06:51:50 +08:00
|
|
|
if (rc >= 0) {
|
|
|
|
info.sset_mask |= (1ULL << i);
|
|
|
|
info_buf[idx++] = rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = -EFAULT;
|
|
|
|
if (copy_to_user(useraddr, &info, sizeof(info)))
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
useraddr += offsetof(struct ethtool_sset_info, data);
|
|
|
|
if (copy_to_user(useraddr, info_buf, idx * sizeof(u32)))
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
out:
|
|
|
|
kfree(info_buf);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2010-04-08 12:54:42 +08:00
|
|
|
static noinline_for_stack int ethtool_set_rxnfc(struct net_device *dev,
|
2010-06-28 16:45:58 +08:00
|
|
|
u32 cmd, void __user *useraddr)
|
2008-07-02 18:47:41 +08:00
|
|
|
{
|
2010-06-28 16:45:58 +08:00
|
|
|
struct ethtool_rxnfc info;
|
|
|
|
size_t info_size = sizeof(info);
|
2008-07-02 18:47:41 +08:00
|
|
|
|
2009-02-20 16:58:13 +08:00
|
|
|
if (!dev->ethtool_ops->set_rxnfc)
|
2008-07-02 18:47:41 +08:00
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
2010-06-28 16:45:58 +08:00
|
|
|
/* struct ethtool_rxnfc was originally defined for
|
|
|
|
* ETHTOOL_{G,S}RXFH with only the cmd, flow_type and data
|
|
|
|
* members. User-space might still be using that
|
|
|
|
* definition. */
|
|
|
|
if (cmd == ETHTOOL_SRXFH)
|
|
|
|
info_size = (offsetof(struct ethtool_rxnfc, data) +
|
|
|
|
sizeof(info.data));
|
|
|
|
|
|
|
|
if (copy_from_user(&info, useraddr, info_size))
|
2008-07-02 18:47:41 +08:00
|
|
|
return -EFAULT;
|
|
|
|
|
2010-06-28 16:45:58 +08:00
|
|
|
return dev->ethtool_ops->set_rxnfc(dev, &info);
|
2008-07-02 18:47:41 +08:00
|
|
|
}
|
|
|
|
|
2010-04-08 12:54:42 +08:00
|
|
|
static noinline_for_stack int ethtool_get_rxnfc(struct net_device *dev,
|
2010-06-28 16:45:58 +08:00
|
|
|
u32 cmd, void __user *useraddr)
|
2008-07-02 18:47:41 +08:00
|
|
|
{
|
|
|
|
struct ethtool_rxnfc info;
|
2010-06-28 16:45:58 +08:00
|
|
|
size_t info_size = sizeof(info);
|
2009-02-20 16:58:13 +08:00
|
|
|
const struct ethtool_ops *ops = dev->ethtool_ops;
|
|
|
|
int ret;
|
|
|
|
void *rule_buf = NULL;
|
2008-07-02 18:47:41 +08:00
|
|
|
|
2009-02-20 16:58:13 +08:00
|
|
|
if (!ops->get_rxnfc)
|
2008-07-02 18:47:41 +08:00
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
2010-06-28 16:45:58 +08:00
|
|
|
/* struct ethtool_rxnfc was originally defined for
|
|
|
|
* ETHTOOL_{G,S}RXFH with only the cmd, flow_type and data
|
|
|
|
* members. User-space might still be using that
|
|
|
|
* definition. */
|
|
|
|
if (cmd == ETHTOOL_GRXFH)
|
|
|
|
info_size = (offsetof(struct ethtool_rxnfc, data) +
|
|
|
|
sizeof(info.data));
|
|
|
|
|
|
|
|
if (copy_from_user(&info, useraddr, info_size))
|
2008-07-02 18:47:41 +08:00
|
|
|
return -EFAULT;
|
|
|
|
|
2009-02-20 16:58:13 +08:00
|
|
|
if (info.cmd == ETHTOOL_GRXCLSRLALL) {
|
|
|
|
if (info.rule_cnt > 0) {
|
2010-06-28 16:44:07 +08:00
|
|
|
if (info.rule_cnt <= KMALLOC_MAX_SIZE / sizeof(u32))
|
2010-10-07 18:03:48 +08:00
|
|
|
rule_buf = kzalloc(info.rule_cnt * sizeof(u32),
|
2010-06-28 16:44:07 +08:00
|
|
|
GFP_USER);
|
2009-02-20 16:58:13 +08:00
|
|
|
if (!rule_buf)
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
}
|
2008-07-02 18:47:41 +08:00
|
|
|
|
2009-02-20 16:58:13 +08:00
|
|
|
ret = ops->get_rxnfc(dev, &info, rule_buf);
|
|
|
|
if (ret < 0)
|
|
|
|
goto err_out;
|
|
|
|
|
|
|
|
ret = -EFAULT;
|
2010-06-28 16:45:58 +08:00
|
|
|
if (copy_to_user(useraddr, &info, info_size))
|
2009-02-20 16:58:13 +08:00
|
|
|
goto err_out;
|
|
|
|
|
|
|
|
if (rule_buf) {
|
|
|
|
useraddr += offsetof(struct ethtool_rxnfc, rule_locs);
|
|
|
|
if (copy_to_user(useraddr, rule_buf,
|
|
|
|
info.rule_cnt * sizeof(u32)))
|
|
|
|
goto err_out;
|
|
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
err_out:
|
2009-04-01 06:06:26 +08:00
|
|
|
kfree(rule_buf);
|
2009-02-20 16:58:13 +08:00
|
|
|
|
|
|
|
return ret;
|
2008-07-02 18:47:41 +08:00
|
|
|
}
|
|
|
|
|
2010-06-30 13:05:23 +08:00
|
|
|
static noinline_for_stack int ethtool_get_rxfh_indir(struct net_device *dev,
|
|
|
|
void __user *useraddr)
|
|
|
|
{
|
|
|
|
struct ethtool_rxfh_indir *indir;
|
|
|
|
u32 table_size;
|
|
|
|
size_t full_size;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!dev->ethtool_ops->get_rxfh_indir)
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
|
|
|
if (copy_from_user(&table_size,
|
|
|
|
useraddr + offsetof(struct ethtool_rxfh_indir, size),
|
|
|
|
sizeof(table_size)))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
if (table_size >
|
|
|
|
(KMALLOC_MAX_SIZE - sizeof(*indir)) / sizeof(*indir->ring_index))
|
|
|
|
return -ENOMEM;
|
|
|
|
full_size = sizeof(*indir) + sizeof(*indir->ring_index) * table_size;
|
2010-10-12 03:23:25 +08:00
|
|
|
indir = kzalloc(full_size, GFP_USER);
|
2010-06-30 13:05:23 +08:00
|
|
|
if (!indir)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
indir->cmd = ETHTOOL_GRXFHINDIR;
|
|
|
|
indir->size = table_size;
|
|
|
|
ret = dev->ethtool_ops->get_rxfh_indir(dev, indir);
|
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
if (copy_to_user(useraddr, indir, full_size))
|
|
|
|
ret = -EFAULT;
|
|
|
|
|
|
|
|
out:
|
|
|
|
kfree(indir);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static noinline_for_stack int ethtool_set_rxfh_indir(struct net_device *dev,
|
|
|
|
void __user *useraddr)
|
|
|
|
{
|
|
|
|
struct ethtool_rxfh_indir *indir;
|
|
|
|
u32 table_size;
|
|
|
|
size_t full_size;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!dev->ethtool_ops->set_rxfh_indir)
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
|
|
|
if (copy_from_user(&table_size,
|
|
|
|
useraddr + offsetof(struct ethtool_rxfh_indir, size),
|
|
|
|
sizeof(table_size)))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
if (table_size >
|
|
|
|
(KMALLOC_MAX_SIZE - sizeof(*indir)) / sizeof(*indir->ring_index))
|
|
|
|
return -ENOMEM;
|
|
|
|
full_size = sizeof(*indir) + sizeof(*indir->ring_index) * table_size;
|
|
|
|
indir = kmalloc(full_size, GFP_USER);
|
|
|
|
if (!indir)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
if (copy_from_user(indir, useraddr, full_size)) {
|
|
|
|
ret = -EFAULT;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = dev->ethtool_ops->set_rxfh_indir(dev, indir);
|
|
|
|
|
|
|
|
out:
|
|
|
|
kfree(indir);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2010-02-12 21:48:05 +08:00
|
|
|
static void __rx_ntuple_filter_add(struct ethtool_rx_ntuple_list *list,
|
2010-04-08 12:54:42 +08:00
|
|
|
struct ethtool_rx_ntuple_flow_spec *spec,
|
|
|
|
struct ethtool_rx_ntuple_flow_spec_container *fsc)
|
2010-02-11 12:03:05 +08:00
|
|
|
{
|
|
|
|
|
|
|
|
/* don't add filters forever */
|
2010-02-12 21:48:05 +08:00
|
|
|
if (list->count >= ETHTOOL_MAX_NTUPLE_LIST_ENTRY) {
|
|
|
|
/* free the container */
|
|
|
|
kfree(fsc);
|
|
|
|
return;
|
|
|
|
}
|
2010-02-11 12:03:05 +08:00
|
|
|
|
|
|
|
/* Copy the whole filter over */
|
|
|
|
fsc->fs.flow_type = spec->flow_type;
|
|
|
|
memcpy(&fsc->fs.h_u, &spec->h_u, sizeof(spec->h_u));
|
|
|
|
memcpy(&fsc->fs.m_u, &spec->m_u, sizeof(spec->m_u));
|
|
|
|
|
|
|
|
fsc->fs.vlan_tag = spec->vlan_tag;
|
|
|
|
fsc->fs.vlan_tag_mask = spec->vlan_tag_mask;
|
|
|
|
fsc->fs.data = spec->data;
|
|
|
|
fsc->fs.data_mask = spec->data_mask;
|
|
|
|
fsc->fs.action = spec->action;
|
|
|
|
|
|
|
|
/* add to the list */
|
|
|
|
list_add_tail_rcu(&fsc->list, &list->list);
|
|
|
|
list->count++;
|
|
|
|
}
|
|
|
|
|
2010-09-16 19:28:07 +08:00
|
|
|
/*
|
|
|
|
* ethtool does not (or did not) set masks for flow parameters that are
|
|
|
|
* not specified, so if both value and mask are 0 then this must be
|
|
|
|
* treated as equivalent to a mask with all bits set. Implement that
|
|
|
|
* here rather than in drivers.
|
|
|
|
*/
|
|
|
|
static void rx_ntuple_fix_masks(struct ethtool_rx_ntuple_flow_spec *fs)
|
|
|
|
{
|
|
|
|
struct ethtool_tcpip4_spec *entry = &fs->h_u.tcp_ip4_spec;
|
|
|
|
struct ethtool_tcpip4_spec *mask = &fs->m_u.tcp_ip4_spec;
|
|
|
|
|
|
|
|
if (fs->flow_type != TCP_V4_FLOW &&
|
|
|
|
fs->flow_type != UDP_V4_FLOW &&
|
|
|
|
fs->flow_type != SCTP_V4_FLOW)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!(entry->ip4src | mask->ip4src))
|
|
|
|
mask->ip4src = htonl(0xffffffff);
|
|
|
|
if (!(entry->ip4dst | mask->ip4dst))
|
|
|
|
mask->ip4dst = htonl(0xffffffff);
|
|
|
|
if (!(entry->psrc | mask->psrc))
|
|
|
|
mask->psrc = htons(0xffff);
|
|
|
|
if (!(entry->pdst | mask->pdst))
|
|
|
|
mask->pdst = htons(0xffff);
|
|
|
|
if (!(entry->tos | mask->tos))
|
|
|
|
mask->tos = 0xff;
|
|
|
|
if (!(fs->vlan_tag | fs->vlan_tag_mask))
|
|
|
|
fs->vlan_tag_mask = 0xffff;
|
|
|
|
if (!(fs->data | fs->data_mask))
|
|
|
|
fs->data_mask = 0xffffffffffffffffULL;
|
|
|
|
}
|
|
|
|
|
2010-04-08 12:54:42 +08:00
|
|
|
static noinline_for_stack int ethtool_set_rx_ntuple(struct net_device *dev,
|
|
|
|
void __user *useraddr)
|
2010-02-11 12:03:05 +08:00
|
|
|
{
|
|
|
|
struct ethtool_rx_ntuple cmd;
|
|
|
|
const struct ethtool_ops *ops = dev->ethtool_ops;
|
2010-02-12 21:48:05 +08:00
|
|
|
struct ethtool_rx_ntuple_flow_spec_container *fsc = NULL;
|
2010-02-11 12:03:05 +08:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!(dev->features & NETIF_F_NTUPLE))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (copy_from_user(&cmd, useraddr, sizeof(cmd)))
|
|
|
|
return -EFAULT;
|
|
|
|
|
2010-09-16 19:28:07 +08:00
|
|
|
rx_ntuple_fix_masks(&cmd.fs);
|
|
|
|
|
2010-02-11 12:03:05 +08:00
|
|
|
/*
|
|
|
|
* Cache filter in dev struct for GET operation only if
|
|
|
|
* the underlying driver doesn't have its own GET operation, and
|
2010-02-12 21:48:05 +08:00
|
|
|
* only if the filter was added successfully. First make sure we
|
|
|
|
* can allocate the filter, then continue if successful.
|
2010-02-11 12:03:05 +08:00
|
|
|
*/
|
2010-02-12 21:48:05 +08:00
|
|
|
if (!ops->get_rx_ntuple) {
|
|
|
|
fsc = kmalloc(sizeof(*fsc), GFP_ATOMIC);
|
|
|
|
if (!fsc)
|
2010-02-11 12:03:05 +08:00
|
|
|
return -ENOMEM;
|
2010-02-12 21:48:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
ret = ops->set_rx_ntuple(dev, &cmd);
|
|
|
|
if (ret) {
|
|
|
|
kfree(fsc);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ops->get_rx_ntuple)
|
|
|
|
__rx_ntuple_filter_add(&dev->ethtool_ntuple_list, &cmd.fs, fsc);
|
2010-02-11 12:03:05 +08:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ethtool_get_rx_ntuple(struct net_device *dev, void __user *useraddr)
|
|
|
|
{
|
|
|
|
struct ethtool_gstrings gstrings;
|
|
|
|
const struct ethtool_ops *ops = dev->ethtool_ops;
|
|
|
|
struct ethtool_rx_ntuple_flow_spec_container *fsc;
|
|
|
|
u8 *data;
|
|
|
|
char *p;
|
|
|
|
int ret, i, num_strings = 0;
|
|
|
|
|
|
|
|
if (!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);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
gstrings.len = ret;
|
|
|
|
|
2010-10-12 03:23:25 +08:00
|
|
|
data = kzalloc(gstrings.len * ETH_GSTRING_LEN, GFP_USER);
|
2010-02-11 12:03:05 +08:00
|
|
|
if (!data)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
if (ops->get_rx_ntuple) {
|
|
|
|
/* driver-specific filter grab */
|
|
|
|
ret = ops->get_rx_ntuple(dev, gstrings.string_set, data);
|
|
|
|
goto copy;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* default ethtool filter grab */
|
|
|
|
i = 0;
|
|
|
|
p = (char *)data;
|
|
|
|
list_for_each_entry(fsc, &dev->ethtool_ntuple_list.list, list) {
|
|
|
|
sprintf(p, "Filter %d:\n", i);
|
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
|
|
|
|
switch (fsc->fs.flow_type) {
|
|
|
|
case TCP_V4_FLOW:
|
|
|
|
sprintf(p, "\tFlow Type: TCP\n");
|
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
break;
|
|
|
|
case UDP_V4_FLOW:
|
|
|
|
sprintf(p, "\tFlow Type: UDP\n");
|
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
break;
|
|
|
|
case SCTP_V4_FLOW:
|
|
|
|
sprintf(p, "\tFlow Type: SCTP\n");
|
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
break;
|
|
|
|
case AH_ESP_V4_FLOW:
|
|
|
|
sprintf(p, "\tFlow Type: AH ESP\n");
|
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
break;
|
|
|
|
case ESP_V4_FLOW:
|
|
|
|
sprintf(p, "\tFlow Type: ESP\n");
|
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
break;
|
|
|
|
case IP_USER_FLOW:
|
|
|
|
sprintf(p, "\tFlow Type: Raw IP\n");
|
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
break;
|
|
|
|
case IPV4_FLOW:
|
|
|
|
sprintf(p, "\tFlow Type: IPv4\n");
|
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
sprintf(p, "\tFlow Type: Unknown\n");
|
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
goto unknown_filter;
|
2010-05-14 18:58:26 +08:00
|
|
|
}
|
2010-02-11 12:03:05 +08:00
|
|
|
|
|
|
|
/* now the rest of the filters */
|
|
|
|
switch (fsc->fs.flow_type) {
|
|
|
|
case TCP_V4_FLOW:
|
|
|
|
case UDP_V4_FLOW:
|
|
|
|
case SCTP_V4_FLOW:
|
|
|
|
sprintf(p, "\tSrc IP addr: 0x%x\n",
|
2010-04-08 12:54:42 +08:00
|
|
|
fsc->fs.h_u.tcp_ip4_spec.ip4src);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
sprintf(p, "\tSrc IP mask: 0x%x\n",
|
2010-04-08 12:54:42 +08:00
|
|
|
fsc->fs.m_u.tcp_ip4_spec.ip4src);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
sprintf(p, "\tDest IP addr: 0x%x\n",
|
2010-04-08 12:54:42 +08:00
|
|
|
fsc->fs.h_u.tcp_ip4_spec.ip4dst);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
sprintf(p, "\tDest IP mask: 0x%x\n",
|
2010-04-08 12:54:42 +08:00
|
|
|
fsc->fs.m_u.tcp_ip4_spec.ip4dst);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
sprintf(p, "\tSrc Port: %d, mask: 0x%x\n",
|
2010-04-08 12:54:42 +08:00
|
|
|
fsc->fs.h_u.tcp_ip4_spec.psrc,
|
|
|
|
fsc->fs.m_u.tcp_ip4_spec.psrc);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
sprintf(p, "\tDest Port: %d, mask: 0x%x\n",
|
2010-04-08 12:54:42 +08:00
|
|
|
fsc->fs.h_u.tcp_ip4_spec.pdst,
|
|
|
|
fsc->fs.m_u.tcp_ip4_spec.pdst);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
sprintf(p, "\tTOS: %d, mask: 0x%x\n",
|
2010-04-08 12:54:42 +08:00
|
|
|
fsc->fs.h_u.tcp_ip4_spec.tos,
|
|
|
|
fsc->fs.m_u.tcp_ip4_spec.tos);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
break;
|
|
|
|
case AH_ESP_V4_FLOW:
|
|
|
|
case ESP_V4_FLOW:
|
|
|
|
sprintf(p, "\tSrc IP addr: 0x%x\n",
|
2010-04-08 12:54:42 +08:00
|
|
|
fsc->fs.h_u.ah_ip4_spec.ip4src);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
sprintf(p, "\tSrc IP mask: 0x%x\n",
|
2010-04-08 12:54:42 +08:00
|
|
|
fsc->fs.m_u.ah_ip4_spec.ip4src);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
sprintf(p, "\tDest IP addr: 0x%x\n",
|
2010-04-08 12:54:42 +08:00
|
|
|
fsc->fs.h_u.ah_ip4_spec.ip4dst);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
sprintf(p, "\tDest IP mask: 0x%x\n",
|
2010-04-08 12:54:42 +08:00
|
|
|
fsc->fs.m_u.ah_ip4_spec.ip4dst);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
sprintf(p, "\tSPI: %d, mask: 0x%x\n",
|
2010-04-08 12:54:42 +08:00
|
|
|
fsc->fs.h_u.ah_ip4_spec.spi,
|
|
|
|
fsc->fs.m_u.ah_ip4_spec.spi);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
sprintf(p, "\tTOS: %d, mask: 0x%x\n",
|
2010-04-08 12:54:42 +08:00
|
|
|
fsc->fs.h_u.ah_ip4_spec.tos,
|
|
|
|
fsc->fs.m_u.ah_ip4_spec.tos);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
break;
|
|
|
|
case IP_USER_FLOW:
|
|
|
|
sprintf(p, "\tSrc IP addr: 0x%x\n",
|
2010-09-14 17:13:08 +08:00
|
|
|
fsc->fs.h_u.usr_ip4_spec.ip4src);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
sprintf(p, "\tSrc IP mask: 0x%x\n",
|
2010-09-14 17:13:08 +08:00
|
|
|
fsc->fs.m_u.usr_ip4_spec.ip4src);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
sprintf(p, "\tDest IP addr: 0x%x\n",
|
2010-09-14 17:13:08 +08:00
|
|
|
fsc->fs.h_u.usr_ip4_spec.ip4dst);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
sprintf(p, "\tDest IP mask: 0x%x\n",
|
2010-09-14 17:13:08 +08:00
|
|
|
fsc->fs.m_u.usr_ip4_spec.ip4dst);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
break;
|
|
|
|
case IPV4_FLOW:
|
|
|
|
sprintf(p, "\tSrc IP addr: 0x%x\n",
|
2010-04-08 12:54:42 +08:00
|
|
|
fsc->fs.h_u.usr_ip4_spec.ip4src);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
sprintf(p, "\tSrc IP mask: 0x%x\n",
|
2010-04-08 12:54:42 +08:00
|
|
|
fsc->fs.m_u.usr_ip4_spec.ip4src);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
sprintf(p, "\tDest IP addr: 0x%x\n",
|
2010-04-08 12:54:42 +08:00
|
|
|
fsc->fs.h_u.usr_ip4_spec.ip4dst);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
sprintf(p, "\tDest IP mask: 0x%x\n",
|
2010-04-08 12:54:42 +08:00
|
|
|
fsc->fs.m_u.usr_ip4_spec.ip4dst);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
sprintf(p, "\tL4 bytes: 0x%x, mask: 0x%x\n",
|
2010-04-08 12:54:42 +08:00
|
|
|
fsc->fs.h_u.usr_ip4_spec.l4_4_bytes,
|
|
|
|
fsc->fs.m_u.usr_ip4_spec.l4_4_bytes);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
sprintf(p, "\tTOS: %d, mask: 0x%x\n",
|
2010-04-08 12:54:42 +08:00
|
|
|
fsc->fs.h_u.usr_ip4_spec.tos,
|
|
|
|
fsc->fs.m_u.usr_ip4_spec.tos);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
sprintf(p, "\tIP Version: %d, mask: 0x%x\n",
|
2010-04-08 12:54:42 +08:00
|
|
|
fsc->fs.h_u.usr_ip4_spec.ip_ver,
|
|
|
|
fsc->fs.m_u.usr_ip4_spec.ip_ver);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
sprintf(p, "\tProtocol: %d, mask: 0x%x\n",
|
2010-04-08 12:54:42 +08:00
|
|
|
fsc->fs.h_u.usr_ip4_spec.proto,
|
|
|
|
fsc->fs.m_u.usr_ip4_spec.proto);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
break;
|
2010-05-14 18:58:26 +08:00
|
|
|
}
|
2010-02-11 12:03:05 +08:00
|
|
|
sprintf(p, "\tVLAN: %d, mask: 0x%x\n",
|
2010-04-08 12:54:42 +08:00
|
|
|
fsc->fs.vlan_tag, fsc->fs.vlan_tag_mask);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
sprintf(p, "\tUser-defined: 0x%Lx\n", fsc->fs.data);
|
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
sprintf(p, "\tUser-defined mask: 0x%Lx\n", fsc->fs.data_mask);
|
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
if (fsc->fs.action == ETHTOOL_RXNTUPLE_ACTION_DROP)
|
|
|
|
sprintf(p, "\tAction: Drop\n");
|
|
|
|
else
|
|
|
|
sprintf(p, "\tAction: Direct to queue %d\n",
|
2010-04-08 12:54:42 +08:00
|
|
|
fsc->fs.action);
|
2010-02-11 12:03:05 +08:00
|
|
|
p += ETH_GSTRING_LEN;
|
|
|
|
num_strings++;
|
|
|
|
unknown_filter:
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
copy:
|
|
|
|
/* indicate to userspace how many strings we actually have */
|
|
|
|
gstrings.len = num_strings;
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
static int ethtool_get_regs(struct net_device *dev, char __user *useraddr)
|
|
|
|
{
|
|
|
|
struct ethtool_regs regs;
|
2006-09-09 02:16:13 +08:00
|
|
|
const struct ethtool_ops *ops = dev->ethtool_ops;
|
2005-04-17 06:20:36 +08:00
|
|
|
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;
|
|
|
|
|
2011-01-25 13:05:17 +08:00
|
|
|
regbuf = vzalloc(reglen);
|
2005-04-17 06:20:36 +08:00
|
|
|
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:
|
2010-09-20 16:42:17 +08:00
|
|
|
vfree(regbuf);
|
2005-04-17 06:20:36 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2009-10-05 18:59:58 +08:00
|
|
|
static int ethtool_reset(struct net_device *dev, char __user *useraddr)
|
|
|
|
{
|
|
|
|
struct ethtool_value reset;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!dev->ethtool_ops->reset)
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
|
|
|
if (copy_from_user(&reset, useraddr, sizeof(reset)))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
ret = dev->ethtool_ops->reset(dev, &reset.data);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
if (copy_to_user(useraddr, &reset, sizeof(reset)))
|
|
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
static int ethtool_get_wol(struct net_device *dev, char __user *useraddr)
|
|
|
|
{
|
2010-02-12 04:14:23 +08:00
|
|
|
struct ethtool_wolinfo wol = { .cmd = ETHTOOL_GWOL };
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
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_nway_reset(struct net_device *dev)
|
|
|
|
{
|
|
|
|
if (!dev->ethtool_ops->nway_reset)
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
|
|
|
return dev->ethtool_ops->nway_reset(dev);
|
|
|
|
}
|
|
|
|
|
2010-12-09 20:08:35 +08:00
|
|
|
static int ethtool_get_link(struct net_device *dev, char __user *useraddr)
|
|
|
|
{
|
|
|
|
struct ethtool_value edata = { .cmd = ETHTOOL_GLINK };
|
|
|
|
|
|
|
|
if (!dev->ethtool_ops->get_link)
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
|
|
|
edata.data = netif_running(dev) && dev->ethtool_ops->get_link(dev);
|
|
|
|
|
|
|
|
if (copy_to_user(useraddr, &edata, sizeof(edata)))
|
|
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
static int ethtool_get_eeprom(struct net_device *dev, void __user *useraddr)
|
|
|
|
{
|
|
|
|
struct ethtool_eeprom eeprom;
|
2006-09-09 02:16:13 +08:00
|
|
|
const struct ethtool_ops *ops = dev->ethtool_ops;
|
2008-04-16 10:24:17 +08:00
|
|
|
void __user *userbuf = useraddr + sizeof(eeprom);
|
|
|
|
u32 bytes_remaining;
|
2005-04-17 06:20:36 +08:00
|
|
|
u8 *data;
|
2008-04-16 10:24:17 +08:00
|
|
|
int ret = 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2008-04-16 10:24:17 +08:00
|
|
|
data = kmalloc(PAGE_SIZE, GFP_USER);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!data)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2008-04-16 10:24:17 +08:00
|
|
|
bytes_remaining = eeprom.len;
|
|
|
|
while (bytes_remaining > 0) {
|
|
|
|
eeprom.len = min(bytes_remaining, (u32)PAGE_SIZE);
|
|
|
|
|
|
|
|
ret = ops->get_eeprom(dev, &eeprom, data);
|
|
|
|
if (ret)
|
|
|
|
break;
|
|
|
|
if (copy_to_user(userbuf, data, eeprom.len)) {
|
|
|
|
ret = -EFAULT;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
userbuf += eeprom.len;
|
|
|
|
eeprom.offset += eeprom.len;
|
|
|
|
bytes_remaining -= eeprom.len;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-04-25 11:55:56 +08:00
|
|
|
eeprom.len = userbuf - (useraddr + sizeof(eeprom));
|
|
|
|
eeprom.offset -= eeprom.len;
|
|
|
|
if (copy_to_user(useraddr, &eeprom, sizeof(eeprom)))
|
|
|
|
ret = -EFAULT;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
kfree(data);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ethtool_set_eeprom(struct net_device *dev, void __user *useraddr)
|
|
|
|
{
|
|
|
|
struct ethtool_eeprom eeprom;
|
2006-09-09 02:16:13 +08:00
|
|
|
const struct ethtool_ops *ops = dev->ethtool_ops;
|
2008-04-16 10:24:17 +08:00
|
|
|
void __user *userbuf = useraddr + sizeof(eeprom);
|
|
|
|
u32 bytes_remaining;
|
2005-04-17 06:20:36 +08:00
|
|
|
u8 *data;
|
2008-04-16 10:24:17 +08:00
|
|
|
int ret = 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2008-04-16 10:24:17 +08:00
|
|
|
data = kmalloc(PAGE_SIZE, GFP_USER);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!data)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2008-04-16 10:24:17 +08:00
|
|
|
bytes_remaining = eeprom.len;
|
|
|
|
while (bytes_remaining > 0) {
|
|
|
|
eeprom.len = min(bytes_remaining, (u32)PAGE_SIZE);
|
|
|
|
|
|
|
|
if (copy_from_user(data, userbuf, eeprom.len)) {
|
|
|
|
ret = -EFAULT;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
ret = ops->set_eeprom(dev, &eeprom, data);
|
|
|
|
if (ret)
|
|
|
|
break;
|
|
|
|
userbuf += eeprom.len;
|
|
|
|
eeprom.offset += eeprom.len;
|
|
|
|
bytes_remaining -= eeprom.len;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
kfree(data);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2010-04-08 12:54:42 +08:00
|
|
|
static noinline_for_stack int ethtool_get_coalesce(struct net_device *dev,
|
|
|
|
void __user *useraddr)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2010-02-12 04:14:23 +08:00
|
|
|
struct ethtool_coalesce coalesce = { .cmd = ETHTOOL_GCOALESCE };
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2010-04-08 12:54:42 +08:00
|
|
|
static noinline_for_stack int ethtool_set_coalesce(struct net_device *dev,
|
|
|
|
void __user *useraddr)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct ethtool_coalesce coalesce;
|
|
|
|
|
2005-06-07 06:07:19 +08:00
|
|
|
if (!dev->ethtool_ops->set_coalesce)
|
2005-04-17 06:20:36 +08:00
|
|
|
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)
|
|
|
|
{
|
2010-02-12 04:14:23 +08:00
|
|
|
struct ethtool_ringparam ringparam = { .cmd = ETHTOOL_GRINGPARAM };
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2006-07-18 00:54:40 +08:00
|
|
|
if (!dev->ethtool_ops->set_pauseparam)
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
|
|
|
if (copy_from_user(&pauseparam, useraddr, sizeof(pauseparam)))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
return dev->ethtool_ops->set_pauseparam(dev, &pauseparam);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __ethtool_set_sg(struct net_device *dev, u32 data)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
2011-02-16 00:59:17 +08:00
|
|
|
if (data && !(dev->features & NETIF_F_ALL_CSUM))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!data && dev->ethtool_ops->set_tso) {
|
|
|
|
err = dev->ethtool_ops->set_tso(dev, 0);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2005-10-19 06:46:41 +08:00
|
|
|
if (!data && dev->ethtool_ops->set_ufo) {
|
|
|
|
err = dev->ethtool_ops->set_ufo(dev, 0);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
return dev->ethtool_ops->set_sg(dev, data);
|
|
|
|
}
|
|
|
|
|
2011-02-16 00:59:17 +08:00
|
|
|
static int __ethtool_set_tx_csum(struct net_device *dev, u32 data)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
|
|
|
if (!dev->ethtool_ops->set_tx_csum)
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
2011-02-16 00:59:17 +08:00
|
|
|
if (!data && dev->ethtool_ops->set_sg) {
|
2005-04-17 06:20:36 +08:00
|
|
|
err = __ethtool_set_sg(dev, 0);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2011-02-16 00:59:17 +08:00
|
|
|
return dev->ethtool_ops->set_tx_csum(dev, data);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2011-02-16 00:59:18 +08:00
|
|
|
static int __ethtool_set_rx_csum(struct net_device *dev, u32 data)
|
2008-12-16 15:44:31 +08:00
|
|
|
{
|
|
|
|
if (!dev->ethtool_ops->set_rx_csum)
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
2011-02-16 00:59:18 +08:00
|
|
|
if (!data)
|
2008-12-16 15:44:31 +08:00
|
|
|
dev->features &= ~NETIF_F_GRO;
|
|
|
|
|
2011-02-16 00:59:18 +08:00
|
|
|
return dev->ethtool_ops->set_rx_csum(dev, data);
|
2008-12-16 15:44:31 +08:00
|
|
|
}
|
|
|
|
|
2011-02-16 00:59:17 +08:00
|
|
|
static int __ethtool_set_tso(struct net_device *dev, u32 data)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
if (!dev->ethtool_ops->set_tso)
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
2011-02-16 00:59:17 +08:00
|
|
|
if (data && !(dev->features & NETIF_F_SG))
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
2011-02-16 00:59:17 +08:00
|
|
|
return dev->ethtool_ops->set_tso(dev, data);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2011-02-16 00:59:17 +08:00
|
|
|
static int __ethtool_set_ufo(struct net_device *dev, u32 data)
|
2005-10-19 06:46:41 +08:00
|
|
|
{
|
|
|
|
if (!dev->ethtool_ops->set_ufo)
|
|
|
|
return -EOPNOTSUPP;
|
2011-02-16 00:59:17 +08:00
|
|
|
if (data && !(dev->features & NETIF_F_SG))
|
2005-10-19 06:46:41 +08:00
|
|
|
return -EINVAL;
|
2011-02-16 00:59:17 +08:00
|
|
|
if (data && !((dev->features & NETIF_F_GEN_CSUM) ||
|
2010-11-30 14:38:00 +08:00
|
|
|
(dev->features & (NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM))
|
|
|
|
== (NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM)))
|
2005-10-19 06:46:41 +08:00
|
|
|
return -EINVAL;
|
2011-02-16 00:59:17 +08:00
|
|
|
return dev->ethtool_ops->set_ufo(dev, data);
|
2008-12-16 15:44:31 +08:00
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
static int ethtool_self_test(struct net_device *dev, char __user *useraddr)
|
|
|
|
{
|
|
|
|
struct ethtool_test test;
|
2006-09-09 02:16:13 +08:00
|
|
|
const struct ethtool_ops *ops = dev->ethtool_ops;
|
2005-04-17 06:20:36 +08:00
|
|
|
u64 *data;
|
2007-08-16 07:01:08 +08:00
|
|
|
int ret, test_len;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2009-10-01 19:33:03 +08:00
|
|
|
if (!ops->self_test || !ops->get_sset_count)
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
2009-10-01 19:33:03 +08:00
|
|
|
test_len = ops->get_sset_count(dev, ETH_SS_TEST);
|
2007-08-16 07:01:08 +08:00
|
|
|
if (test_len < 0)
|
|
|
|
return test_len;
|
|
|
|
WARN_ON(test_len == 0);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
if (copy_from_user(&test, useraddr, sizeof(test)))
|
|
|
|
return -EFAULT;
|
|
|
|
|
2007-08-16 07:01:08 +08:00
|
|
|
test.len = test_len;
|
|
|
|
data = kmalloc(test_len * sizeof(u64), GFP_USER);
|
2005-04-17 06:20:36 +08:00
|
|
|
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;
|
|
|
|
u8 *data;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (copy_from_user(&gstrings, useraddr, sizeof(gstrings)))
|
|
|
|
return -EFAULT;
|
|
|
|
|
2011-02-16 00:59:16 +08:00
|
|
|
ret = __ethtool_get_sset_count(dev, gstrings.string_set);
|
2009-10-01 19:33:03 +08:00
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
gstrings.len = ret;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
data = kmalloc(gstrings.len * ETH_GSTRING_LEN, GFP_USER);
|
|
|
|
if (!data)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2011-02-16 00:59:16 +08:00
|
|
|
__ethtool_get_strings(dev, gstrings.string_set, data);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2011-02-16 00:59:16 +08:00
|
|
|
out:
|
2005-04-17 06:20:36 +08:00
|
|
|
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;
|
2006-09-09 02:16:13 +08:00
|
|
|
const struct ethtool_ops *ops = dev->ethtool_ops;
|
2005-04-17 06:20:36 +08:00
|
|
|
u64 *data;
|
2007-08-16 07:01:08 +08:00
|
|
|
int ret, n_stats;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2009-10-01 19:33:03 +08:00
|
|
|
if (!ops->get_ethtool_stats || !ops->get_sset_count)
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
2009-10-01 19:33:03 +08:00
|
|
|
n_stats = ops->get_sset_count(dev, ETH_SS_STATS);
|
2007-08-16 07:01:08 +08:00
|
|
|
if (n_stats < 0)
|
|
|
|
return n_stats;
|
|
|
|
WARN_ON(n_stats == 0);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
if (copy_from_user(&stats, useraddr, sizeof(stats)))
|
|
|
|
return -EFAULT;
|
|
|
|
|
2007-08-16 07:01:08 +08:00
|
|
|
stats.n_stats = n_stats;
|
|
|
|
data = kmalloc(n_stats * sizeof(u64), GFP_USER);
|
2005-04-17 06:20:36 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2005-09-05 10:26:18 +08:00
|
|
|
static int ethtool_get_perm_addr(struct net_device *dev, void __user *useraddr)
|
2005-08-21 08:15:54 +08:00
|
|
|
{
|
|
|
|
struct ethtool_perm_addr epaddr;
|
|
|
|
|
2007-08-01 05:00:29 +08:00
|
|
|
if (copy_from_user(&epaddr, useraddr, sizeof(epaddr)))
|
2005-08-21 08:15:54 +08:00
|
|
|
return -EFAULT;
|
|
|
|
|
2007-08-01 05:00:29 +08:00
|
|
|
if (epaddr.size < dev->addr_len)
|
|
|
|
return -ETOOSMALL;
|
|
|
|
epaddr.size = dev->addr_len;
|
2005-08-21 08:15:54 +08:00
|
|
|
|
|
|
|
if (copy_to_user(useraddr, &epaddr, sizeof(epaddr)))
|
2007-08-01 05:00:29 +08:00
|
|
|
return -EFAULT;
|
2005-08-21 08:15:54 +08:00
|
|
|
useraddr += sizeof(epaddr);
|
2007-08-01 05:00:29 +08:00
|
|
|
if (copy_to_user(useraddr, dev->perm_addr, epaddr.size))
|
|
|
|
return -EFAULT;
|
|
|
|
return 0;
|
2005-08-21 08:15:54 +08:00
|
|
|
}
|
|
|
|
|
2007-08-16 07:01:56 +08:00
|
|
|
static int ethtool_get_value(struct net_device *dev, char __user *useraddr,
|
|
|
|
u32 cmd, u32 (*actor)(struct net_device *))
|
2007-08-16 07:00:51 +08:00
|
|
|
{
|
2010-02-12 04:14:23 +08:00
|
|
|
struct ethtool_value edata = { .cmd = cmd };
|
2007-08-16 07:00:51 +08:00
|
|
|
|
2007-08-16 07:01:56 +08:00
|
|
|
if (!actor)
|
2007-08-16 07:00:51 +08:00
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
2007-08-16 07:01:56 +08:00
|
|
|
edata.data = actor(dev);
|
2007-08-16 07:00:51 +08:00
|
|
|
|
|
|
|
if (copy_to_user(useraddr, &edata, sizeof(edata)))
|
|
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-08-16 07:01:56 +08:00
|
|
|
static int ethtool_set_value_void(struct net_device *dev, char __user *useraddr,
|
|
|
|
void (*actor)(struct net_device *, u32))
|
2007-08-16 07:00:51 +08:00
|
|
|
{
|
|
|
|
struct ethtool_value edata;
|
|
|
|
|
2007-08-16 07:01:56 +08:00
|
|
|
if (!actor)
|
2007-08-16 07:00:51 +08:00
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
|
|
|
if (copy_from_user(&edata, useraddr, sizeof(edata)))
|
|
|
|
return -EFAULT;
|
|
|
|
|
2007-08-16 07:01:56 +08:00
|
|
|
actor(dev, edata.data);
|
2007-08-16 07:01:32 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-08-16 07:01:56 +08:00
|
|
|
static int ethtool_set_value(struct net_device *dev, char __user *useraddr,
|
|
|
|
int (*actor)(struct net_device *, u32))
|
2007-08-16 07:01:32 +08:00
|
|
|
{
|
|
|
|
struct ethtool_value edata;
|
|
|
|
|
2007-08-16 07:01:56 +08:00
|
|
|
if (!actor)
|
2007-08-16 07:01:32 +08:00
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
|
|
|
if (copy_from_user(&edata, useraddr, sizeof(edata)))
|
|
|
|
return -EFAULT;
|
|
|
|
|
2007-08-16 07:01:56 +08:00
|
|
|
return actor(dev, edata.data);
|
2007-08-16 07:01:32 +08:00
|
|
|
}
|
|
|
|
|
2010-04-08 12:54:42 +08:00
|
|
|
static noinline_for_stack int ethtool_flash_device(struct net_device *dev,
|
|
|
|
char __user *useraddr)
|
2009-09-03 01:02:55 +08:00
|
|
|
{
|
|
|
|
struct ethtool_flash efl;
|
|
|
|
|
|
|
|
if (copy_from_user(&efl, useraddr, sizeof(efl)))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
if (!dev->ethtool_ops->flash_device)
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
|
|
|
return dev->ethtool_ops->flash_device(dev, &efl);
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/* The main entry point in this file. Called from net/core/dev.c */
|
|
|
|
|
2007-09-18 02:56:21 +08:00
|
|
|
int dev_ethtool(struct net *net, struct ifreq *ifr)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2007-09-18 02:56:21 +08:00
|
|
|
struct net_device *dev = __dev_get_by_name(net, ifr->ifr_name);
|
2005-04-17 06:20:36 +08:00
|
|
|
void __user *useraddr = ifr->ifr_data;
|
|
|
|
u32 ethcmd;
|
|
|
|
int rc;
|
2011-01-25 07:32:47 +08:00
|
|
|
u32 old_features;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
if (!dev || !netif_device_present(dev))
|
|
|
|
return -ENODEV;
|
|
|
|
|
2010-04-08 12:54:42 +08:00
|
|
|
if (copy_from_user(ðcmd, useraddr, sizeof(ethcmd)))
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EFAULT;
|
|
|
|
|
2010-08-17 17:31:15 +08:00
|
|
|
if (!dev->ethtool_ops) {
|
|
|
|
/* ETHTOOL_GDRVINFO does not require any driver support.
|
|
|
|
* It is also unprivileged and does not change anything,
|
|
|
|
* so we can take a shortcut to it. */
|
|
|
|
if (ethcmd == ETHTOOL_GDRVINFO)
|
|
|
|
return ethtool_get_drvinfo(dev, useraddr);
|
|
|
|
else
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
}
|
|
|
|
|
2006-09-29 06:13:37 +08:00
|
|
|
/* Allow some commands to be done by anyone */
|
2010-04-08 12:54:42 +08:00
|
|
|
switch (ethcmd) {
|
2010-08-23 18:24:18 +08:00
|
|
|
case ETHTOOL_GSET:
|
2006-09-29 06:13:37 +08:00
|
|
|
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:
|
2010-02-11 21:48:29 +08:00
|
|
|
case ETHTOOL_GGRO:
|
2007-08-16 07:01:32 +08:00
|
|
|
case ETHTOOL_GFLAGS:
|
|
|
|
case ETHTOOL_GPFLAGS:
|
2008-07-02 18:47:41 +08:00
|
|
|
case ETHTOOL_GRXFH:
|
2009-02-20 16:58:13 +08:00
|
|
|
case ETHTOOL_GRXRINGS:
|
|
|
|
case ETHTOOL_GRXCLSRLCNT:
|
|
|
|
case ETHTOOL_GRXCLSRULE:
|
|
|
|
case ETHTOOL_GRXCLSRLALL:
|
2011-02-16 00:59:17 +08:00
|
|
|
case ETHTOOL_GFEATURES:
|
2006-09-29 06:13:37 +08:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
if (!capable(CAP_NET_ADMIN))
|
|
|
|
return -EPERM;
|
|
|
|
}
|
|
|
|
|
2010-04-08 12:54:42 +08:00
|
|
|
if (dev->ethtool_ops->begin) {
|
|
|
|
rc = dev->ethtool_ops->begin(dev);
|
|
|
|
if (rc < 0)
|
2005-04-17 06:20:36 +08:00
|
|
|
return rc;
|
2010-04-08 12:54:42 +08:00
|
|
|
}
|
2005-05-30 05:13:47 +08:00
|
|
|
old_features = dev->features;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
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:
|
2007-08-16 07:01:56 +08:00
|
|
|
rc = ethtool_get_value(dev, useraddr, ethcmd,
|
|
|
|
dev->ethtool_ops->get_msglevel);
|
2005-04-17 06:20:36 +08:00
|
|
|
break;
|
|
|
|
case ETHTOOL_SMSGLVL:
|
2007-08-16 07:01:56 +08:00
|
|
|
rc = ethtool_set_value_void(dev, useraddr,
|
|
|
|
dev->ethtool_ops->set_msglevel);
|
2005-04-17 06:20:36 +08:00
|
|
|
break;
|
|
|
|
case ETHTOOL_NWAY_RST:
|
|
|
|
rc = ethtool_nway_reset(dev);
|
|
|
|
break;
|
|
|
|
case ETHTOOL_GLINK:
|
2010-12-09 20:08:35 +08:00
|
|
|
rc = ethtool_get_link(dev, useraddr);
|
2005-04-17 06:20:36 +08:00
|
|
|
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_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;
|
2005-08-21 08:15:54 +08:00
|
|
|
case ETHTOOL_GPERMADDR:
|
|
|
|
rc = ethtool_get_perm_addr(dev, useraddr);
|
|
|
|
break;
|
2007-08-16 07:00:51 +08:00
|
|
|
case ETHTOOL_GFLAGS:
|
2007-08-16 07:01:56 +08:00
|
|
|
rc = ethtool_get_value(dev, useraddr, ethcmd,
|
2009-07-22 21:38:22 +08:00
|
|
|
(dev->ethtool_ops->get_flags ?
|
|
|
|
dev->ethtool_ops->get_flags :
|
|
|
|
ethtool_op_get_flags));
|
2007-08-16 07:00:51 +08:00
|
|
|
break;
|
|
|
|
case ETHTOOL_SFLAGS:
|
2011-02-16 00:59:18 +08:00
|
|
|
rc = ethtool_set_value(dev, useraddr, __ethtool_set_flags);
|
2007-08-16 07:00:51 +08:00
|
|
|
break;
|
2007-08-16 07:01:32 +08:00
|
|
|
case ETHTOOL_GPFLAGS:
|
2007-08-16 07:01:56 +08:00
|
|
|
rc = ethtool_get_value(dev, useraddr, ethcmd,
|
|
|
|
dev->ethtool_ops->get_priv_flags);
|
2007-08-16 07:01:32 +08:00
|
|
|
break;
|
|
|
|
case ETHTOOL_SPFLAGS:
|
2007-08-16 07:01:56 +08:00
|
|
|
rc = ethtool_set_value(dev, useraddr,
|
|
|
|
dev->ethtool_ops->set_priv_flags);
|
2007-08-16 07:01:32 +08:00
|
|
|
break;
|
2008-07-02 18:47:41 +08:00
|
|
|
case ETHTOOL_GRXFH:
|
2009-02-20 16:58:13 +08:00
|
|
|
case ETHTOOL_GRXRINGS:
|
|
|
|
case ETHTOOL_GRXCLSRLCNT:
|
|
|
|
case ETHTOOL_GRXCLSRULE:
|
|
|
|
case ETHTOOL_GRXCLSRLALL:
|
2010-06-28 16:45:58 +08:00
|
|
|
rc = ethtool_get_rxnfc(dev, ethcmd, useraddr);
|
2008-07-02 18:47:41 +08:00
|
|
|
break;
|
|
|
|
case ETHTOOL_SRXFH:
|
2009-02-20 16:58:13 +08:00
|
|
|
case ETHTOOL_SRXCLSRLDEL:
|
|
|
|
case ETHTOOL_SRXCLSRLINS:
|
2010-06-28 16:45:58 +08:00
|
|
|
rc = ethtool_set_rxnfc(dev, ethcmd, useraddr);
|
2008-07-02 18:47:41 +08:00
|
|
|
break;
|
2009-09-03 01:02:55 +08:00
|
|
|
case ETHTOOL_FLASHDEV:
|
|
|
|
rc = ethtool_flash_device(dev, useraddr);
|
|
|
|
break;
|
2009-10-05 18:59:58 +08:00
|
|
|
case ETHTOOL_RESET:
|
|
|
|
rc = ethtool_reset(dev, useraddr);
|
|
|
|
break;
|
2010-02-11 12:03:05 +08:00
|
|
|
case ETHTOOL_SRXNTUPLE:
|
|
|
|
rc = ethtool_set_rx_ntuple(dev, useraddr);
|
|
|
|
break;
|
|
|
|
case ETHTOOL_GRXNTUPLE:
|
|
|
|
rc = ethtool_get_rx_ntuple(dev, useraddr);
|
|
|
|
break;
|
2010-03-04 06:51:50 +08:00
|
|
|
case ETHTOOL_GSSET_INFO:
|
|
|
|
rc = ethtool_get_sset_info(dev, useraddr);
|
|
|
|
break;
|
2010-06-30 13:05:23 +08:00
|
|
|
case ETHTOOL_GRXFHINDIR:
|
|
|
|
rc = ethtool_get_rxfh_indir(dev, useraddr);
|
|
|
|
break;
|
|
|
|
case ETHTOOL_SRXFHINDIR:
|
|
|
|
rc = ethtool_set_rxfh_indir(dev, useraddr);
|
|
|
|
break;
|
2011-02-16 00:59:17 +08:00
|
|
|
case ETHTOOL_GFEATURES:
|
|
|
|
rc = ethtool_get_features(dev, useraddr);
|
|
|
|
break;
|
|
|
|
case ETHTOOL_SFEATURES:
|
|
|
|
rc = ethtool_set_features(dev, useraddr);
|
|
|
|
break;
|
2011-02-16 00:59:17 +08:00
|
|
|
case ETHTOOL_GTXCSUM:
|
2011-02-16 00:59:18 +08:00
|
|
|
case ETHTOOL_GRXCSUM:
|
2011-02-16 00:59:17 +08:00
|
|
|
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:
|
2011-02-16 00:59:18 +08:00
|
|
|
case ETHTOOL_SRXCSUM:
|
2011-02-16 00:59:17 +08:00
|
|
|
case ETHTOOL_SSG:
|
|
|
|
case ETHTOOL_STSO:
|
|
|
|
case ETHTOOL_SUFO:
|
|
|
|
case ETHTOOL_SGSO:
|
|
|
|
case ETHTOOL_SGRO:
|
|
|
|
rc = ethtool_set_one_feature(dev, useraddr, ethcmd);
|
|
|
|
break;
|
2005-04-17 06:20:36 +08:00
|
|
|
default:
|
2007-08-01 05:00:02 +08:00
|
|
|
rc = -EOPNOTSUPP;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2007-02-09 22:24:36 +08:00
|
|
|
|
2007-04-11 11:10:33 +08:00
|
|
|
if (dev->ethtool_ops->complete)
|
2005-04-17 06:20:36 +08:00
|
|
|
dev->ethtool_ops->complete(dev);
|
2005-05-30 05:13:47 +08:00
|
|
|
|
|
|
|
if (old_features != dev->features)
|
|
|
|
netdev_features_change(dev);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
return rc;
|
|
|
|
}
|