Subject: [PATCH net] failover: eliminate callback hell

From: Stephen Hemminger <[email-masked]>
Date: Mon, 4 Jun 2018 20:42:31 -0700

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.

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 <[email-masked]>
---

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);
-- 
2.17.1