2018-05-24 19:55:13 +03:00
// 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 ;
}
2022-12-09 18:21:40 +03:00
slave_dev - > priv_flags | = ( IFF_FAILOVER_SLAVE | IFF_NO_ADDRCONF ) ;
2018-05-24 19:55:13 +03:00
if ( fops & & fops - > slave_register & &
! fops - > slave_register ( slave_dev , failover_dev ) )
return NOTIFY_OK ;
netdev_upper_dev_unlink ( slave_dev , failover_dev ) ;
2022-12-09 18:21:40 +03:00
slave_dev - > priv_flags & = ~ ( IFF_FAILOVER_SLAVE | IFF_NO_ADDRCONF ) ;
2018-05-24 19:55:13 +03:00
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 ) ;
2022-12-09 18:21:40 +03:00
slave_dev - > priv_flags & = ~ ( IFF_FAILOVER_SLAVE | IFF_NO_ADDRCONF ) ;
2018-05-24 19:55:13 +03:00
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 ) ;
2022-06-08 07:39:55 +03:00
netdev_hold ( dev , & failover - > dev_tracker , GFP_KERNEL ) ;
2018-05-24 19:55:13 +03:00
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 ;
2022-06-08 07:39:55 +03:00
netdev_put ( failover_dev , & failover - > dev_tracker ) ;
2018-05-24 19:55:13 +03:00
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 " ) ;