diff options
author | Vitaly Kuznetsov <vkuznets@redhat.com> | 2016-05-13 13:55:21 +0200 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2016-05-16 19:26:00 +0200 |
commit | 1bdcec8a5f05445752a0639edd603ac09ae6c553 (patch) | |
tree | 09c637dad4d6a3a111646b5354c1a1852c3fa581 /drivers/net/hyperv/netvsc_drv.c | |
parent | hv_netvsc: move start_remove flag to net_device_context (diff) | |
download | linux-1bdcec8a5f05445752a0639edd603ac09ae6c553.tar.xz linux-1bdcec8a5f05445752a0639edd603ac09ae6c553.zip |
hv_netvsc: use start_remove flag to protect netvsc_link_change()
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>
Diffstat (limited to 'drivers/net/hyperv/netvsc_drv.c')
-rw-r--r-- | drivers/net/hyperv/netvsc_drv.c | 21 |
1 files changed, 17 insertions, 4 deletions
diff --git a/drivers/net/hyperv/netvsc_drv.c b/drivers/net/hyperv/netvsc_drv.c index b3fa2cdcb3f6..01de2dcef170 100644 --- a/drivers/net/hyperv/netvsc_drv.c +++ b/drivers/net/hyperv/netvsc_drv.c @@ -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) |