net: Introduce new feature setting ops
authorMichał Mirosław <mirq-linux@rere.qmqm.pl>
Tue, 15 Feb 2011 16:59:17 +0000 (16:59 +0000)
committerDavid S. Miller <davem@davemloft.net>
Thu, 17 Feb 2011 22:16:33 +0000 (14:16 -0800)
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>
include/linux/ethtool.h
include/linux/netdevice.h
net/core/dev.c
net/core/ethtool.c

index 1908929204a9eb284a11eaabcd7959ee3a1614a8..806e716bb4fb534c377fa9323db18fafb871b085 100644 (file)
@@ -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
index dede3fdbb4be3b98050bf36e0337e038cd81ff0d..85f67e225f60121371e261deddd906c53e4b17fa 100644 (file)
@@ -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);
index 8686f6ffe7f0a560b5e2d9e1f9072ac58241abbf..4f6943928fe8d4ba8e9706633babb005d0f3234e 100644 (file)
@@ -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
index c3fb8f90de6d492caadb37af517f3d1869318197..95773960dc77f27ee3d7c3798a7543cedc88198f 100644 (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: