hv_netvsc: fix deadlock on hotplug
Subsystems (3)
PATCH 1/1 hv_netvsc: fix deadlock on hotplug
When a virtual device is added dynamically (via host console), then the vmbus sends an offer message for the primary channel. The processing of this message for networking causes the network device to then initialize the sub channels.
The problem is that setting up the sub channels needs to wait until the subsequent subchannel offers have been processed. These offers come in on the same ring buffer and work queue as where the primary offer is being processed; leading to a deadlock.
This did not happen in older kernels, because the sub channel waiting logic was broken (it wasn't really waiting).
The solution is to do the sub channel setup in its own work queue context that is scheduled by the primary channel setup; and then happens later.
---
This is version for 4.13 (linux-net)
drivers/net/hyperv/hyperv_net.h | 3 + drivers/net/hyperv/netvsc.c | 3 + drivers/net/hyperv/netvsc_drv.c | 18 +----- drivers/net/hyperv/rndis_filter.c | 118 +++++++++++++++++++++++++++----------- 4 files changed, 94 insertions(+), 48 deletions(-)
drivers/net/hyperv/hyperv_net.h +3 -0
@@ -200,6 +200,8 @@ int netvsc_recv_callback(struct net_device *net, const struct ndis_pkt_8021q_info *vlan);void netvsc_channel_cb (void *context);int netvsc_poll (struct napi_struct *napi,int budget); + +void rndis_set_subchannel (struct work_struct *w);int rndis_filter_open (struct netvsc_device *nvdev);int rndis_filter_close (struct netvsc_device *nvdev); int rndis_filter_device_add(struct hv_device *dev,
@@ -766,6 +768,7 @@ struct netvsc_device {u32 num_chn;atomic_t open_chn; +struct work_struct subchan_work;wait_queue_head_t subchan_open;struct rndis_device *extension;
drivers/net/hyperv/netvsc.c +3 -0
@@ -79,6 +79,7 @@ static struct netvsc_device *alloc_net_device(void) net_device->pkt_align =RNDIS_PKT_ALIGN_DEFAULT ;init_completion (&net_device->channel_init_wait);init_waitqueue_head (&net_device->subchan_open); +INIT_WORK (&net_device->subchan_work, rndis_set_subchannel);return net_device; }
@@ -553,6 +554,8 @@ void netvsc_device_remove(struct hv_device *device)struct netvsc_device *net_device = net_device_ctx->nvdev;int i; +cancel_work_sync (&net_device->subchan_work); +netvsc_disconnect_vsp (device);RCU_INIT_POINTER (net_device_ctx->nvdev,NULL );
drivers/net/hyperv/netvsc_drv.c +2 -16
@@ -740,24 +740,13 @@ static int netvsc_set_queues(struct net_device *net, struct hv_device *dev, u32 num_chn) {struct netvsc_device_info device_info; -int ret;memset (&device_info,0 ,sizeof (device_info)); device_info.num_chn = num_chn; device_info.ring_size = ring_size; device_info.max_num_vrss_chns = num_chn; - ret =rndis_filter_device_add (dev, &device_info); -if (ret) -return ret; - - ret =netif_set_real_num_tx_queues (net, num_chn); -if (ret) -return ret; - - ret =netif_set_real_num_rx_queues (net, num_chn); - -return ret; +return rndis_filter_device_add (dev, &device_info); } static int netvsc_set_channels(struct net_device *net,
@@ -1573,8 +1562,6 @@ static int netvsc_probe(struct hv_device *dev,/* RCU not necessary here, device not registered */ nvdev = net_device_ctx->nvdev; -netif_set_real_num_tx_queues (net, nvdev->num_chn); -netif_set_real_num_rx_queues (net, nvdev->num_chn);/* MTU range: 68 - 1500 or 65521 */ net->min_mtu =NETVSC_MTU_MIN ;
@@ -1616,11 +1603,10 @@ static int netvsc_remove(struct hv_device *dev)* removed. Also blocks mtu and channel changes. */ rtnl_lock (); +unregister_netdevice (net);rndis_filter_device_remove (dev, ndev_ctx->nvdev);rtnl_unlock (); -unregister_netdev (net); -hv_set_drvdata (dev,NULL );free_netdev (net);
drivers/net/hyperv/rndis_filter.c +86 -32
160 lines not on this page · raw message
Signature · 1 line
2.11.0