2014-07-05 01:34:38 +04:00
/* Copyright 2011-2014 Autronica Fire and Security AS
2013-10-31 00:10:47 +04:00
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation ; either version 2 of the License , or ( at your option )
* any later version .
*
* Author ( s ) :
2014-07-05 01:34:38 +04:00
* 2011 - 2014 Arvid Brodin , arvid . brodin @ alten . se
2013-10-31 00:10:47 +04:00
*/
# include <linux/netdevice.h>
# include <linux/rculist.h>
# include <linux/timer.h>
# include <linux/etherdevice.h>
# include "hsr_main.h"
# include "hsr_device.h"
# include "hsr_netlink.h"
# include "hsr_framereg.h"
/* List of all registered virtual HSR devices */
static LIST_HEAD ( hsr_list ) ;
2014-07-05 01:34:38 +04:00
void register_hsr_master ( struct hsr_priv * hsr )
2013-10-31 00:10:47 +04:00
{
2014-07-05 01:34:38 +04:00
list_add_tail_rcu ( & hsr - > hsr_list , & hsr_list ) ;
2013-10-31 00:10:47 +04:00
}
2014-07-05 01:34:38 +04:00
void unregister_hsr_master ( struct hsr_priv * hsr )
2013-10-31 00:10:47 +04:00
{
2014-07-05 01:34:38 +04:00
struct hsr_priv * hsr_it ;
2013-10-31 00:10:47 +04:00
2014-07-05 01:34:38 +04:00
list_for_each_entry ( hsr_it , & hsr_list , hsr_list )
if ( hsr_it = = hsr ) {
list_del_rcu ( & hsr_it - > hsr_list ) ;
2013-10-31 00:10:47 +04:00
return ;
}
}
bool is_hsr_slave ( struct net_device * dev )
{
2014-07-05 01:34:38 +04:00
struct hsr_priv * hsr_it ;
2013-10-31 00:10:47 +04:00
2014-07-05 01:34:38 +04:00
list_for_each_entry_rcu ( hsr_it , & hsr_list , hsr_list ) {
if ( dev = = hsr_it - > slave [ 0 ] )
2013-10-31 00:10:47 +04:00
return true ;
2014-07-05 01:34:38 +04:00
if ( dev = = hsr_it - > slave [ 1 ] )
2013-10-31 00:10:47 +04:00
return true ;
}
return false ;
}
/* If dev is a HSR slave device, return the virtual master device. Return NULL
* otherwise .
*/
2014-07-05 01:35:24 +04:00
struct hsr_priv * get_hsr_master ( struct net_device * dev )
2013-10-31 00:10:47 +04:00
{
2014-07-05 01:34:38 +04:00
struct hsr_priv * hsr ;
2013-10-31 00:10:47 +04:00
rcu_read_lock ( ) ;
2014-07-05 01:34:38 +04:00
list_for_each_entry_rcu ( hsr , & hsr_list , hsr_list )
if ( ( dev = = hsr - > slave [ 0 ] ) | |
( dev = = hsr - > slave [ 1 ] ) ) {
2013-10-31 00:10:47 +04:00
rcu_read_unlock ( ) ;
2014-07-05 01:34:38 +04:00
return hsr ;
2013-10-31 00:10:47 +04:00
}
rcu_read_unlock ( ) ;
return NULL ;
}
/* If dev is a HSR slave device, return the other slave device. Return NULL
* otherwise .
*/
2014-07-05 01:35:24 +04:00
struct net_device * get_other_slave ( struct hsr_priv * hsr ,
struct net_device * dev )
2013-10-31 00:10:47 +04:00
{
2014-07-05 01:34:38 +04:00
if ( dev = = hsr - > slave [ 0 ] )
return hsr - > slave [ 1 ] ;
if ( dev = = hsr - > slave [ 1 ] )
return hsr - > slave [ 0 ] ;
2013-10-31 00:10:47 +04:00
return NULL ;
}
static int hsr_netdev_notify ( struct notifier_block * nb , unsigned long event ,
void * ptr )
{
struct net_device * slave , * other_slave ;
2014-07-05 01:34:38 +04:00
struct hsr_priv * hsr ;
2013-10-31 00:10:47 +04:00
int mtu_max ;
int res ;
struct net_device * dev ;
dev = netdev_notifier_info_to_dev ( ptr ) ;
2014-07-05 01:34:38 +04:00
hsr = get_hsr_master ( dev ) ;
if ( hsr ) {
2013-10-31 00:10:47 +04:00
/* dev is a slave device */
slave = dev ;
2014-07-05 01:34:38 +04:00
other_slave = get_other_slave ( hsr , slave ) ;
2013-10-31 00:10:47 +04:00
} else {
if ( ! is_hsr_master ( dev ) )
return NOTIFY_DONE ;
2014-07-05 01:34:38 +04:00
hsr = netdev_priv ( dev ) ;
slave = hsr - > slave [ 0 ] ;
other_slave = hsr - > slave [ 1 ] ;
2013-10-31 00:10:47 +04:00
}
switch ( event ) {
case NETDEV_UP : /* Administrative state DOWN */
case NETDEV_DOWN : /* Administrative state UP */
case NETDEV_CHANGE : /* Link (carrier) state changes */
2014-07-05 01:36:40 +04:00
hsr_check_carrier_and_operstate ( hsr ) ;
2013-10-31 00:10:47 +04:00
break ;
case NETDEV_CHANGEADDR :
/* This should not happen since there's no ndo_set_mac_address()
* for HSR devices - i . e . not supported .
*/
2014-07-05 01:34:38 +04:00
if ( dev = = hsr - > dev )
2013-10-31 00:10:47 +04:00
break ;
2014-07-05 01:34:38 +04:00
if ( dev = = hsr - > slave [ 0 ] )
ether_addr_copy ( hsr - > dev - > dev_addr ,
hsr - > slave [ 0 ] - > dev_addr ) ;
2013-10-31 00:10:47 +04:00
/* Make sure we recognize frames from ourselves in hsr_rcv() */
2014-07-05 01:34:38 +04:00
res = hsr_create_self_node ( & hsr - > self_node_db ,
hsr - > dev - > dev_addr ,
hsr - > slave [ 1 ] ?
hsr - > slave [ 1 ] - > dev_addr :
hsr - > dev - > dev_addr ) ;
2013-10-31 00:10:47 +04:00
if ( res )
2014-07-05 01:34:38 +04:00
netdev_warn ( hsr - > dev ,
2013-10-31 00:10:47 +04:00
" Could not update HSR node address. \n " ) ;
2014-07-05 01:34:38 +04:00
if ( dev = = hsr - > slave [ 0 ] )
call_netdevice_notifiers ( NETDEV_CHANGEADDR , hsr - > dev ) ;
2013-10-31 00:10:47 +04:00
break ;
case NETDEV_CHANGEMTU :
2014-07-05 01:34:38 +04:00
if ( dev = = hsr - > dev )
2013-10-31 00:10:47 +04:00
break ; /* Handled in ndo_change_mtu() */
2014-07-05 01:34:38 +04:00
mtu_max = hsr_get_max_mtu ( hsr ) ;
if ( hsr - > dev - > mtu > mtu_max )
dev_set_mtu ( hsr - > dev , mtu_max ) ;
2013-10-31 00:10:47 +04:00
break ;
case NETDEV_UNREGISTER :
2014-07-05 01:34:38 +04:00
if ( dev = = hsr - > slave [ 0 ] )
hsr - > slave [ 0 ] = NULL ;
if ( dev = = hsr - > slave [ 1 ] )
hsr - > slave [ 1 ] = NULL ;
2013-10-31 00:10:47 +04:00
/* There should really be a way to set a new slave device... */
break ;
case NETDEV_PRE_TYPE_CHANGE :
/* HSR works only on Ethernet devices. Refuse slave to change
* its type .
*/
return NOTIFY_BAD ;
}
return NOTIFY_DONE ;
}
static struct notifier_block hsr_nb = {
. notifier_call = hsr_netdev_notify , /* Slave event notifications */
} ;
static int __init hsr_init ( void )
{
int res ;
2014-07-05 01:34:38 +04:00
BUILD_BUG_ON ( sizeof ( struct hsr_tag ) ! = HSR_HLEN ) ;
2013-10-31 00:10:47 +04:00
register_netdevice_notifier ( & hsr_nb ) ;
res = hsr_netlink_init ( ) ;
return res ;
}
static void __exit hsr_exit ( void )
{
unregister_netdevice_notifier ( & hsr_nb ) ;
hsr_netlink_exit ( ) ;
}
module_init ( hsr_init ) ;
module_exit ( hsr_exit ) ;
MODULE_LICENSE ( " GPL " ) ;