Skip to content

Commit 3ffe64f

Browse files
shemmingerdavem330
authored andcommitted
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: Thomas Walker <[email protected]> Fixes: 8195b13 ("hv_netvsc: fix deadlock on hotplug") Fixes: 732e498 ("netvsc: fix race on sub channel creation") Signed-off-by: Stephen Hemminger <[email protected]> Signed-off-by: Haiyang Zhang <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent 3f76df1 commit 3ffe64f

File tree

4 files changed

+65
-52
lines changed

4 files changed

+65
-52
lines changed

drivers/net/hyperv/hyperv_net.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ int netvsc_recv_callback(struct net_device *net,
210210
void netvsc_channel_cb(void *context);
211211
int netvsc_poll(struct napi_struct *napi, int budget);
212212

213-
void rndis_set_subchannel(struct work_struct *w);
213+
int rndis_set_subchannel(struct net_device *ndev, struct netvsc_device *nvdev);
214214
int rndis_filter_open(struct netvsc_device *nvdev);
215215
int rndis_filter_close(struct netvsc_device *nvdev);
216216
struct netvsc_device *rndis_filter_device_add(struct hv_device *dev,

drivers/net/hyperv/netvsc.c

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,41 @@ void netvsc_switch_datapath(struct net_device *ndev, bool vf)
6565
VM_PKT_DATA_INBAND, 0);
6666
}
6767

68+
/* Worker to setup sub channels on initial setup
69+
* Initial hotplug event occurs in softirq context
70+
* and can't wait for channels.
71+
*/
72+
static void netvsc_subchan_work(struct work_struct *w)
73+
{
74+
struct netvsc_device *nvdev =
75+
container_of(w, struct netvsc_device, subchan_work);
76+
struct rndis_device *rdev;
77+
int i, ret;
78+
79+
/* Avoid deadlock with device removal already under RTNL */
80+
if (!rtnl_trylock()) {
81+
schedule_work(w);
82+
return;
83+
}
84+
85+
rdev = nvdev->extension;
86+
if (rdev) {
87+
ret = rndis_set_subchannel(rdev->ndev, nvdev);
88+
if (ret == 0) {
89+
netif_device_attach(rdev->ndev);
90+
} else {
91+
/* fallback to only primary channel */
92+
for (i = 1; i < nvdev->num_chn; i++)
93+
netif_napi_del(&nvdev->chan_table[i].napi);
94+
95+
nvdev->max_chn = 1;
96+
nvdev->num_chn = 1;
97+
}
98+
}
99+
100+
rtnl_unlock();
101+
}
102+
68103
static struct netvsc_device *alloc_net_device(void)
69104
{
70105
struct netvsc_device *net_device;
@@ -81,7 +116,7 @@ static struct netvsc_device *alloc_net_device(void)
81116

82117
init_completion(&net_device->channel_init_wait);
83118
init_waitqueue_head(&net_device->subchan_open);
84-
INIT_WORK(&net_device->subchan_work, rndis_set_subchannel);
119+
INIT_WORK(&net_device->subchan_work, netvsc_subchan_work);
85120

86121
return net_device;
87122
}

drivers/net/hyperv/netvsc_drv.c

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -905,8 +905,20 @@ static int netvsc_attach(struct net_device *ndev,
905905
if (IS_ERR(nvdev))
906906
return PTR_ERR(nvdev);
907907

908-
/* Note: enable and attach happen when sub-channels setup */
908+
if (nvdev->num_chn > 1) {
909+
ret = rndis_set_subchannel(ndev, nvdev);
910+
911+
/* if unavailable, just proceed with one queue */
912+
if (ret) {
913+
nvdev->max_chn = 1;
914+
nvdev->num_chn = 1;
915+
}
916+
}
917+
918+
/* In any case device is now ready */
919+
netif_device_attach(ndev);
909920

921+
/* Note: enable and attach happen when sub-channels setup */
910922
netif_carrier_off(ndev);
911923

912924
if (netif_running(ndev)) {
@@ -2089,6 +2101,9 @@ static int netvsc_probe(struct hv_device *dev,
20892101

20902102
memcpy(net->dev_addr, device_info.mac_adr, ETH_ALEN);
20912103

2104+
if (nvdev->num_chn > 1)
2105+
schedule_work(&nvdev->subchan_work);
2106+
20922107
/* hw_features computed in rndis_netdev_set_hwcaps() */
20932108
net->features = net->hw_features |
20942109
NETIF_F_HIGHDMA | NETIF_F_SG |

drivers/net/hyperv/rndis_filter.c

Lines changed: 12 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,29 +1062,15 @@ static void netvsc_sc_open(struct vmbus_channel *new_sc)
10621062
* This breaks overlap of processing the host message for the
10631063
* new primary channel with the initialization of sub-channels.
10641064
*/
1065-
void rndis_set_subchannel(struct work_struct *w)
1065+
int rndis_set_subchannel(struct net_device *ndev, struct netvsc_device *nvdev)
10661066
{
1067-
struct netvsc_device *nvdev
1068-
= container_of(w, struct netvsc_device, subchan_work);
10691067
struct nvsp_message *init_packet = &nvdev->channel_init_pkt;
1070-
struct net_device_context *ndev_ctx;
1071-
struct rndis_device *rdev;
1072-
struct net_device *ndev;
1073-
struct hv_device *hv_dev;
1068+
struct net_device_context *ndev_ctx = netdev_priv(ndev);
1069+
struct hv_device *hv_dev = ndev_ctx->device_ctx;
1070+
struct rndis_device *rdev = nvdev->extension;
10741071
int i, ret;
10751072

1076-
if (!rtnl_trylock()) {
1077-
schedule_work(w);
1078-
return;
1079-
}
1080-
1081-
rdev = nvdev->extension;
1082-
if (!rdev)
1083-
goto unlock; /* device was removed */
1084-
1085-
ndev = rdev->ndev;
1086-
ndev_ctx = netdev_priv(ndev);
1087-
hv_dev = ndev_ctx->device_ctx;
1073+
ASSERT_RTNL();
10881074

10891075
memset(init_packet, 0, sizeof(struct nvsp_message));
10901076
init_packet->hdr.msg_type = NVSP_MSG5_TYPE_SUBCHANNEL;
@@ -1100,13 +1086,13 @@ void rndis_set_subchannel(struct work_struct *w)
11001086
VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED);
11011087
if (ret) {
11021088
netdev_err(ndev, "sub channel allocate send failed: %d\n", ret);
1103-
goto failed;
1089+
return ret;
11041090
}
11051091

11061092
wait_for_completion(&nvdev->channel_init_wait);
11071093
if (init_packet->msg.v5_msg.subchn_comp.status != NVSP_STAT_SUCCESS) {
11081094
netdev_err(ndev, "sub channel request failed\n");
1109-
goto failed;
1095+
return -EIO;
11101096
}
11111097

11121098
nvdev->num_chn = 1 +
@@ -1125,21 +1111,7 @@ void rndis_set_subchannel(struct work_struct *w)
11251111
for (i = 0; i < VRSS_SEND_TAB_SIZE; i++)
11261112
ndev_ctx->tx_table[i] = i % nvdev->num_chn;
11271113

1128-
netif_device_attach(ndev);
1129-
rtnl_unlock();
1130-
return;
1131-
1132-
failed:
1133-
/* fallback to only primary channel */
1134-
for (i = 1; i < nvdev->num_chn; i++)
1135-
netif_napi_del(&nvdev->chan_table[i].napi);
1136-
1137-
nvdev->max_chn = 1;
1138-
nvdev->num_chn = 1;
1139-
1140-
netif_device_attach(ndev);
1141-
unlock:
1142-
rtnl_unlock();
1114+
return 0;
11431115
}
11441116

11451117
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,
13601332
netif_napi_add(net, &net_device->chan_table[i].napi,
13611333
netvsc_poll, NAPI_POLL_WEIGHT);
13621334

1363-
if (net_device->num_chn > 1)
1364-
schedule_work(&net_device->subchan_work);
1335+
return net_device;
13651336

13661337
out:
1367-
/* if unavailable, just proceed with one queue */
1368-
if (ret) {
1369-
net_device->max_chn = 1;
1370-
net_device->num_chn = 1;
1371-
}
1372-
1373-
/* No sub channels, device is ready */
1374-
if (net_device->num_chn == 1)
1375-
netif_device_attach(net);
1376-
1377-
return net_device;
1338+
/* setting up multiple channels failed */
1339+
net_device->max_chn = 1;
1340+
net_device->num_chn = 1;
13781341

13791342
err_dev_remv:
13801343
rndis_filter_device_remove(dev, net_device);

0 commit comments

Comments
 (0)