]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
hv_netvsc: preserve hw_features on mtu/channels/ringparam changes
authorVitaly Kuznetsov <vkuznets@redhat.com>
Mon, 14 May 2018 22:32:06 +0000 (15:32 -0700)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 25 May 2018 14:17:27 +0000 (16:17 +0200)
[ Commit aefd80e874e98a864915df5b7d90824a4340b450 upstream. ]

rndis_filter_device_add() is called both from netvsc_probe() when we
initially create the device and from set channels/mtu/ringparam
routines where we basically remove the device and add it back.

hw_features is reset in rndis_filter_device_add() and filled with
host data. However, we lose all additional flags which are set outside
of the driver, e.g. register_netdevice() adds NETIF_F_SOFT_FEATURES and
many others.

Unfortunately, calls to rndis_{query_hwcaps(), _set_offload_params()}
calls cannot be avoided on every RNDIS reset: host expects us to set
required features explicitly. Moreover, in theory hardware capabilities
can change and we need to reflect the change in hw_features.

Reset net->hw_features bits according to host data in
rndis_netdev_set_hwcaps(), clear corresponding feature bits
from net->features in case some features went missing (will never happen
in real life I guess but let's be consistent).

Signed-off-by: Vitaly Kuznetsov <vkuznets@redhat.com>
Reviewed-by: Haiyang Zhang <haiyangz@microsoft.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/net/hyperv/hyperv_net.h
drivers/net/hyperv/netvsc_drv.c
drivers/net/hyperv/rndis_filter.c

index 94de7ca1e52ebeddae7889d2b14e9481d54a7d24..a3f628c3c9ed1c174315312fdf2e5d046704003e 100644 (file)
@@ -659,6 +659,10 @@ struct nvsp_message {
 #define NETVSC_RECEIVE_BUFFER_ID               0xcafe
 #define NETVSC_SEND_BUFFER_ID                  0
 
+#define NETVSC_SUPPORTED_HW_FEATURES (NETIF_F_RXCSUM | NETIF_F_IP_CSUM | \
+                                     NETIF_F_TSO | NETIF_F_IPV6_CSUM | \
+                                     NETIF_F_TSO6)
+
 #define VRSS_SEND_TAB_SIZE 16  /* must be power of 2 */
 #define VRSS_CHANNEL_MAX 64
 #define VRSS_CHANNEL_DEFAULT 8
index f7cc2bb1f3e54e6a447aeafce507d4312e1335ab..717899993933f0ec1d7e6d8f2000fa04413c3542 100644 (file)
@@ -1956,7 +1956,7 @@ static int netvsc_probe(struct hv_device *dev,
 
        memcpy(net->dev_addr, device_info.mac_adr, ETH_ALEN);
 
-       /* hw_features computed in rndis_filter_device_add */
+       /* hw_features computed in rndis_netdev_set_hwcaps() */
        net->features = net->hw_features |
                NETIF_F_HIGHDMA | NETIF_F_SG |
                NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_RX;
index 0648eebda829cf14408b103b03913af48dab876d..be57639bee29948d382e5579f1becbaae27ece4a 100644 (file)
@@ -1131,69 +1131,20 @@ unlock:
        rtnl_unlock();
 }
 
-struct netvsc_device *rndis_filter_device_add(struct hv_device *dev,
-                                     struct netvsc_device_info *device_info)
+static int rndis_netdev_set_hwcaps(struct rndis_device *rndis_device,
+                                  struct netvsc_device *nvdev)
 {
-       struct net_device *net = hv_get_drvdata(dev);
+       struct net_device *net = rndis_device->ndev;
        struct net_device_context *net_device_ctx = netdev_priv(net);
-       struct netvsc_device *net_device;
-       struct rndis_device *rndis_device;
        struct ndis_offload hwcaps;
        struct ndis_offload_params offloads;
-       struct ndis_recv_scale_cap rsscap;
-       u32 rsscap_size = sizeof(struct ndis_recv_scale_cap);
        unsigned int gso_max_size = GSO_MAX_SIZE;
-       u32 mtu, size;
-       const struct cpumask *node_cpu_mask;
-       u32 num_possible_rss_qs;
-       int i, ret;
-
-       rndis_device = get_rndis_device();
-       if (!rndis_device)
-               return ERR_PTR(-ENODEV);
-
-       /*
-        * Let the inner driver handle this first to create the netvsc channel
-        * NOTE! Once the channel is created, we may get a receive callback
-        * (RndisFilterOnReceive()) before this call is completed
-        */
-       net_device = netvsc_device_add(dev, device_info);
-       if (IS_ERR(net_device)) {
-               kfree(rndis_device);
-               return net_device;
-       }
-
-       /* Initialize the rndis device */
-       net_device->max_chn = 1;
-       net_device->num_chn = 1;
-
-       net_device->extension = rndis_device;
-       rndis_device->ndev = net;
-
-       /* Send the rndis initialization message */
-       ret = rndis_filter_init_device(rndis_device, net_device);
-       if (ret != 0)
-               goto err_dev_remv;
-
-       /* Get the MTU from the host */
-       size = sizeof(u32);
-       ret = rndis_filter_query_device(rndis_device, net_device,
-                                       RNDIS_OID_GEN_MAXIMUM_FRAME_SIZE,
-                                       &mtu, &size);
-       if (ret == 0 && size == sizeof(u32) && mtu < net->mtu)
-               net->mtu = mtu;
-
-       /* Get the mac address */
-       ret = rndis_filter_query_device_mac(rndis_device, net_device);
-       if (ret != 0)
-               goto err_dev_remv;
-
-       memcpy(device_info->mac_adr, rndis_device->hw_mac_adr, ETH_ALEN);
+       int ret;
 
        /* Find HW offload capabilities */
-       ret = rndis_query_hwcaps(rndis_device, net_device, &hwcaps);
+       ret = rndis_query_hwcaps(rndis_device, nvdev, &hwcaps);
        if (ret != 0)
-               goto err_dev_remv;
+               return ret;
 
        /* A value of zero means "no change"; now turn on what we want. */
        memset(&offloads, 0, sizeof(struct ndis_offload_params));
@@ -1201,8 +1152,12 @@ struct netvsc_device *rndis_filter_device_add(struct hv_device *dev,
        /* Linux does not care about IP checksum, always does in kernel */
        offloads.ip_v4_csum = NDIS_OFFLOAD_PARAMETERS_TX_RX_DISABLED;
 
+       /* Reset previously set hw_features flags */
+       net->hw_features &= ~NETVSC_SUPPORTED_HW_FEATURES;
+       net_device_ctx->tx_checksum_mask = 0;
+
        /* Compute tx offload settings based on hw capabilities */
-       net->hw_features = NETIF_F_RXCSUM;
+       net->hw_features |= NETIF_F_RXCSUM;
 
        if ((hwcaps.csum.ip4_txcsum & NDIS_TXCSUM_ALL_TCP4) == NDIS_TXCSUM_ALL_TCP4) {
                /* Can checksum TCP */
@@ -1246,10 +1201,75 @@ struct netvsc_device *rndis_filter_device_add(struct hv_device *dev,
                }
        }
 
+       /* In case some hw_features disappeared we need to remove them from
+        * net->features list as they're no longer supported.
+        */
+       net->features &= ~NETVSC_SUPPORTED_HW_FEATURES | net->hw_features;
+
        netif_set_gso_max_size(net, gso_max_size);
 
-       ret = rndis_filter_set_offload_params(net, net_device, &offloads);
-       if (ret)
+       ret = rndis_filter_set_offload_params(net, nvdev, &offloads);
+
+       return ret;
+}
+
+struct netvsc_device *rndis_filter_device_add(struct hv_device *dev,
+                                     struct netvsc_device_info *device_info)
+{
+       struct net_device *net = hv_get_drvdata(dev);
+       struct netvsc_device *net_device;
+       struct rndis_device *rndis_device;
+       struct ndis_recv_scale_cap rsscap;
+       u32 rsscap_size = sizeof(struct ndis_recv_scale_cap);
+       u32 mtu, size;
+       const struct cpumask *node_cpu_mask;
+       u32 num_possible_rss_qs;
+       int i, ret;
+
+       rndis_device = get_rndis_device();
+       if (!rndis_device)
+               return ERR_PTR(-ENODEV);
+
+       /* Let the inner driver handle this first to create the netvsc channel
+        * NOTE! Once the channel is created, we may get a receive callback
+        * (RndisFilterOnReceive()) before this call is completed
+        */
+       net_device = netvsc_device_add(dev, device_info);
+       if (IS_ERR(net_device)) {
+               kfree(rndis_device);
+               return net_device;
+       }
+
+       /* Initialize the rndis device */
+       net_device->max_chn = 1;
+       net_device->num_chn = 1;
+
+       net_device->extension = rndis_device;
+       rndis_device->ndev = net;
+
+       /* Send the rndis initialization message */
+       ret = rndis_filter_init_device(rndis_device, net_device);
+       if (ret != 0)
+               goto err_dev_remv;
+
+       /* Get the MTU from the host */
+       size = sizeof(u32);
+       ret = rndis_filter_query_device(rndis_device, net_device,
+                                       RNDIS_OID_GEN_MAXIMUM_FRAME_SIZE,
+                                       &mtu, &size);
+       if (ret == 0 && size == sizeof(u32) && mtu < net->mtu)
+               net->mtu = mtu;
+
+       /* Get the mac address */
+       ret = rndis_filter_query_device_mac(rndis_device, net_device);
+       if (ret != 0)
+               goto err_dev_remv;
+
+       memcpy(device_info->mac_adr, rndis_device->hw_mac_adr, ETH_ALEN);
+
+       /* Query and set hardware capabilities */
+       ret = rndis_netdev_set_hwcaps(rndis_device, net_device);
+       if (ret != 0)
                goto err_dev_remv;
 
        rndis_filter_query_device_link_status(rndis_device, net_device);