diff options
author | Vitaly Kuznetsov <vkuznets@redhat.com> | 2016-05-13 13:55:25 +0200 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2016-05-16 19:26:01 +0200 |
commit | 88098834827025cc04c15f1b4b0d9bbef3cf55af (patch) | |
tree | 9029cc6548f66604f49a6397c3e80de2d0734300 /drivers/net/hyperv | |
parent | hv_netvsc: synchronize netvsc_change_mtu()/netvsc_set_channels() with netvsc_... (diff) | |
download | linux-88098834827025cc04c15f1b4b0d9bbef3cf55af.tar.xz linux-88098834827025cc04c15f1b4b0d9bbef3cf55af.zip |
hv_netvsc: set nvdev link after populating chn_table
Crash in netvsc_send() is observed when netvsc device is re-created on
mtu change/set channels. The crash is caused by dereferencing of NULL
channel pointer which comes from chn_table. The root cause is a mixture
of two facts:
- we set nvdev pointer in net_device_context in alloc_net_device()
before we populate chn_table.
- we populate chn_table[0] only.
The issue could be papered over by checking channel != NULL in
netvsc_send() but populating the whole chn_table and writing the
nvdev pointer afterwards seems more appropriate.
Signed-off-by: Vitaly Kuznetsov <vkuznets@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/hyperv')
-rw-r--r-- | drivers/net/hyperv/netvsc.c | 29 |
1 files changed, 18 insertions, 11 deletions
diff --git a/drivers/net/hyperv/netvsc.c b/drivers/net/hyperv/netvsc.c index 96b3c32a7deb..719cb3578e55 100644 --- a/drivers/net/hyperv/netvsc.c +++ b/drivers/net/hyperv/netvsc.c @@ -60,11 +60,9 @@ void netvsc_switch_datapath(struct net_device *ndev, bool vf) } -static struct netvsc_device *alloc_net_device(struct hv_device *device) +static struct netvsc_device *alloc_net_device(void) { struct netvsc_device *net_device; - struct net_device *ndev = hv_get_drvdata(device); - struct net_device_context *net_device_ctx = netdev_priv(ndev); net_device = kzalloc(sizeof(struct netvsc_device), GFP_KERNEL); if (!net_device) @@ -86,8 +84,6 @@ static struct netvsc_device *alloc_net_device(struct hv_device *device) net_device->vf_netdev = NULL; net_device->vf_inject = false; - net_device_ctx->nvdev = net_device; - return net_device; } @@ -1240,20 +1236,19 @@ void netvsc_channel_cb(void *context) */ int netvsc_device_add(struct hv_device *device, void *additional_info) { - int ret = 0; + int i, ret = 0; int ring_size = ((struct netvsc_device_info *)additional_info)->ring_size; struct netvsc_device *net_device; - struct net_device *ndev; + struct net_device *ndev = hv_get_drvdata(device); + struct net_device_context *net_device_ctx = netdev_priv(ndev); - net_device = alloc_net_device(device); + net_device = alloc_net_device(); if (!net_device) return -ENOMEM; net_device->ring_size = ring_size; - ndev = hv_get_drvdata(device); - /* Initialize the NetVSC channel extension */ init_completion(&net_device->channel_init_wait); @@ -1272,7 +1267,19 @@ int netvsc_device_add(struct hv_device *device, void *additional_info) /* Channel is opened */ pr_info("hv_netvsc channel opened successfully\n"); - net_device->chn_table[0] = device->channel; + /* If we're reopening the device we may have multiple queues, fill the + * chn_table with the default channel to use it before subchannels are + * opened. + */ + for (i = 0; i < VRSS_CHANNEL_MAX; i++) + net_device->chn_table[i] = device->channel; + + /* Writing nvdev pointer unlocks netvsc_send(), make sure chn_table is + * populated. + */ + wmb(); + + net_device_ctx->nvdev = net_device; /* Connect with the NetVsp */ ret = netvsc_connect_vsp(device); |