|  | // 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/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 && 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 && 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 && 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 && 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 && 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 && 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); | 
|  | 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"); |