hv_netvsc: use start_remove flag to protect netvsc_link_change()
authorVitaly Kuznetsov <vkuznets@redhat.com>
Fri, 13 May 2016 11:55:21 +0000 (13:55 +0200)
committerDavid S. Miller <davem@davemloft.net>
Mon, 16 May 2016 17:26:00 +0000 (13:26 -0400)
netvsc_link_change() can race with netvsc_change_mtu() or
netvsc_set_channels() as these functions destroy struct netvsc_device and
rndis filter. Use start_remove flag for syncronization. As
netvsc_change_mtu()/netvsc_set_channels() are called with rtnl lock held
we need to take it before checking start_remove value in
netvsc_link_change().

Reported-by: Haiyang Zhang <haiyangz@microsoft.com>
Signed-off-by: Vitaly Kuznetsov <vkuznets@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/hyperv/netvsc_drv.c

index b3fa2cd..01de2dc 100644 (file)
@@ -838,6 +838,8 @@ static int netvsc_set_channels(struct net_device *net,
  out:
        netvsc_open(net);
        net_device_ctx->start_remove = false;
+       /* We may have missed link change notifications */
+       schedule_delayed_work(&net_device_ctx->dwork, 0);
 
        return ret;
 
@@ -946,6 +948,9 @@ out:
        netvsc_open(ndev);
        ndevctx->start_remove = false;
 
+       /* We may have missed link change notifications */
+       schedule_delayed_work(&ndevctx->dwork, 0);
+
        return ret;
 }
 
@@ -1066,6 +1071,11 @@ static void netvsc_link_change(struct work_struct *w)
        unsigned long flags, next_reconfig, delay;
 
        ndev_ctx = container_of(w, struct net_device_context, dwork.work);
+
+       rtnl_lock();
+       if (ndev_ctx->start_remove)
+               goto out_unlock;
+
        net_device = hv_get_drvdata(ndev_ctx->device_ctx);
        rdev = net_device->extension;
        net = net_device->ndev;
@@ -1079,7 +1089,7 @@ static void netvsc_link_change(struct work_struct *w)
                delay = next_reconfig - jiffies;
                delay = delay < LINKCHANGE_INT ? delay : LINKCHANGE_INT;
                schedule_delayed_work(&ndev_ctx->dwork, delay);
-               return;
+               goto out_unlock;
        }
        ndev_ctx->last_reconfig = jiffies;
 
@@ -1093,9 +1103,7 @@ static void netvsc_link_change(struct work_struct *w)
        spin_unlock_irqrestore(&ndev_ctx->lock, flags);
 
        if (!event)
-               return;
-
-       rtnl_lock();
+               goto out_unlock;
 
        switch (event->event) {
                /* Only the following events are possible due to the check in
@@ -1144,6 +1152,11 @@ static void netvsc_link_change(struct work_struct *w)
         */
        if (reschedule)
                schedule_delayed_work(&ndev_ctx->dwork, LINKCHANGE_INT);
+
+       return;
+
+out_unlock:
+       rtnl_unlock();
 }
 
 static void netvsc_free_netdev(struct net_device *netdev)