Skip to content

Commit

Permalink
Merge branch 'hv_netvsc-fix-race-of-netvsc-vf-register-and-slave-bit'
Browse files Browse the repository at this point in the history
Haiyang Zhang says:

====================
hv_netvsc: fix race of netvsc, VF register, and slave bit

There are some races between netvsc probe, set notifier, VF register,
and slave bit setting.
This patch set fixes them.
====================

Link: https://lore.kernel.org/r/[email protected]
Signed-off-by: Paolo Abeni <[email protected]>
  • Loading branch information
Paolo Abeni committed Nov 21, 2023
2 parents c0e2926 + c807d6c commit 54d4434
Showing 1 changed file with 45 additions and 21 deletions.
66 changes: 45 additions & 21 deletions drivers/net/hyperv/netvsc_drv.c
Original file line number Diff line number Diff line change
Expand Up @@ -2206,9 +2206,6 @@ static int netvsc_vf_join(struct net_device *vf_netdev,
goto upper_link_failed;
}

/* set slave flag before open to prevent IPv6 addrconf */
vf_netdev->flags |= IFF_SLAVE;

schedule_delayed_work(&ndev_ctx->vf_takeover, VF_TAKEOVER_INT);

call_netdevice_notifiers(NETDEV_JOIN, vf_netdev);
Expand Down Expand Up @@ -2315,23 +2312,38 @@ static struct net_device *get_netvsc_byslot(const struct net_device *vf_netdev)

}

/* Fallback path to check synthetic vf with
* help of mac addr
/* Fallback path to check synthetic vf with help of mac addr.
* Because this function can be called before vf_netdev is
* initialized (NETDEV_POST_INIT) when its perm_addr has not been copied
* from dev_addr, also try to match to its dev_addr.
* Note: On Hyper-V and Azure, it's not possible to set a MAC address
* on a VF that matches to the MAC of a unrelated NETVSC device.
*/
list_for_each_entry(ndev_ctx, &netvsc_dev_list, list) {
ndev = hv_get_drvdata(ndev_ctx->device_ctx);
if (ether_addr_equal(vf_netdev->perm_addr, ndev->perm_addr)) {
netdev_notice(vf_netdev,
"falling back to mac addr based matching\n");
if (ether_addr_equal(vf_netdev->perm_addr, ndev->perm_addr) ||
ether_addr_equal(vf_netdev->dev_addr, ndev->perm_addr))
return ndev;
}
}

netdev_notice(vf_netdev,
"no netdev found for vf serial:%u\n", serial);
return NULL;
}

static int netvsc_prepare_bonding(struct net_device *vf_netdev)
{
struct net_device *ndev;

ndev = get_netvsc_byslot(vf_netdev);
if (!ndev)
return NOTIFY_DONE;

/* set slave flag before open to prevent IPv6 addrconf */
vf_netdev->flags |= IFF_SLAVE;
return NOTIFY_DONE;
}

static int netvsc_register_vf(struct net_device *vf_netdev)
{
struct net_device_context *net_device_ctx;
Expand Down Expand Up @@ -2531,25 +2543,30 @@ static int netvsc_probe(struct hv_device *dev,
goto devinfo_failed;
}

nvdev = rndis_filter_device_add(dev, device_info);
if (IS_ERR(nvdev)) {
ret = PTR_ERR(nvdev);
netdev_err(net, "unable to add netvsc device (ret %d)\n", ret);
goto rndis_failed;
}

eth_hw_addr_set(net, device_info->mac_adr);

/* We must get rtnl lock before scheduling nvdev->subchan_work,
* otherwise netvsc_subchan_work() can get rtnl lock first and wait
* all subchannels to show up, but that may not happen because
* netvsc_probe() can't get rtnl lock and as a result vmbus_onoffer()
* -> ... -> device_add() -> ... -> __device_attach() can't get
* the device lock, so all the subchannels can't be processed --
* finally netvsc_subchan_work() hangs forever.
*
* The rtnl lock also needs to be held before rndis_filter_device_add()
* which advertises nvsp_2_vsc_capability / sriov bit, and triggers
* VF NIC offering and registering. If VF NIC finished register_netdev()
* earlier it may cause name based config failure.
*/
rtnl_lock();

nvdev = rndis_filter_device_add(dev, device_info);
if (IS_ERR(nvdev)) {
ret = PTR_ERR(nvdev);
netdev_err(net, "unable to add netvsc device (ret %d)\n", ret);
goto rndis_failed;
}

eth_hw_addr_set(net, device_info->mac_adr);

if (nvdev->num_chn > 1)
schedule_work(&nvdev->subchan_work);

Expand Down Expand Up @@ -2586,9 +2603,9 @@ static int netvsc_probe(struct hv_device *dev,
return 0;

register_failed:
rtnl_unlock();
rndis_filter_device_remove(dev, nvdev);
rndis_failed:
rtnl_unlock();
netvsc_devinfo_put(device_info);
devinfo_failed:
free_percpu(net_device_ctx->vf_stats);
Expand Down Expand Up @@ -2753,6 +2770,8 @@ static int netvsc_netdev_event(struct notifier_block *this,
return NOTIFY_DONE;

switch (event) {
case NETDEV_POST_INIT:
return netvsc_prepare_bonding(event_dev);
case NETDEV_REGISTER:
return netvsc_register_vf(event_dev);
case NETDEV_UNREGISTER:
Expand Down Expand Up @@ -2788,12 +2807,17 @@ static int __init netvsc_drv_init(void)
}
netvsc_ring_bytes = ring_size * PAGE_SIZE;

register_netdevice_notifier(&netvsc_netdev_notifier);

ret = vmbus_driver_register(&netvsc_drv);
if (ret)
return ret;
goto err_vmbus_reg;

register_netdevice_notifier(&netvsc_netdev_notifier);
return 0;

err_vmbus_reg:
unregister_netdevice_notifier(&netvsc_netdev_notifier);
return ret;
}

MODULE_LICENSE("GPL");
Expand Down

0 comments on commit 54d4434

Please sign in to comment.