1869 lines · 44.3 KB · C
RawDownload
/* * net/core/ethtool.c - Ethtool ioctl handler * Copyright (c) 2003 Matthew Wilcox <matthew@wil.cx> * * This file is where we call all the ethtool_ops commands to get * the information ethtool needs. * * 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. */ #include <linux/module.h>#include <linux/types.h>#include <linux/capability.h>#include <linux/errno.h>#include <linux/ethtool.h>#include <linux/netdevice.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. * 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;}EXPORT_SYMBOL(ethtool_op_get_link); 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); 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;}EXPORT_SYMBOL(ethtool_op_set_tx_csum); 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;}EXPORT_SYMBOL(ethtool_op_set_tx_hw_csum); 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;}EXPORT_SYMBOL(ethtool_op_set_tx_ipv6_csum); u32 ethtool_op_get_sg(struct net_device *dev){	return (dev->features & NETIF_F_SG) != 0;}EXPORT_SYMBOL(ethtool_op_get_sg); 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;}EXPORT_SYMBOL(ethtool_op_set_sg); u32 ethtool_op_get_tso(struct net_device *dev){	return (dev->features & NETIF_F_TSO) != 0;}EXPORT_SYMBOL(ethtool_op_get_tso); 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;}EXPORT_SYMBOL(ethtool_op_set_tso); u32 ethtool_op_get_ufo(struct net_device *dev){	return (dev->features & NETIF_F_UFO) != 0;}EXPORT_SYMBOL(ethtool_op_get_ufo); 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;}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); 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;}EXPORT_SYMBOL(ethtool_op_get_flags); /* Check if device can enable (or disable) particular feature coded in "data" * argument. Flags "supported" describe features that can be toggled by device. * If feature can not be toggled, it state (enabled or disabled) must match * hardcoded device features state, otherwise flags are marked as invalid. */bool ethtool_invalid_flags(struct net_device *dev, u32 data, u32 supported){	u32 features = dev->features & flags_dup_features;	/* "data" can contain only flags_dup_features bits,	 * see __ethtool_set_flags */ 	return (features & ~supported) != (data & ~supported);}EXPORT_SYMBOL(ethtool_invalid_flags); int ethtool_op_set_flags(struct net_device *dev, u32 data, u32 supported){	if (ethtool_invalid_flags(dev, data, supported))		return -EINVAL; 	dev->features = ((dev->features & ~flags_dup_features) |			 (data & flags_dup_features));	return 0;}EXPORT_SYMBOL(ethtool_op_set_flags); /* Handlers for each ethtool command */ #define ETHTOOL_DEV_FEATURE_WORDS	1 static void ethtool_get_features_compat(struct net_device *dev,	struct ethtool_get_features_block *features){	if (!dev->ethtool_ops)		return; 	/* getting RX checksum */	if (dev->ethtool_ops->get_rx_csum)		if (dev->ethtool_ops->get_rx_csum(dev))			features[0].active |= NETIF_F_RXCSUM; 	/* mark legacy-changeable features */	if (dev->ethtool_ops->set_sg)		features[0].available |= NETIF_F_SG;	if (dev->ethtool_ops->set_tx_csum)		features[0].available |= NETIF_F_ALL_CSUM;	if (dev->ethtool_ops->set_tso)		features[0].available |= NETIF_F_ALL_TSO;	if (dev->ethtool_ops->set_rx_csum)		features[0].available |= NETIF_F_RXCSUM;	if (dev->ethtool_ops->set_flags)		features[0].available |= flags_dup_features;} static int ethtool_set_feature_compat(struct net_device *dev,	int (*legacy_set)(struct net_device *, u32),	struct ethtool_set_features_block *features, u32 mask){	u32 do_set; 	if (!legacy_set)		return 0; 	if (!(features[0].valid & mask))		return 0; 	features[0].valid &= ~mask; 	do_set = !!(features[0].requested & mask); 	if (legacy_set(dev, do_set) < 0)		netdev_info(dev,			"Legacy feature change (%s) failed for 0x%08x\n",			do_set ? "set" : "clear", mask); 	return 1;} static int ethtool_set_flags_compat(struct net_device *dev,	int (*legacy_set)(struct net_device *, u32),	struct ethtool_set_features_block *features, u32 mask){	u32 value; 	if (!legacy_set)		return 0; 	if (!(features[0].valid & mask))		return 0; 	value = dev->features & ~features[0].valid;	value |= features[0].requested; 	features[0].valid &= ~mask; 	if (legacy_set(dev, value & mask) < 0)		netdev_info(dev, "Legacy flags change failed\n"); 	return 1;} static int ethtool_set_features_compat(struct net_device *dev,	struct ethtool_set_features_block *features){	int compat; 	if (!dev->ethtool_ops)		return 0; 	compat  = ethtool_set_feature_compat(dev, dev->ethtool_ops->set_sg,		features, NETIF_F_SG);	compat |= ethtool_set_feature_compat(dev, dev->ethtool_ops->set_tx_csum,		features, NETIF_F_ALL_CSUM);	compat |= ethtool_set_feature_compat(dev, dev->ethtool_ops->set_tso,		features, NETIF_F_ALL_TSO);	compat |= ethtool_set_feature_compat(dev, dev->ethtool_ops->set_rx_csum,		features, NETIF_F_RXCSUM);	compat |= ethtool_set_flags_compat(dev, dev->ethtool_ops->set_flags,		features, flags_dup_features); 	return compat;} 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; 	ethtool_get_features_compat(dev, features); 	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 (ethtool_set_features_compat(dev, features))		ret |= ETHTOOL_F_COMPAT; 	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",	/* NETIF_F_RXCSUM */          "rx-checksum",	/* NETIF_F_NOCACHE_COPY */    "tx-nocache-copy",	/* NETIF_F_LOOPBACK */        "loopback",}; static int __ethtool_get_sset_count(struct net_device *dev, int sset){	const struct ethtool_ops *ops = dev->ethtool_ops; 	if (sset == ETH_SS_FEATURES)		return ARRAY_SIZE(netdev_features_strings); 	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; 	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);} 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;	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();	}} 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;	case ETHTOOL_GRXCSUM:		return ops->get_rx_csum;	case ETHTOOL_SSG:		return ops->get_sg;	case ETHTOOL_STSO:		return ops->get_tso;	case ETHTOOL_SUFO:		return ops->get_ufo;	default:		return NULL;	}} static u32 __ethtool_get_rx_csum_oldbug(struct net_device *dev){	return !!(dev->features & NETIF_F_ALL_CSUM);} static int ethtool_get_one_feature(struct net_device *dev,	char __user *useraddr, u32 ethcmd){	u32 mask = ethtool_get_feature_mask(ethcmd);	struct ethtool_value edata = {		.cmd = ethcmd,		.data = !!(dev->features & mask),	}; 	/* compatibility with discrete get_ ops */	if (!(dev->hw_features & mask)) {		u32 (*actor)(struct net_device *); 		actor = __ethtool_get_one_feature_actor(dev, ethcmd); 		/* bug compatibility with old get_rx_csum */		if (ethcmd == ETHTOOL_GRXCSUM && !actor)			actor = __ethtool_get_rx_csum_oldbug; 		if (actor)			edata.data = actor(dev);	} 	if (copy_to_user(useraddr, &edata, sizeof(edata)))		return -EFAULT;	return 0;} static int __ethtool_set_tx_csum(struct net_device *dev, u32 data);static int __ethtool_set_rx_csum(struct net_device *dev, u32 data);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){

Lines 1–500 of 1869.