Commit 3ffe64f1 authored by Stephen Hemminger's avatar Stephen Hemminger Committed by David S. Miller

hv_netvsc: split sub-channel setup into async and sync

When doing device hotplug the sub channel must be async to avoid
deadlock issues because device is discovered in softirq context.

When doing changes to MTU and number of channels, the setup
must be synchronous to avoid races such as when MTU and device
settings are done in a single ip command.
Reported-by: default avatarThomas Walker <Thomas.Walker@twosigma.com>
Fixes: 8195b139 ("hv_netvsc: fix deadlock on hotplug")
Fixes: 732e4985 ("netvsc: fix race on sub channel creation")
Signed-off-by: default avatarStephen Hemminger <sthemmin@microsoft.com>
Signed-off-by: default avatarHaiyang Zhang <haiyangz@microsoft.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 3f76df19
...@@ -210,7 +210,7 @@ int netvsc_recv_callback(struct net_device *net, ...@@ -210,7 +210,7 @@ int netvsc_recv_callback(struct net_device *net,
void netvsc_channel_cb(void *context); void netvsc_channel_cb(void *context);
int netvsc_poll(struct napi_struct *napi, int budget); int netvsc_poll(struct napi_struct *napi, int budget);
void rndis_set_subchannel(struct work_struct *w); int rndis_set_subchannel(struct net_device *ndev, struct netvsc_device *nvdev);
int rndis_filter_open(struct netvsc_device *nvdev); int rndis_filter_open(struct netvsc_device *nvdev);
int rndis_filter_close(struct netvsc_device *nvdev); int rndis_filter_close(struct netvsc_device *nvdev);
struct netvsc_device *rndis_filter_device_add(struct hv_device *dev, struct netvsc_device *rndis_filter_device_add(struct hv_device *dev,
......
...@@ -65,6 +65,41 @@ void netvsc_switch_datapath(struct net_device *ndev, bool vf) ...@@ -65,6 +65,41 @@ void netvsc_switch_datapath(struct net_device *ndev, bool vf)
VM_PKT_DATA_INBAND, 0); VM_PKT_DATA_INBAND, 0);
} }
/* Worker to setup sub channels on initial setup
* Initial hotplug event occurs in softirq context
* and can't wait for channels.
*/
static void netvsc_subchan_work(struct work_struct *w)
{
struct netvsc_device *nvdev =
container_of(w, struct netvsc_device, subchan_work);
struct rndis_device *rdev;
int i, ret;
/* Avoid deadlock with device removal already under RTNL */
if (!rtnl_trylock()) {
schedule_work(w);
return;
}
rdev = nvdev->extension;
if (rdev) {
ret = rndis_set_subchannel(rdev->ndev, nvdev);
if (ret == 0) {
netif_device_attach(rdev->ndev);
} else {
/* fallback to only primary channel */
for (i = 1; i < nvdev->num_chn; i++)
netif_napi_del(&nvdev->chan_table[i].napi);
nvdev->max_chn = 1;
nvdev->num_chn = 1;
}
}
rtnl_unlock();
}
static struct netvsc_device *alloc_net_device(void) static struct netvsc_device *alloc_net_device(void)
{ {
struct netvsc_device *net_device; struct netvsc_device *net_device;
...@@ -81,7 +116,7 @@ static struct netvsc_device *alloc_net_device(void) ...@@ -81,7 +116,7 @@ static struct netvsc_device *alloc_net_device(void)
init_completion(&net_device->channel_init_wait); init_completion(&net_device->channel_init_wait);
init_waitqueue_head(&net_device->subchan_open); init_waitqueue_head(&net_device->subchan_open);
INIT_WORK(&net_device->subchan_work, rndis_set_subchannel); INIT_WORK(&net_device->subchan_work, netvsc_subchan_work);
return net_device; return net_device;
} }
......
...@@ -905,8 +905,20 @@ static int netvsc_attach(struct net_device *ndev, ...@@ -905,8 +905,20 @@ static int netvsc_attach(struct net_device *ndev,
if (IS_ERR(nvdev)) if (IS_ERR(nvdev))
return PTR_ERR(nvdev); return PTR_ERR(nvdev);
/* Note: enable and attach happen when sub-channels setup */ if (nvdev->num_chn > 1) {
ret = rndis_set_subchannel(ndev, nvdev);
/* if unavailable, just proceed with one queue */
if (ret) {
nvdev->max_chn = 1;
nvdev->num_chn = 1;
}
}
/* In any case device is now ready */
netif_device_attach(ndev);
/* Note: enable and attach happen when sub-channels setup */
netif_carrier_off(ndev); netif_carrier_off(ndev);
if (netif_running(ndev)) { if (netif_running(ndev)) {
...@@ -2089,6 +2101,9 @@ static int netvsc_probe(struct hv_device *dev, ...@@ -2089,6 +2101,9 @@ static int netvsc_probe(struct hv_device *dev,
memcpy(net->dev_addr, device_info.mac_adr, ETH_ALEN); memcpy(net->dev_addr, device_info.mac_adr, ETH_ALEN);
if (nvdev->num_chn > 1)
schedule_work(&nvdev->subchan_work);
/* hw_features computed in rndis_netdev_set_hwcaps() */ /* hw_features computed in rndis_netdev_set_hwcaps() */
net->features = net->hw_features | net->features = net->hw_features |
NETIF_F_HIGHDMA | NETIF_F_SG | NETIF_F_HIGHDMA | NETIF_F_SG |
......
...@@ -1062,29 +1062,15 @@ static void netvsc_sc_open(struct vmbus_channel *new_sc) ...@@ -1062,29 +1062,15 @@ static void netvsc_sc_open(struct vmbus_channel *new_sc)
* This breaks overlap of processing the host message for the * This breaks overlap of processing the host message for the
* new primary channel with the initialization of sub-channels. * new primary channel with the initialization of sub-channels.
*/ */
void rndis_set_subchannel(struct work_struct *w) int rndis_set_subchannel(struct net_device *ndev, struct netvsc_device *nvdev)
{ {
struct netvsc_device *nvdev
= container_of(w, struct netvsc_device, subchan_work);
struct nvsp_message *init_packet = &nvdev->channel_init_pkt; struct nvsp_message *init_packet = &nvdev->channel_init_pkt;
struct net_device_context *ndev_ctx; struct net_device_context *ndev_ctx = netdev_priv(ndev);
struct rndis_device *rdev; struct hv_device *hv_dev = ndev_ctx->device_ctx;
struct net_device *ndev; struct rndis_device *rdev = nvdev->extension;
struct hv_device *hv_dev;
int i, ret; int i, ret;
if (!rtnl_trylock()) { ASSERT_RTNL();
schedule_work(w);
return;
}
rdev = nvdev->extension;
if (!rdev)
goto unlock; /* device was removed */
ndev = rdev->ndev;
ndev_ctx = netdev_priv(ndev);
hv_dev = ndev_ctx->device_ctx;
memset(init_packet, 0, sizeof(struct nvsp_message)); memset(init_packet, 0, sizeof(struct nvsp_message));
init_packet->hdr.msg_type = NVSP_MSG5_TYPE_SUBCHANNEL; init_packet->hdr.msg_type = NVSP_MSG5_TYPE_SUBCHANNEL;
...@@ -1100,13 +1086,13 @@ void rndis_set_subchannel(struct work_struct *w) ...@@ -1100,13 +1086,13 @@ void rndis_set_subchannel(struct work_struct *w)
VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED);
if (ret) { if (ret) {
netdev_err(ndev, "sub channel allocate send failed: %d\n", ret); netdev_err(ndev, "sub channel allocate send failed: %d\n", ret);
goto failed; return ret;
} }
wait_for_completion(&nvdev->channel_init_wait); wait_for_completion(&nvdev->channel_init_wait);
if (init_packet->msg.v5_msg.subchn_comp.status != NVSP_STAT_SUCCESS) { if (init_packet->msg.v5_msg.subchn_comp.status != NVSP_STAT_SUCCESS) {
netdev_err(ndev, "sub channel request failed\n"); netdev_err(ndev, "sub channel request failed\n");
goto failed; return -EIO;
} }
nvdev->num_chn = 1 + nvdev->num_chn = 1 +
...@@ -1125,21 +1111,7 @@ void rndis_set_subchannel(struct work_struct *w) ...@@ -1125,21 +1111,7 @@ void rndis_set_subchannel(struct work_struct *w)
for (i = 0; i < VRSS_SEND_TAB_SIZE; i++) for (i = 0; i < VRSS_SEND_TAB_SIZE; i++)
ndev_ctx->tx_table[i] = i % nvdev->num_chn; ndev_ctx->tx_table[i] = i % nvdev->num_chn;
netif_device_attach(ndev); return 0;
rtnl_unlock();
return;
failed:
/* fallback to only primary channel */
for (i = 1; i < nvdev->num_chn; i++)
netif_napi_del(&nvdev->chan_table[i].napi);
nvdev->max_chn = 1;
nvdev->num_chn = 1;
netif_device_attach(ndev);
unlock:
rtnl_unlock();
} }
static int rndis_netdev_set_hwcaps(struct rndis_device *rndis_device, static int rndis_netdev_set_hwcaps(struct rndis_device *rndis_device,
...@@ -1360,21 +1332,12 @@ struct netvsc_device *rndis_filter_device_add(struct hv_device *dev, ...@@ -1360,21 +1332,12 @@ struct netvsc_device *rndis_filter_device_add(struct hv_device *dev,
netif_napi_add(net, &net_device->chan_table[i].napi, netif_napi_add(net, &net_device->chan_table[i].napi,
netvsc_poll, NAPI_POLL_WEIGHT); netvsc_poll, NAPI_POLL_WEIGHT);
if (net_device->num_chn > 1) return net_device;
schedule_work(&net_device->subchan_work);
out: out:
/* if unavailable, just proceed with one queue */ /* setting up multiple channels failed */
if (ret) {
net_device->max_chn = 1; net_device->max_chn = 1;
net_device->num_chn = 1; net_device->num_chn = 1;
}
/* No sub channels, device is ready */
if (net_device->num_chn == 1)
netif_device_attach(net);
return net_device;
err_dev_remv: err_dev_remv:
rndis_filter_device_remove(dev, net_device); rndis_filter_device_remove(dev, net_device);
......
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