| // SPDX-License-Identifier: GPL-2.0 | |
| /* Copyright (c) 2018, Intel Corporation. */ | |
| /* A common module to handle registrations and notifications for paravirtual | |
| * 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> | |
| #include <linux/etherdevice.h> | |
| #include <uapi/linux/if_arp.h> | |
| #include <linux/rtnetlink.h> | |
| #include <linux/if_vlan.h> | |
| #include <net/netdev_lock.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) | |
| { | |
| 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; | |
| if (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); | |
| if (err) { | |
| netdev_err(slave_dev, "can not register failover rx handler (err = %d)\n", | |
| err); | |
| goto done; | |
| } | |
| 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); | |
| if (err) { | |
| netdev_err(slave_dev, "can not set failover device %s (err = %d)\n", | |
| failover_dev->name, err); | |
| goto err_upper_link; | |
| } | |
| slave_dev->priv_flags |= (IFF_FAILOVER_SLAVE | IFF_NO_ADDRCONF); | |
| if (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 | IFF_NO_ADDRCONF); | |
| 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->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 | IFF_NO_ADDRCONF); | |
| if (fops->slave_unregister && | |
| !fops->slave_unregister(slave_dev, failover_dev)) | |
| return NOTIFY_OK; | |
| done: | |
| return NOTIFY_DONE; | |
| } | |
| EXPORT_SYMBOL_GPL(failover_slave_unregister); | |
| static int failover_slave_link_change(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 (!netif_running(failover_dev)) | |
| goto done; | |
| if (fops->slave_link_change && | |
| !fops->slave_link_change(slave_dev, failover_dev)) | |
| return NOTIFY_OK; | |
| done: | |
| return NOTIFY_DONE; | |
| } | |
| static int failover_slave_name_change(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 (!netif_running(failover_dev)) | |
| goto done; | |
| if (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)) { | |
| netdev_lock_ops(dev); | |
| failover_slave_register(dev); | |
| netdev_unlock_ops(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 || !ops) | |
| return ERR_PTR(-EINVAL); | |
| failover = kzalloc_obj(*failover); | |
| if (!failover) | |
| return ERR_PTR(-ENOMEM); | |
| rcu_assign_pointer(failover->ops, ops); | |
| netdev_hold(dev, &failover->dev_tracker, GFP_KERNEL); | |
| 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; | |
| netdev_put(failover_dev, &failover->dev_tracker); | |
| spin_lock(&failover_lock); | |
| list_del(&failover->list); | |
| spin_unlock(&failover_lock); | |
| kfree(failover); | |
| } | |
| 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"); |