net: Introduce new feature setting ops

This introduces a new framework to handle device features setting.
It consists of:
  - new fields in struct net_device:
	+ hw_features - features that hw/driver supports toggling
	+ wanted_features - features that user wants enabled, when possible
  - new netdev_ops:
	+ feat = ndo_fix_features(dev, feat) - API checking constraints for
		enabling features or their combinations
	+ ndo_set_features(dev) - API updating hardware state to match
		changed dev->features
  - new ethtool commands:
	+ ETHTOOL_GFEATURES/ETHTOOL_SFEATURES: get/set dev->wanted_features
		and trigger device reconfiguration if resulting dev->features
		changed
	+ ETHTOOL_GSTRINGS(ETH_SS_FEATURES): get feature bits names (meaning)

Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Michał Mirosław
2011-02-15 16:59:17 +00:00
committed by David S. Miller
parent 0a41770477
commit 5455c6998d
4 changed files with 283 additions and 10 deletions

View File

@@ -172,10 +172,120 @@ EXPORT_SYMBOL(ethtool_ntuple_flush);
/* Handlers for each ethtool command */
#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",
"",
"",
"",
};
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
@@ -187,8 +297,12 @@ static void __ethtool_get_strings(struct net_device *dev,
{
const struct ethtool_ops *ops = dev->ethtool_ops;
/* ops->get_strings is valid because checked earlier */
ops->get_strings(dev, stringset, data);
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)
@@ -1533,6 +1647,7 @@ int dev_ethtool(struct net *net, struct ifreq *ifr)
case ETHTOOL_GRXCLSRLCNT:
case ETHTOOL_GRXCLSRULE:
case ETHTOOL_GRXCLSRLALL:
case ETHTOOL_GFEATURES:
break;
default:
if (!capable(CAP_NET_ADMIN))
@@ -1678,6 +1793,12 @@ int dev_ethtool(struct net *net, struct ifreq *ifr)
case ETHTOOL_SRXFHINDIR:
rc = ethtool_set_rxfh_indir(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_GSG:
case ETHTOOL_GTSO: