lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <0d97888a-9eb9-a3a1-f96c-39367dc11a00@intel.com>
Date:   Tue, 5 Jun 2018 10:22:13 -0700
From:   "Samudrala, Sridhar" <sridhar.samudrala@...el.com>
To:     Stephen Hemminger <stephen@...workplumber.org>, kys@...rosoft.com,
        haiyangz@...rosoft.com, davem@...emloft.net, mst@...hat.com,
        Alexander H <alexander.h.duyck@...el.com>,
        Jiri Pirko <jiri@...nulli.us>
Cc:     netdev@...r.kernel.org, Stephen Hemminger <sthemmin@...rosoft.com>,
        "Brandeburg, Jesse" <jesse.brandeburg@...el.com>
Subject: Re: [PATCH net] failover: eliminate callback hell

On 6/4/2018 8:42 PM, Stephen Hemminger wrote:
> The net failover should be a simple library, not a virtual
> object with function callbacks (see callback hell).
> The code is simpler is smaller both for the netvsc and virtio use case.

I quickly tried this patch and it breaks virtio-net in standby mode.
I don't see failover netdev, unloading virtio-net causes a crash.

With these changes, there is very minimal code that is shared between
netvsc and virtio-net. The notifier and event handling code and the
lookup_bymac routines are now duplicated in both the drivers. I thought
we wanted to keep this code common between the 2 drivers and we went through
multiple revisions to make sure that it works with both netvsc's 2 netdev
and virtio-net's 3 netdev models.

The reason for the indirect ops is to support these 2 different models and
i am not sure if the overhead of the callbacks is that significant considering
that they are not called in the hot path.




>
> The code is restructured in many ways. I should have given these
> as review comments to net_failover during review
> but did not want to overwhelm the original submitter.
> Therefore it was merged prematurely.
>
> Some of the many items changed are:
>
>    * The support routines should just be selected as needed in
>      kernel config, no need for them to be visible config items.
>
>    * Both netvsc and net_failover should keep their list of their
>      own devices. Not a common list.
>
> 	  * The matching of secondary device to primary device policy
>      is up to the network device. Both net_failover and netvsc
>      will use MAC for now but can change separately.
>
>    * The match policy is only used during initial discovery; after
>      that the secondary device knows what the upper device is because
>      of the parent/child relationship; no searching is required.
>
>    * Now, netvsc and net_failover use the same delayed work type
>      mechanism for setup. Previously, net_failover code was triggering off
>      name change but a similar policy was rejected for netvsc.
>      "what is good for the goose is good for the gander"
>
>    * The net_failover private device info 'struct net_failover_info'
>      should have been private to the driver file, not a visible
>      API.
>
>    * The net_failover device should use SET_NETDEV_DEV
>      that is intended only for physical devices not virtual devices.
>
>    * No point in having DocBook style comments on a driver file.
>      They only make sense on an external exposed API.
>
>    * net_failover only supports Ethernet, so use ether_addr_copy.
>
>    * Set permanent and current address of net_failover device
>      to match the primary.
>
>    * Carrier should be marked off before registering device
>      the net_failover device.
>
>    * Use netdev_XXX for log messages, in net_failover (not dev_xxx)
>
>    * Since failover infrastructure is about linking devices just
>      use RTNL no need for other locking in init and teardown.
>
>    * Don't bother with ERR_PTR() style return if only possible
>      return is success or no memory.
>
>    * As much as possible, the terms master and slave should be avoided
>      because of their cultural connotations.
>
> Note; this code has been tested on Hyper-V
> but is compile tested only on virtio.
>
> Fixes: 30c8bd5aa8b2 ("net: Introduce generic failover module")
> Signed-off-by: Stephen Hemminger <sthemmin@...rosoft.com>
> ---
>
> Although this patch needs to go into 4.18 (linux-net),
> this version is based against net-next because net-next
> hasn't been merged into linux-net yet.
>
>
>   drivers/net/hyperv/hyperv_net.h |   3 +-
>   drivers/net/hyperv/netvsc_drv.c | 173 +++++++++++------
>   drivers/net/net_failover.c      | 312 ++++++++++++++++++++-----------
>   drivers/net/virtio_net.c        |   9 +-
>   include/net/failover.h          |  31 +---
>   include/net/net_failover.h      |  32 +---
>   net/Kconfig                     |  13 +-
>   net/core/failover.c             | 316 ++++----------------------------
>   8 files changed, 373 insertions(+), 516 deletions(-)
>
> diff --git a/drivers/net/hyperv/hyperv_net.h b/drivers/net/hyperv/hyperv_net.h
> index 99d8e7398a5b..c7d25d10765e 100644
> --- a/drivers/net/hyperv/hyperv_net.h
> +++ b/drivers/net/hyperv/hyperv_net.h
> @@ -902,6 +902,8 @@ struct net_device_context {
>   	struct hv_device *device_ctx;
>   	/* netvsc_device */
>   	struct netvsc_device __rcu *nvdev;
> +	/* list of netvsc net_devices */
> +	struct list_head list;
>   	/* reconfigure work */
>   	struct delayed_work dwork;
>   	/* last reconfig time */
> @@ -933,7 +935,6 @@ struct net_device_context {
>   	/* Serial number of the VF to team with */
>   	u32 vf_serial;
>   
> -	struct failover *failover;
>   };
>   
>   /* Per channel data */
> diff --git a/drivers/net/hyperv/netvsc_drv.c b/drivers/net/hyperv/netvsc_drv.c
> index bef4d55a108c..074e6b8578df 100644
> --- a/drivers/net/hyperv/netvsc_drv.c
> +++ b/drivers/net/hyperv/netvsc_drv.c
> @@ -70,6 +70,8 @@ static int debug = -1;
>   module_param(debug, int, 0444);
>   MODULE_PARM_DESC(debug, "Debug level (0=none,...,16=all)");
>   
> +static LIST_HEAD(netvsc_dev_list);
> +
>   static void netvsc_change_rx_flags(struct net_device *net, int change)
>   {
>   	struct net_device_context *ndev_ctx = netdev_priv(net);
> @@ -1846,101 +1848,120 @@ static void netvsc_vf_setup(struct work_struct *w)
>   	}
>   
>   	vf_netdev = rtnl_dereference(ndev_ctx->vf_netdev);
> -	if (vf_netdev)
> +	if (vf_netdev) {
>   		__netvsc_vf_setup(ndev, vf_netdev);
> -
> +		dev_put(vf_netdev);
> +	}
>   	rtnl_unlock();
>   }
>   
> -static int netvsc_pre_register_vf(struct net_device *vf_netdev,
> -				  struct net_device *ndev)
> +static struct net_device *get_netvsc_bymac(const u8 *mac)
>   {
> -	struct net_device_context *net_device_ctx;
> -	struct netvsc_device *netvsc_dev;
> +	struct net_device_context *ndev_ctx;
>   
> -	net_device_ctx = netdev_priv(ndev);
> -	netvsc_dev = rtnl_dereference(net_device_ctx->nvdev);
> -	if (!netvsc_dev || rtnl_dereference(net_device_ctx->vf_netdev))
> -		return -ENODEV;
> +	ASSERT_RTNL();
>   
> -	return 0;
> +	list_for_each_entry(ndev_ctx, &netvsc_dev_list, list) {
> +		struct net_device *dev = hv_get_drvdata(ndev_ctx->device_ctx);
> +
> +		if (ether_addr_equal(mac, dev->perm_addr))
> +			return dev;
> +	}
> +
> +	return NULL;
>   }
>   
> -static int netvsc_register_vf(struct net_device *vf_netdev,
> -			      struct net_device *ndev)
> +static int netvsc_register_vf(struct net_device *vf_netdev)
>   {
> -	struct net_device_context *ndev_ctx = netdev_priv(ndev);
> +	struct net_device *ndev;
> +	struct net_device_context *ndev_ctx;
> +
> +	/* Must use Ethernet addresses */
> +	if (vf_netdev->addr_len != ETH_ALEN)
> +		return NOTIFY_DONE;
> +
> +	/* VF must be a physical device not VLAN, etc */
> +	if (!vf_netdev->dev.parent)
> +		return NOTIFY_DONE;
> +
> +	/* Use the MAC address to locate the synthetic interface to
> +	 * associate with the VF interface.
> +	 */
> +	ndev = get_netvsc_bymac(vf_netdev->perm_addr);
> +	if (!ndev)
> +		return NOTIFY_DONE;
> +
> +	/* If network device is being removed, don't do anything */
> +	ndev_ctx = netdev_priv(ndev);
> +	if (!rtnl_dereference(ndev_ctx->nvdev))
> +		return NOTIFY_DONE;
> +
> +	if (netdev_failover_join(vf_netdev, ndev, netvsc_vf_handle_frame)) {
> +		netdev_err(vf_netdev, "could not join: %s", ndev->name);
> +		return NOTIFY_DONE;
> +	}
>   
>   	/* set slave flag before open to prevent IPv6 addrconf */
>   	vf_netdev->flags |= IFF_SLAVE;
>   
> +	dev_hold(vf_netdev);
> +
>   	schedule_delayed_work(&ndev_ctx->vf_takeover, VF_TAKEOVER_INT);
>   
>   	call_netdevice_notifiers(NETDEV_JOIN, vf_netdev);
>   
>   	netdev_info(vf_netdev, "joined to %s\n", ndev->name);
>   
> -	dev_hold(vf_netdev);
>   	rcu_assign_pointer(ndev_ctx->vf_netdev, vf_netdev);
>   
> -	return 0;
> +	return NOTIFY_OK;
>   }
>   
>   /* VF up/down change detected, schedule to change data path */
> -static int netvsc_vf_changed(struct net_device *vf_netdev,
> -			     struct net_device *ndev)
> +static int netvsc_vf_changed(struct net_device *vf_netdev)
>   {
>   	struct net_device_context *net_device_ctx;
>   	struct netvsc_device *netvsc_dev;
> +	struct net_device *ndev;
>   	bool vf_is_up = netif_running(vf_netdev);
>   
> +	ndev = netdev_failover_upper_get(vf_netdev);
> +	if (!ndev)
> +		return NOTIFY_DONE;
> +
>   	net_device_ctx = netdev_priv(ndev);
>   	netvsc_dev = rtnl_dereference(net_device_ctx->nvdev);
>   	if (!netvsc_dev)
> -		return -ENODEV;
> +		return NOTIFY_DONE;
>   
>   	netvsc_switch_datapath(ndev, vf_is_up);
>   	netdev_info(ndev, "Data path switched %s VF: %s\n",
>   		    vf_is_up ? "to" : "from", vf_netdev->name);
>   
> -	return 0;
> +	return NOTIFY_OK;
>   }
>   
> -static int netvsc_pre_unregister_vf(struct net_device *vf_netdev,
> -				    struct net_device *ndev)
> +static int netvsc_unregister_vf(struct net_device *vf_netdev)
>   {
>   	struct net_device_context *net_device_ctx;
> +	struct net_device *ndev;
>   
> -	net_device_ctx = netdev_priv(ndev);
> -	cancel_delayed_work_sync(&net_device_ctx->vf_takeover);
> -
> -	return 0;
> -}
> -
> -static int netvsc_unregister_vf(struct net_device *vf_netdev,
> -				struct net_device *ndev)
> -{
> -	struct net_device_context *net_device_ctx;
> +	ndev = netdev_failover_upper_get(vf_netdev);
> +	if (!ndev)
> +		return NOTIFY_DONE;
>   
>   	net_device_ctx = netdev_priv(ndev);
> +	if (cancel_delayed_work_sync(&net_device_ctx->vf_takeover))
> +		dev_put(vf_netdev);
>   
>   	netdev_info(ndev, "VF unregistering: %s\n", vf_netdev->name);
>   
> +	netdev_failover_unjoin(vf_netdev, ndev);
>   	RCU_INIT_POINTER(net_device_ctx->vf_netdev, NULL);
> -	dev_put(vf_netdev);
>   
> -	return 0;
> +	return NOTIFY_OK;
>   }
>   
> -static struct failover_ops netvsc_failover_ops = {
> -	.slave_pre_register	= netvsc_pre_register_vf,
> -	.slave_register		= netvsc_register_vf,
> -	.slave_pre_unregister	= netvsc_pre_unregister_vf,
> -	.slave_unregister	= netvsc_unregister_vf,
> -	.slave_link_change	= netvsc_vf_changed,
> -	.slave_handle_frame	= netvsc_vf_handle_frame,
> -};
> -
>   static int netvsc_probe(struct hv_device *dev,
>   			const struct hv_vmbus_device_id *dev_id)
>   {
> @@ -2009,6 +2030,8 @@ static int netvsc_probe(struct hv_device *dev,
>   
>   	memcpy(net->dev_addr, device_info.mac_adr, ETH_ALEN);
>   
> +	net->priv_flags |= IFF_FAILOVER;
> +
>   	/* hw_features computed in rndis_netdev_set_hwcaps() */
>   	net->features = net->hw_features |
>   		NETIF_F_HIGHDMA | NETIF_F_SG |
> @@ -2024,23 +2047,19 @@ static int netvsc_probe(struct hv_device *dev,
>   	else
>   		net->max_mtu = ETH_DATA_LEN;
>   
> -	ret = register_netdev(net);
> +	rtnl_lock();
> +	ret = register_netdevice(net);
>   	if (ret != 0) {
>   		pr_err("Unable to register netdev.\n");
>   		goto register_failed;
>   	}
>   
> -	net_device_ctx->failover = failover_register(net, &netvsc_failover_ops);
> -	if (IS_ERR(net_device_ctx->failover)) {
> -		ret = PTR_ERR(net_device_ctx->failover);
> -		goto err_failover;
> -	}
> -
> -	return ret;
> +	list_add(&net_device_ctx->list, &netvsc_dev_list);
> +	rtnl_unlock();
> +	return 0;
>   
> -err_failover:
> -	unregister_netdev(net);
>   register_failed:
> +	rtnl_unlock();
>   	rndis_filter_device_remove(dev, nvdev);
>   rndis_failed:
>   	free_percpu(net_device_ctx->vf_stats);
> @@ -2079,15 +2098,17 @@ static int netvsc_remove(struct hv_device *dev)
>   	 */
>   	rtnl_lock();
>   	vf_netdev = rtnl_dereference(ndev_ctx->vf_netdev);
> -	if (vf_netdev)
> -		failover_slave_unregister(vf_netdev);
> +	if (vf_netdev) {
> +		netdev_failover_unjoin(vf_netdev, net);
> +		dev_put(vf_netdev);
> +	}
>   
>   	if (nvdev)
>   		rndis_filter_device_remove(dev, nvdev);
>   
>   	unregister_netdevice(net);
>   
> -	failover_unregister(ndev_ctx->failover);
> +	list_del(&ndev_ctx->list);
>   
>   	rtnl_unlock();
>   	rcu_read_unlock();
> @@ -2115,8 +2136,47 @@ static struct  hv_driver netvsc_drv = {
>   	.remove = netvsc_remove,
>   };
>   
> +/* On Hyper-V, every VF interface is matched with a corresponding
> + * synthetic interface. The synthetic interface is presented first
> + * to the guest. When the corresponding VF instance is registered,
> + * we will take care of switching the data path.
> + */
> +static int netvsc_netdev_event(struct notifier_block *this,
> +			       unsigned long event, void *ptr)
> +{
> +	struct net_device *event_dev = netdev_notifier_info_to_dev(ptr);
> +
> +	/* Skip parent events */
> +	if (netif_is_failover(event_dev))
> +		return NOTIFY_DONE;
> +
> +	/* Avoid non-Ethernet type devices */
> +	if (event_dev->type != ARPHRD_ETHER)
> +		return NOTIFY_DONE;
> +
> +	switch (event) {
> +	case NETDEV_REGISTER:
> +		return netvsc_register_vf(event_dev);
> +
> +	case NETDEV_UNREGISTER:
> +		return netvsc_unregister_vf(event_dev);
> +
> +	case NETDEV_UP:
> +	case NETDEV_DOWN:
> +		return netvsc_vf_changed(event_dev);
> +
> +	default:
> +		return NOTIFY_DONE;
> +	}
> +}
> +
> +static struct notifier_block netvsc_netdev_notifier = {
> +	.notifier_call = netvsc_netdev_event,
> +};
> +
>   static void __exit netvsc_drv_exit(void)
>   {
> +	unregister_netdevice_notifier(&netvsc_netdev_notifier);
>   	vmbus_driver_unregister(&netvsc_drv);
>   }
>   
> @@ -2136,6 +2196,7 @@ static int __init netvsc_drv_init(void)
>   	if (ret)
>   		return ret;
>   
> +	register_netdevice_notifier(&netvsc_netdev_notifier);
>   	return 0;
>   }
>   
> diff --git a/drivers/net/net_failover.c b/drivers/net/net_failover.c
> index 83f7420ddea5..e0d30527f748 100644
> --- a/drivers/net/net_failover.c
> +++ b/drivers/net/net_failover.c
> @@ -28,6 +28,46 @@
>   #include <uapi/linux/if_arp.h>
>   #include <net/net_failover.h>
>   
> +static LIST_HEAD(net_failover_list);
> +
> +/* failover state */
> +struct net_failover_info {
> +	struct net_device *failover_dev;
> +
> +	/* list of failover virtual devices */
> +	struct list_head list;
> +
> +	/* primary netdev with same MAC */
> +	struct net_device __rcu *primary_dev;
> +
> +	/* standby netdev */
> +	struct net_device __rcu *standby_dev;
> +
> +	/* primary netdev stats */
> +	struct rtnl_link_stats64 primary_stats;
> +
> +	/* standby netdev stats */
> +	struct rtnl_link_stats64 standby_stats;
> +
> +	/* aggregated stats */
> +	struct rtnl_link_stats64 failover_stats;
> +
> +	/* spinlock while updating stats */
> +	spinlock_t stats_lock;
> +
> +	/* delayed setup of slave */
> +	struct delayed_work standby_init;
> +};
> +
> +#define FAILOVER_VLAN_FEATURES	(NETIF_F_HW_CSUM | NETIF_F_SG | \
> +				 NETIF_F_FRAGLIST | NETIF_F_ALL_TSO | \
> +				 NETIF_F_HIGHDMA | NETIF_F_LRO)
> +
> +#define FAILOVER_ENC_FEATURES	(NETIF_F_HW_CSUM | NETIF_F_SG | \
> +				 NETIF_F_RXCSUM | NETIF_F_ALL_TSO)
> +
> +#define FAILOVER_SETUP_INTERVAL	(HZ / 10)
> +
>   static bool net_failover_xmit_ready(struct net_device *dev)
>   {
>   	return netif_running(dev) && netif_carrier_ok(dev);
> @@ -460,22 +500,42 @@ static void net_failover_lower_state_changed(struct net_device *slave_dev,
>   	netdev_lower_state_changed(slave_dev, &info);
>   }
>   
> -static int net_failover_slave_pre_register(struct net_device *slave_dev,
> -					   struct net_device *failover_dev)
> +static struct net_device *get_net_failover_bymac(const u8 *mac)
>   {
> -	struct net_device *standby_dev, *primary_dev;
> +	struct net_failover_info *nfo_info;
> +
> +	ASSERT_RTNL();
> +
> +	list_for_each_entry(nfo_info, &net_failover_list, list) {
> +		struct net_device *failover_dev = nfo_info->failover_dev;
> +
> +		if (ether_addr_equal(mac, failover_dev->perm_addr))
> +			return failover_dev;
> +	}
> +
> +	return NULL;
> +}
> +
> +static int net_failover_register_event(struct net_device *slave_dev)
> +{
> +	struct net_device *failover_dev, *standby_dev, *primary_dev;
>   	struct net_failover_info *nfo_info;
>   	bool slave_is_standby;
>   
> +	failover_dev = get_net_failover_bymac(slave_dev->perm_addr);
> +	if (!failover_dev)
> +		return NOTIFY_DONE;
> +
>   	nfo_info = netdev_priv(failover_dev);
>   	standby_dev = rtnl_dereference(nfo_info->standby_dev);
>   	primary_dev = rtnl_dereference(nfo_info->primary_dev);
>   	slave_is_standby = slave_dev->dev.parent == failover_dev->dev.parent;
>   	if (slave_is_standby ? standby_dev : primary_dev) {
> -		netdev_err(failover_dev, "%s attempting to register as slave dev when %s already present\n",
> +		netdev_err(failover_dev,
> +			   "%s attempting to register as slave dev when %s already present\n",
>   			   slave_dev->name,
>   			   slave_is_standby ? "standby" : "primary");
> -		return -EINVAL;
> +		return NOTIFY_DONE;
>   	}
>   
>   	/* We want to allow only a direct attached VF device as a primary
> @@ -484,23 +544,33 @@ static int net_failover_slave_pre_register(struct net_device *slave_dev,
>   	 */
>   	if (!slave_is_standby && (!slave_dev->dev.parent ||
>   				  !dev_is_pci(slave_dev->dev.parent)))
> -		return -EINVAL;
> +		return NOTIFY_DONE;
>   
>   	if (failover_dev->features & NETIF_F_VLAN_CHALLENGED &&
>   	    vlan_uses_dev(failover_dev)) {
> -		netdev_err(failover_dev, "Device %s is VLAN challenged and failover device has VLAN set up\n",
> +		netdev_err(failover_dev,
> +			   "Device %s is VLAN challenged and failover device has VLAN set up\n",
>   			   failover_dev->name);
> -		return -EINVAL;
> +		return NOTIFY_DONE;
>   	}
>   
> -	return 0;
> +	if (netdev_failover_join(slave_dev, failover_dev,
> +				 net_failover_handle_frame)) {
> +		netdev_err(failover_dev, "could not join: %s", slave_dev->name);
> +		return NOTIFY_DONE;
> +	}
> +
> +	/* Trigger rest of setup in process context */
> +	schedule_delayed_work(&nfo_info->standby_init, FAILOVER_SETUP_INTERVAL);
> +
> +	return NOTIFY_OK;
>   }
>   
> -static int net_failover_slave_register(struct net_device *slave_dev,
> -				       struct net_device *failover_dev)
> +static void __net_failover_setup(struct net_device *failover_dev)
>   {
> +	struct net_failover_info *nfo_info = netdev_priv(failover_dev);
> +	struct net_device *slave_dev = rtnl_dereference(nfo_info->standby_dev);
>   	struct net_device *standby_dev, *primary_dev;
> -	struct net_failover_info *nfo_info;
>   	bool slave_is_standby;
>   	u32 orig_mtu;
>   	int err;
> @@ -509,13 +579,12 @@ static int net_failover_slave_register(struct net_device *slave_dev,
>   	orig_mtu = slave_dev->mtu;
>   	err = dev_set_mtu(slave_dev, failover_dev->mtu);
>   	if (err) {
> -		netdev_err(failover_dev, "unable to change mtu of %s to %u register failed\n",
> +		netdev_err(failover_dev,
> +			   "unable to change mtu of %s to %u register failed\n",
>   			   slave_dev->name, failover_dev->mtu);
>   		goto done;
>   	}
>   
> -	dev_hold(slave_dev);
> -
>   	if (netif_running(failover_dev)) {
>   		err = dev_open(slave_dev);
>   		if (err && (err != -EBUSY)) {
> @@ -537,7 +606,6 @@ static int net_failover_slave_register(struct net_device *slave_dev,
>   		goto err_vlan_add;
>   	}
>   
> -	nfo_info = netdev_priv(failover_dev);
>   	standby_dev = rtnl_dereference(nfo_info->standby_dev);
>   	primary_dev = rtnl_dereference(nfo_info->primary_dev);
>   	slave_is_standby = slave_dev->dev.parent == failover_dev->dev.parent;
> @@ -562,52 +630,56 @@ static int net_failover_slave_register(struct net_device *slave_dev,
>   	netdev_info(failover_dev, "failover %s slave:%s registered\n",
>   		    slave_is_standby ? "standby" : "primary", slave_dev->name);
>   
> -	return 0;
> +	return;
>   
>   err_vlan_add:
>   	dev_uc_unsync(slave_dev, failover_dev);
>   	dev_mc_unsync(slave_dev, failover_dev);
>   	dev_close(slave_dev);
>   err_dev_open:
> -	dev_put(slave_dev);
>   	dev_set_mtu(slave_dev, orig_mtu);
>   done:
> -	return err;
> +	return;
>   }
>   
> -static int net_failover_slave_pre_unregister(struct net_device *slave_dev,
> -					     struct net_device *failover_dev)
> +static void net_failover_setup(struct work_struct *w)
>   {
> -	struct net_device *standby_dev, *primary_dev;
> -	struct net_failover_info *nfo_info;
> +	struct net_failover_info *nfo_info
> +		= container_of(w, struct net_failover_info, standby_init.work);
> +	struct net_device *failover_dev = nfo_info->failover_dev;
>   
> -	nfo_info = netdev_priv(failover_dev);
> -	primary_dev = rtnl_dereference(nfo_info->primary_dev);
> -	standby_dev = rtnl_dereference(nfo_info->standby_dev);
> -
> -	if (slave_dev != primary_dev && slave_dev != standby_dev)
> -		return -ENODEV;
> +	/* handle race with cancel delayed work on removal */
> +	if (!rtnl_trylock()) {
> +		schedule_delayed_work(&nfo_info->standby_init, 0);
> +		return;
> +	}
>   
> -	return 0;
> +	__net_failover_setup(failover_dev);
> +	rtnl_unlock();
>   }
>   
> -static int net_failover_slave_unregister(struct net_device *slave_dev,
> -					 struct net_device *failover_dev)
> +static int net_failover_unregister_event(struct net_device *slave_dev)
>   {
> -	struct net_device *standby_dev, *primary_dev;
> +	struct net_device *failover_dev, *primary_dev, *standby_dev;
>   	struct net_failover_info *nfo_info;
>   	bool slave_is_standby;
>   
> +	failover_dev = netdev_failover_upper_get(slave_dev);
> +	if (!failover_dev)
> +		return NOTIFY_DONE;
> +
>   	nfo_info = netdev_priv(failover_dev);
>   	primary_dev = rtnl_dereference(nfo_info->primary_dev);
>   	standby_dev = rtnl_dereference(nfo_info->standby_dev);
>   
> +	if (slave_dev != primary_dev && slave_dev != standby_dev)
> +		return NOTIFY_DONE;
> +
>   	vlan_vids_del_by_dev(slave_dev, failover_dev);
>   	dev_uc_unsync(slave_dev, failover_dev);
>   	dev_mc_unsync(slave_dev, failover_dev);
>   	dev_close(slave_dev);
>   
> -	nfo_info = netdev_priv(failover_dev);
>   	dev_get_stats(failover_dev, &nfo_info->failover_stats);
>   
>   	slave_is_standby = slave_dev->dev.parent == failover_dev->dev.parent;
> @@ -628,22 +700,25 @@ static int net_failover_slave_unregister(struct net_device *slave_dev,
>   	netdev_info(failover_dev, "failover %s slave:%s unregistered\n",
>   		    slave_is_standby ? "standby" : "primary", slave_dev->name);
>   
> -	return 0;
> +	return NOTIFY_OK;
>   }
>   
> -static int net_failover_slave_link_change(struct net_device *slave_dev,
> -					  struct net_device *failover_dev)
> +static int net_failover_link_event(struct net_device *slave_dev)
> +
>   {
> -	struct net_device *primary_dev, *standby_dev;
> +	struct net_device *failover_dev, *primary_dev, *standby_dev;
>   	struct net_failover_info *nfo_info;
>   
> -	nfo_info = netdev_priv(failover_dev);
> +	failover_dev = netdev_failover_upper_get(slave_dev);
> +	if (!failover_dev)
> +		return NOTIFY_DONE;
>   
> +	nfo_info = netdev_priv(failover_dev);
>   	primary_dev = rtnl_dereference(nfo_info->primary_dev);
>   	standby_dev = rtnl_dereference(nfo_info->standby_dev);
>   
>   	if (slave_dev != primary_dev && slave_dev != standby_dev)
> -		return -ENODEV;
> +		return NOTIFY_DONE;
>   
>   	if ((primary_dev && net_failover_xmit_ready(primary_dev)) ||
>   	    (standby_dev && net_failover_xmit_ready(standby_dev))) {
> @@ -657,43 +732,11 @@ static int net_failover_slave_link_change(struct net_device *slave_dev,
>   
>   	net_failover_lower_state_changed(slave_dev, primary_dev, standby_dev);
>   
> -	return 0;
> +	return NOTIFY_DONE;
>   }
>   
> -static int net_failover_slave_name_change(struct net_device *slave_dev,
> -					  struct net_device *failover_dev)
> -{
> -	struct net_device *primary_dev, *standby_dev;
> -	struct net_failover_info *nfo_info;
> -
> -	nfo_info = netdev_priv(failover_dev);
> -
> -	primary_dev = rtnl_dereference(nfo_info->primary_dev);
> -	standby_dev = rtnl_dereference(nfo_info->standby_dev);
> -
> -	if (slave_dev != primary_dev && slave_dev != standby_dev)
> -		return -ENODEV;
> -
> -	/* We need to bring up the slave after the rename by udev in case
> -	 * open failed with EBUSY when it was registered.
> -	 */
> -	dev_open(slave_dev);
> -
> -	return 0;
> -}
> -
> -static struct failover_ops net_failover_ops = {
> -	.slave_pre_register	= net_failover_slave_pre_register,
> -	.slave_register		= net_failover_slave_register,
> -	.slave_pre_unregister	= net_failover_slave_pre_unregister,
> -	.slave_unregister	= net_failover_slave_unregister,
> -	.slave_link_change	= net_failover_slave_link_change,
> -	.slave_name_change	= net_failover_slave_name_change,
> -	.slave_handle_frame	= net_failover_handle_frame,
> -};
> -
>   /**
> - * net_failover_create - Create and register a failover instance
> + * net_failover_create - Create and register a failover device
>    *
>    * @dev: standby netdev
>    *
> @@ -703,13 +746,12 @@ static struct failover_ops net_failover_ops = {
>    * the original standby netdev and a VF netdev with the same MAC gets
>    * registered as primary netdev.
>    *
> - * Return: pointer to failover instance
> + * Return: pointer to failover network device
>    */
> -struct failover *net_failover_create(struct net_device *standby_dev)
> +struct net_device *net_failover_create(struct net_device *standby_dev)
>   {
> -	struct device *dev = standby_dev->dev.parent;
> +	struct net_failover_info *nfo_info;
>   	struct net_device *failover_dev;
> -	struct failover *failover;
>   	int err;
>   
>   	/* Alloc at least 2 queues, for now we are going with 16 assuming
> @@ -717,18 +759,22 @@ struct failover *net_failover_create(struct net_device *standby_dev)
>   	 */
>   	failover_dev = alloc_etherdev_mq(sizeof(struct net_failover_info), 16);
>   	if (!failover_dev) {
> -		dev_err(dev, "Unable to allocate failover_netdev!\n");
> -		return ERR_PTR(-ENOMEM);
> +		netdev_err(standby_dev, "Unable to allocate failover_netdev!\n");
> +		return NULL;
>   	}
>   
> +	nfo_info = netdev_priv(failover_dev);
>   	dev_net_set(failover_dev, dev_net(standby_dev));
> -	SET_NETDEV_DEV(failover_dev, dev);
> +	nfo_info->failover_dev = failover_dev;
> +	INIT_DELAYED_WORK(&nfo_info->standby_init, net_failover_setup);
>   
>   	failover_dev->netdev_ops = &failover_dev_ops;
>   	failover_dev->ethtool_ops = &failover_ethtool_ops;
>   
>   	/* Initialize the device options */
> -	failover_dev->priv_flags |= IFF_UNICAST_FLT | IFF_NO_QUEUE;
> +	failover_dev->priv_flags |= IFF_UNICAST_FLT |
> +				    IFF_NO_QUEUE |
> +				    IFF_FAILOVER;
>   	failover_dev->priv_flags &= ~(IFF_XMIT_DST_RELEASE |
>   				       IFF_TX_SKB_SHARING);
>   
> @@ -746,29 +792,38 @@ struct failover *net_failover_create(struct net_device *standby_dev)
>   	failover_dev->hw_features |= NETIF_F_GSO_ENCAP_ALL;
>   	failover_dev->features |= failover_dev->hw_features;
>   
> -	memcpy(failover_dev->dev_addr, standby_dev->dev_addr,
> -	       failover_dev->addr_len);
> +	ether_addr_copy(failover_dev->dev_addr, standby_dev->dev_addr);
> +	ether_addr_copy(failover_dev->perm_addr, standby_dev->perm_addr);
>   
>   	failover_dev->min_mtu = standby_dev->min_mtu;
>   	failover_dev->max_mtu = standby_dev->max_mtu;
>   
> -	err = register_netdev(failover_dev);
> +	netif_carrier_off(failover_dev);
> +
> +	rtnl_lock();
> +	err = register_netdevice(failover_dev);
>   	if (err) {
> -		dev_err(dev, "Unable to register failover_dev!\n");
> +		netdev_err(standby_dev, "Unable to register failover_dev!\n");
>   		goto err_register_netdev;
>   	}
>   
> -	netif_carrier_off(failover_dev);
> +	err = netdev_failover_join(standby_dev, failover_dev,
> +				   net_failover_handle_frame);
> +	if (err) {
> +		netdev_err(failover_dev, "Unable to join with %s\n",
> +			   standby_dev->name);
> +		goto err_failover_join;
> +	}
>   
> -	failover = failover_register(failover_dev, &net_failover_ops);
> -	if (IS_ERR(failover))
> -		goto err_failover_register;
> +	list_add(&nfo_info->list, &net_failover_list);
> +	rtnl_unlock();
>   
> -	return failover;
> +	return failover_dev;
>   
> -err_failover_register:
> -	unregister_netdev(failover_dev);
> +err_failover_join:
> +	unregister_netdevice(failover_dev);
>   err_register_netdev:
> +	rtnl_unlock();
>   	free_netdev(failover_dev);
>   
>   	return ERR_PTR(err);
> @@ -786,31 +841,27 @@ EXPORT_SYMBOL_GPL(net_failover_create);
>    * netdev. Used by paravirtual drivers that use 3-netdev model.
>    *
>    */
> -void net_failover_destroy(struct failover *failover)
> +void net_failover_destroy(struct net_device *failover_dev)
>   {
> -	struct net_failover_info *nfo_info;
> -	struct net_device *failover_dev;
> +	struct net_failover_info *nfo_info = netdev_priv(failover_dev);
>   	struct net_device *slave_dev;
>   
> -	if (!failover)
> -		return;
> -
> -	failover_dev = rcu_dereference(failover->failover_dev);
> -	nfo_info = netdev_priv(failover_dev);
> -
>   	netif_device_detach(failover_dev);
>   
>   	rtnl_lock();
> -
>   	slave_dev = rtnl_dereference(nfo_info->primary_dev);
> -	if (slave_dev)
> -		failover_slave_unregister(slave_dev);
> +	if (slave_dev) {
> +		netdev_failover_unjoin(slave_dev, failover_dev);
> +		dev_put(slave_dev);
> +	}
>   
>   	slave_dev = rtnl_dereference(nfo_info->standby_dev);
> -	if (slave_dev)
> -		failover_slave_unregister(slave_dev);
> +	if (slave_dev) {
> +		netdev_failover_unjoin(slave_dev, failover_dev);
> +		dev_put(slave_dev);
> +	}
>   
> -	failover_unregister(failover);
> +	list_del(&nfo_info->list);
>   
>   	unregister_netdevice(failover_dev);
>   
> @@ -820,9 +871,53 @@ void net_failover_destroy(struct failover *failover)
>   }
>   EXPORT_SYMBOL_GPL(net_failover_destroy);
>   
> +static int net_failover_event(struct notifier_block *this,
> +			      unsigned long event, void *ptr)
> +{
> +	struct net_device *event_dev = netdev_notifier_info_to_dev(ptr);
> +
> +	/* Skip parent events */
> +	if (netif_is_failover(event_dev))
> +		return NOTIFY_DONE;
> +
> +	/* Avoid non-Ethernet type devices */
> +	if (event_dev->type != ARPHRD_ETHER)
> +		return NOTIFY_DONE;
> +
> +	/* Avoid Vlan dev with same MAC registering as VF */
> +	if (is_vlan_dev(event_dev))
> +		return NOTIFY_DONE;
> +
> +	/* Avoid Bonding master dev with same MAC registering as VF */
> +	if ((event_dev->priv_flags & IFF_BONDING) &&
> +	    (event_dev->flags & IFF_MASTER))
> +		return NOTIFY_DONE;
> +
> +	switch (event) {
> +	case NETDEV_REGISTER:
> +		return net_failover_register_event(event_dev);
> +
> +	case NETDEV_UNREGISTER:
> +		return net_failover_unregister_event(event_dev);
> +
> +	case NETDEV_UP:
> +	case NETDEV_DOWN:
> +	case NETDEV_CHANGE:
> +		return net_failover_link_event(event_dev);
> +
> +	default:
> +		return NOTIFY_DONE;
> +	}
> +}
> +
> +static struct notifier_block net_failover_notifier = {
> +	.notifier_call = net_failover_event,
> +};
> +
>   static __init int
>   net_failover_init(void)
>   {
> +	register_netdevice_notifier(&net_failover_notifier);
>   	return 0;
>   }
>   module_init(net_failover_init);
> @@ -830,6 +925,7 @@ module_init(net_failover_init);
>   static __exit
>   void net_failover_exit(void)
>   {
> +	unregister_netdevice_notifier(&net_failover_notifier);
>   }
>   module_exit(net_failover_exit);
>   
> diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c
> index 6d710b8b41c5..b40ae28dac93 100644
> --- a/drivers/net/virtio_net.c
> +++ b/drivers/net/virtio_net.c
> @@ -215,7 +215,7 @@ struct virtnet_info {
>   	unsigned long guest_offloads;
>   
>   	/* failover when STANDBY feature enabled */
> -	struct failover *failover;
> +	struct net_device *failover;
>   };
>   
>   struct padded_vnet_hdr {
> @@ -2930,11 +2930,10 @@ static int virtnet_probe(struct virtio_device *vdev)
>   	virtnet_init_settings(dev);
>   
>   	if (virtio_has_feature(vdev, VIRTIO_NET_F_STANDBY)) {
> -		vi->failover = net_failover_create(vi->dev);
> -		if (IS_ERR(vi->failover)) {
> -			err = PTR_ERR(vi->failover);
> +		err = -ENOMEM;
> +		vi->failover = net_failover_create(dev);
> +		if (!vi->failover)
>   			goto free_vqs;
> -		}
>   	}
>   
>   	err = register_netdev(dev);
> diff --git a/include/net/failover.h b/include/net/failover.h
> index bb15438f39c7..22d6c1369101 100644
> --- a/include/net/failover.h
> +++ b/include/net/failover.h
> @@ -6,31 +6,10 @@
>   
>   #include <linux/netdevice.h>
>   
> -struct failover_ops {
> -	int (*slave_pre_register)(struct net_device *slave_dev,
> -				  struct net_device *failover_dev);
> -	int (*slave_register)(struct net_device *slave_dev,
> -			      struct net_device *failover_dev);
> -	int (*slave_pre_unregister)(struct net_device *slave_dev,
> -				    struct net_device *failover_dev);
> -	int (*slave_unregister)(struct net_device *slave_dev,
> -				struct net_device *failover_dev);
> -	int (*slave_link_change)(struct net_device *slave_dev,
> -				 struct net_device *failover_dev);
> -	int (*slave_name_change)(struct net_device *slave_dev,
> -				 struct net_device *failover_dev);
> -	rx_handler_result_t (*slave_handle_frame)(struct sk_buff **pskb);
> -};
> -
> -struct failover {
> -	struct list_head list;
> -	struct net_device __rcu *failover_dev;
> -	struct failover_ops __rcu *ops;
> -};
> -
> -struct failover *failover_register(struct net_device *dev,
> -				   struct failover_ops *ops);
> -void failover_unregister(struct failover *failover);
> -int failover_slave_unregister(struct net_device *slave_dev);
> +int netdev_failover_join(struct net_device *lower, struct net_device *upper,
> +			 rx_handler_func_t *rx_handler);
> +struct net_device *netdev_failover_upper_get(struct net_device *lower);
> +void netdev_failover_unjoin(struct net_device *lower,
> +			    struct net_device *upper);
>   
>   #endif /* _FAILOVER_H */
> diff --git a/include/net/net_failover.h b/include/net/net_failover.h
> index b12a1c469d1c..a99b3b00b4e3 100644
> --- a/include/net/net_failover.h
> +++ b/include/net/net_failover.h
> @@ -6,35 +6,7 @@
>   
>   #include <net/failover.h>
>   
> -/* failover state */
> -struct net_failover_info {
> -	/* primary netdev with same MAC */
> -	struct net_device __rcu *primary_dev;
> -
> -	/* standby netdev */
> -	struct net_device __rcu *standby_dev;
> -
> -	/* primary netdev stats */
> -	struct rtnl_link_stats64 primary_stats;
> -
> -	/* standby netdev stats */
> -	struct rtnl_link_stats64 standby_stats;
> -
> -	/* aggregated stats */
> -	struct rtnl_link_stats64 failover_stats;
> -
> -	/* spinlock while updating stats */
> -	spinlock_t stats_lock;
> -};
> -
> -struct failover *net_failover_create(struct net_device *standby_dev);
> -void net_failover_destroy(struct failover *failover);
> -
> -#define FAILOVER_VLAN_FEATURES	(NETIF_F_HW_CSUM | NETIF_F_SG | \
> -				 NETIF_F_FRAGLIST | NETIF_F_ALL_TSO | \
> -				 NETIF_F_HIGHDMA | NETIF_F_LRO)
> -
> -#define FAILOVER_ENC_FEATURES	(NETIF_F_HW_CSUM | NETIF_F_SG | \
> -				 NETIF_F_RXCSUM | NETIF_F_ALL_TSO)
> +struct net_device *net_failover_create(struct net_device *standby_dev);
> +void net_failover_destroy(struct net_device *failover_dev);
>   
>   #endif /* _NET_FAILOVER_H */
> diff --git a/net/Kconfig b/net/Kconfig
> index f738a6f27665..697d84202695 100644
> --- a/net/Kconfig
> +++ b/net/Kconfig
> @@ -433,17 +433,8 @@ config PAGE_POOL
>          bool
>   
>   config FAILOVER
> -	tristate "Generic failover module"
> -	help
> -	  The failover module provides a generic interface for paravirtual
> -	  drivers to register a netdev and a set of ops with a failover
> -	  instance. The ops are used as event handlers that get called to
> -	  handle netdev register/unregister/link change/name change events
> -	  on slave pci ethernet devices with the same mac address as the
> -	  failover netdev. This enables paravirtual drivers to use a
> -	  VF as an accelerated low latency datapath. It also allows live
> -	  migration of VMs with direct attached VFs by failing over to the
> -	  paravirtual datapath when the VF is unplugged.
> +	bool
> +	default n
>   
>   endif   # if NET
>   
> diff --git a/net/core/failover.c b/net/core/failover.c
> index 4a92a98ccce9..499f0fd7e4d3 100644
> --- a/net/core/failover.c
> +++ b/net/core/failover.c
> @@ -1,10 +1,8 @@
>   // SPDX-License-Identifier: GPL-2.0
>   /* Copyright (c) 2018, Intel Corporation. */
>   
> -/* A common module to handle registrations and notifications for paravirtual
> +/* A library for managing chained upper/oower devices such as
>    * drivers to enable accelerated datapath and support VF live migration.
> - *
> - * The notifier and event handling code is based on netvsc driver.
>    */
>   
>   #include <linux/module.h>
> @@ -14,302 +12,62 @@
>   #include <linux/if_vlan.h>
>   #include <net/failover.h>
>   
> -static LIST_HEAD(failover_list);
> -static DEFINE_SPINLOCK(failover_lock);
> -
> -static struct net_device *failover_get_bymac(u8 *mac, struct failover_ops **ops)
> -{
> -	struct net_device *failover_dev;
> -	struct failover *failover;
> -
> -	spin_lock(&failover_lock);
> -	list_for_each_entry(failover, &failover_list, list) {
> -		failover_dev = rtnl_dereference(failover->failover_dev);
> -		if (ether_addr_equal(failover_dev->perm_addr, mac)) {
> -			*ops = rtnl_dereference(failover->ops);
> -			spin_unlock(&failover_lock);
> -			return failover_dev;
> -		}
> -	}
> -	spin_unlock(&failover_lock);
> -	return NULL;
> -}
> -
> -/**
> - * failover_slave_register - Register a slave netdev
> - *
> - * @slave_dev: slave netdev that is being registered
> - *
> - * Registers a slave device to a failover instance. Only ethernet devices
> - * are supported.
> - */
> -static int failover_slave_register(struct net_device *slave_dev)
> +/* failover_join - Join an lower netdev with an upper device. */
> +int netdev_failover_join(struct net_device *lower_dev,
> +			 struct net_device *upper_dev,
> +			 rx_handler_func_t *rx_handler)
>   {
> -	struct netdev_lag_upper_info lag_upper_info;
> -	struct net_device *failover_dev;
> -	struct failover_ops *fops;
>   	int err;
>   
> -	if (slave_dev->type != ARPHRD_ETHER)
> -		goto done;
> -
>   	ASSERT_RTNL();
>   
> -	failover_dev = failover_get_bymac(slave_dev->perm_addr, &fops);
> -	if (!failover_dev)
> -		goto done;
> +	/* Don't allow joining devices of different protocols */
> +	if (upper_dev->type != lower_dev->type)
> +		return -EINVAL;
>   
> -	if (fops && fops->slave_pre_register &&
> -	    fops->slave_pre_register(slave_dev, failover_dev))
> -		goto done;
> -
> -	err = netdev_rx_handler_register(slave_dev, fops->slave_handle_frame,
> -					 failover_dev);
> +	err = netdev_rx_handler_register(lower_dev, rx_handler, upper_dev);
>   	if (err) {
> -		netdev_err(slave_dev, "can not register failover rx handler (err = %d)\n",
> +		netdev_err(lower_dev,
> +			   "can not register failover rx handler (err = %d)\n",
>   			   err);
> -		goto done;
> +		return err;
>   	}
>   
> -	lag_upper_info.tx_type = NETDEV_LAG_TX_TYPE_ACTIVEBACKUP;
> -	err = netdev_master_upper_dev_link(slave_dev, failover_dev, NULL,
> -					   &lag_upper_info, NULL);
> +	err = netdev_master_upper_dev_link(lower_dev, upper_dev, NULL,
> +					   NULL, NULL);
>   	if (err) {
> -		netdev_err(slave_dev, "can not set failover device %s (err = %d)\n",
> -			   failover_dev->name, err);
> -		goto err_upper_link;
> +		netdev_err(lower_dev,
> +			   "can not set failover device %s (err = %d)\n",
> +			   upper_dev->name, err);
> +		netdev_rx_handler_unregister(lower_dev);
> +		return err;
>   	}
>   
> -	slave_dev->priv_flags |= IFF_FAILOVER_SLAVE;
> -
> -	if (fops && fops->slave_register &&
> -	    !fops->slave_register(slave_dev, failover_dev))
> -		return NOTIFY_OK;
> -
> -	netdev_upper_dev_unlink(slave_dev, failover_dev);
> -	slave_dev->priv_flags &= ~IFF_FAILOVER_SLAVE;
> -err_upper_link:
> -	netdev_rx_handler_unregister(slave_dev);
> -done:
> -	return NOTIFY_DONE;
> -}
> -
> -/**
> - * failover_slave_unregister - Unregister a slave netdev
> - *
> - * @slave_dev: slave netdev that is being unregistered
> - *
> - * Unregisters a slave device from a failover instance.
> - */
> -int failover_slave_unregister(struct net_device *slave_dev)
> -{
> -	struct net_device *failover_dev;
> -	struct failover_ops *fops;
> -
> -	if (!netif_is_failover_slave(slave_dev))
> -		goto done;
> -
> -	ASSERT_RTNL();
> -
> -	failover_dev = failover_get_bymac(slave_dev->perm_addr, &fops);
> -	if (!failover_dev)
> -		goto done;
> -
> -	if (fops && fops->slave_pre_unregister &&
> -	    fops->slave_pre_unregister(slave_dev, failover_dev))
> -		goto done;
> -
> -	netdev_rx_handler_unregister(slave_dev);
> -	netdev_upper_dev_unlink(slave_dev, failover_dev);
> -	slave_dev->priv_flags &= ~IFF_FAILOVER_SLAVE;
> -
> -	if (fops && fops->slave_unregister &&
> -	    !fops->slave_unregister(slave_dev, failover_dev))
> -		return NOTIFY_OK;
> -
> -done:
> -	return NOTIFY_DONE;
> +	dev_hold(lower_dev);
> +	lower_dev->priv_flags |= IFF_FAILOVER_SLAVE;
> +	return 0;
>   }
> -EXPORT_SYMBOL_GPL(failover_slave_unregister);
> +EXPORT_SYMBOL_GPL(netdev_failover_join);
>   
> -static int failover_slave_link_change(struct net_device *slave_dev)
> +/* Find upper network device for failover slave device */
> +struct net_device *netdev_failover_upper_get(struct net_device *lower_dev)
>   {
> -	struct net_device *failover_dev;
> -	struct failover_ops *fops;
> -
> -	if (!netif_is_failover_slave(slave_dev))
> -		goto done;
> -
> -	ASSERT_RTNL();
> -
> -	failover_dev = failover_get_bymac(slave_dev->perm_addr, &fops);
> -	if (!failover_dev)
> -		goto done;
> -
> -	if (!netif_running(failover_dev))
> -		goto done;
> +	if (!netif_is_failover_slave(lower_dev))
> +		return NULL;
>   
> -	if (fops && fops->slave_link_change &&
> -	    !fops->slave_link_change(slave_dev, failover_dev))
> -		return NOTIFY_OK;
> -
> -done:
> -	return NOTIFY_DONE;
> +	return netdev_master_upper_dev_get(lower_dev);
>   }
> +EXPORT_SYMBOL_GPL(netdev_failover_upper_get);
>   
> -static int failover_slave_name_change(struct net_device *slave_dev)
> +/* failover_unjoin - Break connection between lower and upper device. */
> +void netdev_failover_unjoin(struct net_device *lower_dev,
> +			    struct net_device *upper_dev)
>   {
> -	struct net_device *failover_dev;
> -	struct failover_ops *fops;
> -
> -	if (!netif_is_failover_slave(slave_dev))
> -		goto done;
> -
>   	ASSERT_RTNL();
>   
> -	failover_dev = failover_get_bymac(slave_dev->perm_addr, &fops);
> -	if (!failover_dev)
> -		goto done;
> -
> -	if (!netif_running(failover_dev))
> -		goto done;
> -
> -	if (fops && fops->slave_name_change &&
> -	    !fops->slave_name_change(slave_dev, failover_dev))
> -		return NOTIFY_OK;
> -
> -done:
> -	return NOTIFY_DONE;
> -}
> -
> -static int
> -failover_event(struct notifier_block *this, unsigned long event, void *ptr)
> -{
> -	struct net_device *event_dev = netdev_notifier_info_to_dev(ptr);
> -
> -	/* Skip parent events */
> -	if (netif_is_failover(event_dev))
> -		return NOTIFY_DONE;
> -
> -	switch (event) {
> -	case NETDEV_REGISTER:
> -		return failover_slave_register(event_dev);
> -	case NETDEV_UNREGISTER:
> -		return failover_slave_unregister(event_dev);
> -	case NETDEV_UP:
> -	case NETDEV_DOWN:
> -	case NETDEV_CHANGE:
> -		return failover_slave_link_change(event_dev);
> -	case NETDEV_CHANGENAME:
> -		return failover_slave_name_change(event_dev);
> -	default:
> -		return NOTIFY_DONE;
> -	}
> -}
> -
> -static struct notifier_block failover_notifier = {
> -	.notifier_call = failover_event,
> -};
> -
> -static void
> -failover_existing_slave_register(struct net_device *failover_dev)
> -{
> -	struct net *net = dev_net(failover_dev);
> -	struct net_device *dev;
> -
> -	rtnl_lock();
> -	for_each_netdev(net, dev) {
> -		if (netif_is_failover(dev))
> -			continue;
> -		if (ether_addr_equal(failover_dev->perm_addr, dev->perm_addr))
> -			failover_slave_register(dev);
> -	}
> -	rtnl_unlock();
> -}
> -
> -/**
> - * failover_register - Register a failover instance
> - *
> - * @dev: failover netdev
> - * @ops: failover ops
> - *
> - * Allocate and register a failover instance for a failover netdev. ops
> - * provides handlers for slave device register/unregister/link change/
> - * name change events.
> - *
> - * Return: pointer to failover instance
> - */
> -struct failover *failover_register(struct net_device *dev,
> -				   struct failover_ops *ops)
> -{
> -	struct failover *failover;
> -
> -	if (dev->type != ARPHRD_ETHER)
> -		return ERR_PTR(-EINVAL);
> -
> -	failover = kzalloc(sizeof(*failover), GFP_KERNEL);
> -	if (!failover)
> -		return ERR_PTR(-ENOMEM);
> -
> -	rcu_assign_pointer(failover->ops, ops);
> -	dev_hold(dev);
> -	dev->priv_flags |= IFF_FAILOVER;
> -	rcu_assign_pointer(failover->failover_dev, dev);
> -
> -	spin_lock(&failover_lock);
> -	list_add_tail(&failover->list, &failover_list);
> -	spin_unlock(&failover_lock);
> -
> -	netdev_info(dev, "failover master:%s registered\n", dev->name);
> -
> -	failover_existing_slave_register(dev);
> -
> -	return failover;
> -}
> -EXPORT_SYMBOL_GPL(failover_register);
> -
> -/**
> - * failover_unregister - Unregister a failover instance
> - *
> - * @failover: pointer to failover instance
> - *
> - * Unregisters and frees a failover instance.
> - */
> -void failover_unregister(struct failover *failover)
> -{
> -	struct net_device *failover_dev;
> -
> -	failover_dev = rcu_dereference(failover->failover_dev);
> -
> -	netdev_info(failover_dev, "failover master:%s unregistered\n",
> -		    failover_dev->name);
> -
> -	failover_dev->priv_flags &= ~IFF_FAILOVER;
> -	dev_put(failover_dev);
> -
> -	spin_lock(&failover_lock);
> -	list_del(&failover->list);
> -	spin_unlock(&failover_lock);
> -
> -	kfree(failover);
> +	netdev_rx_handler_unregister(lower_dev);
> +	netdev_upper_dev_unlink(lower_dev, upper_dev);
> +	dev_put(lower_dev);
> +	lower_dev->priv_flags &= ~IFF_FAILOVER_SLAVE;
>   }
> -EXPORT_SYMBOL_GPL(failover_unregister);
> -
> -static __init int
> -failover_init(void)
> -{
> -	register_netdevice_notifier(&failover_notifier);
> -
> -	return 0;
> -}
> -module_init(failover_init);
> -
> -static __exit
> -void failover_exit(void)
> -{
> -	unregister_netdevice_notifier(&failover_notifier);
> -}
> -module_exit(failover_exit);
> -
> -MODULE_DESCRIPTION("Generic failover infrastructure/interface");
> -MODULE_LICENSE("GPL v2");
> +EXPORT_SYMBOL_GPL(netdev_failover_unjoin);

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ