diff options
Diffstat (limited to 'net/core/ethtool.c')
| -rw-r--r-- | net/core/ethtool.c | 1553 | 
1 files changed, 865 insertions, 688 deletions
diff --git a/net/core/ethtool.c b/net/core/ethtool.c index d5bc2881888..17cb912793f 100644 --- a/net/core/ethtool.c +++ b/net/core/ethtool.c @@ -17,10 +17,14 @@  #include <linux/errno.h>  #include <linux/ethtool.h>  #include <linux/netdevice.h> +#include <linux/net_tstamp.h> +#include <linux/phy.h>  #include <linux/bitops.h>  #include <linux/uaccess.h>  #include <linux/vmalloc.h>  #include <linux/slab.h> +#include <linux/rtnetlink.h> +#include <linux/sched.h>  /*   * Some useful ethtool_ops methods that're device independent. @@ -34,152 +38,315 @@ u32 ethtool_op_get_link(struct net_device *dev)  }  EXPORT_SYMBOL(ethtool_op_get_link); -u32 ethtool_op_get_rx_csum(struct net_device *dev) +int ethtool_op_get_ts_info(struct net_device *dev, struct ethtool_ts_info *info)  { -	return (dev->features & NETIF_F_ALL_CSUM) != 0; +	info->so_timestamping = +		SOF_TIMESTAMPING_TX_SOFTWARE | +		SOF_TIMESTAMPING_RX_SOFTWARE | +		SOF_TIMESTAMPING_SOFTWARE; +	info->phc_index = -1; +	return 0;  } -EXPORT_SYMBOL(ethtool_op_get_rx_csum); +EXPORT_SYMBOL(ethtool_op_get_ts_info); -u32 ethtool_op_get_tx_csum(struct net_device *dev) -{ -	return (dev->features & NETIF_F_ALL_CSUM) != 0; -} -EXPORT_SYMBOL(ethtool_op_get_tx_csum); +/* Handlers for each ethtool command */ -int ethtool_op_set_tx_csum(struct net_device *dev, u32 data) +#define ETHTOOL_DEV_FEATURE_WORDS	((NETDEV_FEATURE_COUNT + 31) / 32) + +static const char netdev_features_strings[NETDEV_FEATURE_COUNT][ETH_GSTRING_LEN] = { +	[NETIF_F_SG_BIT] =               "tx-scatter-gather", +	[NETIF_F_IP_CSUM_BIT] =          "tx-checksum-ipv4", +	[NETIF_F_HW_CSUM_BIT] =          "tx-checksum-ip-generic", +	[NETIF_F_IPV6_CSUM_BIT] =        "tx-checksum-ipv6", +	[NETIF_F_HIGHDMA_BIT] =          "highdma", +	[NETIF_F_FRAGLIST_BIT] =         "tx-scatter-gather-fraglist", +	[NETIF_F_HW_VLAN_CTAG_TX_BIT] =  "tx-vlan-hw-insert", + +	[NETIF_F_HW_VLAN_CTAG_RX_BIT] =  "rx-vlan-hw-parse", +	[NETIF_F_HW_VLAN_CTAG_FILTER_BIT] = "rx-vlan-filter", +	[NETIF_F_HW_VLAN_STAG_TX_BIT] =  "tx-vlan-stag-hw-insert", +	[NETIF_F_HW_VLAN_STAG_RX_BIT] =  "rx-vlan-stag-hw-parse", +	[NETIF_F_HW_VLAN_STAG_FILTER_BIT] = "rx-vlan-stag-filter", +	[NETIF_F_VLAN_CHALLENGED_BIT] =  "vlan-challenged", +	[NETIF_F_GSO_BIT] =              "tx-generic-segmentation", +	[NETIF_F_LLTX_BIT] =             "tx-lockless", +	[NETIF_F_NETNS_LOCAL_BIT] =      "netns-local", +	[NETIF_F_GRO_BIT] =              "rx-gro", +	[NETIF_F_LRO_BIT] =              "rx-lro", + +	[NETIF_F_TSO_BIT] =              "tx-tcp-segmentation", +	[NETIF_F_UFO_BIT] =              "tx-udp-fragmentation", +	[NETIF_F_GSO_ROBUST_BIT] =       "tx-gso-robust", +	[NETIF_F_TSO_ECN_BIT] =          "tx-tcp-ecn-segmentation", +	[NETIF_F_TSO6_BIT] =             "tx-tcp6-segmentation", +	[NETIF_F_FSO_BIT] =              "tx-fcoe-segmentation", +	[NETIF_F_GSO_GRE_BIT] =		 "tx-gre-segmentation", +	[NETIF_F_GSO_IPIP_BIT] =	 "tx-ipip-segmentation", +	[NETIF_F_GSO_SIT_BIT] =		 "tx-sit-segmentation", +	[NETIF_F_GSO_UDP_TUNNEL_BIT] =	 "tx-udp_tnl-segmentation", +	[NETIF_F_GSO_MPLS_BIT] =	 "tx-mpls-segmentation", + +	[NETIF_F_FCOE_CRC_BIT] =         "tx-checksum-fcoe-crc", +	[NETIF_F_SCTP_CSUM_BIT] =        "tx-checksum-sctp", +	[NETIF_F_FCOE_MTU_BIT] =         "fcoe-mtu", +	[NETIF_F_NTUPLE_BIT] =           "rx-ntuple-filter", +	[NETIF_F_RXHASH_BIT] =           "rx-hashing", +	[NETIF_F_RXCSUM_BIT] =           "rx-checksum", +	[NETIF_F_NOCACHE_COPY_BIT] =     "tx-nocache-copy", +	[NETIF_F_LOOPBACK_BIT] =         "loopback", +	[NETIF_F_RXFCS_BIT] =            "rx-fcs", +	[NETIF_F_RXALL_BIT] =            "rx-all", +	[NETIF_F_HW_L2FW_DOFFLOAD_BIT] = "l2-fwd-offload", +	[NETIF_F_BUSY_POLL_BIT] =        "busy-poll", +}; + +static int ethtool_get_features(struct net_device *dev, void __user *useraddr)  { -	if (data) -		dev->features |= NETIF_F_IP_CSUM; -	else -		dev->features &= ~NETIF_F_IP_CSUM; +	struct ethtool_gfeatures cmd = { +		.cmd = ETHTOOL_GFEATURES, +		.size = ETHTOOL_DEV_FEATURE_WORDS, +	}; +	struct ethtool_get_features_block features[ETHTOOL_DEV_FEATURE_WORDS]; +	u32 __user *sizeaddr; +	u32 copy_size; +	int i; + +	/* in case feature bits run out again */ +	BUILD_BUG_ON(ETHTOOL_DEV_FEATURE_WORDS * sizeof(u32) > sizeof(netdev_features_t)); + +	for (i = 0; i < ETHTOOL_DEV_FEATURE_WORDS; ++i) { +		features[i].available = (u32)(dev->hw_features >> (32 * i)); +		features[i].requested = (u32)(dev->wanted_features >> (32 * i)); +		features[i].active = (u32)(dev->features >> (32 * i)); +		features[i].never_changed = +			(u32)(NETIF_F_NEVER_CHANGE >> (32 * i)); +	} -	return 0; -} +	sizeaddr = useraddr + offsetof(struct ethtool_gfeatures, size); +	if (get_user(copy_size, sizeaddr)) +		return -EFAULT; -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; +	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;  } -EXPORT_SYMBOL(ethtool_op_set_tx_hw_csum); -int ethtool_op_set_tx_ipv6_csum(struct net_device *dev, u32 data) +static int ethtool_set_features(struct net_device *dev, void __user *useraddr)  { -	if (data) -		dev->features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM; -	else -		dev->features &= ~(NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM); +	struct ethtool_sfeatures cmd; +	struct ethtool_set_features_block features[ETHTOOL_DEV_FEATURE_WORDS]; +	netdev_features_t wanted = 0, valid = 0; +	int i, ret = 0; -	return 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; + +	for (i = 0; i < ETHTOOL_DEV_FEATURE_WORDS; ++i) { +		valid |= (netdev_features_t)features[i].valid << (32 * i); +		wanted |= (netdev_features_t)features[i].requested << (32 * i); +	} + +	if (valid & ~NETIF_F_ETHTOOL_BITS) +		return -EINVAL; + +	if (valid & ~dev->hw_features) { +		valid &= dev->hw_features; +		ret |= ETHTOOL_F_UNSUPPORTED; +	} + +	dev->wanted_features &= ~valid; +	dev->wanted_features |= wanted & valid; +	__netdev_update_features(dev); + +	if ((dev->wanted_features ^ dev->features) & valid) +		ret |= ETHTOOL_F_WISH; + +	return ret;  } -EXPORT_SYMBOL(ethtool_op_set_tx_ipv6_csum); -u32 ethtool_op_get_sg(struct net_device *dev) +static int __ethtool_get_sset_count(struct net_device *dev, int sset)  { -	return (dev->features & NETIF_F_SG) != 0; +	const struct ethtool_ops *ops = dev->ethtool_ops; + +	if (sset == ETH_SS_FEATURES) +		return ARRAY_SIZE(netdev_features_strings); + +	if (ops->get_sset_count && ops->get_strings) +		return ops->get_sset_count(dev, sset); +	else +		return -EOPNOTSUPP;  } -EXPORT_SYMBOL(ethtool_op_get_sg); -int ethtool_op_set_sg(struct net_device *dev, u32 data) +static void __ethtool_get_strings(struct net_device *dev, +	u32 stringset, u8 *data)  { -	if (data) -		dev->features |= NETIF_F_SG; -	else -		dev->features &= ~NETIF_F_SG; +	const struct ethtool_ops *ops = dev->ethtool_ops; -	return 0; +	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);  } -EXPORT_SYMBOL(ethtool_op_set_sg); -u32 ethtool_op_get_tso(struct net_device *dev) +static netdev_features_t ethtool_get_feature_mask(u32 eth_cmd)  { -	return (dev->features & NETIF_F_TSO) != 0; +	/* 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; +	case ETHTOOL_GRXCSUM: +	case ETHTOOL_SRXCSUM: +		return NETIF_F_RXCSUM; +	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(); +	}  } -EXPORT_SYMBOL(ethtool_op_get_tso); -int ethtool_op_set_tso(struct net_device *dev, u32 data) +static int ethtool_get_one_feature(struct net_device *dev, +	char __user *useraddr, u32 ethcmd)  { -	if (data) -		dev->features |= NETIF_F_TSO; -	else -		dev->features &= ~NETIF_F_TSO; +	netdev_features_t mask = ethtool_get_feature_mask(ethcmd); +	struct ethtool_value edata = { +		.cmd = ethcmd, +		.data = !!(dev->features & mask), +	}; +	if (copy_to_user(useraddr, &edata, sizeof(edata))) +		return -EFAULT;  	return 0;  } -EXPORT_SYMBOL(ethtool_op_set_tso); -u32 ethtool_op_get_ufo(struct net_device *dev) +static int ethtool_set_one_feature(struct net_device *dev, +	void __user *useraddr, u32 ethcmd)  { -	return (dev->features & NETIF_F_UFO) != 0; -} -EXPORT_SYMBOL(ethtool_op_get_ufo); +	struct ethtool_value edata; +	netdev_features_t mask; -int ethtool_op_set_ufo(struct net_device *dev, u32 data) -{ -	if (data) -		dev->features |= NETIF_F_UFO; +	if (copy_from_user(&edata, useraddr, sizeof(edata))) +		return -EFAULT; + +	mask = ethtool_get_feature_mask(ethcmd); +	mask &= dev->hw_features; +	if (!mask) +		return -EOPNOTSUPP; + +	if (edata.data) +		dev->wanted_features |= mask;  	else -		dev->features &= ~NETIF_F_UFO; +		dev->wanted_features &= ~mask; + +	__netdev_update_features(dev); +  	return 0;  } -EXPORT_SYMBOL(ethtool_op_set_ufo); -/* 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 = -	(ETH_FLAG_LRO | ETH_FLAG_RXVLAN | ETH_FLAG_TXVLAN | ETH_FLAG_NTUPLE | -	 ETH_FLAG_RXHASH); +#define ETH_ALL_FLAGS    (ETH_FLAG_LRO | ETH_FLAG_RXVLAN | ETH_FLAG_TXVLAN | \ +			  ETH_FLAG_NTUPLE | ETH_FLAG_RXHASH) +#define ETH_ALL_FEATURES (NETIF_F_LRO | NETIF_F_HW_VLAN_CTAG_RX | \ +			  NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_NTUPLE | \ +			  NETIF_F_RXHASH) -u32 ethtool_op_get_flags(struct net_device *dev) +static u32 __ethtool_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; +	u32 flags = 0; + +	if (dev->features & NETIF_F_LRO) +		flags |= ETH_FLAG_LRO; +	if (dev->features & NETIF_F_HW_VLAN_CTAG_RX) +		flags |= ETH_FLAG_RXVLAN; +	if (dev->features & NETIF_F_HW_VLAN_CTAG_TX) +		flags |= ETH_FLAG_TXVLAN; +	if (dev->features & NETIF_F_NTUPLE) +		flags |= ETH_FLAG_NTUPLE; +	if (dev->features & NETIF_F_RXHASH) +		flags |= ETH_FLAG_RXHASH; + +	return flags;  } -EXPORT_SYMBOL(ethtool_op_get_flags); -int ethtool_op_set_flags(struct net_device *dev, u32 data, u32 supported) +static int __ethtool_set_flags(struct net_device *dev, u32 data)  { -	if (data & ~supported) +	netdev_features_t features = 0, changed; + +	if (data & ~ETH_ALL_FLAGS)  		return -EINVAL; -	dev->features = ((dev->features & ~flags_dup_features) | -			 (data & flags_dup_features)); +	if (data & ETH_FLAG_LRO) +		features |= NETIF_F_LRO; +	if (data & ETH_FLAG_RXVLAN) +		features |= NETIF_F_HW_VLAN_CTAG_RX; +	if (data & ETH_FLAG_TXVLAN) +		features |= NETIF_F_HW_VLAN_CTAG_TX; +	if (data & ETH_FLAG_NTUPLE) +		features |= NETIF_F_NTUPLE; +	if (data & ETH_FLAG_RXHASH) +		features |= NETIF_F_RXHASH; + +	/* allow changing only bits set in hw_features */ +	changed = (features ^ dev->features) & ETH_ALL_FEATURES; +	if (changed & ~dev->hw_features) +		return (changed & dev->hw_features) ? -EINVAL : -EOPNOTSUPP; + +	dev->wanted_features = +		(dev->wanted_features & ~changed) | (features & changed); + +	__netdev_update_features(dev); +  	return 0;  } -EXPORT_SYMBOL(ethtool_op_set_flags); -void ethtool_ntuple_flush(struct net_device *dev) +int __ethtool_get_settings(struct net_device *dev, struct ethtool_cmd *cmd)  { -	struct ethtool_rx_ntuple_flow_spec_container *fsc, *f; +	ASSERT_RTNL(); -	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); +	if (!dev->ethtool_ops->get_settings) +		return -EOPNOTSUPP; -/* Handlers for each ethtool command */ +	memset(cmd, 0, sizeof(struct ethtool_cmd)); +	cmd->cmd = ETHTOOL_GSET; +	return dev->ethtool_ops->get_settings(dev, cmd); +} +EXPORT_SYMBOL(__ethtool_get_settings);  static int ethtool_get_settings(struct net_device *dev, void __user *useraddr)  { -	struct ethtool_cmd cmd = { .cmd = ETHTOOL_GSET };  	int err; +	struct ethtool_cmd cmd; -	if (!dev->ethtool_ops->get_settings) -		return -EOPNOTSUPP; - -	err = dev->ethtool_ops->get_settings(dev, &cmd); +	err = __ethtool_get_settings(dev, &cmd);  	if (err < 0)  		return err; @@ -209,7 +376,7 @@ static noinline_for_stack int ethtool_get_drvinfo(struct net_device *dev,  	memset(&info, 0, sizeof(info));  	info.cmd = ETHTOOL_GDRVINFO; -	if (ops && ops->get_drvinfo) { +	if (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), @@ -224,7 +391,7 @@ static noinline_for_stack int ethtool_get_drvinfo(struct net_device *dev,  	 * this method of obtaining string set info is deprecated;  	 * Use ETHTOOL_GSSET_INFO instead.  	 */ -	if (ops && ops->get_sset_count) { +	if (ops->get_sset_count) {  		int rc;  		rc = ops->get_sset_count(dev, ETH_SS_TEST); @@ -237,9 +404,9 @@ static noinline_for_stack int ethtool_get_drvinfo(struct net_device *dev,  		if (rc >= 0)  			info.n_priv_flags = rc;  	} -	if (ops && ops->get_regs_len) +	if (ops->get_regs_len)  		info.regdump_len = ops->get_regs_len(dev); -	if (ops && ops->get_eeprom_len) +	if (ops->get_eeprom_len)  		info.eedump_len = ops->get_eeprom_len(dev);  	if (copy_to_user(useraddr, &info, sizeof(info))) @@ -251,14 +418,10 @@ static noinline_for_stack int ethtool_get_sset_info(struct net_device *dev,  						    void __user *useraddr)  {  	struct ethtool_sset_info info; -	const struct ethtool_ops *ops = dev->ethtool_ops;  	u64 sset_mask;  	int i, idx = 0, n_bits = 0, ret, rc;  	u32 *info_buf = NULL; -	if (!ops->get_sset_count) -		return -EOPNOTSUPP; -  	if (copy_from_user(&info, useraddr, sizeof(info)))  		return -EFAULT; @@ -285,7 +448,7 @@ static noinline_for_stack int ethtool_get_sset_info(struct net_device *dev,  		if (!(sset_mask & (1ULL << i)))  			continue; -		rc = ops->get_sset_count(dev, i); +		rc = __ethtool_get_sset_count(dev, i);  		if (rc >= 0) {  			info.sset_mask |= (1ULL << i);  			info_buf[idx++] = rc; @@ -312,6 +475,7 @@ static noinline_for_stack int ethtool_set_rxnfc(struct net_device *dev,  {  	struct ethtool_rxnfc info;  	size_t info_size = sizeof(info); +	int rc;  	if (!dev->ethtool_ops->set_rxnfc)  		return -EOPNOTSUPP; @@ -327,7 +491,15 @@ static noinline_for_stack int ethtool_set_rxnfc(struct net_device *dev,  	if (copy_from_user(&info, useraddr, info_size))  		return -EFAULT; -	return dev->ethtool_ops->set_rxnfc(dev, &info); +	rc = dev->ethtool_ops->set_rxnfc(dev, &info); +	if (rc) +		return rc; + +	if (cmd == ETHTOOL_SRXCLSRLINS && +	    copy_to_user(useraddr, &info, info_size)) +		return -EFAULT; + +	return 0;  }  static noinline_for_stack int ethtool_get_rxnfc(struct net_device *dev, @@ -385,37 +557,64 @@ err_out:  	return ret;  } +static int ethtool_copy_validate_indir(u32 *indir, void __user *useraddr, +					struct ethtool_rxnfc *rx_rings, +					u32 size) +{ +	int i; + +	if (copy_from_user(indir, useraddr, size * sizeof(indir[0]))) +		return -EFAULT; + +	/* Validate ring indices */ +	for (i = 0; i < size; i++) +		if (indir[i] >= rx_rings->data) +			return -EINVAL; + +	return 0; +} +  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; +	u32 user_size, dev_size; +	u32 *indir;  	int ret; -	if (!dev->ethtool_ops->get_rxfh_indir) +	if (!dev->ethtool_ops->get_rxfh_indir_size || +	    !dev->ethtool_ops->get_rxfh) +		return -EOPNOTSUPP; +	dev_size = dev->ethtool_ops->get_rxfh_indir_size(dev); +	if (dev_size == 0)  		return -EOPNOTSUPP; -	if (copy_from_user(&table_size, +	if (copy_from_user(&user_size,  			   useraddr + offsetof(struct ethtool_rxfh_indir, size), -			   sizeof(table_size))) +			   sizeof(user_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 = kzalloc(full_size, GFP_USER); +	if (copy_to_user(useraddr + offsetof(struct ethtool_rxfh_indir, size), +			 &dev_size, sizeof(dev_size))) +		return -EFAULT; + +	/* If the user buffer size is 0, this is just a query for the +	 * device table size.  Otherwise, if it's smaller than the +	 * device table size it's an error. +	 */ +	if (user_size < dev_size) +		return user_size == 0 ? 0 : -EINVAL; + +	indir = kcalloc(dev_size, sizeof(indir[0]), GFP_USER);  	if (!indir)  		return -ENOMEM; -	indir->cmd = ETHTOOL_GRXFHINDIR; -	indir->size = table_size; -	ret = dev->ethtool_ops->get_rxfh_indir(dev, indir); +	ret = dev->ethtool_ops->get_rxfh(dev, indir, NULL);  	if (ret)  		goto out; -	if (copy_to_user(useraddr, indir, full_size)) +	if (copy_to_user(useraddr + +			 offsetof(struct ethtool_rxfh_indir, ring_index[0]), +			 indir, dev_size * sizeof(indir[0])))  		ret = -EFAULT;  out: @@ -426,377 +625,220 @@ out:  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; +	struct ethtool_rxnfc rx_rings; +	u32 user_size, dev_size, i; +	u32 *indir; +	const struct ethtool_ops *ops = dev->ethtool_ops;  	int ret; +	u32 ringidx_offset = offsetof(struct ethtool_rxfh_indir, ring_index[0]); + +	if (!ops->get_rxfh_indir_size || !ops->set_rxfh || +	    !ops->get_rxnfc) +		return -EOPNOTSUPP; -	if (!dev->ethtool_ops->set_rxfh_indir) +	dev_size = ops->get_rxfh_indir_size(dev); +	if (dev_size == 0)  		return -EOPNOTSUPP; -	if (copy_from_user(&table_size, +	if (copy_from_user(&user_size,  			   useraddr + offsetof(struct ethtool_rxfh_indir, size), -			   sizeof(table_size))) +			   sizeof(user_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 (user_size != 0 && user_size != dev_size) +		return -EINVAL; + +	indir = kcalloc(dev_size, sizeof(indir[0]), GFP_USER);  	if (!indir)  		return -ENOMEM; -	if (copy_from_user(indir, useraddr, full_size)) { -		ret = -EFAULT; +	rx_rings.cmd = ETHTOOL_GRXRINGS; +	ret = ops->get_rxnfc(dev, &rx_rings, NULL); +	if (ret)  		goto out; + +	if (user_size == 0) { +		for (i = 0; i < dev_size; i++) +			indir[i] = ethtool_rxfh_indir_default(i, rx_rings.data); +	} else { +		ret = ethtool_copy_validate_indir(indir, +						  useraddr + ringidx_offset, +						  &rx_rings, +						  dev_size); +		if (ret) +			goto out;  	} -	ret = dev->ethtool_ops->set_rxfh_indir(dev, indir); +	ret = ops->set_rxfh(dev, indir, NULL);  out:  	kfree(indir);  	return ret;  } -static void __rx_ntuple_filter_add(struct ethtool_rx_ntuple_list *list, -			struct ethtool_rx_ntuple_flow_spec *spec, -			struct ethtool_rx_ntuple_flow_spec_container *fsc) +static noinline_for_stack int ethtool_get_rxfh(struct net_device *dev, +					       void __user *useraddr)  { +	int ret; +	const struct ethtool_ops *ops = dev->ethtool_ops; +	u32 user_indir_size, user_key_size; +	u32 dev_indir_size = 0, dev_key_size = 0; +	struct ethtool_rxfh rxfh; +	u32 total_size; +	u32 indir_bytes; +	u32 *indir = NULL; +	u8 *hkey = NULL; +	u8 *rss_config; + +	if (!(dev->ethtool_ops->get_rxfh_indir_size || +	      dev->ethtool_ops->get_rxfh_key_size) || +	      !dev->ethtool_ops->get_rxfh) +		return -EOPNOTSUPP; -	/* don't add filters forever */ -	if (list->count >= ETHTOOL_MAX_NTUPLE_LIST_ENTRY) { -		/* free the container */ -		kfree(fsc); -		return; -	} +	if (ops->get_rxfh_indir_size) +		dev_indir_size = ops->get_rxfh_indir_size(dev); +	if (ops->get_rxfh_key_size) +		dev_key_size = ops->get_rxfh_key_size(dev); -	/* 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)); +	if ((dev_key_size + dev_indir_size) == 0) +		return -EOPNOTSUPP; -	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; +	if (copy_from_user(&rxfh, useraddr, sizeof(rxfh))) +		return -EFAULT; +	user_indir_size = rxfh.indir_size; +	user_key_size = rxfh.key_size; -	/* add to the list */ -	list_add_tail_rcu(&fsc->list, &list->list); -	list->count++; -} +	/* Check that reserved fields are 0 for now */ +	if (rxfh.rss_context || rxfh.rsvd[0] || rxfh.rsvd[1]) +		return -EINVAL; -/* - * 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; -} +	rxfh.indir_size = dev_indir_size; +	rxfh.key_size = dev_key_size; +	if (copy_to_user(useraddr, &rxfh, sizeof(rxfh))) +		return -EFAULT; -static noinline_for_stack int ethtool_set_rx_ntuple(struct net_device *dev, -						    void __user *useraddr) -{ -	struct ethtool_rx_ntuple cmd; -	const struct ethtool_ops *ops = dev->ethtool_ops; -	struct ethtool_rx_ntuple_flow_spec_container *fsc = NULL; -	int ret; +	/* If the user buffer size is 0, this is just a query for the +	 * device table size and key size.  Otherwise, if the User size is +	 * not equal to device table size or key size it's an error. +	 */ +	if (!user_indir_size && !user_key_size) +		return 0; -	if (!(dev->features & NETIF_F_NTUPLE)) +	if ((user_indir_size && (user_indir_size != dev_indir_size)) || +	    (user_key_size && (user_key_size != dev_key_size)))  		return -EINVAL; -	if (copy_from_user(&cmd, useraddr, sizeof(cmd))) -		return -EFAULT; +	indir_bytes = user_indir_size * sizeof(indir[0]); +	total_size = indir_bytes + user_key_size; +	rss_config = kzalloc(total_size, GFP_USER); +	if (!rss_config) +		return -ENOMEM; -	rx_ntuple_fix_masks(&cmd.fs); +	if (user_indir_size) +		indir = (u32 *)rss_config; -	/* -	 * Cache filter in dev struct for GET operation only if -	 * the underlying driver doesn't have its own GET operation, and -	 * only if the filter was added successfully.  First make sure we -	 * can allocate the filter, then continue if successful. -	 */ -	if (!ops->get_rx_ntuple) { -		fsc = kmalloc(sizeof(*fsc), GFP_ATOMIC); -		if (!fsc) -			return -ENOMEM; -	} +	if (user_key_size) +		hkey = rss_config + indir_bytes; -	ret = ops->set_rx_ntuple(dev, &cmd); -	if (ret) { -		kfree(fsc); -		return ret; +	ret = dev->ethtool_ops->get_rxfh(dev, indir, hkey); +	if (!ret) { +		if (copy_to_user(useraddr + +				 offsetof(struct ethtool_rxfh, rss_config[0]), +				 rss_config, total_size)) +			ret = -EFAULT;  	} -	if (!ops->get_rx_ntuple) -		__rx_ntuple_filter_add(&dev->ethtool_ntuple_list, &cmd.fs, fsc); +	kfree(rss_config);  	return ret;  } -static int ethtool_get_rx_ntuple(struct net_device *dev, void __user *useraddr) +static noinline_for_stack int ethtool_set_rxfh(struct net_device *dev, +					       void __user *useraddr)  { -	struct ethtool_gstrings gstrings; +	int ret;  	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; +	struct ethtool_rxnfc rx_rings; +	struct ethtool_rxfh rxfh; +	u32 dev_indir_size = 0, dev_key_size = 0, i; +	u32 *indir = NULL, indir_bytes = 0; +	u8 *hkey = NULL; +	u8 *rss_config; +	u32 rss_cfg_offset = offsetof(struct ethtool_rxfh, rss_config[0]); + +	if (!(ops->get_rxfh_indir_size || ops->get_rxfh_key_size) || +	    !ops->get_rxnfc || !ops->set_rxfh) +		return -EOPNOTSUPP; -	if (!ops->get_sset_count) +	if (ops->get_rxfh_indir_size) +		dev_indir_size = ops->get_rxfh_indir_size(dev); +	if (ops->get_rxfh_key_size) +		dev_key_size = dev->ethtool_ops->get_rxfh_key_size(dev); +	if ((dev_key_size + dev_indir_size) == 0)  		return -EOPNOTSUPP; -	if (copy_from_user(&gstrings, useraddr, sizeof(gstrings))) +	if (copy_from_user(&rxfh, useraddr, sizeof(rxfh)))  		return -EFAULT; -	ret = ops->get_sset_count(dev, gstrings.string_set); -	if (ret < 0) -		return ret; +	/* Check that reserved fields are 0 for now */ +	if (rxfh.rss_context || rxfh.rsvd[0] || rxfh.rsvd[1]) +		return -EINVAL; -	gstrings.len = ret; +	/* If either indir or hash key is valid, proceed further. +	 * It is not valid to request that both be unchanged. +	 */ +	if ((rxfh.indir_size && +	     rxfh.indir_size != ETH_RXFH_INDIR_NO_CHANGE && +	     rxfh.indir_size != dev_indir_size) || +	    (rxfh.key_size && (rxfh.key_size != dev_key_size)) || +	    (rxfh.indir_size == ETH_RXFH_INDIR_NO_CHANGE && +	     rxfh.key_size == 0)) +		return -EINVAL; -	data = kzalloc(gstrings.len * ETH_GSTRING_LEN, GFP_USER); -	if (!data) +	if (rxfh.indir_size != ETH_RXFH_INDIR_NO_CHANGE) +		indir_bytes = dev_indir_size * sizeof(indir[0]); + +	rss_config = kzalloc(indir_bytes + rxfh.key_size, GFP_USER); +	if (!rss_config)  		return -ENOMEM; -	if (ops->get_rx_ntuple) { -		/* driver-specific filter grab */ -		ret = ops->get_rx_ntuple(dev, gstrings.string_set, data); -		goto copy; -	} +	rx_rings.cmd = ETHTOOL_GRXRINGS; +	ret = ops->get_rxnfc(dev, &rx_rings, NULL); +	if (ret) +		goto out; -	/* 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; -		} +	/* rxfh.indir_size == 0 means reset the indir table to default. +	 * rxfh.indir_size == ETH_RXFH_INDIR_NO_CHANGE means leave it unchanged. +	 */ +	if (rxfh.indir_size && +	    rxfh.indir_size != ETH_RXFH_INDIR_NO_CHANGE) { +		indir = (u32 *)rss_config; +		ret = ethtool_copy_validate_indir(indir, +						  useraddr + rss_cfg_offset, +						  &rx_rings, +						  rxfh.indir_size); +		if (ret) +			goto out; +	} else if (rxfh.indir_size == 0) { +		indir = (u32 *)rss_config; +		for (i = 0; i < dev_indir_size; i++) +			indir[i] = ethtool_rxfh_indir_default(i, rx_rings.data); +	} -		/* 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", -				fsc->fs.h_u.tcp_ip4_spec.ip4src); -			p += ETH_GSTRING_LEN; -			num_strings++; -			sprintf(p, "\tSrc IP mask: 0x%x\n", -				fsc->fs.m_u.tcp_ip4_spec.ip4src); -			p += ETH_GSTRING_LEN; -			num_strings++; -			sprintf(p, "\tDest IP addr: 0x%x\n", -				fsc->fs.h_u.tcp_ip4_spec.ip4dst); -			p += ETH_GSTRING_LEN; -			num_strings++; -			sprintf(p, "\tDest IP mask: 0x%x\n", -				fsc->fs.m_u.tcp_ip4_spec.ip4dst); -			p += ETH_GSTRING_LEN; -			num_strings++; -			sprintf(p, "\tSrc Port: %d, mask: 0x%x\n", -				fsc->fs.h_u.tcp_ip4_spec.psrc, -				fsc->fs.m_u.tcp_ip4_spec.psrc); -			p += ETH_GSTRING_LEN; -			num_strings++; -			sprintf(p, "\tDest Port: %d, mask: 0x%x\n", -				fsc->fs.h_u.tcp_ip4_spec.pdst, -				fsc->fs.m_u.tcp_ip4_spec.pdst); -			p += ETH_GSTRING_LEN; -			num_strings++; -			sprintf(p, "\tTOS: %d, mask: 0x%x\n", -				fsc->fs.h_u.tcp_ip4_spec.tos, -				fsc->fs.m_u.tcp_ip4_spec.tos); -			p += ETH_GSTRING_LEN; -			num_strings++; -			break; -		case AH_ESP_V4_FLOW: -		case ESP_V4_FLOW: -			sprintf(p, "\tSrc IP addr: 0x%x\n", -				fsc->fs.h_u.ah_ip4_spec.ip4src); -			p += ETH_GSTRING_LEN; -			num_strings++; -			sprintf(p, "\tSrc IP mask: 0x%x\n", -				fsc->fs.m_u.ah_ip4_spec.ip4src); -			p += ETH_GSTRING_LEN; -			num_strings++; -			sprintf(p, "\tDest IP addr: 0x%x\n", -				fsc->fs.h_u.ah_ip4_spec.ip4dst); -			p += ETH_GSTRING_LEN; -			num_strings++; -			sprintf(p, "\tDest IP mask: 0x%x\n", -				fsc->fs.m_u.ah_ip4_spec.ip4dst); -			p += ETH_GSTRING_LEN; -			num_strings++; -			sprintf(p, "\tSPI: %d, mask: 0x%x\n", -				fsc->fs.h_u.ah_ip4_spec.spi, -				fsc->fs.m_u.ah_ip4_spec.spi); -			p += ETH_GSTRING_LEN; -			num_strings++; -			sprintf(p, "\tTOS: %d, mask: 0x%x\n", -				fsc->fs.h_u.ah_ip4_spec.tos, -				fsc->fs.m_u.ah_ip4_spec.tos); -			p += ETH_GSTRING_LEN; -			num_strings++; -			break; -		case IP_USER_FLOW: -			sprintf(p, "\tSrc IP addr: 0x%x\n", -				fsc->fs.h_u.usr_ip4_spec.ip4src); -			p += ETH_GSTRING_LEN; -			num_strings++; -			sprintf(p, "\tSrc IP mask: 0x%x\n", -				fsc->fs.m_u.usr_ip4_spec.ip4src); -			p += ETH_GSTRING_LEN; -			num_strings++; -			sprintf(p, "\tDest IP addr: 0x%x\n", -				fsc->fs.h_u.usr_ip4_spec.ip4dst); -			p += ETH_GSTRING_LEN; -			num_strings++; -			sprintf(p, "\tDest IP mask: 0x%x\n", -				fsc->fs.m_u.usr_ip4_spec.ip4dst); -			p += ETH_GSTRING_LEN; -			num_strings++; -			break; -		case IPV4_FLOW: -			sprintf(p, "\tSrc IP addr: 0x%x\n", -				fsc->fs.h_u.usr_ip4_spec.ip4src); -			p += ETH_GSTRING_LEN; -			num_strings++; -			sprintf(p, "\tSrc IP mask: 0x%x\n", -				fsc->fs.m_u.usr_ip4_spec.ip4src); -			p += ETH_GSTRING_LEN; -			num_strings++; -			sprintf(p, "\tDest IP addr: 0x%x\n", -				fsc->fs.h_u.usr_ip4_spec.ip4dst); -			p += ETH_GSTRING_LEN; -			num_strings++; -			sprintf(p, "\tDest IP mask: 0x%x\n", -				fsc->fs.m_u.usr_ip4_spec.ip4dst); -			p += ETH_GSTRING_LEN; -			num_strings++; -			sprintf(p, "\tL4 bytes: 0x%x, mask: 0x%x\n", -				fsc->fs.h_u.usr_ip4_spec.l4_4_bytes, -				fsc->fs.m_u.usr_ip4_spec.l4_4_bytes); -			p += ETH_GSTRING_LEN; -			num_strings++; -			sprintf(p, "\tTOS: %d, mask: 0x%x\n", -				fsc->fs.h_u.usr_ip4_spec.tos, -				fsc->fs.m_u.usr_ip4_spec.tos); -			p += ETH_GSTRING_LEN; -			num_strings++; -			sprintf(p, "\tIP Version: %d, mask: 0x%x\n", -				fsc->fs.h_u.usr_ip4_spec.ip_ver, -				fsc->fs.m_u.usr_ip4_spec.ip_ver); -			p += ETH_GSTRING_LEN; -			num_strings++; -			sprintf(p, "\tProtocol: %d, mask: 0x%x\n", -				fsc->fs.h_u.usr_ip4_spec.proto, -				fsc->fs.m_u.usr_ip4_spec.proto); -			p += ETH_GSTRING_LEN; -			num_strings++; -			break; +	if (rxfh.key_size) { +		hkey = rss_config + indir_bytes; +		if (copy_from_user(hkey, +				   useraddr + rss_cfg_offset + indir_bytes, +				   rxfh.key_size)) { +			ret = -EFAULT; +			goto out;  		} -		sprintf(p, "\tVLAN: %d, mask: 0x%x\n", -			fsc->fs.vlan_tag, fsc->fs.vlan_tag_mask); -		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", -				fsc->fs.action); -		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; + +	ret = ops->set_rxfh(dev, indir, hkey);  out: -	kfree(data); +	kfree(rss_config);  	return ret;  } @@ -817,8 +859,8 @@ static int ethtool_get_regs(struct net_device *dev, char __user *useraddr)  	if (regs.len > reglen)  		regs.len = reglen; -	regbuf = vmalloc(reglen); -	if (!regbuf) +	regbuf = vzalloc(reglen); +	if (reglen && !regbuf)  		return -ENOMEM;  	ops->get_regs(dev, ®s, regbuf); @@ -827,7 +869,7 @@ static int ethtool_get_regs(struct net_device *dev, char __user *useraddr)  	if (copy_to_user(useraddr, ®s, sizeof(regs)))  		goto out;  	useraddr += offsetof(struct ethtool_regs, data); -	if (copy_to_user(useraddr, regbuf, regs.len)) +	if (regbuf && copy_to_user(useraddr, regbuf, regs.len))  		goto out;  	ret = 0; @@ -883,6 +925,40 @@ static int ethtool_set_wol(struct net_device *dev, char __user *useraddr)  	return dev->ethtool_ops->set_wol(dev, &wol);  } +static int ethtool_get_eee(struct net_device *dev, char __user *useraddr) +{ +	struct ethtool_eee edata; +	int rc; + +	if (!dev->ethtool_ops->get_eee) +		return -EOPNOTSUPP; + +	memset(&edata, 0, sizeof(struct ethtool_eee)); +	edata.cmd = ETHTOOL_GEEE; +	rc = dev->ethtool_ops->get_eee(dev, &edata); + +	if (rc) +		return rc; + +	if (copy_to_user(useraddr, &edata, sizeof(edata))) +		return -EFAULT; + +	return 0; +} + +static int ethtool_set_eee(struct net_device *dev, char __user *useraddr) +{ +	struct ethtool_eee edata; + +	if (!dev->ethtool_ops->set_eee) +		return -EOPNOTSUPP; + +	if (copy_from_user(&edata, useraddr, sizeof(edata))) +		return -EFAULT; + +	return dev->ethtool_ops->set_eee(dev, &edata); +} +  static int ethtool_nway_reset(struct net_device *dev)  {  	if (!dev->ethtool_ops->nway_reset) @@ -891,18 +967,31 @@ static int ethtool_nway_reset(struct net_device *dev)  	return dev->ethtool_ops->nway_reset(dev);  } -static int ethtool_get_eeprom(struct net_device *dev, void __user *useraddr) +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; +} + +static int ethtool_get_any_eeprom(struct net_device *dev, void __user *useraddr, +				  int (*getter)(struct net_device *, +						struct ethtool_eeprom *, u8 *), +				  u32 total_len)  {  	struct ethtool_eeprom eeprom; -	const struct ethtool_ops *ops = dev->ethtool_ops;  	void __user *userbuf = useraddr + sizeof(eeprom);  	u32 bytes_remaining;  	u8 *data;  	int ret = 0; -	if (!ops->get_eeprom || !ops->get_eeprom_len) -		return -EOPNOTSUPP; -  	if (copy_from_user(&eeprom, useraddr, sizeof(eeprom)))  		return -EFAULT; @@ -911,7 +1000,7 @@ static int ethtool_get_eeprom(struct net_device *dev, void __user *useraddr)  		return -EINVAL;  	/* Check for exceeding total eeprom len */ -	if (eeprom.offset + eeprom.len > ops->get_eeprom_len(dev)) +	if (eeprom.offset + eeprom.len > total_len)  		return -EINVAL;  	data = kmalloc(PAGE_SIZE, GFP_USER); @@ -922,7 +1011,7 @@ static int ethtool_get_eeprom(struct net_device *dev, void __user *useraddr)  	while (bytes_remaining > 0) {  		eeprom.len = min(bytes_remaining, (u32)PAGE_SIZE); -		ret = ops->get_eeprom(dev, &eeprom, data); +		ret = getter(dev, &eeprom, data);  		if (ret)  			break;  		if (copy_to_user(userbuf, data, eeprom.len)) { @@ -943,6 +1032,17 @@ static int ethtool_get_eeprom(struct net_device *dev, void __user *useraddr)  	return ret;  } +static int ethtool_get_eeprom(struct net_device *dev, void __user *useraddr) +{ +	const struct ethtool_ops *ops = dev->ethtool_ops; + +	if (!ops->get_eeprom || !ops->get_eeprom_len) +		return -EOPNOTSUPP; + +	return ethtool_get_any_eeprom(dev, useraddr, ops->get_eeprom, +				      ops->get_eeprom_len(dev)); +} +  static int ethtool_set_eeprom(struct net_device *dev, void __user *useraddr)  {  	struct ethtool_eeprom eeprom; @@ -1046,190 +1146,60 @@ static int ethtool_set_ringparam(struct net_device *dev, void __user *useraddr)  	return dev->ethtool_ops->set_ringparam(dev, &ringparam);  } -static int ethtool_get_pauseparam(struct net_device *dev, void __user *useraddr) +static noinline_for_stack int ethtool_get_channels(struct net_device *dev, +						   void __user *useraddr)  { -	struct ethtool_pauseparam pauseparam = { ETHTOOL_GPAUSEPARAM }; +	struct ethtool_channels channels = { .cmd = ETHTOOL_GCHANNELS }; -	if (!dev->ethtool_ops->get_pauseparam) +	if (!dev->ethtool_ops->get_channels)  		return -EOPNOTSUPP; -	dev->ethtool_ops->get_pauseparam(dev, &pauseparam); +	dev->ethtool_ops->get_channels(dev, &channels); -	if (copy_to_user(useraddr, &pauseparam, sizeof(pauseparam))) +	if (copy_to_user(useraddr, &channels, sizeof(channels)))  		return -EFAULT;  	return 0;  } -static int ethtool_set_pauseparam(struct net_device *dev, void __user *useraddr) -{ -	struct ethtool_pauseparam pauseparam; - -	if (!dev->ethtool_ops->set_pauseparam) -		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; - -	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); -} -EXPORT_SYMBOL(ethtool_op_set_tx_csum); - -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; - -	if (!edata.data && dev->ethtool_ops->set_sg) -		dev->features &= ~NETIF_F_GRO; - -	return dev->ethtool_ops->set_rx_csum(dev, edata.data); -} - -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_ALL_CSUM)) -		return -EINVAL; - -	return __ethtool_set_sg(dev, edata.data); -} - -static int ethtool_set_tso(struct net_device *dev, char __user *useraddr) +static noinline_for_stack int ethtool_set_channels(struct net_device *dev, +						   void __user *useraddr)  { -	struct ethtool_value edata; +	struct ethtool_channels channels; -	if (!dev->ethtool_ops->set_tso) +	if (!dev->ethtool_ops->set_channels)  		return -EOPNOTSUPP; -	if (copy_from_user(&edata, useraddr, sizeof(edata))) +	if (copy_from_user(&channels, useraddr, sizeof(channels)))  		return -EFAULT; -	if (edata.data && !(dev->features & NETIF_F_SG)) -		return -EINVAL; - -	return dev->ethtool_ops->set_tso(dev, edata.data); +	return dev->ethtool_ops->set_channels(dev, &channels);  } -static int ethtool_set_ufo(struct net_device *dev, char __user *useraddr) +static int ethtool_get_pauseparam(struct net_device *dev, void __user *useraddr)  { -	struct ethtool_value edata; +	struct ethtool_pauseparam pauseparam = { ETHTOOL_GPAUSEPARAM }; -	if (!dev->ethtool_ops->set_ufo) +	if (!dev->ethtool_ops->get_pauseparam)  		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_GEN_CSUM) || -		(dev->features & (NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM)) -			== (NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM))) -		return -EINVAL; -	return dev->ethtool_ops->set_ufo(dev, edata.data); -} -static int ethtool_get_gso(struct net_device *dev, char __user *useraddr) -{ -	struct ethtool_value edata = { ETHTOOL_GGSO }; - -	edata.data = dev->features & NETIF_F_GSO; -	if (copy_to_user(useraddr, &edata, sizeof(edata))) -		return -EFAULT; -	return 0; -} - -static int ethtool_set_gso(struct net_device *dev, char __user *useraddr) -{ -	struct ethtool_value edata; +	dev->ethtool_ops->get_pauseparam(dev, &pauseparam); -	if (copy_from_user(&edata, useraddr, sizeof(edata))) +	if (copy_to_user(useraddr, &pauseparam, sizeof(pauseparam)))  		return -EFAULT; -	if (edata.data) -		dev->features |= NETIF_F_GSO; -	else -		dev->features &= ~NETIF_F_GSO;  	return 0;  } -static int ethtool_get_gro(struct net_device *dev, char __user *useraddr) +static int ethtool_set_pauseparam(struct net_device *dev, void __user *useraddr)  { -	struct ethtool_value edata = { ETHTOOL_GGRO }; - -	edata.data = dev->features & NETIF_F_GRO; -	if (copy_to_user(useraddr, &edata, sizeof(edata))) -		return -EFAULT; -	return 0; -} +	struct ethtool_pauseparam pauseparam; -static int ethtool_set_gro(struct net_device *dev, char __user *useraddr) -{ -	struct ethtool_value edata; +	if (!dev->ethtool_ops->set_pauseparam) +		return -EOPNOTSUPP; -	if (copy_from_user(&edata, useraddr, sizeof(edata))) +	if (copy_from_user(&pauseparam, useraddr, sizeof(pauseparam)))  		return -EFAULT; -	if (edata.data) { -		u32 rxcsum = dev->ethtool_ops->get_rx_csum ? -				dev->ethtool_ops->get_rx_csum(dev) : -				ethtool_op_get_rx_csum(dev); - -		if (!rxcsum) -			return -EINVAL; -		dev->features |= NETIF_F_GRO; -	} else -		dev->features &= ~NETIF_F_GRO; - -	return 0; +	return dev->ethtool_ops->set_pauseparam(dev, &pauseparam);  }  static int ethtool_self_test(struct net_device *dev, char __user *useraddr) @@ -1273,17 +1243,13 @@ static int ethtool_self_test(struct net_device *dev, char __user *useraddr)  static int ethtool_get_strings(struct net_device *dev, void __user *useraddr)  {  	struct ethtool_gstrings gstrings; -	const struct ethtool_ops *ops = dev->ethtool_ops;  	u8 *data;  	int ret; -	if (!ops->get_strings || !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); +	ret = __ethtool_get_sset_count(dev, gstrings.string_set);  	if (ret < 0)  		return ret; @@ -1293,7 +1259,7 @@ static int ethtool_get_strings(struct net_device *dev, void __user *useraddr)  	if (!data)  		return -ENOMEM; -	ops->get_strings(dev, gstrings.string_set, data); +	__ethtool_get_strings(dev, gstrings.string_set, data);  	ret = -EFAULT;  	if (copy_to_user(useraddr, &gstrings, sizeof(gstrings))) @@ -1303,7 +1269,7 @@ static int ethtool_get_strings(struct net_device *dev, void __user *useraddr)  		goto out;  	ret = 0; - out: +out:  	kfree(data);  	return ret;  } @@ -1311,14 +1277,61 @@ static int ethtool_get_strings(struct net_device *dev, void __user *useraddr)  static int ethtool_phys_id(struct net_device *dev, void __user *useraddr)  {  	struct ethtool_value id; +	static bool busy; +	const struct ethtool_ops *ops = dev->ethtool_ops; +	int rc; -	if (!dev->ethtool_ops->phys_id) +	if (!ops->set_phys_id)  		return -EOPNOTSUPP; +	if (busy) +		return -EBUSY; +  	if (copy_from_user(&id, useraddr, sizeof(id)))  		return -EFAULT; -	return dev->ethtool_ops->phys_id(dev, id.data); +	rc = ops->set_phys_id(dev, ETHTOOL_ID_ACTIVE); +	if (rc < 0) +		return rc; + +	/* Drop the RTNL lock while waiting, but prevent reentry or +	 * removal of the device. +	 */ +	busy = true; +	dev_hold(dev); +	rtnl_unlock(); + +	if (rc == 0) { +		/* Driver will handle this itself */ +		schedule_timeout_interruptible( +			id.data ? (id.data * HZ) : MAX_SCHEDULE_TIMEOUT); +	} else { +		/* Driver expects to be called at twice the frequency in rc */ +		int n = rc * 2, i, interval = HZ / n; + +		/* Count down seconds */ +		do { +			/* Count down iterations per second */ +			i = n; +			do { +				rtnl_lock(); +				rc = ops->set_phys_id(dev, +				    (i & 1) ? ETHTOOL_ID_OFF : ETHTOOL_ID_ON); +				rtnl_unlock(); +				if (rc) +					break; +				schedule_timeout_interruptible(interval); +			} while (!signal_pending(current) && --i != 0); +		} while (!signal_pending(current) && +			 (id.data == 0 || --id.data != 0)); +	} + +	rtnl_lock(); +	dev_put(dev); +	busy = false; + +	(void) ops->set_phys_id(dev, ETHTOOL_ID_INACTIVE); +	return rc;  }  static int ethtool_get_stats(struct net_device *dev, void __user *useraddr) @@ -1433,10 +1446,182 @@ static noinline_for_stack int ethtool_flash_device(struct net_device *dev,  	if (!dev->ethtool_ops->flash_device)  		return -EOPNOTSUPP; +	efl.data[ETHTOOL_FLASH_MAX_FILENAME - 1] = 0; +  	return dev->ethtool_ops->flash_device(dev, &efl);  } -/* The main entry point in this file.  Called from net/core/dev.c */ +static int ethtool_set_dump(struct net_device *dev, +			void __user *useraddr) +{ +	struct ethtool_dump dump; + +	if (!dev->ethtool_ops->set_dump) +		return -EOPNOTSUPP; + +	if (copy_from_user(&dump, useraddr, sizeof(dump))) +		return -EFAULT; + +	return dev->ethtool_ops->set_dump(dev, &dump); +} + +static int ethtool_get_dump_flag(struct net_device *dev, +				void __user *useraddr) +{ +	int ret; +	struct ethtool_dump dump; +	const struct ethtool_ops *ops = dev->ethtool_ops; + +	if (!ops->get_dump_flag) +		return -EOPNOTSUPP; + +	if (copy_from_user(&dump, useraddr, sizeof(dump))) +		return -EFAULT; + +	ret = ops->get_dump_flag(dev, &dump); +	if (ret) +		return ret; + +	if (copy_to_user(useraddr, &dump, sizeof(dump))) +		return -EFAULT; +	return 0; +} + +static int ethtool_get_dump_data(struct net_device *dev, +				void __user *useraddr) +{ +	int ret; +	__u32 len; +	struct ethtool_dump dump, tmp; +	const struct ethtool_ops *ops = dev->ethtool_ops; +	void *data = NULL; + +	if (!ops->get_dump_data || !ops->get_dump_flag) +		return -EOPNOTSUPP; + +	if (copy_from_user(&dump, useraddr, sizeof(dump))) +		return -EFAULT; + +	memset(&tmp, 0, sizeof(tmp)); +	tmp.cmd = ETHTOOL_GET_DUMP_FLAG; +	ret = ops->get_dump_flag(dev, &tmp); +	if (ret) +		return ret; + +	len = min(tmp.len, dump.len); +	if (!len) +		return -EFAULT; + +	/* Don't ever let the driver think there's more space available +	 * than it requested with .get_dump_flag(). +	 */ +	dump.len = len; + +	/* Always allocate enough space to hold the whole thing so that the +	 * driver does not need to check the length and bother with partial +	 * dumping. +	 */ +	data = vzalloc(tmp.len); +	if (!data) +		return -ENOMEM; +	ret = ops->get_dump_data(dev, &dump, data); +	if (ret) +		goto out; + +	/* There are two sane possibilities: +	 * 1. The driver's .get_dump_data() does not touch dump.len. +	 * 2. Or it may set dump.len to how much it really writes, which +	 *    should be tmp.len (or len if it can do a partial dump). +	 * In any case respond to userspace with the actual length of data +	 * it's receiving. +	 */ +	WARN_ON(dump.len != len && dump.len != tmp.len); +	dump.len = len; + +	if (copy_to_user(useraddr, &dump, sizeof(dump))) { +		ret = -EFAULT; +		goto out; +	} +	useraddr += offsetof(struct ethtool_dump, data); +	if (copy_to_user(useraddr, data, len)) +		ret = -EFAULT; +out: +	vfree(data); +	return ret; +} + +static int ethtool_get_ts_info(struct net_device *dev, void __user *useraddr) +{ +	int err = 0; +	struct ethtool_ts_info info; +	const struct ethtool_ops *ops = dev->ethtool_ops; +	struct phy_device *phydev = dev->phydev; + +	memset(&info, 0, sizeof(info)); +	info.cmd = ETHTOOL_GET_TS_INFO; + +	if (phydev && phydev->drv && phydev->drv->ts_info) { +		err = phydev->drv->ts_info(phydev, &info); +	} else if (ops->get_ts_info) { +		err = ops->get_ts_info(dev, &info); +	} else { +		info.so_timestamping = +			SOF_TIMESTAMPING_RX_SOFTWARE | +			SOF_TIMESTAMPING_SOFTWARE; +		info.phc_index = -1; +	} + +	if (err) +		return err; + +	if (copy_to_user(useraddr, &info, sizeof(info))) +		err = -EFAULT; + +	return err; +} + +static int ethtool_get_module_info(struct net_device *dev, +				   void __user *useraddr) +{ +	int ret; +	struct ethtool_modinfo modinfo; +	const struct ethtool_ops *ops = dev->ethtool_ops; + +	if (!ops->get_module_info) +		return -EOPNOTSUPP; + +	if (copy_from_user(&modinfo, useraddr, sizeof(modinfo))) +		return -EFAULT; + +	ret = ops->get_module_info(dev, &modinfo); +	if (ret) +		return ret; + +	if (copy_to_user(useraddr, &modinfo, sizeof(modinfo))) +		return -EFAULT; + +	return 0; +} + +static int ethtool_get_module_eeprom(struct net_device *dev, +				     void __user *useraddr) +{ +	int ret; +	struct ethtool_modinfo modinfo; +	const struct ethtool_ops *ops = dev->ethtool_ops; + +	if (!ops->get_module_info || !ops->get_module_eeprom) +		return -EOPNOTSUPP; + +	ret = ops->get_module_info(dev, &modinfo); +	if (ret) +		return ret; + +	return ethtool_get_any_eeprom(dev, useraddr, ops->get_module_eeprom, +				      modinfo.eeprom_len); +} + +/* The main entry point in this file.  Called from net/core/dev_ioctl.c */  int dev_ethtool(struct net *net, struct ifreq *ifr)  { @@ -1444,7 +1629,7 @@ int dev_ethtool(struct net *net, struct ifreq *ifr)  	void __user *useraddr = ifr->ifr_data;  	u32 ethcmd;  	int rc; -	unsigned long old_features; +	netdev_features_t old_features;  	if (!dev || !netif_device_present(dev))  		return -ENODEV; @@ -1452,28 +1637,21 @@ int dev_ethtool(struct net *net, struct ifreq *ifr)  	if (copy_from_user(ðcmd, useraddr, sizeof(ethcmd)))  		return -EFAULT; -	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; -	} -  	/* Allow some commands to be done by anyone */  	switch (ethcmd) {  	case ETHTOOL_GSET:  	case ETHTOOL_GDRVINFO:  	case ETHTOOL_GMSGLVL: +	case ETHTOOL_GLINK:  	case ETHTOOL_GCOALESCE:  	case ETHTOOL_GRINGPARAM:  	case ETHTOOL_GPAUSEPARAM:  	case ETHTOOL_GRXCSUM:  	case ETHTOOL_GTXCSUM:  	case ETHTOOL_GSG: +	case ETHTOOL_GSSET_INFO:  	case ETHTOOL_GSTRINGS: +	case ETHTOOL_GSTATS:  	case ETHTOOL_GTSO:  	case ETHTOOL_GPERMADDR:  	case ETHTOOL_GUFO: @@ -1486,9 +1664,15 @@ int dev_ethtool(struct net *net, struct ifreq *ifr)  	case ETHTOOL_GRXCLSRLCNT:  	case ETHTOOL_GRXCLSRULE:  	case ETHTOOL_GRXCLSRLALL: +	case ETHTOOL_GRXFHINDIR: +	case ETHTOOL_GRSSH: +	case ETHTOOL_GFEATURES: +	case ETHTOOL_GCHANNELS: +	case ETHTOOL_GET_TS_INFO: +	case ETHTOOL_GEEE:  		break;  	default: -		if (!capable(CAP_NET_ADMIN)) +		if (!ns_capable(net->user_ns, CAP_NET_ADMIN))  			return -EPERM;  	} @@ -1526,12 +1710,17 @@ int dev_ethtool(struct net *net, struct ifreq *ifr)  		rc = ethtool_set_value_void(dev, useraddr,  				       dev->ethtool_ops->set_msglevel);  		break; +	case ETHTOOL_GEEE: +		rc = ethtool_get_eee(dev, useraddr); +		break; +	case ETHTOOL_SEEE: +		rc = ethtool_set_eee(dev, useraddr); +		break;  	case ETHTOOL_NWAY_RST:  		rc = ethtool_nway_reset(dev);  		break;  	case ETHTOOL_GLINK: -		rc = ethtool_get_value(dev, useraddr, ethcmd, -				       dev->ethtool_ops->get_link); +		rc = ethtool_get_link(dev, useraddr);  		break;  	case ETHTOOL_GEEPROM:  		rc = ethtool_get_eeprom(dev, useraddr); @@ -1557,42 +1746,6 @@ int dev_ethtool(struct net *net, struct ifreq *ifr)  	case ETHTOOL_SPAUSEPARAM:  		rc = ethtool_set_pauseparam(dev, useraddr);  		break; -	case ETHTOOL_GRXCSUM: -		rc = ethtool_get_value(dev, useraddr, ethcmd, -				       (dev->ethtool_ops->get_rx_csum ? -					dev->ethtool_ops->get_rx_csum : -					ethtool_op_get_rx_csum)); -		break; -	case ETHTOOL_SRXCSUM: -		rc = ethtool_set_rx_csum(dev, useraddr); -		break; -	case ETHTOOL_GTXCSUM: -		rc = ethtool_get_value(dev, useraddr, ethcmd, -				       (dev->ethtool_ops->get_tx_csum ? -					dev->ethtool_ops->get_tx_csum : -					ethtool_op_get_tx_csum)); -		break; -	case ETHTOOL_STXCSUM: -		rc = ethtool_set_tx_csum(dev, useraddr); -		break; -	case ETHTOOL_GSG: -		rc = ethtool_get_value(dev, useraddr, ethcmd, -				       (dev->ethtool_ops->get_sg ? -					dev->ethtool_ops->get_sg : -					ethtool_op_get_sg)); -		break; -	case ETHTOOL_SSG: -		rc = ethtool_set_sg(dev, useraddr); -		break; -	case ETHTOOL_GTSO: -		rc = ethtool_get_value(dev, useraddr, ethcmd, -				       (dev->ethtool_ops->get_tso ? -					dev->ethtool_ops->get_tso : -					ethtool_op_get_tso)); -		break; -	case ETHTOOL_STSO: -		rc = ethtool_set_tso(dev, useraddr); -		break;  	case ETHTOOL_TEST:  		rc = ethtool_self_test(dev, useraddr);  		break; @@ -1608,30 +1761,12 @@ int dev_ethtool(struct net *net, struct ifreq *ifr)  	case ETHTOOL_GPERMADDR:  		rc = ethtool_get_perm_addr(dev, useraddr);  		break; -	case ETHTOOL_GUFO: -		rc = ethtool_get_value(dev, useraddr, ethcmd, -				       (dev->ethtool_ops->get_ufo ? -					dev->ethtool_ops->get_ufo : -					ethtool_op_get_ufo)); -		break; -	case ETHTOOL_SUFO: -		rc = ethtool_set_ufo(dev, useraddr); -		break; -	case ETHTOOL_GGSO: -		rc = ethtool_get_gso(dev, useraddr); -		break; -	case ETHTOOL_SGSO: -		rc = ethtool_set_gso(dev, useraddr); -		break;  	case ETHTOOL_GFLAGS:  		rc = ethtool_get_value(dev, useraddr, ethcmd, -				       (dev->ethtool_ops->get_flags ? -					dev->ethtool_ops->get_flags : -					ethtool_op_get_flags)); +					__ethtool_get_flags);  		break;  	case ETHTOOL_SFLAGS: -		rc = ethtool_set_value(dev, useraddr, -				       dev->ethtool_ops->set_flags); +		rc = ethtool_set_value(dev, useraddr, __ethtool_set_flags);  		break;  	case ETHTOOL_GPFLAGS:  		rc = ethtool_get_value(dev, useraddr, ethcmd, @@ -1653,24 +1788,12 @@ int dev_ethtool(struct net *net, struct ifreq *ifr)  	case ETHTOOL_SRXCLSRLINS:  		rc = ethtool_set_rxnfc(dev, ethcmd, useraddr);  		break; -	case ETHTOOL_GGRO: -		rc = ethtool_get_gro(dev, useraddr); -		break; -	case ETHTOOL_SGRO: -		rc = ethtool_set_gro(dev, useraddr); -		break;  	case ETHTOOL_FLASHDEV:  		rc = ethtool_flash_device(dev, useraddr);  		break;  	case ETHTOOL_RESET:  		rc = ethtool_reset(dev, useraddr);  		break; -	case ETHTOOL_SRXNTUPLE: -		rc = ethtool_set_rx_ntuple(dev, useraddr); -		break; -	case ETHTOOL_GRXNTUPLE: -		rc = ethtool_get_rx_ntuple(dev, useraddr); -		break;  	case ETHTOOL_GSSET_INFO:  		rc = ethtool_get_sset_info(dev, useraddr);  		break; @@ -1680,6 +1803,60 @@ int dev_ethtool(struct net *net, struct ifreq *ifr)  	case ETHTOOL_SRXFHINDIR:  		rc = ethtool_set_rxfh_indir(dev, useraddr);  		break; +	case ETHTOOL_GRSSH: +		rc = ethtool_get_rxfh(dev, useraddr); +		break; +	case ETHTOOL_SRSSH: +		rc = ethtool_set_rxfh(dev, useraddr); +		break; +	case ETHTOOL_GFEATURES: +		rc = ethtool_get_features(dev, useraddr); +		break; +	case ETHTOOL_SFEATURES: +		rc = ethtool_set_features(dev, useraddr); +		break; +	case ETHTOOL_GTXCSUM: +	case ETHTOOL_GRXCSUM: +	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: +	case ETHTOOL_SRXCSUM: +	case ETHTOOL_SSG: +	case ETHTOOL_STSO: +	case ETHTOOL_SUFO: +	case ETHTOOL_SGSO: +	case ETHTOOL_SGRO: +		rc = ethtool_set_one_feature(dev, useraddr, ethcmd); +		break; +	case ETHTOOL_GCHANNELS: +		rc = ethtool_get_channels(dev, useraddr); +		break; +	case ETHTOOL_SCHANNELS: +		rc = ethtool_set_channels(dev, useraddr); +		break; +	case ETHTOOL_SET_DUMP: +		rc = ethtool_set_dump(dev, useraddr); +		break; +	case ETHTOOL_GET_DUMP_FLAG: +		rc = ethtool_get_dump_flag(dev, useraddr); +		break; +	case ETHTOOL_GET_DUMP_DATA: +		rc = ethtool_get_dump_data(dev, useraddr); +		break; +	case ETHTOOL_GET_TS_INFO: +		rc = ethtool_get_ts_info(dev, useraddr); +		break; +	case ETHTOOL_GMODULEINFO: +		rc = ethtool_get_module_info(dev, useraddr); +		break; +	case ETHTOOL_GMODULEEEPROM: +		rc = ethtool_get_module_eeprom(dev, useraddr); +		break;  	default:  		rc = -EOPNOTSUPP;  	}  | 
