Commit 1bdcec8a authored by Vitaly Kuznetsov's avatar Vitaly Kuznetsov Committed by David S. Miller

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: default avatarHaiyang Zhang <haiyangz@microsoft.com>
Signed-off-by: default avatarVitaly Kuznetsov <vkuznets@redhat.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent f580aec4
...@@ -838,6 +838,8 @@ static int netvsc_set_channels(struct net_device *net, ...@@ -838,6 +838,8 @@ static int netvsc_set_channels(struct net_device *net,
out: out:
netvsc_open(net); netvsc_open(net);
net_device_ctx->start_remove = false; net_device_ctx->start_remove = false;
/* We may have missed link change notifications */
schedule_delayed_work(&net_device_ctx->dwork, 0);
return ret; return ret;
...@@ -946,6 +948,9 @@ static int netvsc_change_mtu(struct net_device *ndev, int mtu) ...@@ -946,6 +948,9 @@ static int netvsc_change_mtu(struct net_device *ndev, int mtu)
netvsc_open(ndev); netvsc_open(ndev);
ndevctx->start_remove = false; ndevctx->start_remove = false;
/* We may have missed link change notifications */
schedule_delayed_work(&ndevctx->dwork, 0);
return ret; return ret;
} }
...@@ -1066,6 +1071,11 @@ static void netvsc_link_change(struct work_struct *w) ...@@ -1066,6 +1071,11 @@ static void netvsc_link_change(struct work_struct *w)
unsigned long flags, next_reconfig, delay; unsigned long flags, next_reconfig, delay;
ndev_ctx = container_of(w, struct net_device_context, dwork.work); 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); net_device = hv_get_drvdata(ndev_ctx->device_ctx);
rdev = net_device->extension; rdev = net_device->extension;
net = net_device->ndev; net = net_device->ndev;
...@@ -1079,7 +1089,7 @@ static void netvsc_link_change(struct work_struct *w) ...@@ -1079,7 +1089,7 @@ static void netvsc_link_change(struct work_struct *w)
delay = next_reconfig - jiffies; delay = next_reconfig - jiffies;
delay = delay < LINKCHANGE_INT ? delay : LINKCHANGE_INT; delay = delay < LINKCHANGE_INT ? delay : LINKCHANGE_INT;
schedule_delayed_work(&ndev_ctx->dwork, delay); schedule_delayed_work(&ndev_ctx->dwork, delay);
return; goto out_unlock;
} }
ndev_ctx->last_reconfig = jiffies; ndev_ctx->last_reconfig = jiffies;
...@@ -1093,9 +1103,7 @@ static void netvsc_link_change(struct work_struct *w) ...@@ -1093,9 +1103,7 @@ static void netvsc_link_change(struct work_struct *w)
spin_unlock_irqrestore(&ndev_ctx->lock, flags); spin_unlock_irqrestore(&ndev_ctx->lock, flags);
if (!event) if (!event)
return; goto out_unlock;
rtnl_lock();
switch (event->event) { switch (event->event) {
/* Only the following events are possible due to the check in /* Only the following events are possible due to the check in
...@@ -1144,6 +1152,11 @@ static void netvsc_link_change(struct work_struct *w) ...@@ -1144,6 +1152,11 @@ static void netvsc_link_change(struct work_struct *w)
*/ */
if (reschedule) if (reschedule)
schedule_delayed_work(&ndev_ctx->dwork, LINKCHANGE_INT); schedule_delayed_work(&ndev_ctx->dwork, LINKCHANGE_INT);
return;
out_unlock:
rtnl_unlock();
} }
static void netvsc_free_netdev(struct net_device *netdev) static void netvsc_free_netdev(struct net_device *netdev)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment