2013-10-31 00:10:47 +04:00
/* Copyright 2011-2013 Autronica Fire and Security AS
*
* 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 ) :
* 2011 - 2013 Arvid Brodin , arvid . brodin @ xdin . com
*
* The HSR spec says never to forward the same frame twice on the same
* interface . A frame is identified by its source MAC address and its HSR
* sequence number . This code keeps track of senders and their sequence numbers
* to allow filtering of duplicate frames , and to detect HSR ring errors .
*/
# include <linux/if_ether.h>
# include <linux/etherdevice.h>
# include <linux/slab.h>
# include <linux/rculist.h>
# include "hsr_main.h"
# include "hsr_framereg.h"
# include "hsr_netlink.h"
struct node_entry {
struct list_head mac_list ;
unsigned char MacAddressA [ ETH_ALEN ] ;
unsigned char MacAddressB [ ETH_ALEN ] ;
enum hsr_dev_idx AddrB_if ; /* The local slave through which AddrB
* frames are received from this node
*/
unsigned long time_in [ HSR_MAX_SLAVE ] ;
bool time_in_stale [ HSR_MAX_SLAVE ] ;
u16 seq_out [ HSR_MAX_DEV ] ;
struct rcu_head rcu_head ;
} ;
/* TODO: use hash lists for mac addresses (linux/jhash.h)? */
/* Search for mac entry. Caller must hold rcu read lock.
*/
static struct node_entry * find_node_by_AddrA ( struct list_head * node_db ,
const unsigned char addr [ ETH_ALEN ] )
{
struct node_entry * node ;
list_for_each_entry_rcu ( node , node_db , mac_list ) {
if ( ether_addr_equal ( node - > MacAddressA , addr ) )
return node ;
}
return NULL ;
}
/* Search for mac entry. Caller must hold rcu read lock.
*/
static struct node_entry * find_node_by_AddrB ( struct list_head * node_db ,
const unsigned char addr [ ETH_ALEN ] )
{
struct node_entry * node ;
list_for_each_entry_rcu ( node , node_db , mac_list ) {
if ( ether_addr_equal ( node - > MacAddressB , addr ) )
return node ;
}
return NULL ;
}
/* Search for mac entry. Caller must hold rcu read lock.
*/
struct node_entry * hsr_find_node ( struct list_head * node_db , struct sk_buff * skb )
{
struct node_entry * node ;
struct ethhdr * ethhdr ;
if ( ! skb_mac_header_was_set ( skb ) )
return NULL ;
ethhdr = ( struct ethhdr * ) skb_mac_header ( skb ) ;
list_for_each_entry_rcu ( node , node_db , mac_list ) {
if ( ether_addr_equal ( node - > MacAddressA , ethhdr - > h_source ) )
return node ;
if ( ether_addr_equal ( node - > MacAddressB , ethhdr - > h_source ) )
return node ;
}
return NULL ;
}
/* Helper for device init; the self_node_db is used in hsr_rcv() to recognize
* frames from self that ' s been looped over the HSR ring .
*/
int hsr_create_self_node ( struct list_head * self_node_db ,
unsigned char addr_a [ ETH_ALEN ] ,
unsigned char addr_b [ ETH_ALEN ] )
{
struct node_entry * node , * oldnode ;
node = kmalloc ( sizeof ( * node ) , GFP_KERNEL ) ;
if ( ! node )
return - ENOMEM ;
2014-02-18 22:37:20 +04:00
ether_addr_copy ( node - > MacAddressA , addr_a ) ;
ether_addr_copy ( node - > MacAddressB , addr_b ) ;
2013-10-31 00:10:47 +04:00
rcu_read_lock ( ) ;
oldnode = list_first_or_null_rcu ( self_node_db ,
struct node_entry , mac_list ) ;
if ( oldnode ) {
list_replace_rcu ( & oldnode - > mac_list , & node - > mac_list ) ;
rcu_read_unlock ( ) ;
synchronize_rcu ( ) ;
kfree ( oldnode ) ;
} else {
rcu_read_unlock ( ) ;
list_add_tail_rcu ( & node - > mac_list , self_node_db ) ;
}
return 0 ;
}
/* Add/merge node to the database of nodes. 'skb' must contain an HSR
* supervision frame .
* - If the supervision header ' s MacAddressA field is not yet in the database ,
* this frame is from an hitherto unknown node - add it to the database .
* - If the sender ' s MAC address is not the same as its MacAddressA address ,
* the node is using PICS_SUBS ( address substitution ) . Record the sender ' s
* address as the node ' s MacAddressB .
*
* This function needs to work even if the sender node has changed one of its
* slaves ' MAC addresses . In this case , there are four different cases described
* by ( Addr - changed , received - from ) pairs as follows . Note that changing the
* SlaveA address is equal to changing the node ' s own address :
*
* - ( AddrB , SlaveB ) : The new AddrB will be recorded by PICS_SUBS code since
* node = = NULL .
* - ( AddrB , SlaveA ) : Will work as usual ( the AddrB change won ' t be detected
* from this frame ) .
*
* - ( AddrA , SlaveB ) : The old node will be found . We need to detect this and
* remove the node .
* - ( AddrA , SlaveA ) : A new node will be registered ( non - PICS_SUBS at first ) .
* The old one will be pruned after HSR_NODE_FORGET_TIME .
*
* We also need to detect if the sender ' s SlaveA and SlaveB cables have been
* swapped .
*/
struct node_entry * hsr_merge_node ( struct hsr_priv * hsr_priv ,
struct node_entry * node ,
struct sk_buff * skb ,
enum hsr_dev_idx dev_idx )
{
struct hsr_sup_payload * hsr_sp ;
struct hsr_ethhdr_sp * hsr_ethsup ;
int i ;
unsigned long now ;
hsr_ethsup = ( struct hsr_ethhdr_sp * ) skb_mac_header ( skb ) ;
hsr_sp = ( struct hsr_sup_payload * ) skb - > data ;
if ( node & & ! ether_addr_equal ( node - > MacAddressA , hsr_sp - > MacAddressA ) ) {
/* Node has changed its AddrA, frame was received from SlaveB */
list_del_rcu ( & node - > mac_list ) ;
2013-12-16 10:05:50 +04:00
kfree_rcu ( node , rcu_head ) ;
2013-10-31 00:10:47 +04:00
node = NULL ;
}
if ( node & & ( dev_idx = = node - > AddrB_if ) & &
! ether_addr_equal ( node - > MacAddressB , hsr_ethsup - > ethhdr . h_source ) ) {
/* Cables have been swapped */
list_del_rcu ( & node - > mac_list ) ;
2013-12-16 10:05:50 +04:00
kfree_rcu ( node , rcu_head ) ;
2013-10-31 00:10:47 +04:00
node = NULL ;
}
if ( node & & ( dev_idx ! = node - > AddrB_if ) & &
( node - > AddrB_if ! = HSR_DEV_NONE ) & &
! ether_addr_equal ( node - > MacAddressA , hsr_ethsup - > ethhdr . h_source ) ) {
/* Cables have been swapped */
list_del_rcu ( & node - > mac_list ) ;
2013-12-16 10:05:50 +04:00
kfree_rcu ( node , rcu_head ) ;
2013-10-31 00:10:47 +04:00
node = NULL ;
}
if ( node )
return node ;
node = find_node_by_AddrA ( & hsr_priv - > node_db , hsr_sp - > MacAddressA ) ;
if ( node ) {
/* Node is known, but frame was received from an unknown
* address . Node is PICS_SUBS capable ; merge its AddrB .
*/
2014-02-18 22:37:20 +04:00
ether_addr_copy ( node - > MacAddressB , hsr_ethsup - > ethhdr . h_source ) ;
2013-10-31 00:10:47 +04:00
node - > AddrB_if = dev_idx ;
return node ;
}
node = kzalloc ( sizeof ( * node ) , GFP_ATOMIC ) ;
if ( ! node )
return NULL ;
2014-02-18 22:37:20 +04:00
ether_addr_copy ( node - > MacAddressA , hsr_sp - > MacAddressA ) ;
ether_addr_copy ( node - > MacAddressB , hsr_ethsup - > ethhdr . h_source ) ;
2013-10-31 00:10:47 +04:00
if ( ! ether_addr_equal ( hsr_sp - > MacAddressA , hsr_ethsup - > ethhdr . h_source ) )
node - > AddrB_if = dev_idx ;
else
node - > AddrB_if = HSR_DEV_NONE ;
/* We are only interested in time diffs here, so use current jiffies
* as initialization . ( 0 could trigger an spurious ring error warning ) .
*/
now = jiffies ;
for ( i = 0 ; i < HSR_MAX_SLAVE ; i + + )
node - > time_in [ i ] = now ;
for ( i = 0 ; i < HSR_MAX_DEV ; i + + )
node - > seq_out [ i ] = ntohs ( hsr_ethsup - > hsr_sup . sequence_nr ) - 1 ;
list_add_tail_rcu ( & node - > mac_list , & hsr_priv - > node_db ) ;
return node ;
}
/* 'skb' is a frame meant for this host, that is to be passed to upper layers.
*
* If the frame was sent by a node ' s B interface , replace the sender
* address with that node ' s " official " address ( MacAddressA ) so that upper
* layers recognize where it came from .
*/
void hsr_addr_subst_source ( struct hsr_priv * hsr_priv , struct sk_buff * skb )
{
struct ethhdr * ethhdr ;
struct node_entry * node ;
if ( ! skb_mac_header_was_set ( skb ) ) {
WARN_ONCE ( 1 , " %s: Mac header not set \n " , __func__ ) ;
return ;
}
ethhdr = ( struct ethhdr * ) skb_mac_header ( skb ) ;
rcu_read_lock ( ) ;
node = find_node_by_AddrB ( & hsr_priv - > node_db , ethhdr - > h_source ) ;
if ( node )
2014-02-18 22:37:20 +04:00
ether_addr_copy ( ethhdr - > h_source , node - > MacAddressA ) ;
2013-10-31 00:10:47 +04:00
rcu_read_unlock ( ) ;
}
/* 'skb' is a frame meant for another host.
* ' hsr_dev_idx ' is the HSR index of the outgoing device
*
* Substitute the target ( dest ) MAC address if necessary , so the it matches the
* recipient interface MAC address , regardless of whether that is the
* recipient ' s A or B interface .
* This is needed to keep the packets flowing through switches that learn on
* which " side " the different interfaces are .
*/
void hsr_addr_subst_dest ( struct hsr_priv * hsr_priv , struct ethhdr * ethhdr ,
enum hsr_dev_idx dev_idx )
{
struct node_entry * node ;
rcu_read_lock ( ) ;
node = find_node_by_AddrA ( & hsr_priv - > node_db , ethhdr - > h_dest ) ;
if ( node & & ( node - > AddrB_if = = dev_idx ) )
2014-02-18 22:37:20 +04:00
ether_addr_copy ( ethhdr - > h_dest , node - > MacAddressB ) ;
2013-10-31 00:10:47 +04:00
rcu_read_unlock ( ) ;
}
/* seq_nr_after(a, b) - return true if a is after (higher in sequence than) b,
* false otherwise .
*/
static bool seq_nr_after ( u16 a , u16 b )
{
/* Remove inconsistency where
2013-11-30 02:37:07 +04:00
* seq_nr_after ( a , b ) = = seq_nr_before ( a , b )
*/
2013-10-31 00:10:47 +04:00
if ( ( int ) b - a = = 32768 )
return false ;
return ( ( ( s16 ) ( b - a ) ) < 0 ) ;
}
# define seq_nr_before(a, b) seq_nr_after((b), (a))
# define seq_nr_after_or_eq(a, b) (!seq_nr_before((a), (b)))
# define seq_nr_before_or_eq(a, b) (!seq_nr_after((a), (b)))
void hsr_register_frame_in ( struct node_entry * node , enum hsr_dev_idx dev_idx )
{
2014-03-01 03:18:57 +04:00
if ( ( dev_idx < 0 ) | | ( dev_idx > = HSR_MAX_SLAVE ) ) {
2013-10-31 00:10:47 +04:00
WARN_ONCE ( 1 , " %s: Invalid dev_idx (%d) \n " , __func__ , dev_idx ) ;
return ;
}
node - > time_in [ dev_idx ] = jiffies ;
node - > time_in_stale [ dev_idx ] = false ;
}
/* 'skb' is a HSR Ethernet frame (with a HSR tag inserted), with a valid
* ethhdr - > h_source address and skb - > mac_header set .
*
* Return :
* 1 if frame can be shown to have been sent recently on this interface ,
* 0 otherwise , or
* negative error code on error
*/
int hsr_register_frame_out ( struct node_entry * node , enum hsr_dev_idx dev_idx ,
struct sk_buff * skb )
{
struct hsr_ethhdr * hsr_ethhdr ;
u16 sequence_nr ;
if ( ( dev_idx < 0 ) | | ( dev_idx > = HSR_MAX_DEV ) ) {
WARN_ONCE ( 1 , " %s: Invalid dev_idx (%d) \n " , __func__ , dev_idx ) ;
return - EINVAL ;
}
if ( ! skb_mac_header_was_set ( skb ) ) {
WARN_ONCE ( 1 , " %s: Mac header not set \n " , __func__ ) ;
return - EINVAL ;
}
hsr_ethhdr = ( struct hsr_ethhdr * ) skb_mac_header ( skb ) ;
sequence_nr = ntohs ( hsr_ethhdr - > hsr_tag . sequence_nr ) ;
if ( seq_nr_before_or_eq ( sequence_nr , node - > seq_out [ dev_idx ] ) )
return 1 ;
node - > seq_out [ dev_idx ] = sequence_nr ;
return 0 ;
}
static bool is_late ( struct node_entry * node , enum hsr_dev_idx dev_idx )
{
enum hsr_dev_idx other ;
if ( node - > time_in_stale [ dev_idx ] )
return true ;
if ( dev_idx = = HSR_DEV_SLAVE_A )
other = HSR_DEV_SLAVE_B ;
else
other = HSR_DEV_SLAVE_A ;
if ( node - > time_in_stale [ other ] )
return false ;
if ( time_after ( node - > time_in [ other ] , node - > time_in [ dev_idx ] +
msecs_to_jiffies ( MAX_SLAVE_DIFF ) ) )
return true ;
return false ;
}
/* Remove stale sequence_nr records. Called by timer every
* HSR_LIFE_CHECK_INTERVAL ( two seconds or so ) .
*/
void hsr_prune_nodes ( struct hsr_priv * hsr_priv )
{
struct node_entry * node ;
unsigned long timestamp ;
unsigned long time_a , time_b ;
rcu_read_lock ( ) ;
list_for_each_entry_rcu ( node , & hsr_priv - > node_db , mac_list ) {
/* Shorthand */
time_a = node - > time_in [ HSR_DEV_SLAVE_A ] ;
time_b = node - > time_in [ HSR_DEV_SLAVE_B ] ;
/* Check for timestamps old enough to risk wrap-around */
if ( time_after ( jiffies , time_a + MAX_JIFFY_OFFSET / 2 ) )
node - > time_in_stale [ HSR_DEV_SLAVE_A ] = true ;
if ( time_after ( jiffies , time_b + MAX_JIFFY_OFFSET / 2 ) )
node - > time_in_stale [ HSR_DEV_SLAVE_B ] = true ;
/* Get age of newest frame from node.
* At least one time_in is OK here ; nodes get pruned long
* before both time_ins can get stale
*/
timestamp = time_a ;
if ( node - > time_in_stale [ HSR_DEV_SLAVE_A ] | |
( ! node - > time_in_stale [ HSR_DEV_SLAVE_B ] & &
time_after ( time_b , time_a ) ) )
timestamp = time_b ;
/* Warn of ring error only as long as we get frames at all */
if ( time_is_after_jiffies ( timestamp +
msecs_to_jiffies ( 1.5 * MAX_SLAVE_DIFF ) ) ) {
if ( is_late ( node , HSR_DEV_SLAVE_A ) )
hsr_nl_ringerror ( hsr_priv , node - > MacAddressA ,
HSR_DEV_SLAVE_A ) ;
else if ( is_late ( node , HSR_DEV_SLAVE_B ) )
hsr_nl_ringerror ( hsr_priv , node - > MacAddressA ,
HSR_DEV_SLAVE_B ) ;
}
/* Prune old entries */
if ( time_is_before_jiffies ( timestamp +
msecs_to_jiffies ( HSR_NODE_FORGET_TIME ) ) ) {
hsr_nl_nodedown ( hsr_priv , node - > MacAddressA ) ;
list_del_rcu ( & node - > mac_list ) ;
/* Note that we need to free this entry later: */
2013-12-16 10:05:50 +04:00
kfree_rcu ( node , rcu_head ) ;
2013-10-31 00:10:47 +04:00
}
}
rcu_read_unlock ( ) ;
}
void * hsr_get_next_node ( struct hsr_priv * hsr_priv , void * _pos ,
unsigned char addr [ ETH_ALEN ] )
{
struct node_entry * node ;
if ( ! _pos ) {
node = list_first_or_null_rcu ( & hsr_priv - > node_db ,
struct node_entry , mac_list ) ;
if ( node )
2014-02-18 22:37:20 +04:00
ether_addr_copy ( addr , node - > MacAddressA ) ;
2013-10-31 00:10:47 +04:00
return node ;
}
node = _pos ;
list_for_each_entry_continue_rcu ( node , & hsr_priv - > node_db , mac_list ) {
2014-02-18 22:37:20 +04:00
ether_addr_copy ( addr , node - > MacAddressA ) ;
2013-10-31 00:10:47 +04:00
return node ;
}
return NULL ;
}
int hsr_get_node_data ( struct hsr_priv * hsr_priv ,
const unsigned char * addr ,
unsigned char addr_b [ ETH_ALEN ] ,
unsigned int * addr_b_ifindex ,
int * if1_age ,
u16 * if1_seq ,
int * if2_age ,
u16 * if2_seq )
{
struct node_entry * node ;
unsigned long tdiff ;
rcu_read_lock ( ) ;
node = find_node_by_AddrA ( & hsr_priv - > node_db , addr ) ;
if ( ! node ) {
rcu_read_unlock ( ) ;
return - ENOENT ; /* No such entry */
}
2014-02-18 22:37:20 +04:00
ether_addr_copy ( addr_b , node - > MacAddressB ) ;
2013-10-31 00:10:47 +04:00
tdiff = jiffies - node - > time_in [ HSR_DEV_SLAVE_A ] ;
if ( node - > time_in_stale [ HSR_DEV_SLAVE_A ] )
* if1_age = INT_MAX ;
# if HZ <= MSEC_PER_SEC
else if ( tdiff > msecs_to_jiffies ( INT_MAX ) )
* if1_age = INT_MAX ;
# endif
else
* if1_age = jiffies_to_msecs ( tdiff ) ;
tdiff = jiffies - node - > time_in [ HSR_DEV_SLAVE_B ] ;
if ( node - > time_in_stale [ HSR_DEV_SLAVE_B ] )
* if2_age = INT_MAX ;
# if HZ <= MSEC_PER_SEC
else if ( tdiff > msecs_to_jiffies ( INT_MAX ) )
* if2_age = INT_MAX ;
# endif
else
* if2_age = jiffies_to_msecs ( tdiff ) ;
/* Present sequence numbers as if they were incoming on interface */
* if1_seq = node - > seq_out [ HSR_DEV_SLAVE_B ] ;
* if2_seq = node - > seq_out [ HSR_DEV_SLAVE_A ] ;
if ( ( node - > AddrB_if ! = HSR_DEV_NONE ) & & hsr_priv - > slave [ node - > AddrB_if ] )
* addr_b_ifindex = hsr_priv - > slave [ node - > AddrB_if ] - > ifindex ;
else
* addr_b_ifindex = - 1 ;
rcu_read_unlock ( ) ;
return 0 ;
}