// 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"