diff options
author | Vitaly Kuznetsov <vkuznets@redhat.com> | 2016-08-15 11:48:40 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2016-08-15 16:48:07 -0400 |
commit | d072218f214929194db06069564495b6b9fff34a (patch) | |
tree | 241e9aba5d342b0fb14f089f9446fdbc9cd31182 | |
parent | f9a7da9130ef0143eb900794c7863dc5c9051fbc (diff) |
hv_netvsc: avoid deadlocks between rtnl lock and vf_use_cnt wait
Here is a deadlock scenario:
- netvsc_vf_up() schedules netvsc_notify_peers() work and quits.
- netvsc_vf_down() runs before netvsc_notify_peers() gets executed. As it
is being executed from netdev notifier chain we hold rtnl lock when we
get here.
- we enter while (atomic_read(&net_device_ctx->vf_use_cnt) != 0) loop and
wait till netvsc_notify_peers() drops vf_use_cnt.
- netvsc_notify_peers() starts on some other CPU but netdev_notify_peers()
will hang on rtnl_lock().
- deadlock!
Instead of introducing additional synchronization I suggest we drop
gwrk.dwrk completely and call NETDEV_NOTIFY_PEERS directly. As we're
acting under rtnl lock this is legitimate.
Signed-off-by: Vitaly Kuznetsov <vkuznets@redhat.com>
Acked-by: Haiyang Zhang <haiyangz@microsoft.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | drivers/net/hyperv/hyperv_net.h | 7 | ||||
-rw-r--r-- | drivers/net/hyperv/netvsc_drv.c | 33 |
2 files changed, 5 insertions, 35 deletions
diff --git a/drivers/net/hyperv/hyperv_net.h b/drivers/net/hyperv/hyperv_net.h index 3b3ecf237a12..591af71eae56 100644 --- a/drivers/net/hyperv/hyperv_net.h +++ b/drivers/net/hyperv/hyperv_net.h | |||
@@ -644,12 +644,6 @@ struct netvsc_reconfig { | |||
644 | u32 event; | 644 | u32 event; |
645 | }; | 645 | }; |
646 | 646 | ||
647 | struct garp_wrk { | ||
648 | struct work_struct dwrk; | ||
649 | struct net_device *netdev; | ||
650 | struct net_device_context *net_device_ctx; | ||
651 | }; | ||
652 | |||
653 | /* The context of the netvsc device */ | 647 | /* The context of the netvsc device */ |
654 | struct net_device_context { | 648 | struct net_device_context { |
655 | /* point back to our device context */ | 649 | /* point back to our device context */ |
@@ -667,7 +661,6 @@ struct net_device_context { | |||
667 | 661 | ||
668 | struct work_struct work; | 662 | struct work_struct work; |
669 | u32 msg_enable; /* debug level */ | 663 | u32 msg_enable; /* debug level */ |
670 | struct garp_wrk gwrk; | ||
671 | 664 | ||
672 | struct netvsc_stats __percpu *tx_stats; | 665 | struct netvsc_stats __percpu *tx_stats; |
673 | struct netvsc_stats __percpu *rx_stats; | 666 | struct netvsc_stats __percpu *rx_stats; |
diff --git a/drivers/net/hyperv/netvsc_drv.c b/drivers/net/hyperv/netvsc_drv.c index 794139ba31ab..70317fa24cde 100644 --- a/drivers/net/hyperv/netvsc_drv.c +++ b/drivers/net/hyperv/netvsc_drv.c | |||
@@ -1151,17 +1151,6 @@ static void netvsc_free_netdev(struct net_device *netdev) | |||
1151 | free_netdev(netdev); | 1151 | free_netdev(netdev); |
1152 | } | 1152 | } |
1153 | 1153 | ||
1154 | static void netvsc_notify_peers(struct work_struct *wrk) | ||
1155 | { | ||
1156 | struct garp_wrk *gwrk; | ||
1157 | |||
1158 | gwrk = container_of(wrk, struct garp_wrk, dwrk); | ||
1159 | |||
1160 | netdev_notify_peers(gwrk->netdev); | ||
1161 | |||
1162 | atomic_dec(&gwrk->net_device_ctx->vf_use_cnt); | ||
1163 | } | ||
1164 | |||
1165 | static struct net_device *get_netvsc_net_device(char *mac) | 1154 | static struct net_device *get_netvsc_net_device(char *mac) |
1166 | { | 1155 | { |
1167 | struct net_device *dev, *found = NULL; | 1156 | struct net_device *dev, *found = NULL; |
@@ -1253,15 +1242,8 @@ static int netvsc_vf_up(struct net_device *vf_netdev) | |||
1253 | 1242 | ||
1254 | netif_carrier_off(ndev); | 1243 | netif_carrier_off(ndev); |
1255 | 1244 | ||
1256 | /* | 1245 | /* Now notify peers through VF device. */ |
1257 | * Now notify peers. We are scheduling work to | 1246 | call_netdevice_notifiers(NETDEV_NOTIFY_PEERS, vf_netdev); |
1258 | * notify peers; take a reference to prevent | ||
1259 | * the VF interface from vanishing. | ||
1260 | */ | ||
1261 | atomic_inc(&net_device_ctx->vf_use_cnt); | ||
1262 | net_device_ctx->gwrk.netdev = vf_netdev; | ||
1263 | net_device_ctx->gwrk.net_device_ctx = net_device_ctx; | ||
1264 | schedule_work(&net_device_ctx->gwrk.dwrk); | ||
1265 | 1247 | ||
1266 | return NOTIFY_OK; | 1248 | return NOTIFY_OK; |
1267 | } | 1249 | } |
@@ -1300,13 +1282,9 @@ static int netvsc_vf_down(struct net_device *vf_netdev) | |||
1300 | netdev_info(ndev, "Data path switched from VF: %s\n", vf_netdev->name); | 1282 | netdev_info(ndev, "Data path switched from VF: %s\n", vf_netdev->name); |
1301 | rndis_filter_close(netvsc_dev); | 1283 | rndis_filter_close(netvsc_dev); |
1302 | netif_carrier_on(ndev); | 1284 | netif_carrier_on(ndev); |
1303 | /* | 1285 | |
1304 | * Notify peers. | 1286 | /* Now notify peers through netvsc device. */ |
1305 | */ | 1287 | call_netdevice_notifiers(NETDEV_NOTIFY_PEERS, ndev); |
1306 | atomic_inc(&net_device_ctx->vf_use_cnt); | ||
1307 | net_device_ctx->gwrk.netdev = ndev; | ||
1308 | net_device_ctx->gwrk.net_device_ctx = net_device_ctx; | ||
1309 | schedule_work(&net_device_ctx->gwrk.dwrk); | ||
1310 | 1288 | ||
1311 | return NOTIFY_OK; | 1289 | return NOTIFY_OK; |
1312 | } | 1290 | } |
@@ -1378,7 +1356,6 @@ static int netvsc_probe(struct hv_device *dev, | |||
1378 | 1356 | ||
1379 | INIT_DELAYED_WORK(&net_device_ctx->dwork, netvsc_link_change); | 1357 | INIT_DELAYED_WORK(&net_device_ctx->dwork, netvsc_link_change); |
1380 | INIT_WORK(&net_device_ctx->work, do_set_multicast); | 1358 | INIT_WORK(&net_device_ctx->work, do_set_multicast); |
1381 | INIT_WORK(&net_device_ctx->gwrk.dwrk, netvsc_notify_peers); | ||
1382 | 1359 | ||
1383 | spin_lock_init(&net_device_ctx->lock); | 1360 | spin_lock_init(&net_device_ctx->lock); |
1384 | INIT_LIST_HEAD(&net_device_ctx->reconfig_events); | 1361 | INIT_LIST_HEAD(&net_device_ctx->reconfig_events); |