Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5455c699 authored by Michał Mirosław's avatar Michał Mirosław Committed by David S. Miller
Browse files

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: default avatarMichał Mirosław <mirq-linux@rere.qmqm.pl>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 0a417704
Loading
Loading
Loading
Loading
+85 −0
Original line number Diff line number Diff line
@@ -251,6 +251,7 @@ enum ethtool_stringset {
	ETH_SS_STATS,
	ETH_SS_PRIV_FLAGS,
	ETH_SS_NTUPLE_FILTERS,
	ETH_SS_FEATURES,
};

/* for passing string sets for data tagging */
@@ -523,6 +524,87 @@ struct ethtool_flash {
	char	data[ETHTOOL_FLASH_MAX_FILENAME];
};

/* for returning and changing feature sets */

/**
 * struct ethtool_get_features_block - block with state of 32 features
 * @available: mask of changeable features
 * @requested: mask of features requested to be enabled if possible
 * @active: mask of currently enabled features
 * @never_changed: mask of features not changeable for any device
 */
struct ethtool_get_features_block {
	__u32	available;
	__u32	requested;
	__u32	active;
	__u32	never_changed;
};

/**
 * struct ethtool_gfeatures - command to get state of device's features
 * @cmd: command number = %ETHTOOL_GFEATURES
 * @size: in: number of elements in the features[] array;
 *       out: number of elements in features[] needed to hold all features
 * @features: state of features
 */
struct ethtool_gfeatures {
	__u32	cmd;
	__u32	size;
	struct ethtool_get_features_block features[0];
};

/**
 * struct ethtool_set_features_block - block with request for 32 features
 * @valid: mask of features to be changed
 * @requested: values of features to be changed
 */
struct ethtool_set_features_block {
	__u32	valid;
	__u32	requested;
};

/**
 * struct ethtool_sfeatures - command to request change in device's features
 * @cmd: command number = %ETHTOOL_SFEATURES
 * @size: array size of the features[] array
 * @features: feature change masks
 */
struct ethtool_sfeatures {
	__u32	cmd;
	__u32	size;
	struct ethtool_set_features_block features[0];
};

/*
 * %ETHTOOL_SFEATURES changes features present in features[].valid to the
 * values of corresponding bits in features[].requested. Bits in .requested
 * not set in .valid or not changeable are ignored.
 *
 * Returns %EINVAL when .valid contains undefined or never-changable bits
 * or size is not equal to required number of features words (32-bit blocks).
 * Returns >= 0 if request was completed; bits set in the value mean:
 *   %ETHTOOL_F_UNSUPPORTED - there were bits set in .valid that are not
 *	changeable (not present in %ETHTOOL_GFEATURES' features[].available)
 *	those bits were ignored.
 *   %ETHTOOL_F_WISH - some or all changes requested were recorded but the
 *      resulting state of bits masked by .valid is not equal to .requested.
 *      Probably there are other device-specific constraints on some features
 *      in the set. When %ETHTOOL_F_UNSUPPORTED is set, .valid is considered
 *      here as though ignored bits were cleared.
 *
 * Meaning of bits in the masks are obtained by %ETHTOOL_GSSET_INFO (number of
 * bits in the arrays - always multiple of 32) and %ETHTOOL_GSTRINGS commands
 * for ETH_SS_FEATURES string set. First entry in the table corresponds to least
 * significant bit in features[0] fields. Empty strings mark undefined features.
 */
enum ethtool_sfeatures_retval_bits {
	ETHTOOL_F_UNSUPPORTED__BIT,
	ETHTOOL_F_WISH__BIT,
};

#define ETHTOOL_F_UNSUPPORTED   (1 << ETHTOOL_F_UNSUPPORTED__BIT)
#define ETHTOOL_F_WISH          (1 << ETHTOOL_F_WISH__BIT)

#ifdef __KERNEL__

#include <linux/rculist.h>
@@ -744,6 +826,9 @@ struct ethtool_ops {
#define ETHTOOL_GRXFHINDIR	0x00000038 /* Get RX flow hash indir'n table */
#define ETHTOOL_SRXFHINDIR	0x00000039 /* Set RX flow hash indir'n table */

#define ETHTOOL_GFEATURES	0x0000003a /* Get device offload settings */
#define ETHTOOL_SFEATURES	0x0000003b /* Change device offload settings */

/* compatibility with older code */
#define SPARC_ETH_GSET		ETHTOOL_GSET
#define SPARC_ETH_SSET		ETHTOOL_SSET
+35 −2
Original line number Diff line number Diff line
@@ -791,6 +791,18 @@ struct netdev_tc_txq {
 *
 * int (*ndo_del_slave)(struct net_device *dev, struct net_device *slave_dev);
 *	Called to release previously enslaved netdev.
 *
 *      Feature/offload setting functions.
 * u32 (*ndo_fix_features)(struct net_device *dev, u32 features);
 *	Adjusts the requested feature flags according to device-specific
 *	constraints, and returns the resulting flags. Must not modify
 *	the device state.
 *
 * int (*ndo_set_features)(struct net_device *dev, u32 features);
 *	Called to update device configuration to new features. Passed
 *	feature set might be less than what was returned by ndo_fix_features()).
 *	Must return >0 or -errno if it changed dev->features itself.
 *
 */
#define HAVE_NET_DEVICE_OPS
struct net_device_ops {
@@ -874,6 +886,10 @@ struct net_device_ops {
						 struct net_device *slave_dev);
	int			(*ndo_del_slave)(struct net_device *dev,
						 struct net_device *slave_dev);
	u32			(*ndo_fix_features)(struct net_device *dev,
						    u32 features);
	int			(*ndo_set_features)(struct net_device *dev,
						    u32 features);
};

/*
@@ -925,12 +941,18 @@ struct net_device {
	struct list_head	napi_list;
	struct list_head	unreg_list;

	/* Net device features */
	/* currently active device features */
	u32			features;

	/* user-changeable features */
	u32			hw_features;
	/* user-requested features */
	u32			wanted_features;
	/* VLAN feature mask */
	u32			vlan_features;

	/* Net device feature bits; if you change something,
	 * also update netdev_features_strings[] in ethtool.c */

#define NETIF_F_SG		1	/* Scatter/gather IO. */
#define NETIF_F_IP_CSUM		2	/* Can checksum TCP/UDP over IPv4. */
#define NETIF_F_NO_CSUM		4	/* Does not require checksum. F.e. loopack. */
@@ -966,6 +988,12 @@ struct net_device {
#define NETIF_F_TSO6		(SKB_GSO_TCPV6 << NETIF_F_GSO_SHIFT)
#define NETIF_F_FSO		(SKB_GSO_FCOE << NETIF_F_GSO_SHIFT)

	/* Features valid for ethtool to change */
	/* = all defined minus driver/device-class-related */
#define NETIF_F_NEVER_CHANGE	(NETIF_F_HIGHDMA | NETIF_F_VLAN_CHALLENGED | \
				  NETIF_F_LLTX | NETIF_F_NETNS_LOCAL)
#define NETIF_F_ETHTOOL_BITS	(0x1f3fffff & ~NETIF_F_NEVER_CHANGE)

	/* List of features with software fallbacks. */
#define NETIF_F_GSO_SOFTWARE	(NETIF_F_TSO | NETIF_F_TSO_ECN | \
				 NETIF_F_TSO6 | NETIF_F_UFO)
@@ -2428,8 +2456,13 @@ extern char *netdev_drivername(const struct net_device *dev, char *buffer, int l

extern void linkwatch_run_queue(void);

static inline u32 netdev_get_wanted_features(struct net_device *dev)
{
	return (dev->features & ~dev->hw_features) | dev->wanted_features;
}
u32 netdev_increment_features(u32 all, u32 one, u32 mask);
u32 netdev_fix_features(struct net_device *dev, u32 features);
void netdev_update_features(struct net_device *dev);

void netif_stacked_transfer_operstate(const struct net_device *rootdev,
					struct net_device *dev);
+40 −6
Original line number Diff line number Diff line
@@ -5302,6 +5302,37 @@ u32 netdev_fix_features(struct net_device *dev, u32 features)
}
EXPORT_SYMBOL(netdev_fix_features);

void netdev_update_features(struct net_device *dev)
{
	u32 features;
	int err = 0;

	features = netdev_get_wanted_features(dev);

	if (dev->netdev_ops->ndo_fix_features)
		features = dev->netdev_ops->ndo_fix_features(dev, features);

	/* driver might be less strict about feature dependencies */
	features = netdev_fix_features(dev, features);

	if (dev->features == features)
		return;

	netdev_info(dev, "Features changed: 0x%08x -> 0x%08x\n",
		dev->features, features);

	if (dev->netdev_ops->ndo_set_features)
		err = dev->netdev_ops->ndo_set_features(dev, features);

	if (!err)
		dev->features = features;
	else if (err < 0)
		netdev_err(dev,
			"set_features() failed (%d); wanted 0x%08x, left 0x%08x\n",
			err, features, dev->features);
}
EXPORT_SYMBOL(netdev_update_features);

/**
 *	netif_stacked_transfer_operstate -	transfer operstate
 *	@rootdev: the root or lower level device to transfer state from
@@ -5436,15 +5467,18 @@ int register_netdevice(struct net_device *dev)
	if (dev->iflink == -1)
		dev->iflink = dev->ifindex;

	/* Enable software offloads by default - will be stripped in
	 * netdev_fix_features() if not supported. */
	dev->features |= NETIF_F_SOFT_FEATURES;
	/* Transfer changeable features to wanted_features and enable
	 * software offloads (GSO and GRO).
	 */
	dev->hw_features |= NETIF_F_SOFT_FEATURES;
	dev->wanted_features = (dev->features & dev->hw_features)
		| NETIF_F_SOFT_FEATURES;

	/* Avoid warning from netdev_fix_features() for GSO without SG */
	if (!(dev->features & NETIF_F_SG))
		dev->features &= ~NETIF_F_GSO;
	if (!(dev->wanted_features & NETIF_F_SG))
		dev->wanted_features &= ~NETIF_F_GSO;

	dev->features = netdev_fix_features(dev, dev->features);
	netdev_update_features(dev);

	/* Enable GRO and NETIF_F_HIGHDMA for vlans by default,
	 * vlan_dev_init() will do the dev->features check, so these features
+123 −2
Original line number Diff line number Diff line
@@ -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,6 +297,10 @@ static void __ethtool_get_strings(struct net_device *dev,
{
	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);
}
@@ -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: